PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.7.0
WP 2FA – Two-factor authentication for WordPress v1.7.0
4.1.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 / WP2FA.php
wp-2fa / includes / classes Last commit date
Admin 5 years ago Authenticator 5 years ago Cron 5 years ago Shortcodes 5 years ago Utils 5 years ago .gitkeep 6 years ago EmailTemplate.php 5 years ago WP2FA.php 5 years ago index.php 5 years ago
WP2FA.php
992 lines
1 <?php // phpcs:ignore
2
3 namespace WP2FA;
4
5 use WP2FA\Admin\User;
6 use WP2FA\Admin\UserListing;
7 use WP2FA\Admin\SettingsPage;
8 use WP2FA\Utils\DateTimeUtils;
9 use WP2FA\Admin\Controllers\Settings;
10 use WP2FA\Utils\SettingsUtils as SettingsUtils;
11
12 /**
13 * Main WP2FA Class.
14 */
15 class WP2FA {
16
17 /**
18 * Plugin version.
19 *
20 * @var string
21 */
22 public $version = WP_2FA_VERSION;
23
24 /**
25 * Options variables.
26 *
27 * @var array
28 */
29 protected static $wp_2fa_options;
30 protected static $wp_2fa_email_templates;
31
32 /**
33 * Count of the available methods
34 *
35 * @var integer
36 */
37 public static $methodsCount = 2;
38
39 /**
40 * Instance wrapper.
41 *
42 * @var object
43 */
44 private static $instance = null;
45
46 /**
47 * Holds array with all the sites in multisite WP installation
48 *
49 * @var array
50 */
51 private static $sites = [];
52
53 /**
54 * Return plugin instance.
55 */
56 public static function get_instance() {
57
58 if ( null === self::$instance ) {
59 self::$instance = new self();
60 }
61
62 return self::$instance;
63 }
64
65 /**
66 * Contructor.
67 */
68 private function __construct() {
69 self::$wp_2fa_options = SettingsUtils::get_option( WP_2FA_SETTINGS_NAME );
70 self::$wp_2fa_email_templates = SettingsUtils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
71
72 /** 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) */
73 if ( SettingsUtils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
74
75 if ( ! SettingsUtils::get_option( WP_2FA_SETTINGS_NAME ) ) {
76 SettingsUtils::update_option( WP_2FA_SETTINGS_NAME, self::getDefaultSettings() );
77 }
78
79 // Set a flag so we know we have default values present, not custom.
80 SettingsUtils::update_option( 'default_settings_applied', true );
81 SettingsUtils::delete_option( 'wizard_not_finished' );
82 }
83
84 // Activation/Deactivation.
85 register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
86 register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
87 // Register our uninstallation hook.
88 register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
89 }
90
91 public static function getDefaultSettings() {
92 $default_settings = array(
93 'enable_totp' => 'enable_totp',
94 'enable_email' => 'enable_email',
95 'backup_codes_enabled' => 'yes',
96 'enforcement-policy' => 'do-not-enforce',
97 'excluded_users' => [],
98 'excluded_roles' => [],
99 'enforced_users' => [],
100 'enforced_roles' => [],
101 'grace-period' => 3,
102 'grace-period-denominator' => 'days',
103 'enable_grace_cron' => '',
104 'enable_destroy_session' => '',
105 'limit_access' => '',
106 '2fa_settings_last_updated_by' => '',
107 '2fa_main_user' => '',
108 'grace-period-expiry-time' => '',
109 'plugin_version' => WP_2FA_VERSION,
110 'delete_data_upon_uninstall' => '',
111 'excluded_sites' => '',
112 'included_sites' => [],
113 'create-custom-user-page' => 'no',
114 'redirect-user-custom-page' => '',
115 'redirect-user-custom-page-global' => '',
116 'custom-user-page-url' => '',
117 'custom-user-page-id' => '',
118 'hide_remove_button' => '',
119 'grace-policy' => 'use-grace-period',
120 'superadmins-role-add' => 'no',
121 'superadmins-role-exclude' => 'no',
122 'default-text-code-page' => __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ),
123 );
124
125 return $default_settings;
126 }
127
128 /**
129 * Fire up classes.
130 */
131 public function init() {
132 // Bootstrap.
133 Core\setup();
134
135 $this->settings = new Admin\SettingsPage();
136 $this->wizard = new Admin\SetupWizard();
137 $this->authentication = new Authenticator\Authentication();
138 $this->backupcodes = new Authenticator\BackupCodes();
139 $this->login = new Authenticator\Login();
140 $this->user_notices = new Admin\UserNotices();
141 $this->crontasks = new Cron\CronTasks();
142 $this->user_registered = new Admin\UserRegistered();
143 $this->shortcodes = new Shortcodes\Shortcodes();
144
145 global $pagenow;
146 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
147 $this->user_profiles = new Admin\UserProfile();
148 }
149
150 if ( is_admin() ) {
151 UserListing::init();
152 }
153
154 $this->add_actions();
155 }
156
157 /**
158 * Add our plugins actions.
159 */
160 public function add_actions() {
161 // Plugin redirect on activation, only if we have no settings currently saved.
162 if ( empty( self::$wp_2fa_options ) || ! isset( self::$wp_2fa_options ) ) {
163 add_action( 'admin_init', array( $this, 'setup_redirect' ), 10 );
164 }
165
166 // SettingsPage.
167 if ( self::is_this_multisite() ) {
168 add_action( 'network_admin_menu', array( $this->settings, 'create_settings_admin_menu_multisite' ) );
169 add_action( 'network_admin_edit_update_wp2fa_network_options', array( $this->settings, 'update_wp2fa_network_options' ) );
170 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( $this->settings, 'update_wp2fa_network_email_options' ) );
171 add_action( 'network_admin_notices', array( $this->settings, 'settings_saved_network_admin_notice' ) );
172 } else {
173 add_action( 'admin_menu', array( $this->settings, 'create_settings_admin_menu' ) );
174 }
175 add_action( 'wp_ajax_get_all_users', array( $this->settings, 'get_all_users' ) );
176 add_action( 'wp_ajax_get_all_network_sites', array( $this->settings, 'get_all_network_sites' ) );
177 add_action( 'wp_ajax_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
178 add_action( 'admin_action_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
179 add_action( 'admin_action_remove_user_2fa', array( $this->settings, 'remove_user_2fa' ), 10, 1 );
180 add_action( 'wp_ajax_remove_user_2fa', array( $this->settings, 'remove_user_2fa' ), 10, 1 );
181 add_action( 'admin_menu', array( $this->settings, 'hide_settings' ), 999 );
182 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( $this->settings, 'add_plugin_action_links' ) );
183 add_filter( 'display_post_states', array( $this->settings, 'add_display_post_states' ), 10, 2 );
184 add_action( 'wp_ajax_wp_2fa_cancel_bg_processes', array( $this->settings, 'cancel_bg_processes' ) );
185
186 // SetupWizard.
187 if ( self::is_this_multisite() ) {
188 add_action( 'network_admin_menu', array( $this->wizard, 'network_admin_menus' ), 10 );
189 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
190 } else {
191 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
192 }
193 add_action( 'plugins_loaded', array( $this, 'add_wizard_actions' ), 10 );
194 add_action( 'wp_ajax_send_authentication_setup_email', array( $this->wizard, 'send_authentication_setup_email' ) );
195 add_action( 'wp_ajax_regenerate_authentication_key', array( $this->wizard, 'regenerate_authentication_key' ) );
196
197 // UserNotices.
198 add_action( 'wp_ajax_dismiss_nag', array( $this->user_notices, 'dismiss_nag' ) );
199 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( $this->user_notices, 'dismiss_nag' ) );
200 add_action( 'wp_logout', array( $this->user_notices, 'reset_nag' ), 10, 1 );
201
202 // UserProfile.
203 global $pagenow;
204 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
205 add_action( 'show_user_profile', array( $this->user_profiles, 'inline_2fa_profile_form' ) );
206 add_action( 'edit_user_profile', array( $this->user_profiles, 'inline_2fa_profile_form' ) );
207 if ( self::is_this_multisite() ) {
208 add_action( 'personal_options_update', array( $this->user_profiles, 'save_user_2fa_options' ) );
209 }
210 }
211 add_filter( 'user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
212 if ( self::is_this_multisite() ) {
213 add_filter( 'ms_user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
214 }
215 add_action( 'wp_ajax_validate_authcode_via_ajax', array( $this->user_profiles, 'validate_authcode_via_ajax' ) );
216 add_action( 'wp_ajax_wp2fa_test_email', array( $this, 'handle_send_test_email_ajax' ) );
217
218 // Login.
219 add_action( 'init', array( $this->login, 'get_providers' ) );
220 add_action( 'wp_login', array( $this->login, 'wp_login' ), 20, 2 );
221 add_action( 'login_form_validate_2fa', array( $this->login, 'login_form_validate_2fa' ) );
222 add_action( 'login_form_backup_2fa', array( $this->login, 'backup_2fa' ) );
223
224 /**
225 * Keep track of all the user sessions for which we need to invalidate the
226 * authentication cookies set during the initial password check.
227 */
228 add_action( 'set_auth_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
229 add_action( 'set_logged_in_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
230
231 // Run only after the core wp_authenticate_username_password() check.
232 add_filter( 'authenticate', array( $this->login, 'filter_authenticate' ), 50 );
233 add_filter( 'wp_authenticate_user', array( $this->login, 'run_authentication_check' ), 10, 2 );
234
235 // User Register.
236 add_action( 'set_user_role', array( $this->user_registered, 'check_user_upon_role_change' ), 10, 3 );
237
238 // Block users from admin if needed.
239 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
240 add_action( $user_block_hook, array( $this, 'block_unconfigured_users_from_admin' ), 10 );
241 // Check if usermeta is out of sync with settings.
242 add_action( $user_block_hook, array( $this, 'update_usermeta_if_required' ), 5 );
243 }
244
245 /**
246 * Add actions specific to the wizard.
247 */
248 public function add_wizard_actions() {
249 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
250 add_action( 'admin_init', array( $this->wizard, 'setup_page' ), 10 );
251 }
252 }
253
254 /**
255 * Redirect user to 1st time setup.
256 */
257 public function setup_redirect() {
258
259 // Bail early before the redirect if the user can't manage options.
260 if ( ! current_user_can( 'manage_options' ) ) {
261 return;
262 }
263
264 if ( SettingsUtils::get_option( 'redirect_on_activate', false ) ) {
265 // Delete redirect option.
266 SettingsUtils::delete_option( 'redirect_on_activate' );
267
268 SettingsUtils::update_option( 'wizard_not_finished', true );
269
270 $page = ( self::is_this_multisite() && is_super_admin() ) ? network_admin_url( 'index.php' ) : admin_url( 'options-general.php' );
271 $redirect = add_query_arg(
272 array(
273 'page' => 'wp-2fa-setup',
274 'is_initial_setup' => 'true',
275 ),
276 admin_url( 'user-edit.php' )
277 );
278
279 wp_safe_redirect( $redirect );
280 exit();
281 }
282 }
283
284 /**
285 * Check is this is a multisite setup.
286 */
287 public static function is_this_multisite() {
288 return function_exists( 'is_multisite' ) && is_multisite();
289 }
290
291 /**
292 * Return user roles.
293 *
294 * @return array User roles.
295 */
296 public static function wp_2fa_get_roles() {
297 global $wp_roles;
298 $roles = $wp_roles->role_names;
299 return $roles;
300 }
301
302 /**
303 * Check to see if the user or user role is excluded.
304 *
305 * @param int $user_id User id.
306 * @return boolean Is user excluded or not.
307 */
308 public static function is_user_excluded( $user_id, $excluded_users = '', $excluded_roles = '', $excluded_sites = '', $included_sites = [] ) {
309 $user = false;
310 $user_roles = false;
311
312 // If we have been passed an object, handle accordingly.
313 if ( is_a( $user_id, '\WP_User' ) ) {
314 $user = $user_id;
315 $user_roles = $user->roles;
316 }
317
318 // If we have an int instead, lets get the user data for that ID.
319 if ( is_numeric( $user_id ) || isset( $_GET['user_id'] ) && is_numeric( $user_id ) ) {
320 $user = get_user_by( 'id', $user_id );
321 $user_roles = $user->roles;
322 }
323
324 // Finally, we could have an array consisting of ID or user_login.
325 if ( is_array( $user_id ) && isset( $user_id['ID'] ) ) {
326 $user = get_user_by( 'id', $user_id['ID'] );
327 $user_roles = $user->roles;
328 }
329
330 // Finally, if we reach this point with no $user or $user_roles lets get the current user.
331 if ( ! $user || ! $user_roles ) {
332 $user = wp_get_current_user();
333 $user_roles = $user->roles;
334 }
335
336 $user_excluded = false;
337
338 if ( isset( $excluded_users ) && ! empty( $excluded_users ) ) {
339 $excluded_users = $excluded_users;
340 } else {
341 $excluded_users = WP2FA::get_wp2fa_setting( 'excluded_users' );
342 }
343
344 if ( ! empty( $excluded_users ) ) {
345 // Turn it into an array.
346 $excluded_users_array = $excluded_users;
347 // Compare our roles with the users and see if we get a match.
348 $result = in_array( $user->user_login, $excluded_users_array, true );
349 if ( $result ) {
350 $user_excluded = true;
351 return true;
352 }
353 }
354
355 if ( isset( $excluded_roles ) && ! empty( $excluded_roles ) ) {
356 $excluded_roles = $excluded_roles;
357 } else {
358 $excluded_roles = WP2FA::get_wp2fa_setting( 'excluded_roles' );
359 }
360
361 if ( ! empty( $excluded_roles ) ) {
362 // Turn it into an array.
363 $excluded_roles_array = array_map('strtolower', $excluded_roles );
364 // Compare our roles with the users and see if we get a match.
365 $result = array_intersect( $excluded_roles_array, $user->roles );
366 if ( $result ) {
367 $user_excluded = true;
368 return true;
369 }
370 }
371
372 if ( self::is_this_multisite() ) {
373 if ( isset( $excluded_sites ) && ! empty( $excluded_sites ) ) {
374 $excluded_sites = $excluded_sites;
375 } else {
376 $excluded_sites = WP2FA::get_wp2fa_setting( 'excluded_sites' );
377 }
378
379 if ( ! empty( $excluded_sites ) && is_array( $excluded_sites ) ) {
380
381 foreach ( $excluded_sites as $site_id ) {
382 if ( is_user_member_of_blog( $user->ID, $site_id ) ) {
383 // User is a member of the a blog we are excluding from 2FA.
384 $user_excluded = true;
385 return true;
386 } else {
387 // User is NOT a member of the a blog we are excluding.
388 $user_excluded = false;
389 }
390 }
391 }
392
393 if ( ! isset( $included_sites ) || empty( $included_sites ) ) {
394 $included_sites = WP2FA::get_wp2fa_setting( 'included_sites' );
395 }
396
397 foreach ( $included_sites as $siteId ) {
398 if ( is_user_member_of_blog( $user->ID, $siteId ) ) {
399 $user_excluded = false;
400 }
401 }
402 }
403
404 if ( true === $user_excluded ) {
405 return true;
406 }
407
408 return false;
409 }
410
411 /**
412 * Checks if user is enforced
413 *
414 * @since 1.6
415 *
416 * @param [type] $user_id
417 * @param string $current_policy
418 * @param string $excluded_users
419 * @param string $excluded_roles
420 * @param string $enforced_users
421 * @param string $enforced_roles
422 *
423 * @return boolean
424 */
425 public static function isUserEnforced( $user_id, $current_policy = '', $excluded_users = '', $excluded_roles = '', $enforced_users = '', $enforced_roles = '' ) {
426 if ( isset( $_GET['user_id'] ) ) {
427 $user_id = (int) $_GET['user_id'];
428 $user = get_user_by( 'id', $user_id );
429 $user_roles = $user->roles;
430 } elseif ( isset( $user_id ) ) {
431 $user = get_user_by( 'id', $user_id );
432 $user_roles = $user->roles;
433 } else {
434 $user = wp_get_current_user();
435 $user_roles = $user->roles;
436 }
437
438 if ( $current_policy ) {
439 $current_policy = $current_policy;
440 } else {
441 $current_policy = self::get_wp2fa_setting( 'enforcement-policy' );
442 }
443
444 $enabled_method = get_user_meta( $user->ID, WP_2FA_PREFIX . 'enabled_methods', true );
445 $user_eligable = false;
446
447 // Lets check the policy settings and if the user has setup totp/email by checking for the usermeta.
448 if ( empty( $enabled_method ) && self::is_this_multisite() && 'superadmins-only' === $current_policy ) {
449 return is_super_admin( $user->ID );
450 } elseif ( empty( $enabled_method ) && self::is_this_multisite() && 'superadmins-siteadmins-only' === $current_policy ) {
451 return is_super_admin( $user->ID ) || User::isAdminUser( $user->ID );
452 } else if ( 'all-users' === $current_policy && empty( $enabled_method ) ) {
453
454 if ( 'yes' === WP2FA::get_wp2fa_setting( 'superadmins-role-exclude' ) && is_super_admin( $user->ID ) ) {
455 return false;
456 }
457
458 if ( isset( $excluded_users ) ) {
459 $excluded_users = $excluded_users;
460 } else {
461 $excluded_users = self::get_wp2fa_setting( 'excluded_users' );
462 }
463
464 if ( ! empty( $excluded_users ) ) {
465 // Turn it into an array.
466 $excluded_users_array = explode( ',', $excluded_users );
467 // Compare our roles with the users and see if we get a match.
468 $result = in_array( $user->user_login, $excluded_users_array, true );
469 if ( ! $result ) {
470 $user_eligable = true;
471 }
472 }
473
474 if ( isset( $excluded_roles ) ) {
475 $excluded_roles = $excluded_roles;
476 } else {
477 $excluded_roles = self::get_wp2fa_setting( 'excluded_roles' );
478 }
479
480 if ( ! empty( $excluded_roles ) ) {
481 // Turn it into an array.
482 $excluded_roles_array = explode( ',', strtolower( $excluded_roles ) );
483 // Compare our roles with the users and see if we get a match.
484 $result = array_intersect( $excluded_roles_array, $user->roles );
485
486 if ( ! empty( $result ) ) {
487 $user_eligable = true;
488 }
489
490 if ( self::is_this_multisite() ) {
491 $users_caps = array();
492 $subsites = get_sites();
493 // Check each site and add to our array so we know each users actual roles.
494 foreach ( $subsites as $subsite ) {
495 $subsite_id = get_object_vars( $subsite )['blog_id'];
496 $users_caps[] = get_user_meta( $user->ID, 'wp_' .$subsite_id .'_capabilities', true );
497 }
498 // Strip the top layer ready.
499 $users_caps = $users_caps;
500 foreach ( $users_caps as $key => $value ) {
501 if ( ! empty( $value ) ) {
502 foreach ( $value as $key => $value ) {
503 $result = in_array( $key, $excluded_roles_array, true );
504 }
505 }
506 }
507 if ( ! empty( $result ) ) {
508 return false;
509 }
510 }
511 }
512
513 if ( true === $user_eligable || empty( $enabled_method ) ) {
514 return true;
515 }
516 } elseif ( 'certain-roles-only' === $current_policy && empty( $enabled_method ) ) {
517
518 if ( isset( $enforced_users ) && ! empty( $enforced_users ) ) {
519 $enforced_users = $enforced_users;
520 } else {
521 $enforced_users = self::get_wp2fa_setting( 'enforced_users' );
522 }
523
524 if ( ! empty( $enforced_users )) {
525 // Turn it into an array.
526 $enforced_users_array = $enforced_users;
527 // Compare our roles with the users and see if we get a match.
528 $result = in_array( $user->user_login, $enforced_users_array, true );
529 // The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
530 if ( ! empty( $result ) ) {
531 return true;
532 }
533 }
534
535 if ( isset( $enforced_roles ) && ! empty( $enforced_roles ) ) {
536 $enforced_roles = $enforced_roles;
537 } else {
538 $enforced_roles = self::get_wp2fa_setting( 'enforced_roles' );
539 }
540
541 if ( ! empty( $enforced_roles ) ) {
542 // Turn it into an array.
543 $enforced_roles_array = SettingsPage::extract_roles_from_input( $enforced_roles );
544 // Compare our roles with the users and see if we get a match.
545 $result = array_intersect( $enforced_roles_array, $user->roles );
546 // The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
547 if ( ! empty( $result ) ) {
548 return true;
549 }
550
551 if ( self::is_this_multisite() ) {
552 $users_caps = array();
553 $subsites = get_sites();
554 // Check each site and add to our array so we know each users actual roles.
555 foreach ( $subsites as $subsite ) {
556 $subsite_id = get_object_vars( $subsite )['blog_id'];
557 $users_caps[] = get_user_meta( $user->ID, 'wp_' .$subsite_id .'_capabilities', true );
558 }
559 // Strip the top layer ready.
560 $users_caps = $users_caps;
561 foreach ( $users_caps as $key => $value ) {
562 if ( ! empty( $value ) ) {
563 foreach ( $value as $key => $value ) {
564 $result = in_array( $key, $enforced_roles_array, true );
565 }
566 }
567 }
568 if ( ! empty( $result ) ) {
569 return true;
570 }
571 }
572 }
573
574 if ( WP2FA::get_wp2fa_setting( 'superadmins-role-add' ) ) {
575 return is_super_admin( $user->ID );
576 }
577
578 } elseif ( 'certain-users-only' === $current_policy && empty( $enabled_method ) ) {
579
580 if ( isset( $enforced_users ) && ! empty( $enforced_users ) ) {
581 $enforced_users = $enforced_users;
582 } else {
583 $enforced_users = self::get_wp2fa_setting( 'enforced_users' );
584 }
585
586 if ( ! empty( $enforced_users ) ) {
587 // Turn it into an array.
588 $enforced_users_array = explode( ',', $enforced_users );
589 // Compare our roles with the users and see if we get a match.
590 $result = in_array( $user->user_login, $enforced_users_array, true );
591 // The user is one of the chosen roles we are forcing 2FA onto, so lets show the nag.
592 if ( ! empty( $result ) ) {
593 return true;
594 }
595 }
596 } elseif ( 'enforce-on-multisite' === $current_policy ) {
597 $includedSites = self::get_wp2fa_setting( 'included_sites' );
598
599 foreach ( $includedSites as $site_id ) {
600 if ( is_user_member_of_blog( $user_id, $site_id ) ) {
601 return true;
602 }
603 }
604 }
605
606 return false;
607 }
608
609 /**
610 * Util function to grab settings or apply defaults if no settings are saved into the db.
611 *
612 * @param string $setting_name Settings to grab value of.
613 * @param boolean $getDefaultOnEmpty return default setting value if current one is empty
614 * @param boolean $getDefaultValue return default value setting (ignore the stored ones)
615 * @return string Settings value or default value.
616 */
617 public static function get_wp2fa_setting( $setting_name = '', $getDefaultOnEmpty = false, $getDefaultValue = false ) {
618 $default_settings = self::getDefaultSettings();
619
620 if ( true === $getDefaultValue ) {
621 if ( isset( $default_settings[ $setting_name ] ) ) {
622 return $default_settings[ $setting_name ];
623 }
624
625 return false;
626 }
627
628 $apply_defaults = false;
629
630 // If we have no setting name, return them all.
631 if ( empty( $setting_name ) ) {
632 return self::$wp_2fa_options;
633 }
634
635 // First lets check if any options have been saved.
636 if ( empty( self::$wp_2fa_options ) || ! isset( self::$wp_2fa_options ) ) {
637 $apply_defaults = true;
638 }
639
640 if ( $apply_defaults ) {
641 return $default_settings[ $setting_name ];
642 } elseif ( ! isset( self::$wp_2fa_options[ $setting_name ] ) ) {
643 if ( true === $getDefaultOnEmpty ) {
644 if ( isset( $default_settings[ $setting_name ] ) ) {
645 return $default_settings[ $setting_name ];
646 }
647 }
648 return false;
649 } else {
650 return self::$wp_2fa_options[ $setting_name ];
651 }
652 }
653
654 /**
655 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
656 *
657 * @param string $setting_name Settings to grab value of.
658 * @return string Settings value or default value.
659 */
660 public static function get_wp2fa_email_templates( $setting_name = '', $grab_default = '' ) {
661
662 $apply_defaults = false;
663
664 // First lets check if any options have been saved.
665 if ( empty( self::$wp_2fa_email_templates ) || ! isset( self::$wp_2fa_email_templates ) ) {
666 $apply_defaults = true;
667 }
668
669 // If we have no setting name, return what ever is saved.
670 if ( empty( $setting_name ) ) {
671 return self::$wp_2fa_email_templates;
672 }
673
674 // If we have a saved setting, return it.
675 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
676 return self::$wp_2fa_email_templates[ $setting_name ];
677 }
678
679 // Create Login Code Message.
680 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
681
682
683 $login_code_body = '<p>' . sprintf(
684 esc_html__( 'Enter %1$s to log in.', 'wp-2fa' ),
685 '<strong>{login_code}</strong>'
686 );
687 $login_code_body .= '</p>';
688 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
689 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
690 $login_code_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
691 $login_code_body .= '</p>';
692
693 // Create User Locked Message.
694 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
695
696 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
697 $user_locked_body .= '<p>' . sprintf(
698 esc_html__( 'Since you have not enabled two-factor authentication for the user %1$s on the website %2$s within the grace period, your account has been locked.', 'wp-2fa' ),
699 '{user_login_name}',
700 '{site_name}'
701 );
702 $user_locked_body .= '</p>';
703 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
704 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
705 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
706 $user_locked_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
707 $user_locked_body .= '</p>';
708
709 // Create User unlocked Message.
710 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
711 $user_unlocked_body = '';
712
713 $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>';
714
715 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
716 $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>';
717 }
718
719 $user_unlocked_body .='<p>'. __( 'Thank you.', 'wp-2fa' ) .'</p><p>'. __( 'Email sent by', 'wp-2fa' ) .' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">'. __( 'WP 2FA plugin', 'wp-2fa' ) .'</a></p>';
720
721 // Array of defaults, now we have things setup above.
722 $default_settings = array(
723 'email_from_setting' => 'use-defaults',
724 'custom_from_email_address' => '',
725 'custom_from_display_name' => '',
726 'login_code_email_subject' => $login_code_subject,
727 'login_code_email_body' => $login_code_body,
728 'user_account_locked_email_subject' => $user_locked_subject,
729 'user_account_locked_email_body' => $user_locked_body,
730 'user_account_unlocked_email_subject' => $user_unlocked_subject,
731 'user_account_unlocked_email_body' => $user_unlocked_body,
732 'send_account_locked_email' => 'enable_account_locked_email',
733 'send_account_unlocked_email' => 'enable_account_unlocked_email',
734 );
735
736 if ( $apply_defaults || ! empty( $grab_default ) ) {
737 return $default_settings[ $setting_name ];
738 }
739 }
740
741 /**
742 * Util which we use to replace our {strings} with actual, useful stuff.
743 *
744 * @param string $input Text we are working on.
745 * @param int|string $user_id User id, if its needed.
746 * @param string $token Login code, if its needed..
747 * @return string The output, with all the {strings} swapped out.
748 */
749 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
750
751 // Gather grace period.
752 $grace_period_string = '';
753 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
754 $grace_period_string = $override_grace_period;
755 } else {
756 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
757 $grace_period_string = DateTimeUtils::format_grace_period_expiration_string( $grace_policy );
758 }
759
760 // Setup user data.
761 if ( isset( $user_id ) && ! empty( $user_id ) ) {
762 $user = get_userdata( $user_id );
763 } else {
764 $user = wp_get_current_user();
765 }
766
767 // Setup token.
768 if ( isset( $token ) && ! empty( $token ) ) {
769 $login_code = $token;
770 } else {
771 $login_code = '';
772 }
773
774 $new_page_id = WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
775 if ( ! empty( $new_page_id ) ) {
776 $new_page_permalink = get_permalink( $new_page_id );
777 } else {
778 $new_page_permalink = '';
779 }
780
781 // These are the strings we are going to search for, as well as there respective replacements.
782 $replacements = array(
783 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
784 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
785 '{grace_period}' => sanitize_text_field( $grace_period_string ),
786 '{user_login_name}' => sanitize_text_field( $user->user_login ),
787 '{user_first_name}' => sanitize_text_field( $user->firstname ),
788 '{user_last_name}' => sanitize_text_field( $user->lastname ),
789 '{user_display_name}' => sanitize_text_field( $user->display_name ),
790 '{login_code}' => sanitize_text_field( $login_code ),
791 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
792 );
793
794 $replacements = apply_filters(
795 'wp_2fa_replacement_email_strings',
796 $replacements
797 );
798
799 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
800 return $final_output;
801 }
802
803 /**
804 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
805 */
806 public function block_unconfigured_users_from_admin() {
807 global $pagenow;
808
809 if ( 'use-grace-period' !== WP2FA::get_wp2fa_setting( 'grace-policy' ) ) {
810 $user = wp_get_current_user();
811 $is_user_instantly_enforced = get_user_meta( $user->ID, WP_2FA_PREFIX . 'user_enforced_instantly', true );
812 $grace_period_expiry_time = get_user_meta( $user->ID, WP_2FA_PREFIX . 'grace_period_expiry', true );
813 $time_now = time();
814 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! WP2FA::is_user_excluded( $user->ID ) ) {
815
816 /*
817 * We should only allow:
818 * - 2FA setup wizard in the administration
819 * - custom 2FA page if enabled and created
820 * - AJAX requests originating from these 2FA setup UIs
821 */
822 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], [ 'send_authentication_setup_email', 'validate_authcode_via_ajax', 'heartbeat' ] )) {
823 return;
824 }
825
826 if ( is_admin() || is_network_admin() ) {
827 $allowed_admin_page = 'profile.php';
828 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && $_GET['show'] === 'wp-2fa-setup' ) ) {
829 return;
830 }
831 }
832
833 if ( is_page() ) {
834 $custom_user_page_id = \WP2FA\WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
835 if ( !empty( $custom_user_page_id ) && get_the_ID() == (int) $custom_user_page_id ) {
836 return;
837 }
838 }
839
840 // force a redirect to the 2FA set-up page if it exists
841 $custom_user_page_id = \WP2FA\WP2FA::get_wp2fa_setting( 'custom-user-page-id' );
842 if ( !empty( $custom_user_page_id ) ) {
843 wp_redirect( Settings::getCustomPageLink() );
844 exit;
845 }
846
847 // custom 2FA page is not set-up, force redirect to the wizard in administration
848 wp_redirect( Settings::getSetupPageLink() );
849 exit;
850 }
851 }
852 }
853
854 /**
855 * Checks if user's settings hash matches the current one, and if not, updates it.
856 *
857 * @return void
858 * @since 1.7.0
859 */
860 public function update_usermeta_if_required() {
861
862 if ( wp_doing_ajax() || ! is_user_logged_in()) {
863 return;
864 }
865
866 $user = wp_get_current_user();
867 $users_settings_hash = get_user_meta( $user->ID, WP_2FA_PREFIX . 'global_settings_hash', true );
868 $current_settings_hash = SettingsUtils::get_option( 'settings_hash' );
869 if ( $users_settings_hash !== $current_settings_hash ) {
870 // Doing this envokes setUserPoliciesAndGrace and setGlobalSettingsHash in the User class.
871 new \WP2FA\Admin\User();
872 }
873 }
874
875 /**
876 * Handles AJAX calls for sending test emails.
877 */
878 public function handle_send_test_email_ajax() {
879
880 // check user permissions
881 if ( ! current_user_can( 'manage_options' ) ) {
882 wp_send_json_error();
883 }
884
885 // check email id
886 $email_id = filter_input(INPUT_POST, 'email_id', FILTER_SANITIZE_STRING);
887 if ($email_id === null || $email_id === false) {
888 wp_send_json_error();
889 }
890
891 // check nonce
892 $nonce = filter_input(INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING);
893 if ($nonce === null || $nonce === false || ! wp_verify_nonce($nonce, 'wp-2fa-email-test-' . $email_id)) {
894 wp_send_json_error();
895 }
896
897 $user_id = get_current_user_id();
898 // Grab user data.
899 $user = get_userdata( $user_id );
900 // Grab user email.
901 $email = $user->user_email;
902
903 if ('config_test' === $email_id) {
904 $email_sent = SettingsPage::send_email(
905 $email,
906 esc_html__('Test email from WP 2FA', 'wp-2fa'),
907 esc_html__('This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa')
908 );
909 if ( $email_sent ) {
910 wp_send_json_success('Test email was successfully sent to <strong>' . $email . '</strong>' );
911 }
912
913 wp_send_json_error();
914 }
915
916 /** @var EmailTemplate[] $email_templates */
917 $email_templates = $this->settings->get_email_notification_definitions();
918 foreach ($email_templates as $email_template) {
919 if ($email_id === $email_template->getEmailContentId()) {
920 // send the test email
921
922
923
924 // Setup the email contents.
925 $subject = wp_strip_all_tags( WP2FA::get_wp2fa_email_templates( $email_id . '_email_subject' ) );
926 $message = wpautop( WP2FA::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id );
927
928 $email_sent = SettingsPage::send_email( $email, $subject, $message );
929 if ( $email_sent ) {
930 wp_send_json_success('Test email <strong>' . $email_template->getTitle() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
931 }
932
933 wp_send_json_error();
934 }
935 }
936 }
937
938 /**
939 * Collects all the sites from multisite WP installation
940 *
941 * @return array
942 */
943 public static function getMultiSites() {
944 if ( self::is_this_multisite() ) {
945 if ( empty( self::$sites ) ) {
946
947 self::$sites = \get_sites();
948 }
949
950 return self::$sites;
951 }
952
953 return [];
954 }
955
956 /**
957 * Returns text with the number of plugins supported
958 *
959 * @since 1.6
960 *
961 * @return string
962 */
963 public static function getNumberOfPluginsText() {
964 $methodsCount = self::$methodsCount;
965
966 if ( \class_exists('NumberFormatter') ) {
967 $number_formatter = new \NumberFormatter( get_locale(), \NumberFormatter::SPELLOUT );
968 $methodsCount = $number_formatter->format( self::$methodsCount );
969 }
970
971 return
972 sprintf(
973 \_n(
974 'There is %s method available from which you can choose for 2FA:',
975 'There are %s methods available from which you can choose for 2FA:',
976 self::$methodsCount,
977 'wp-2fa'
978 ),
979 $methodsCount
980 );
981 }
982
983 /**
984 * Returns currently stored settings
985 *
986 * @return void
987 */
988 public static function getAllSettings() {
989 return self::$wp_2fa_options;
990 }
991 }
992