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