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