PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.6.0
WP 2FA – Two-factor authentication for WordPress v2.6.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / class-wp2fa.php
wp-2fa / includes / classes Last commit date
Admin 2 years ago App 2 years ago Authenticator 2 years ago Shortcodes 2 years ago Utils 2 years ago class-email-template.php 2 years ago class-wp2fa.php 2 years ago index.php 5 years ago
class-wp2fa.php
1188 lines
1 <?php
2 /**
3 * Main plugin class.
4 *
5 * @package wp2fa
6 * @copyright %%YEAR%% Melapress
7 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
8 * @link https://wordpress.org/plugins/wp-2fa/
9 */
10
11 namespace WP2FA;
12
13 use WP2FA\Admin\User_Listing;
14 use WP2FA\Admin\User_Notices;
15 use WP2FA\Admin\Settings_Page;
16 use WP2FA\Utils\Request_Utils;
17 use WP2FA\Utils\Settings_Utils;
18 use WP2FA\Shortcodes\Shortcodes;
19 use WP2FA\Utils\Date_Time_Utils;
20 use WP2FA\Authenticator\Open_SSL;
21 use WP2FA\Admin\Helpers\WP_Helper;
22 use WP2FA\Freemius\User_Licensing;
23 use WP2FA\Freemius\Freemius_Helper;
24 use WP2FA\Admin\Controllers\Methods;
25 use WP2FA\Admin\Helpers\File_Writer;
26 use WP2FA\Admin\Helpers\User_Helper;
27 use WP2FA\Admin\Controllers\Settings;
28 use WP2FA\Admin\Helpers\Classes_Helper;
29 use WP2FA\Admin\Helpers\Methods_Helper;
30 use WP2FA\Admin\Views\Password_Reset_2FA;
31 use WP2FA\Admin\Views\Grace_Period_Notifications;
32
33 if ( ! class_exists( '\WP2FA\WP2FA' ) ) {
34 /**
35 * Main WP2FA Class.
36 */
37 class WP2FA {
38
39 /**
40 * Holds the global plugin secret key
41 *
42 * @var string
43 *
44 * @since 2.0.0
45 */
46 private static $secret_key = null;
47
48 /**
49 * Local static cache for plugins settings.
50 *
51 * @var array
52 *
53 * @since 2.0.0
54 */
55 private static $plugin_settings = array();
56
57 /**
58 * Local static cache for email template settings.
59 *
60 * @var array
61 */
62 protected static $wp_2fa_email_templates;
63
64 /**
65 * Array with all the plugin default settings.
66 *
67 * @return array
68 *
69 * @since 2.2.0
70 */
71 public static function get_default_settings() {
72 $default_settings = array(
73 'enforcement-policy' => 'do-not-enforce',
74 'excluded_users' => array(),
75 'excluded_roles' => array(),
76 'enforced_users' => array(),
77 'enforced_roles' => array(),
78 'grace-period' => 3,
79 'grace-period-denominator' => 'days',
80 'enable_destroy_session' => '',
81 'limit_access' => '',
82 'brute_force_disable' => '',
83 '2fa_settings_last_updated_by' => '',
84 '2fa_main_user' => '',
85 'grace-period-expiry-time' => '',
86 'plugin_version' => WP_2FA_VERSION,
87 'delete_data_upon_uninstall' => '',
88 'excluded_sites' => '',
89 'included_sites' => array(),
90 'create-custom-user-page' => 'no',
91 'redirect-user-custom-page' => '',
92 'redirect-user-custom-page-global' => '',
93 'custom-user-page-url' => '',
94 'custom-user-page-id' => '',
95 'hide_remove_button' => '',
96 'separate-multisite-page-url' => '',
97 'grace-policy' => 'use-grace-period',
98 'superadmins-role-add' => 'no',
99 'superadmins-role-exclude' => 'no',
100 'default-text-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
101 'default-text-pw-reset-code-page' => '<p>' . __( 'You have been sent a one-time code via email. Please enter the code below and then click Get New Password to proceed with the password reset.', 'wp-2fa' ) . '</p><br><p><strong>' . __( 'Note: If you have not received the code please click the button Resend Code. If you still do not get the code after pressing the button, please contact the website\'s administrator.', 'wp-2fa' ) . '</strong></p>',
102 'default-2fa-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
103 'default-2fa-resetup-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
104 'custom-text-authy-code-page-intro' => __( 'If you are using the Authy app approve the OneTouch request to log in.', 'wp-2fa' ),
105 'custom-text-authy-code-page-awaiting' => __( 'Waiting for approval from application...', 'wp-2fa' ),
106 'custom-text-authy-code-page' => __( 'Manually enter the code from the mobile app.', 'wp-2fa' ),
107 'custom-text-twilio-code-page' => __( 'Enter the 2FA code you have received over SMS.', 'wp-2fa' ),
108 'custom-text-app-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
109 'custom-text-email-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( 'Note: if you are supposed to receive an email but did not receive any, please click the Resend Code button to request another code.', 'wp-2fa' ) . '</strong></p>',
110
111
112 'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
113 'method_invalid_setting' => 'login_block',
114 'enable_wizard_styling' => 'enable_wizard_styling',
115 'show_help_text' => 'show_help_text',
116 'enable_wizard_logo' => '',
117 'enable_welcome' => '',
118 'welcome' => '',
119 'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . Methods::get_number_of_methods_text(),
120 'method_selection_single' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3><p>' . __( 'Only the below 2FA method is allowed on this website:', 'wp-2fa' ) . '</p>',
121 'method_help_authy_intro' => '<h3>' . __( 'Setting up Push notifications', 'wp-2fa' ) . '</h3><p>' . __( 'To enable push notifications enter the country and cellphone number in order to use it with this account.', 'wp-2fa' ) . '</p>',
122 'method_help_twilio_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
123 'method_help_oob_intro' => '<h3>' . __( 'Setting up Link over email 2FA', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address to where the out-of-band link should be sent:', 'wp-2fa' ) . '</p>',
124 'method_verification_oob_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent to your email address to finalize the setup. Once the code is confirmed and 2FA is set up, you only have to verify a login by clicking on a link sent to you via email.', 'wp-2fa' ) . '</p>',
125 'method_verification_authy_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the code from your Authy application with name {authy_name}', 'wp-2fa' ) . '</p>',
126 'method_verification_twilio_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
127 'backup_codes_intro_multi' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to have a backup 2FA method in case you cannot generate a code from your 2FA app and you need to log in. You can configure any of the below. You can always configure any or both from your user profile page later.', 'wp-2fa' ) . '</p>',
128 'backup_codes_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p>',
129 'backup_codes_intro_continue' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ) . '</p><p>' . __( 'You should now generate the list of backup method. Although this is optional, it is highly recommended to have a secondary 2FA method. This can be used as a backup should the primary 2FA method fail. This can happen if, for example, you forget your smartphone, the smartphone runs out of battery, or there are email deliverability problems.', 'wp-2fa' ) . '</p>',
130 'backup_codes_generate_intro' => '<h3>' . __( 'Generate list of backup codes', 'wp-2fa' ) . '</h3><p>' . __( 'It is recommended to generate and print some backup codes in case you lose access to your primary 2FA method.', 'wp-2fa' ) . '</p>',
131 'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
132 'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
133 '2fa_required_intro' => '<h3>' . __( 'You are required to configure 2FA.', 'wp-2fa' ) . '</h3><p>' . __( 'In order to keep this site - and your details secure, this website’s administrator requires you to enable 2FA authentication to continue.', 'wp-2fa' ) . '</p><p>' . __( 'Two factor authentication ensures only you have access to your account by creating an added layer of security when logging in -', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel="noopener">' . __( 'Learn more', 'wp-2fa' ) . '</a></p>',
134 'authy_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where link should be send:', 'wp-2fa' ) . '</p>',
135 'authy_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
136 'twilio_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where code should be send:', 'wp-2fa' ) . '</p>',
137 'twilio_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
138 'oob_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} link over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address where the OOB code should be sent:', 'wp-2fa' ) . '</p>',
139 'custom_css' => '',
140 'login_custom_css' => '',
141 'logo-code-page' => '',
142 'login-to-view-area' => '<p>' . __( 'You must be logged in to view this page. {login_url}', 'wp-2fa' ) . '</p>',
143 'backup_email_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Well done on configuring 2FA, your login has just got more secure. To make sure you never get locked out you are required to confirm your email address and use email as an alternative and backup 2FA method in case your primary method is unavailable. Please confirm your email address below', 'wp-2fa' ) . '</p>',
144 'user-profile-form-preamble-title' => __( 'Two-factor authentication settings', 'wp-2fa' ),
145 'user-profile-form-preamble-desc' => __( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' ),
146 'use_custom_2fa_message' => 'use-defaults',
147
148 );
149 /**
150 * Gives the ability to filter the default settings array of the plugin
151 *
152 * @param array $settings - The array with all the default settings.
153 *
154 * @since 2.0.0
155 */
156 $default_settings = apply_filters( WP_2FA_PREFIX . 'default_settings', $default_settings );
157
158 return $default_settings;
159 }
160
161 /**
162 * Inits the plugin related classes and settings
163 *
164 * @return void
165 *
166 * @since 2.6.0
167 */
168 public static function init() {
169
170 Methods_Helper::init();
171
172 self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() );
173 self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() );
174 self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
175
176 self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
177
178 /** We need to exclude all the possible ways, that logic to be executed by some WP request which could come from cron job or AJAX call, which will break the wizard (by storing the settings for the plugin) before it is completed by the user. We also have to check if the user is still processing first time wizard ($_GET parameter), and if the wizard has been finished already (wp_2fa_wizard_not_finished) */
179 if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
180
181 if ( ! Settings_Utils::get_option( WP_2FA_SETTINGS_NAME ) ) {
182 self::update_plugin_settings( self::get_default_settings() );
183 }
184
185 // Set a flag so we know we have default values present, not custom.
186 Settings_Utils::update_option( 'default_settings_applied', true );
187 Settings_Utils::delete_option( 'wizard_not_finished' );
188 }
189
190
191 WP_Helper::init();
192
193 // Bootstrap.
194 Core\setup();
195
196 if ( is_admin() ) {
197 User_Listing::init();
198 // Hide all unrelated to the plugin notices on the plugin admin pages.
199 add_action( 'admin_print_scripts', array( '\WP2FA\Admin\Helpers\WP_Helper', 'hide_unrelated_notices' ) );
200 }
201
202 Grace_Period_Notifications::init();
203 Password_Reset_2FA::init();
204
205 Shortcodes::init();
206 User_Notices::init();
207
208 self::add_actions();
209
210 // Inits all the additional free app extensions.
211 $free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' );
212
213 foreach ( $free_extensions as $extension ) {
214 if ( method_exists( $extension, 'init' ) ) {
215 call_user_func_array( array( $extension, 'init' ), array() );
216 }
217 }
218 }
219
220 /**
221 * Inits all the plugin hooks
222 *
223 * @return void
224 *
225 * @since 2.6.0
226 */
227 public static function add_actions() {
228 // Plugin redirect on activation, only if we have no settings currently saved.
229 if ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || Settings_Utils::get_option( 'redirect_on_activate', false ) ) {
230 add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 );
231
232 self::update_plugin_settings( self::get_default_settings() );
233 } elseif ( ! \is_array( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) {
234 Settings_Utils::delete_option( WP_2FA_POLICY_SETTINGS_NAME );
235 self::update_plugin_settings( self::get_default_settings() );
236 }
237
238 // SettingsPage.
239 if ( WP_Helper::is_multisite() ) {
240 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu_multisite' ) );
241 add_action( 'network_admin_edit_update_wp2fa_network_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_options' ) );
242 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_email_options' ) );
243 add_action( 'network_admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_network_admin_notice' ) );
244 add_action( 'network_admin_notices', array( __CLASS__, 'wp_not_writable' ) );
245 } else {
246 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu' ) );
247 add_action( 'admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_admin_notice' ) );
248 add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) );
249 }
250 add_action( 'wp_ajax_wp2fa_dismiss_notice_mail_domain', array( '\WP2FA\Admin\Settings_Page', 'dismiss_notice_mail_domain' ) );
251 \add_action( 'wp_ajax_nopriv_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
252 \add_action( 'wp_ajax_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
253
254 add_action( 'wp_ajax_wp_2fa_get_all_users', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_users' ) );
255 add_action( 'wp_ajax_wp_2fa_get_all_roles', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_ajax_user_roles' ) );
256 add_action( 'wp_ajax_wp_2fa_get_all_network_sites', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_network_sites' ) );
257 add_action( 'wp_ajax_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
258 add_action( 'admin_action_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
259 add_action( 'admin_action_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
260 add_action( 'wp_ajax_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
261 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'hide_settings' ), 999 );
262 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( '\WP2FA\Admin\Settings_Page', 'add_plugin_action_links' ) );
263 add_filter( 'display_post_states', array( '\WP2FA\Admin\Settings_Page', 'add_display_post_states' ), 10, 2 );
264 add_action( 'wp_ajax_send_authentication_setup_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_authentication_setup_email' ) );
265 add_action( 'wp_ajax_send_backup_codes_email', array( '\WP2FA\Methods\Backup_Codes', 'send_backup_codes_email' ) );
266 add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Methods\TOTP', 'regenerate_authentication_key' ) );
267
268 // User_Notices.
269 add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
270 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
271 add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
272
273 // User_Profile.
274 global $pagenow;
275 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
276 add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
277 add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
278 if ( WP_Helper::is_multisite() ) {
279 add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
280 }
281 }
282 add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
283 if ( WP_Helper::is_multisite() ) {
284 add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
285 }
286 add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
287 add_action( 'wp_ajax_wp2fa_test_email', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'handle_send_test_email_ajax' ) );
288
289 // Login.
290 add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
291 add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
292 add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
293 add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
294 add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
295
296 // Reset password.
297 add_action( 'lostpassword_post', array( '\WP2FA\Authenticator\Reset_Password', 'lostpassword_post' ), 20, 2 );
298 add_action( 'login_form_lostpassword', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ), 20 );
299 // add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ) );
300
301 /**
302 * Keep track of all the user sessions for which we need to invalidate the
303 * authentication cookies set during the initial password check.
304 */
305 add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
306 add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
307
308 // Run only after the core wp_authenticate_username_password() check.
309 add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
310 add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
311
312 // User Register.
313 add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
314
315 // Block users from admin if needed.
316 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
317 add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
318
319 // Help & Contact Us.
320 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
321
322 // phpcs:disable
323 /* @free:start */
324 // phpcs:enable
325 // Premium Features.
326 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
327 add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
328 add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
329 // phpcs:disable
330 /* @free:end */
331 // phpcs:enable
332
333 add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
334
335 \add_action( WP_2FA_PREFIX . 'user_authenticated', array( __CLASS__, 'clear_user_after_login' ), 10, 1 );
336
337 \add_filter( 'mepr-auto-login', array( '\WP2FA\Authenticator\Login', 'mepr_login' ) );
338 }
339
340 /**
341 * Add actions specific to the wizard.
342 */
343 public static function add_wizard_actions() {
344 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
345 add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 );
346 }
347 }
348
349 /**
350 * Redirect user to 1st time setup.
351 *
352 * @SuppressWarnings(PHPMD.ExitExpression)
353 */
354 public static function setup_redirect() {
355
356 // Bail early before the redirect if the user can't manage options.
357 if ( ! current_user_can( 'manage_options' ) ) {
358 return;
359 }
360
361 $registered_and_active = 'yes';
362 if ( function_exists( 'wp2fa_freemius' ) ) {
363 $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
364 }
365
366 if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
367 // Delete redirect option.
368 Settings_Utils::delete_option( 'redirect_on_activate' );
369
370 Settings_Utils::update_option( 'wizard_not_finished', true );
371
372 $redirect = add_query_arg(
373 array(
374 'page' => 'wp-2fa-setup',
375 'is_initial_setup' => 'true',
376 ),
377 admin_url( 'user-edit.php' )
378 );
379
380 wp_safe_redirect( $redirect );
381 exit();
382 }
383 }
384
385 /**
386 * Return user roles.
387 *
388 * @return array User roles.
389 */
390 public static function wp_2fa_get_roles() {
391 return WP_Helper::get_roles_wp();
392 }
393
394 /**
395 * Util function to grab settings or apply defaults if no settings are saved into the db.
396 *
397 * @param string $setting_name Settings to grab value of.
398 * @param boolean $get_default_on_empty return default setting value if current one is empty.
399 * @param boolean $get_default_value return default value setting (ignore the stored ones).
400 * @param string $role - The name of the user role.
401 *
402 * @return mixed Settings value or default value.
403 */
404 public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
405 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
406 return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role );
407 }
408
409 /**
410 * Util function to grab settings or apply defaults if no settings are saved into the db.
411 *
412 * @param string $setting_name Settings to grab value of.
413 * @param boolean $get_default_on_empty return default setting value if current one is empty.
414 * @param boolean $get_default_value return default value setting (ignore the stored ones).
415 *
416 * @return mixed Settings value or default value.
417 */
418 public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
419
420 return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
421 }
422
423 /**
424 * Util function to grab white label settings or apply defaults if no settings are saved into the db.
425 *
426 * @param string $setting_name Settings to grab value of.
427 * @param boolean $get_default_on_empty return default setting value if current one is empty.
428 * @param boolean $get_default_value return default value setting (ignore the stored ones).
429 *
430 * @return string Settings value or default value.
431 */
432 public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
433
434 return self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
435 }
436
437 /**
438 * Generic method for extracting settings from the plugin
439 *
440 * @param string $wp_2fa_setting - The name of the settings type.
441 * @param string $setting_name - The name of the setting to extract.
442 * @param boolean $get_default_on_empty - Should we use default value on empty.
443 * @param boolean $get_default_value - Extract default value.
444 * @param string $role - The name of the user role.
445 *
446 * @return mixed
447 */
448 private static function get_wp2fa_setting_generic( $wp_2fa_setting = WP_2FA_POLICY_SETTINGS_NAME, $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
449 $default_settings = self::get_default_settings();
450 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
451
452 if ( true === $get_default_value ) {
453 if ( isset( $default_settings[ $setting_name ] ) ) {
454 return $default_settings[ $setting_name ];
455 }
456
457 return false;
458 }
459
460 $apply_defaults = false;
461
462 $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
463
464 // If we have no setting name, return them all.
465 if ( empty( $setting_name ) ) {
466 return $wp2fa_setting;
467 }
468
469 // First lets check if any options have been saved.
470 if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
471 $apply_defaults = true;
472 }
473
474 if ( $apply_defaults ) {
475 return $default_settings[ $setting_name ];
476 } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
477 if ( true === $get_default_on_empty ) {
478 if ( isset( $default_settings[ $setting_name ] ) ) {
479 return $default_settings[ $setting_name ];
480 }
481 }
482 return false;
483 } elseif ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
484
485 /**
486 * Extensions could change the extracted value, based on custom / different / specific for role settings.
487 *
488 * @param mixed - Value of the setting.
489 * @param string - The name of the setting.
490 * @param string - The role name.
491 *
492 * @since 2.0.0
493 */
494 return apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
495 } else {
496 return $wp2fa_setting[ $setting_name ];
497 }
498 }
499
500 /**
501 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
502 *
503 * @param string $setting_name Settings to grab value of.
504 */
505 public static function get_wp2fa_email_templates( $setting_name = '' ) {
506
507 // If we have no setting name, return what ever is saved.
508 if ( empty( $setting_name ) ) {
509 return self::$wp_2fa_email_templates;
510 }
511
512 // If we have a saved setting, return it.
513 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
514 return self::$wp_2fa_email_templates[ $setting_name ];
515 }
516
517 // Create Login Code Message.
518 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
519
520 $login_code_body = '<p>' . sprintf(
521 // translators: The login code provided from the plugin.
522 esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ),
523 '<strong>{login_code}</strong>'
524 );
525 $login_code_body .= '</p>';
526 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
527 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
528 $login_code_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
529 $login_code_body .= '</p>';
530
531 // Create Reset PW Code Message.
532 $reset_password_code_subject = __( '2FA code for password reset', 'wp-2fa' );
533
534 $reset_password_code_body = '<p>' . esc_html__( 'Hello,', 'wp-2fa' ) . '</p>';
535
536 $reset_password_code_body = '<p>' . sprintf(
537 // translators: The login code provided from the plugin.
538 esc_html__( 'Someone from the IP address %1$1s has requested a password reset for the user %2$2s on the website %3$3s. If this was you please use the below code to proceed with the password reset:', 'wp-2fa' ),
539 '{user_ip_address}',
540 '{user_login_name}',
541 '{site_url}'
542 );
543
544 $reset_password_code_body .= '<p><strong>{login_code}</strong></p>';
545
546 $reset_password_code_body .= '</p>';
547 $reset_password_code_body .= '<p>' . esc_html__( 'If this was not you, ignore this email and contact your website administrator.', 'wp-2fa' ) . '</p>';
548
549 $login_code_setup_body = '<p>' . sprintf(
550 // translators: The login code provided from the plugin.
551 esc_html__( 'Please enter this code to confirm 2FA setup: %1$1s', 'wp-2fa' ),
552 '<strong>{login_code}</strong>'
553 );
554 $login_code_setup_body .= '</p>';
555 $login_code_setup_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
556 $login_code_setup_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
557 $login_code_setup_body .= ' <a href="hhttps://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
558 $login_code_setup_body .= '</p>';
559
560 // Create User Locked Message.
561 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
562
563 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
564 $user_locked_body .= '<p>' . sprintf(
565 // translators: %1s - the name of the user
566 // translators: %2s - the name of the site.
567 esc_html__( 'Since you have not enabled two-factor authentication for the user %1$1s on the website %2$2s within the grace period, your account has been locked.', 'wp-2fa' ),
568 '{user_login_name}',
569 '{site_name}'
570 );
571 $user_locked_body .= '</p>';
572 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
573 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
574 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
575 $user_locked_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
576 $user_locked_body .= '</p>';
577
578 // Create User unlocked Message.
579 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
580 $user_unlocked_body = '';
581
582 $user_unlocked_body .= '<p>' . __( 'Hello,', 'wp-2fa' ) . '</p><p>' . esc_html__( 'Your user', 'wp-2fa' ) . ' <strong>{user_login_name}</strong> ' . esc_html__( 'on the website', 'wp-2fa' ) . ' {site_url} ' . __( 'has been unlocked. Please configure two-factor authentication within the grace period, otherwise your account will be locked again.', 'wp-2fa' ) . '</p>';
583
584 if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
585 $user_unlocked_body .= '<p>' . __( 'You can configure 2FA from this page:', 'wp-2fa' ) . ' <a href="{2fa_settings_page_url}" target="_blank">{2fa_settings_page_url}.</a></p>';
586 }
587
588 $user_unlocked_body .= '<p>' . __( 'Thank you.', 'wp-2fa' ) . '</p><p>' . __( 'Email sent by', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
589
590 // Create User backup codes Message.
591 $user_backup_codes_subject = __( '2FA backup codes for user {user_login_name} on {site_name}', 'wp-2fa' );
592 $user_backup_codes_body = '';
593
594 $user_backup_codes_body .= '<p>' . __( 'Hello,', 'wp-2fa' ) . '</p><p>' . esc_html__( 'Below please find the 2FA backup codes for your user', 'wp-2fa' ) . ' <strong>{user_login_name}</strong> ' . esc_html__( 'on the website', 'wp-2fa' ) . ' <strong>{site_name}</strong>. ' . __( 'The website\'s URL is', 'wp-2fa' ) . ' {site_url} </p>';
595
596 $user_backup_codes_body .= '{backup_codes}';
597
598 $user_backup_codes_body .= '<p>' . __( 'Thank you for enabling 2FA on your account and helping us keeping the website secure.', 'wp-2fa' ) . '</p><p>' . __( 'Email sent by', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
599
600 // Array of defaults, now we have things setup above.
601 $default_settings = array(
602 'email_from_setting' => 'use-defaults',
603 'custom_from_email_address' => '',
604 'custom_from_display_name' => '',
605 'login_code_email_subject' => $login_code_subject,
606 'login_code_email_body' => $login_code_body,
607 'reset_password_code_email_subject' => $reset_password_code_subject,
608 'reset_password_code_email_body' => $reset_password_code_body,
609 'login_code_setup_email_subject' => $login_code_subject,
610 'login_code_setup_email_body' => $login_code_setup_body,
611 'user_account_locked_email_subject' => $user_locked_subject,
612 'user_account_locked_email_body' => $user_locked_body,
613 'user_account_unlocked_email_subject' => $user_unlocked_subject,
614 'user_account_unlocked_email_body' => $user_unlocked_body,
615 'user_backup_codes_email_subject' => $user_backup_codes_subject,
616 'user_backup_codes_email_body' => $user_backup_codes_body,
617 'send_account_locked_email' => 'enable_account_locked_email',
618 'send_account_unlocked_email' => 'enable_account_unlocked_email',
619 'send_login_code_email' => 'enable_send_login_code_email',
620 'send_reset_password_code_email' => 'enable_send_reset_password_code_email',
621 );
622
623 /**
624 * Allows 3rd party providers to their own settings for the mail templates.
625 *
626 * @param array $default_settings - Array with the default settings.
627 *
628 * @since 2.0.0
629 */
630 $default_settings = apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
631
632 return $default_settings[ $setting_name ];
633 }
634
635 /**
636 * Util which we use to replace our {strings} with actual, useful stuff.
637 *
638 * @param string $input Text we are working on.
639 * @param int|string $user_id User id, if its needed.
640 * @param string $token Login code, if its needed..
641 * @param string $override_grace_period - Value to override grace period with.
642 *
643 * @return string The output, with all the {strings} swapped out.
644 */
645 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
646
647 $token = trim( (string) $token );
648
649 // Gather grace period.
650 $grace_period_string = '';
651 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
652 $grace_period_string = $override_grace_period;
653 } else {
654 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
655 $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
656 }
657
658 // Setup user data.
659 if ( isset( $user_id ) && ! empty( $user_id ) ) {
660 $user = get_userdata( $user_id );
661 } else {
662 $user = wp_get_current_user();
663 }
664
665 // Setup token.
666 if ( isset( $token ) && ! empty( $token ) ) {
667 $login_code = $token;
668 } else {
669 $login_code = '';
670 }
671
672 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
673 if ( ! empty( $new_page_id ) ) {
674 $new_page_permalink = get_permalink( $new_page_id );
675 } else {
676 $new_page_id = Settings::get_custom_settings_page_id( '', $user );
677 if ( ! empty( $new_page_id ) ) {
678 $new_page_permalink = get_permalink( $new_page_id );
679 } else {
680 $new_page_permalink = '';
681 }
682 }
683
684 // These are the strings we are going to search for, as well as there respective replacements.
685 $replacements = array(
686 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
687 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
688 '{grace_period}' => sanitize_text_field( $grace_period_string ),
689 '{user_login_name}' => sanitize_text_field( $user->user_login ),
690 '{user_first_name}' => sanitize_text_field( $user->user_firstname ),
691 '{user_last_name}' => sanitize_text_field( $user->user_lastname ),
692 '{user_display_name}' => sanitize_text_field( $user->display_name ),
693 '{login_code}' => $login_code,
694 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
695 '{user_ip_address}' => Request_Utils::get_ip(),
696 );
697
698 /**
699 * 3rd party plugins could change the mail strings, or provide their own.
700 *
701 * @param array $replacements - The array with all the currently supported strings.
702 */
703 $replacements = apply_filters(
704 WP_2FA_PREFIX . 'replacement_email_strings',
705 $replacements
706 );
707
708 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
709 return $final_output;
710 }
711
712 /**
713 * Util which contextualizes the wording 'reconfigure'/'configure' as needed.
714 *
715 * @param string $input - Text we are working on.
716 * @param int|string $user_id - User id, if its needed.
717 * @param string $method_to_check - Name of the method to check for.
718 *
719 * @return string The output, with all the {strings} swapped out.
720 *
721 * @since 2.5.0
722 */
723 public static function contextual_reconfigure_text( $input = '', $user_id = '', $method_to_check = '' ) {
724
725 if ( empty( trim( (string) $input ) ) ) {
726 return $input;
727 }
728
729 $enabled_method = User_Helper::get_enabled_method_for_user( $user_id );
730
731 $text = ( $enabled_method === $method_to_check ) ? esc_html__( 'Reconfigure', 'wp-2fa' ) : esc_html__( 'Configure', 'wp-2fa' );
732
733 $replacements = array(
734 '{reconfigure_or_configure_capitalized}' => $text,
735 '{reconfigure_or_configure}' => strtolower( $text ),
736 );
737
738 /**
739 * 3rd party plugins could change this to their own.
740 *
741 * @param array $replacements - The array with all the currently supported strings.
742 *
743 * @since 2.5.0
744 */
745 $replacements = apply_filters(
746 WP_2FA_PREFIX . 'replacement_reconfigure_strings',
747 $replacements
748 );
749
750 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
751 }
752
753 /**
754 * Util replace replace a placeholder with the actual remaining grace period for a user..
755 *
756 * @param string $input - Text we are working on.
757 * @param int $grace_expiry - Expiration time.
758 *
759 * @return string The output, with all the {strings} swapped out.
760 *
761 * @since 2.5.0
762 */
763 public static function replace_remaining_grace_period( $input = '', $grace_expiry = -1 ) {
764 if ( empty( trim( (string) $input ) ) || empty( trim( (string) $grace_expiry ) ) ) {
765 return $input;
766 }
767
768 $replacements = array(
769 '{grace_period_remaining}' => esc_attr( Date_Time_Utils::format_grace_period_expiration_string( null, $grace_expiry ) ),
770 );
771
772 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
773 }
774
775 /**
776 * Util which we use to replace our {strings} with actual, useful stuff.
777 *
778 * @param string $input Text we are working on.
779 * @param WP_User $user The WP User.
780 *
781 * @return string The output, with all the {strings} swapped out.
782 */
783 public static function replace_wizard_strings( $input = '', $user = false ) {
784
785 if ( ! $user ) {
786 return $input;
787 }
788
789 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
790
791 // These are the strings we are going to search for, as well as there respective replacements.
792 $replacements = array(
793 '{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
794 );
795
796 /**
797 * 3rd party plugins could change the mail strings, or provide their own.
798 *
799 * @param array $replacements - The array with all the currently supported strings.
800 */
801 $replacements = apply_filters(
802 WP_2FA_PREFIX . 'replacement_wizard_strings',
803 $replacements
804 );
805
806 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
807 return $final_output;
808 }
809
810 /**
811 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
812 *
813 * @SuppressWarnings(PHPMD.ExitExpression)
814 */
815 public static function block_unconfigured_users_from_admin() {
816 global $pagenow;
817
818 $user = User_Helper::get_user();
819 if ( 0 === $user->ID ) {
820 return;
821 }
822
823 $redirect = true;
824
825 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
826 $redirect = User_Licensing::enable_2fa_user_setting( true );
827 }
828
829
830 if ( $redirect ) {
831 $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
832 $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
833 $time_now = time();
834 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
835
836 $has_cap = true;
837 if ( class_exists( 'WooCommerce', false ) ) {
838
839 // Lets check if the user has the required capabilities to view the 2FA settings page (or profile page in the Admin section - dashboard).
840 $has_cap = false;
841
842 $access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' );
843
844 foreach ( $access_caps as $access_cap ) {
845 if ( current_user_can( $access_cap ) ) {
846 $has_cap = true;
847 break;
848 }
849 }
850 }
851
852 /**
853 * We should only allow:
854 * - 2FA setup wizard in the administration
855 * - custom 2FA page if enabled and created
856 * - AJAX requests originating from these 2FA setup UIs
857 */
858 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
859 return;
860 }
861
862 if ( is_admin() || is_network_admin() ) {
863 $allowed_admin_page = 'profile.php';
864 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
865 return;
866 }
867 }
868
869 if ( is_page() ) {
870 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
871 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
872 return;
873 } else {
874 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
875 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
876 return;
877 }
878 }
879 }
880
881 // force a redirect to the 2FA set-up page if it exists.
882 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
883 if ( ! empty( $custom_user_page_id ) ) {
884 wp_redirect( Settings::get_custom_page_link( $user ) );
885 exit;
886 } else {
887 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
888 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
889 wp_redirect( get_permalink( $custom_user_page_id ) );
890 exit;
891 }
892 }
893
894 // There is nowhere to redirect, so we have to fall back to the default which is the dashboard. If the user does not have the required capabilities to view the dashboard - lets stop the redirection.
895 if ( ! $has_cap ) {
896
897 // Is there WOO installed? If so, then lets try to extract the redirection rules from there.
898 if ( class_exists( 'WooCommerce', false ) ) {
899
900 // Lets check if there is a 2FA implemented within the WOOCommerce myaccount page.
901 $items = \wc_get_account_menu_items();
902
903 if ( isset( $items['wp-2fa'] ) ) {
904
905 if ( ! isset( $_GET['wp-2fa'] ) ) {
906 $url = add_query_arg(
907 array(
908 'wp-2fa' => '',
909 ),
910 get_permalink( get_option( 'woocommerce_myaccount_page_id' ) )
911 );
912
913 wp_redirect( $url );
914
915 exit;
916 }
917 }
918 }
919
920 // Nothing suitable found - notify the admin and bail.
921 $transient_name = WP_2FA_PREFIX . '_notified_admin_mail_nowhere_to_redirect_' . $user->ID;
922 if ( false === \get_transient( $transient_name ) ) {
923 $subject = sprintf(
924 // translators: The username.
925 esc_html__(
926 'User %1$s logged in without 2FA',
927 'wp-2fa'
928 ),
929 $user->user_login,
930 );
931
932 $text = sprintf(
933 // translators: The username.
934 // translators: the site name.
935 esc_html__(
936 '2FA is enforced on the user %1$s on the website %2$s. However, since the WP 2FA plugin has not been configured properly it cannot enforce the user to configure 2FA, so the user logged in without 2FA.',
937 'wp-2fa'
938 ),
939 $user->user_login,
940 get_bloginfo( 'name' )
941 );
942 $text .= '<p>' . sprintf(
943 // translators: the settings page.
944 // translators: the support e-mail.
945 esc_html__(
946 'To enforce 2FA on users logging in from non default WordPress login pages please configure the %1$s. If you need assistance, please contact us at %2$s.',
947 'wp-2fa'
948 ),
949 '<a href="' . esc_url(
950 add_query_arg(
951 array(
952 'page' => 'wp-2fa-settings',
953 'tab' => 'integrations',
954 ),
955 network_admin_url( 'admin.php' )
956 )
957 ) . '">front-end 2FA page</a>',
958 '<a href="mailto:support@melapress.com">support@melapress.com</a>'
959 ) . '</p>';
960
961 Settings_Page::send_email(
962 get_option( 'admin_email' ),
963 $subject,
964 $text
965 );
966
967 \set_transient( $transient_name, 'sent', DAY_IN_SECONDS * 2 );
968 }
969
970 return;
971 }
972
973 // custom 2FA page is not set-up, force redirect to the wizard in administration.
974 wp_redirect( Settings::get_setup_page_link() );
975 exit;
976 }
977 }
978 }
979
980 /**
981 * Returns currently stored settings
982 *
983 * @return array
984 */
985 public static function get_policy_settings() {
986 /**
987 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
988 *
989 * @param array - Value of the settings.
990 *
991 * @since 2.0.0
992 */
993 $settings = apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
994
995 return $settings;
996 }
997
998 /**
999 * Checks the action parameter against given list of actions
1000 *
1001 * @return bool
1002 *
1003 * @since 2.0.0
1004 */
1005 private static function action_check() {
1006 if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
1007 return false;
1008 }
1009 $actions_array = array(
1010 'send_authentication_setup_email',
1011 'validate_authcode_via_ajax',
1012 'heartbeat',
1013 'regenerate_authentication_key',
1014 'send_backup_codes_email',
1015 'register_user_twilio',
1016 );
1017
1018 /**
1019 * Allows 3rd party providers to their own settings for the mail templates.
1020 *
1021 * @param array $actions_array - Array with the default settings.
1022 *
1023 * @since 2.0.0
1024 */
1025 $actions_array = apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
1026
1027 return in_array( $_REQUEST['action'], $actions_array, true );
1028 }
1029
1030 /**
1031 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
1032 *
1033 * @param array $settings - The settings values.
1034 * @param bool $skip_option_save If true, the settings themselves are not saved. This is needed when saving settings from settings page as WordPress options API takes care of that.
1035 * @param string $settings_name - The name of the settings to extract.
1036 *
1037 * @since 2.0.0
1038 */
1039 public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
1040 // update local copy of settings.
1041 self::$plugin_settings[ $settings_name ] = $settings;
1042
1043 if ( ! $skip_option_save ) {
1044 // update the database option itself.
1045 Settings_Utils::update_option( $settings_name, $settings );
1046 }
1047
1048 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
1049 // Create a hash for comparison when we interact with a use.
1050 $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
1051 Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
1052 }
1053 }
1054
1055 /**
1056 * Getter for the ecret key of the plugin for the current instance
1057 *
1058 * Note: that is legacy code and will be removed.
1059 *
1060 * @return string
1061 *
1062 * @since 2.0.0
1063 */
1064 public static function get_secret_key() {
1065 if ( null === self::$secret_key ) {
1066 if ( ! defined( File_Writer::SECRET_NAME ) ) {
1067 self::check_for_key();
1068 } else {
1069 self::$secret_key = constant( File_Writer::SECRET_NAME );
1070 }
1071 }
1072
1073 return self::$secret_key;
1074 }
1075
1076 /**
1077 * Checks if the wp-config.php file is writable, show notice to the admin if it is not
1078 *
1079 * @return void
1080 *
1081 * @since 2.4.0
1082 */
1083 public static function wp_not_writable() {
1084
1085 if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) {
1086 return;
1087 }
1088
1089 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
1090 $whitelist_admin_pages = array(
1091 'wp-2fa_page_wp-2fa-settings',
1092 'wp-2fa_page_wp-2fa-settings-network',
1093 'toplevel_page_wp-2fa-policies',
1094 'toplevel_page_wp-2fa-policies-network',
1095 'wp-2fa_page_wp-2fa-help-contact-us',
1096 'wp-2fa_page_wp-2fa-help-contact-us-network',
1097 'wp-2fa_page_wp-2fa-policies-account',
1098 'wp-2fa_page_wp-2fa-policies-account-network',
1099 'wp-2fa_page_wp-2fa-reports',
1100 'wp-2fa_page_wp-2fa-reports-network',
1101 );
1102 $admin_page = get_current_screen();
1103 if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
1104 ?>
1105 <div class="notice notice-warning" id="config-update-notice">
1106 <?php
1107 $message = sprintf(
1108 '<p>%1$s <a href="https://melapress.com/support/kb/wp-2fa-add-2fa-plugin-encryption-key-wp-config/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" noopener target="_blank">%2$s</a><br>%3$s</p>',
1109 esc_html__( 'For security reasons WP 2FA needs to store the private key in the wp-config.php file. However, it is unable to. This can happen because of restrictive permissions, or the file is not in the default location. To fix this you can:', 'wp-2fa' ) . '<br><br>' .
1110
1111 esc_html__( 'Option A) allow the plugin to write to the wp-config.php file temporarily by changing the wp-config.php permissions to 755. Once ready, click the button to proceed.', 'wp-2fa' ) . '<br>' .
1112
1113 esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ),
1114 esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>',
1115 esc_html__(
1116 'Once you complete any of the above, please click the button below.
1117 ',
1118 'wp-2fa'
1119 ),
1120 )
1121 ?>
1122 <?php echo $message; // phpcs:ignore ?>
1123 <p><button id="salt-update" type="button">
1124 <span><?php esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span>
1125 </button></p>
1126 </div>
1127 <script>
1128 jQuery(document).ready(function($) {
1129 $(document).on('click', '#salt-update', function( event ) {
1130 const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
1131 const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
1132 jQuery.ajax({
1133 url: ajaxURL,
1134 data: {
1135 action: 'set_salt_key',
1136 _wpnonce: nonceValue
1137 },
1138 success: function (data) {
1139 if (data.success) {
1140 jQuery('#config-update-notice .notice-dismiss').click();
1141 } else {
1142 alert(data.data);
1143 }
1144 },
1145 error: function (data) {
1146 alert(data.responseJSON.data[0].message);
1147 }
1148 });
1149 });
1150 });
1151 </script>
1152 <?php
1153 }
1154 }
1155 }
1156
1157 /**
1158 * Remove the user meta related with the code has been sent to the user.
1159 * That is so we can lower the security by giving the option not to resend codes, so eventual brute force could succeed.
1160 * The setting name - brute_force_disable
1161 *
1162 * @return void
1163 *
1164 * @since 2.5.0
1165 */
1166 public static function clear_user_after_login() {
1167 User_Helper::remove_meta( WP_2FA_PREFIX . 'code_sent' );
1168 }
1169
1170 /**
1171 * Checks and sets the global wp2fa salt
1172 *
1173 * @return void
1174 *
1175 * @since 2.4.0
1176 */
1177 private static function check_for_key() {
1178 self::$secret_key = Settings_Utils::get_option( 'secret_key' );
1179 if ( empty( self::$secret_key ) ) {
1180 self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
1181 if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
1182 Settings_Utils::update_option( 'secret_key', self::$secret_key );
1183 }
1184 }
1185 }
1186 }
1187 }
1188