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