PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.2.0
WP 2FA – Two-factor authentication for WordPress v1.2.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 6 years ago Authenticator 6 years ago Cron 6 years ago .gitkeep 6 years ago WP2FA.php 6 years ago
WP2FA.php
532 lines
1 <?php // phpcs:ignore
2
3 namespace WP2FA;
4
5 /**
6 * Main WP2FA Class.
7 */
8 class WP2FA {
9
10 /**
11 * Plugin version.
12 *
13 * @var string
14 */
15 public $version = WP_2FA_VERSION;
16
17 /**
18 * Options variables.
19 *
20 * @var array
21 */
22 protected static $wp_2fa_options;
23 protected static $wp_2fa_email_templates;
24
25 /**
26 * Instance wrapper.
27 *
28 * @var object
29 */
30 private static $instance = null;
31
32 /**
33 * Return plugin instance.
34 */
35 public static function get_instance() {
36
37 if ( null === self::$instance ) {
38 self::$instance = new self();
39 }
40
41 return self::$instance;
42 }
43
44 /**
45 * Contructor.
46 */
47 private function __construct() {
48 if ( self::is_this_multisite() ) {
49 self::$wp_2fa_options = get_network_option( null, 'wp_2fa_settings' );
50 self::$wp_2fa_email_templates = get_network_option( null, 'wp_2fa_email_settings' );
51 } else {
52 self::$wp_2fa_options = get_option( 'wp_2fa_settings' );
53 self::$wp_2fa_email_templates = get_option( 'wp_2fa_email_settings' );
54 }
55
56 // Activation/Deactivation.
57 register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
58 register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
59 // Register our uninstallation hook.
60 register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
61 }
62
63 /**
64 * Fire up classes.
65 */
66 public function init() {
67 // Bootstrap.
68 Core\setup();
69
70 $this->settings = new Admin\SettingsPage();
71 $this->wizard = new Admin\SetupWizard();
72 $this->authentication = new Authenticator\Authentication();
73 $this->backupcodes = new Authenticator\BackupCodes();
74 $this->login = new Authenticator\Login();
75 $this->user_notices = new Admin\UserNotices();
76 $this->crontasks = new Cron\CronTasks();
77 $this->user_registered = new Admin\UserRegistered();
78
79 global $pagenow;
80 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
81 $this->user_profiles = new Admin\UserProfile();
82 }
83
84 $this->add_actions();
85 }
86
87 /**
88 * Add our plugins actions.
89 */
90 public function add_actions() {
91 // Plugin redirect on activation, only if we have no settings currently saved.
92 if ( empty( self::$wp_2fa_options ) || ! isset( self::$wp_2fa_options ) ) {
93 add_action( 'admin_init', array( $this, 'setup_redirect' ), 10 );
94 }
95
96 // SettingsPage.
97 if ( self::is_this_multisite() ) {
98 add_action( 'network_admin_menu', array( $this->settings, 'create_settings_admin_menu_multisite' ) );
99 add_action( 'network_admin_edit_update_wp2fa_network_options', array( $this->settings, 'update_wp2fa_network_options' ) );
100 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( $this->settings, 'update_wp2fa_network_email_options' ) );
101 add_action( 'network_admin_notices', array( $this->settings, 'settings_saved_network_admin_notice' ) );
102 } else {
103 add_action( 'admin_menu', array( $this->settings, 'create_settings_admin_menu' ) );
104 }
105 add_action( 'wp_ajax_get_all_users', array( $this->settings, 'get_all_users' ) );
106 add_action( 'wp_ajax_get_all_network_sites', array( $this->settings, 'get_all_network_sites' ) );
107 add_action( 'wp_ajax_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
108 add_action( 'admin_action_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
109 add_action( 'admin_action_remove_user_2fa', array( $this->settings, 'remove_user_2fa' ), 10, 1 );
110 add_action( 'admin_menu', array( $this->settings, 'hide_settings' ), 999 );
111 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( $this->settings, 'add_plugin_action_links' ) );
112
113 // SetupWizard.
114 if ( self::is_this_multisite() ) {
115 add_action( 'network_admin_menu', array( $this->wizard, 'network_admin_menus' ), 10 );
116 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
117 } else {
118 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
119 }
120 add_action( 'plugins_loaded', array( $this, 'add_wizard_actions' ), 10 );
121 add_action( 'wp_ajax_send_authentication_setup_email', array( $this->wizard, 'send_authentication_setup_email' ) );
122 add_action( 'wp_ajax_regenerate_authentication_key', array( $this->wizard, 'regenerate_authentication_key' ) );
123
124 // UserNotices.
125 add_action( 'wp_ajax_dismiss_nag', array( $this->user_notices, 'dismiss_nag' ) );
126 add_action( 'wp_ajax_dismiss_nag', array( $this->user_notices, 'dismiss_reconfigure_nag' ) );
127 add_action( 'clear_auth_cookie', array( $this->user_notices, 'reset_nag' ) );
128
129 // UserProfile.
130 global $pagenow;
131 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
132 add_action( 'show_user_profile', array( $this->user_profiles, 'user_2fa_options' ) );
133 add_action( 'edit_user_profile', array( $this->user_profiles, 'user_2fa_options' ) );
134 if ( self::is_this_multisite() ) {
135 add_action( 'personal_options_update', array( $this->user_profiles, 'save_user_2fa_options' ) );
136 }
137 }
138 add_filter( 'user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
139 if ( self::is_this_multisite() ) {
140 add_filter( 'ms_user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
141 }
142 add_action( 'wp_ajax_validate_authcode_via_ajax', array( $this->user_profiles, 'validate_authcode_via_ajax' ) );
143
144 // Login.
145 add_action( 'init', array( $this->login, 'get_providers' ) );
146 add_action( 'wp_login', array( $this->login, 'wp_login' ), 10, 2 );
147 add_action( 'login_form_validate_2fa', array( $this->login, 'login_form_validate_2fa' ) );
148 add_action( 'login_form_backup_2fa', array( $this->login, 'backup_2fa' ) );
149
150 /**
151 * Keep track of all the user sessions for which we need to invalidate the
152 * authentication cookies set during the initial password check.
153 */
154 add_action( 'set_auth_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
155 add_action( 'set_logged_in_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
156
157 // Run only after the core wp_authenticate_username_password() check.
158 add_filter( 'authenticate', array( $this->login, 'filter_authenticate' ), 50 );
159 add_filter( 'wp_authenticate_user', array( $this->login, 'is_user_locked' ), 10, 2 );
160
161 // User Register.
162 add_action( 'set_user_role', array( $this->user_registered, 'check_user_upon_role_change' ), 10, 3 );
163 }
164
165 /**
166 * Add actions specific to the wizard.
167 */
168 public function add_wizard_actions() {
169 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
170 add_action( 'admin_init', array( $this->wizard, 'setup_page' ), 10 );
171 }
172 }
173
174 /**
175 * Redirect user to 1st time setup.
176 */
177 public function setup_redirect() {
178
179 // Bail early before the redirect if the user can't manage options.
180 if ( ! current_user_can( 'manage_options' ) ) {
181 return;
182 }
183
184 if ( get_option( 'wp_2fa_redirect_on_activate', false ) ) {
185 // Delete redirect option.
186 delete_option( 'wp_2fa_redirect_on_activate' );
187
188 // Redirect URL.
189 $redirect = '';
190
191 // If current site is multisite and user is super-admin then redirect to network audit log.
192 if ( self::is_this_multisite() && is_super_admin() ) {
193 $redirect = add_query_arg( 'page', 'wp-2fa-setup', network_admin_url( 'index.php' ) );
194 } else {
195 // Otherwise redirect to main audit log view.
196 $redirect = add_query_arg( 'page', 'wp-2fa-setup', admin_url( 'options-general.php' ) );
197 }
198 wp_safe_redirect( $redirect );
199 exit();
200 }
201 }
202
203 /**
204 * Check is this is a multisite setup.
205 */
206 public static function is_this_multisite() {
207 return function_exists( 'is_multisite' ) && is_multisite();
208 }
209
210 /**
211 * Return user roles.
212 *
213 * @return array User roles.
214 */
215 public static function wp_2fa_get_roles() {
216 global $wp_roles;
217 $roles = $wp_roles->role_names;
218 return $roles;
219 }
220
221 /**
222 * Check to see if the user or user role is excluded.
223 *
224 * @param int $user_id User id.
225 * @return boolean Is user excluded or not.
226 */
227 public static function is_user_excluded( $user_id, $excluded_users = '', $excluded_roles = '', $excluded_sites = '' ) {
228
229 // Check if the $user_id is actually an object, if so lets just use it.
230 if ( ! is_a( $user_id, '\WP_User' ) ) {
231 if ( isset( $user_id ) ) {
232 $user = get_user_by( 'id', $user_id );
233 $user_roles = $user->roles;
234 } elseif ( isset( $_GET['user_id'] ) ) {
235 $user_id = (int) $_GET['user_id'];
236 $user = get_user_by( 'id', $user_id );
237 $user_roles = $user->roles;
238 } else {
239 $user = wp_get_current_user();
240 $user_roles = $user->roles;
241 }
242 } else {
243 $user = $user_id;
244 $user_roles = $user->roles;
245 }
246
247 $user_excluded = false;
248
249 if ( isset( $excluded_users ) && ! empty( $excluded_users ) ) {
250 $excluded_users = $excluded_users;
251 } else {
252 $excluded_users = WP2FA::get_wp2fa_setting( 'excluded_users' );
253 }
254
255 if ( ! empty( $excluded_users ) ) {
256 // Turn it into an array.
257 $excluded_users_array = explode( ',', $excluded_users );
258 // Compare our roles with the users and see if we get a match.
259 $result = in_array( $user->user_login, $excluded_users_array, true );
260 if ( $result ) {
261 $user_excluded = true;
262 return true;
263 }
264 }
265
266 if ( isset( $excluded_roles ) && ! empty( $excluded_roles ) ) {
267 $excluded_roles = $excluded_roles;
268 } else {
269 $excluded_roles = WP2FA::get_wp2fa_setting( 'excluded_roles' );
270 }
271
272 if ( ! empty( $excluded_roles ) ) {
273 // Turn it into an array.
274 $excluded_roles_array = explode( ',', strtolower( $excluded_roles ) );
275 // Compare our roles with the users and see if we get a match.
276 $result = array_intersect( $excluded_roles_array, $user->roles );
277 if ( $result ) {
278 $user_excluded = true;
279 return true;
280 }
281 }
282
283 if ( self::is_this_multisite() ) {
284 if ( isset( $excluded_sites ) && ! empty( $excluded_sites ) ) {
285 $excluded_sites = $excluded_sites;
286 } else {
287 $excluded_sites = WP2FA::get_wp2fa_setting( 'excluded_sites' );
288 }
289
290 if ( ! empty( $excluded_sites ) ) {
291 // Turn it into an array.
292 $excluded_site_array = explode( ',', strtolower( $excluded_sites ) );
293 $site_ids_only = array();
294 // Remove everything but the excluded sites ID, which we will use to check if user is a member.
295 foreach ( $excluded_site_array as $excluded_site ) {
296 if ( isset( explode( ':', $excluded_site )[1] ) ) {
297 $id = trim( explode( ':', $excluded_site )[1] );
298 $site_ids_only[] = $id;
299 }
300 }
301 foreach ( $site_ids_only as $site_id ) {
302 if ( is_user_member_of_blog( $user->ID, $site_id ) ) {
303 // User is a member of the a blog we are excluding from 2FA.
304 $user_excluded = true;
305 return true;
306 } else {
307 // User is NOT a member of the a blog we are excluding.
308 $user_excluded = false;
309 }
310 }
311 }
312 }
313
314 if ( true === $user_excluded ) {
315 return true;
316 }
317
318 return false;
319 }
320
321 /**
322 * Util function to grab settings or apply defaults if no settings are saved into the db.
323 *
324 * @param string $setting_name Settings to grab value of.
325 * @return string Settings value or default value.
326 */
327 public static function get_wp2fa_setting( $setting_name = '' ) {
328 $default_settings = array(
329 'enable_totp' => 'enable_totp',
330 'enable_email' => 'enable_email',
331 'enforcment-policy' => 'do-not-enforce',
332 'excluded_users' => '',
333 'excluded_roles' => '',
334 'enforced_users' => '',
335 'enforced_roles' => '',
336 'grace-period' => 3,
337 'grace-period-denominator' => 'days',
338 'enable_grace_cron' => '',
339 'enable_destroy_session' => '',
340 'limit_access' => '',
341 '2fa_settings_last_updated_by' => '',
342 '2fa_main_user' => '',
343 'grace-period-expiry-time' => '',
344 'plugin_version' => WP_2FA_VERSION,
345 'delete_data_upon_uninstall' => '',
346 'excluded_sites' => '',
347 );
348
349 $apply_defaults = false;
350
351 // If we have no setting name, return them all.
352 if ( empty( $setting_name ) ) {
353 return self::$wp_2fa_options;
354 }
355
356 // First lets check if any options have been saved.
357 if ( empty( self::$wp_2fa_options ) || ! isset( self::$wp_2fa_options ) ) {
358 $apply_defaults = true;
359 }
360
361 if ( $apply_defaults ) {
362 return $default_settings[ $setting_name ];
363 } elseif ( ! isset( self::$wp_2fa_options[ $setting_name ] ) ) {
364 return false;
365 } else {
366 return self::$wp_2fa_options[ $setting_name ];
367 }
368 }
369
370 /**
371 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
372 *
373 * @param string $setting_name Settings to grab value of.
374 * @return string Settings value or default value.
375 */
376 public static function get_wp2fa_email_templates( $setting_name = '' ) {
377
378 // Setup enable 2fa email.
379 $enable_2fa_message = sprintf(
380 /* translators: %1$s: is the user name, %2$s is the website address */
381 '<p>%1$s %2$s %3$s <strong>%4$s</strong> %5$s <strong>%6$s.</strong></p><p>%7$s</p><p>%8$s</p><p>%9$s <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">%10$s</a></p>',
382 esc_html__( 'The administrator of the website', 'wp-2fa' ),
383 '{site_url}',
384 esc_html__( 'enforced two-factor authentication. You have', 'wp-2fa' ),
385 '{grace_period}',
386 esc_html__( 'to enable and configure 2FA on your WordPress user', 'wp-2fa' ),
387 '{user_login_name}',
388 esc_html__( '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' ),
389 esc_html__( 'Thank you.', 'wp-2fa' ),
390 esc_html__( 'Email sent by', 'wp-2fa' ),
391 esc_html__( 'WP 2FA plugin.', 'wp-2fa' )
392 );
393
394 $enable_2fa_subject = __( 'Please enable 2FA on {site_url}', 'wp-2fa' );
395 $enable_2fa_body = $enable_2fa_message;
396
397 // Setup login code email.
398 $login_code_message = sprintf(
399 /* translators: %s: the token for the user to use */
400 '<p>%1$s <strong>%2$s</strong> %3$s</p><p>%4$s</p><p>%5$s <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">%6$s</a></p>',
401 esc_html__( 'Enter', 'wp-2fa' ),
402 '{login_code}',
403 esc_html__( 'to log in.', 'wp-2fa' ),
404 esc_html__( 'Thank you.', 'wp-2fa' ),
405 esc_html__( 'Email sent by', 'wp-2fa' ),
406 esc_html__( 'WP 2FA plugin.', 'wp-2fa' )
407 );
408
409 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
410 $login_code_body = $login_code_message;
411
412 // Setup user locked email.
413 $user_locked_message = sprintf(
414 /* translators: %1$s: is the user name, %2$s is the website name */
415 '<p>%1$s</p><p>%2$s %3$s %4$s %5$s %6$s</p><p>%7$s</p><p>%8$s</p><p>%9$s <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">%10$s</a></p>',
416 esc_html__( 'Hello.', 'wp-2fa' ),
417 esc_html__( 'Since you have not enabled two-factor authentication for the user', 'wp-2fa' ),
418 '{user_login_name}',
419 esc_html__( 'on the website', 'wp-2fa' ),
420 '{site_name}',
421 esc_html__( 'within the grace period, your account has been locked.', 'wp-2fa' ),
422 esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ),
423 esc_html__( 'Thank you.', 'wp-2fa' ),
424 esc_html__( 'Email sent by', 'wp-2fa' ),
425 esc_html__( 'WP 2FA plugin.', 'wp-2fa' )
426 );
427
428 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
429 $user_locked_body = $user_locked_message;
430
431 // Setup user unlocked email.
432 $user_unlocked_message = sprintf(
433 /* translators: %1$s: is the user name, %2$s is the website address */
434 '<p>%1$s</p><p>%2$s <strong>%3$s</strong> %4$s %5$s %6$s</p><p>%7$s</p><p>%8$s <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">%9$s</a></p>',
435 esc_html__( 'Hello,', 'wp-2fa' ),
436 esc_html__( 'Your user', 'wp-2fa' ),
437 '{user_login_name}',
438 esc_html__( 'on the website', 'wp-2fa' ),
439 '{site_url}',
440 esc_html__( 'has been unlocked. Please configure two-factor authentication within the grace period, otherwise your account will be locked again.', 'wp-2fa' ),
441 esc_html__( 'Thank you.', 'wp-2fa' ),
442 esc_html__( 'Email sent by', 'wp-2fa' ),
443 esc_html__( 'WP 2FA plugin', 'wp-2fa' )
444 );
445
446 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
447 $user_unlocked_body = $user_unlocked_message;
448
449 // Array of defaults, now we have things setup above.
450 $default_settings = array(
451 'email_from_setting' => 'use-defaults',
452 'custom_from_email_address' => '',
453 'custom_from_display_name' => '',
454 'enforced_email_subject' => $enable_2fa_subject,
455 'enforced_email_body' => $enable_2fa_body,
456 'login_code_email_subject' => $login_code_subject,
457 'login_code_email_body' => $login_code_body,
458 'user_account_locked_email_subject' => $user_locked_subject,
459 'user_account_locked_email_body' => $user_locked_body,
460 'user_account_unlocked_email_subject' => $user_unlocked_subject,
461 'user_account_unlocked_email_body' => $user_unlocked_body,
462 );
463
464 $apply_defaults = false;
465
466 // If we have no setting name, return them all.
467 if ( empty( $setting_name ) ) {
468 return self::$wp_2fa_email_templates;
469 }
470
471 // First lets check if any options have been saved.
472 if ( empty( self::$wp_2fa_email_templates ) || ! isset( self::$wp_2fa_email_templates ) ) {
473 $apply_defaults = true;
474 }
475
476 if ( $apply_defaults ) {
477 return $default_settings[ $setting_name ];
478 } elseif ( ! isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
479 return false;
480 } else {
481 return self::$wp_2fa_email_templates[ $setting_name ];
482 }
483 }
484
485 /**
486 * Util which we use to replace our {strings} with actual, useful stuff.
487 *
488 * @param string $input Text we are working on.
489 * @param int $user_id User id, if its needed.
490 * @param string $token Login code, if its needed..
491 * @return string The output, with all the {strings} swapped out.
492 */
493 public static function replace_email_strings( $input = '', $user_id = '', $token = '' ) {
494
495 // Gather grace period.
496 $grace_period = self::get_wp2fa_setting( 'grace-period' );
497 $grace_period_denominator = self::get_wp2fa_setting( 'grace-period-denominator' );
498 $grace_period = $grace_period . ' ' . $grace_period_denominator;
499
500 // Setup user data.
501 if ( isset( $user_id ) && ! empty( $user_id ) ) {
502 $user = get_userdata( $user_id );
503 } else {
504 $user = wp_get_current_user();
505 }
506
507 // Setup token.
508 if ( isset( $token ) && ! empty( $token ) ) {
509 $login_code = $token;
510 } else {
511 $login_code = '';
512 }
513
514 // These are the strings we are going to search for, as well as there respective replacements.
515 $replacements = array(
516 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
517 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
518 '{grace_period}' => sanitize_text_field( $grace_period ),
519 '{user_login_name}' => sanitize_text_field( $user->user_login ),
520 '{login_code}' => sanitize_text_field( $login_code ),
521 );
522
523 $replacements = apply_filters(
524 'wp_2fa_replacement_email_strings',
525 $replacements
526 );
527
528 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
529 return $final_output;
530 }
531 }
532