PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.0.1
WP 2FA – Two-factor authentication for WordPress v2.0.1
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 4 years ago Authenticator 4 years ago Cron 4 years ago Shortcodes 4 years ago Utils 4 years ago .gitkeep 6 years ago EmailTemplate.php 5 years ago WP2FA.php 4 years ago index.php 5 years ago
WP2FA.php
854 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\Admin\HelpContactUs;
9 use WP2FA\Utils\RequestUtils;
10 use WP2FA\Utils\DateTimeUtils;
11 use WP2FA\Authenticator\Open_SSL;
12 use WP2FA\Admin\Controllers\Settings;
13 use WP2FA\Utils\SettingsUtils as SettingsUtils;
14
15 /**
16 * Main WP2FA Class.
17 */
18 class WP2FA {
19
20 /**
21 * Plugin version.
22 *
23 * @var string
24 */
25 public $version = WP_2FA_VERSION;
26
27 /**
28 * Holds the global plugin secret key for storing the TOTP
29 *
30 * @var string
31 *
32 * @since 2.0.0
33 */
34 private static $secret_key = null;
35
36 /**
37 * Local static cache for plugins settings.
38 *
39 * @var array
40 *
41 * @since 2.0.0
42 */
43 private static $plugin_settings = array();
44
45 /**
46 * Local static cache for email template settings.
47 *
48 * @var array
49 */
50 protected static $wp_2fa_email_templates;
51
52 /**
53 * Count of the available methods
54 *
55 * @var integer
56 */
57 public static $methodsCount = 3;
58
59 /**
60 * Instance wrapper.
61 *
62 * @var WP2FA
63 */
64 private static $instance = null;
65
66 /**
67 * Holds array with all the sites in multisite WP installation
68 *
69 * @var array
70 */
71 private static $sites = [];
72
73 /**
74 * Return plugin instance.
75 */
76 public static function get_instance() {
77 if ( null === self::$instance ) {
78 self::$instance = new self();
79 }
80
81 return self::$instance;
82 }
83
84 /**
85 * Constructor.
86 */
87 private function __construct() {
88
89 self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = SettingsUtils::get_option( WP_2FA_POLICY_SETTINGS_NAME );
90 self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = SettingsUtils::get_option( WP_2FA_SETTINGS_NAME );
91 self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = SettingsUtils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME );
92
93 self::$wp_2fa_email_templates = SettingsUtils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
94
95 /** 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) */
96 if ( SettingsUtils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
97
98 if ( ! SettingsUtils::get_option( WP_2FA_SETTINGS_NAME ) ) {
99 self::updatePluginSettings( self::getDefaultSettings() );
100 }
101
102 // Set a flag so we know we have default values present, not custom.
103 SettingsUtils::update_option( 'default_settings_applied', true );
104 SettingsUtils::delete_option( 'wizard_not_finished' );
105 }
106
107 // Activation/Deactivation.
108 register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
109 register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
110 // Register our uninstallation hook.
111 register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
112 }
113
114 public static function getDefaultSettings() {
115 $default_settings = array(
116 'enable_totp' => 'enable_totp',
117 'enable_email' => 'enable_email',
118 'backup_codes_enabled' => 'yes',
119 'enforcement-policy' => 'do-not-enforce',
120 'excluded_users' => array(),
121 'excluded_roles' => array(),
122 'enforced_users' => array(),
123 'enforced_roles' => array(),
124 'grace-period' => 3,
125 'grace-period-denominator' => 'days',
126 'enable_grace_cron' => '',
127 'enable_destroy_session' => '',
128 'limit_access' => '',
129 '2fa_settings_last_updated_by' => '',
130 '2fa_main_user' => '',
131 'grace-period-expiry-time' => '',
132 'plugin_version' => WP_2FA_VERSION,
133 'delete_data_upon_uninstall' => '',
134 'excluded_sites' => '',
135 'included_sites' => array(),
136 'create-custom-user-page' => 'no',
137 'redirect-user-custom-page' => '',
138 'redirect-user-custom-page-global' => '',
139 'custom-user-page-url' => '',
140 'custom-user-page-id' => '',
141 'hide_remove_button' => '',
142 'grace-policy' => 'use-grace-period',
143 'superadmins-role-add' => 'no',
144 'superadmins-role-exclude' => 'no',
145 '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' ),
146 'email-code-period' => 5,
147 'specify-email_hotp' => '',
148 'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
149 );
150
151 $default_settings = apply_filters( 'wp_2fa_default_settings', $default_settings );
152
153 return $default_settings;
154 }
155
156 /**
157 * Fire up classes.
158 */
159 public function init() {
160 // Bootstrap.
161 Core\setup();
162
163 $this->settings = new Admin\SettingsPage();
164 $this->settings_email = new Admin\SettingsPages\Settings_Page_Email();
165 $this->wizard = new Admin\SetupWizard();
166 $this->authentication = new Authenticator\Authentication();
167 $this->backupcodes = new Authenticator\BackupCodes();
168 $this->login = new Authenticator\Login();
169 $this->user_notices = new Admin\UserNotices();
170 $this->crontasks = new Cron\CronTasks();
171 $this->user_registered = new Admin\UserRegistered();
172 $this->shortcodes = new Shortcodes\Shortcodes();
173 $this->helpcontactus = new Admin\HelpContactUs();
174 $this->premiumfeatures = new Admin\PremiumFeatures();
175
176 global $pagenow;
177 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
178 $this->user_profiles = new Admin\UserProfile();
179 }
180
181 if ( is_admin() ) {
182 UserListing::init();
183
184 }
185
186 $this->add_actions();
187
188 }
189
190 /**
191 * Add our plugins actions.
192 */
193 public function add_actions() {
194 // Plugin redirect on activation, only if we have no settings currently saved.
195 if ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) {
196 add_action( 'admin_init', array( $this, 'setup_redirect' ), 10 );
197 }
198
199 // SettingsPage.
200 if ( self::is_this_multisite() ) {
201 add_action( 'network_admin_menu', array( $this->settings, 'create_settings_admin_menu_multisite' ) );
202 add_action( 'network_admin_edit_update_wp2fa_network_options', array( $this->settings, 'update_wp2fa_network_options' ) );
203 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( $this->settings, 'update_wp2fa_network_email_options' ) );
204 add_action( 'network_admin_notices', array( $this->settings, 'settings_saved_network_admin_notice' ) );
205 } else {
206 add_action( 'admin_menu', array( $this->settings, 'create_settings_admin_menu' ) );
207 add_action( 'admin_notices', array( $this->settings, 'settings_saved_admin_notice' ) );
208 }
209 add_action( 'wp_ajax_get_all_users', array( $this->settings, 'get_all_users' ) );
210 add_action( 'wp_ajax_get_all_network_sites', array( $this->settings, 'get_all_network_sites' ) );
211 add_action( 'wp_ajax_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
212 add_action( 'admin_action_unlock_account', array( $this->settings, 'unlock_account' ), 10, 1 );
213 add_action( 'admin_action_remove_user_2fa', array( $this->settings, 'remove_user_2fa' ), 10, 1 );
214 add_action( 'wp_ajax_remove_user_2fa', array( $this->settings, 'remove_user_2fa' ), 10, 1 );
215 add_action( 'admin_menu', array( $this->settings, 'hide_settings' ), 999 );
216 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( $this->settings, 'add_plugin_action_links' ) );
217 add_filter( 'display_post_states', array( $this->settings, 'add_display_post_states' ), 10, 2 );
218
219 // SetupWizard.
220 if ( self::is_this_multisite() ) {
221 add_action( 'network_admin_menu', array( $this->wizard, 'network_admin_menus' ), 10 );
222 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
223 } else {
224 add_action( 'admin_menu', array( $this->wizard, 'admin_menus' ), 10 );
225 }
226 add_action( 'plugins_loaded', array( $this, 'add_wizard_actions' ), 10 );
227 add_action( 'wp_ajax_send_authentication_setup_email', array( $this->wizard, 'send_authentication_setup_email' ) );
228 add_action( 'wp_ajax_regenerate_authentication_key', array( $this->wizard, 'regenerate_authentication_key' ) );
229
230 // UserNotices.
231 add_action( 'wp_ajax_dismiss_nag', array( $this->user_notices, 'dismiss_nag' ) );
232 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( $this->user_notices, 'dismiss_nag' ) );
233 add_action( 'wp_logout', array( $this->user_notices, 'reset_nag' ), 10, 1 );
234
235 // UserProfile.
236 global $pagenow;
237 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
238 add_action( 'show_user_profile', array( $this->user_profiles, 'inline_2fa_profile_form' ) );
239 add_action( 'edit_user_profile', array( $this->user_profiles, 'inline_2fa_profile_form' ) );
240 if ( self::is_this_multisite() ) {
241 add_action( 'personal_options_update', array( $this->user_profiles, 'save_user_2fa_options' ) );
242 }
243 }
244 add_filter( 'user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
245 if ( self::is_this_multisite() ) {
246 add_filter( 'ms_user_row_actions', array( $this->user_profiles, 'user_2fa_row_actions' ), 10, 2 );
247 }
248 add_action( 'wp_ajax_validate_authcode_via_ajax', array( $this->user_profiles, 'validate_authcode_via_ajax' ) );
249 add_action( 'wp_ajax_wp2fa_test_email', array( $this, 'handle_send_test_email_ajax' ) );
250
251 // Login.
252 add_action( 'init', array( $this->login, 'get_providers' ) );
253 add_action( 'wp_login', array( $this->login, 'wp_login' ), 20, 2 );
254 add_action( 'login_form_validate_2fa', array( $this->login, 'login_form_validate_2fa' ) );
255 add_action( 'login_form_backup_2fa', array( $this->login, 'backup_2fa' ) );
256
257 /**
258 * Keep track of all the user sessions for which we need to invalidate the
259 * authentication cookies set during the initial password check.
260 */
261 add_action( 'set_auth_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
262 add_action( 'set_logged_in_cookie', array( $this->login, 'collect_auth_cookie_tokens' ) );
263
264 // Run only after the core wp_authenticate_username_password() check.
265 add_filter( 'authenticate', array( $this->login, 'filter_authenticate' ), 50 );
266 add_filter( 'wp_authenticate_user', array( $this->login, 'run_authentication_check' ), 10, 2 );
267
268 // User Register.
269 add_action( 'set_user_role', array( $this->user_registered, 'check_user_upon_role_change' ), 10, 3 );
270
271 // Block users from admin if needed.
272 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
273 add_action( $user_block_hook, array( $this, 'block_unconfigured_users_from_admin' ), 10 );
274 // Check if usermeta is out of sync with settings.
275 add_action( $user_block_hook, array( $this, 'update_usermeta_if_required' ), 5 );
276
277 // Help & Contact Us.
278 add_action( 'wp_2fa_after_admin_menu_created', array( $this->helpcontactus, 'add_extra_menu_item' ) );
279 // Premium Features.
280 add_action( 'wp_2fa_after_admin_menu_created', array( $this->premiumfeatures, 'add_extra_menu_item' ) );
281 }
282
283 /**
284 * Add actions specific to the wizard.
285 */
286 public function add_wizard_actions() {
287 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
288 add_action( 'admin_init', array( $this->wizard, 'setup_page' ), 10 );
289 }
290 }
291
292 /**
293 * Redirect user to 1st time setup.
294 */
295 public function setup_redirect() {
296
297 // Bail early before the redirect if the user can't manage options.
298 if ( ! current_user_can( 'manage_options' ) ) {
299 return;
300 }
301
302 $registered_and_active = 'yes';
303 if ( function_exists( 'wp2fa_freemius' ) ) {
304 $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
305 }
306
307 if ( SettingsUtils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
308 // Delete redirect option.
309 SettingsUtils::delete_option( 'redirect_on_activate' );
310
311 SettingsUtils::update_option( 'wizard_not_finished', true );
312
313 $redirect = add_query_arg(
314 array(
315 'page' => 'wp-2fa-setup',
316 'is_initial_setup' => 'true',
317 ),
318 admin_url( 'user-edit.php' )
319 );
320
321 wp_safe_redirect( $redirect );
322 exit();
323 }
324 }
325
326 /**
327 * Check is this is a multisite setup.
328 */
329 public static function is_this_multisite() {
330 return function_exists( 'is_multisite' ) && is_multisite();
331 }
332
333 /**
334 * Return user roles.
335 *
336 * @return array User roles.
337 */
338 public static function wp_2fa_get_roles() {
339 global $wp_roles;
340 return $wp_roles->role_names;
341 }
342
343 /**
344 * Util function to grab settings or apply defaults if no settings are saved into the db.
345 *
346 * @param string $setting_name Settings to grab value of.
347 * @param boolean $getDefaultOnEmpty return default setting value if current one is empty
348 * @param boolean $getDefaultValue return default value setting (ignore the stored ones)
349 * @return mixed Settings value or default value.
350 */
351 public static function get_wp2fa_setting( $setting_name = '', $getDefaultOnEmpty = false, $getDefaultValue = false, string $role = 'global' ) {
352
353 return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $getDefaultOnEmpty, $getDefaultValue, $role );
354 }
355
356 /**
357 * Util function to grab settings or apply defaults if no settings are saved into the db.
358 *
359 * @param string $setting_name Settings to grab value of.
360 * @param boolean $getDefaultOnEmpty return default setting value if current one is empty
361 * @param boolean $getDefaultValue return default value setting (ignore the stored ones)
362 * @return mixed Settings value or default value.
363 */
364 public static function get_wp2fa_general_setting( $setting_name = '', $getDefaultOnEmpty = false, $getDefaultValue = false ) {
365
366 return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $getDefaultOnEmpty, $getDefaultValue );
367 }
368
369 /**
370 * Util function to grab white label settings or apply defaults if no settings are saved into the db.
371 *
372 * @param string $setting_name Settings to grab value of.
373 * @param boolean $getDefaultOnEmpty return default setting value if current one is empty
374 * @param boolean $getDefaultValue return default value setting (ignore the stored ones)
375 * @return string Settings value or default value.
376 */
377 public static function get_wp2fa_white_label_setting( $setting_name = '', $getDefaultOnEmpty = false, $getDefaultValue = false ) {
378
379 return self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $getDefaultOnEmpty, $getDefaultValue );
380 }
381
382 private static function get_wp2fa_setting_generic( $wp_2fa_setting = WP_2FA_POLICY_SETTINGS_NAME, $setting_name = '', $get_default_on_empty = false, $get_default_value = false, string $role = 'global' ) {
383 $default_settings = self::getDefaultSettings();
384
385 if ( true === $get_default_value ) {
386 if ( isset( $default_settings[ $setting_name ] ) ) {
387 return $default_settings[ $setting_name ];
388 }
389
390 return false;
391 }
392
393 $apply_defaults = false;
394
395 $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
396
397 // If we have no setting name, return them all.
398 if ( empty( $setting_name ) ) {
399 return $wp2fa_setting;
400 }
401
402 // First lets check if any options have been saved.
403 if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
404 $apply_defaults = true;
405 }
406
407 if ( $apply_defaults ) {
408 return $default_settings[ $setting_name ];
409 } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
410 if ( true === $get_default_on_empty ) {
411 if ( isset( $default_settings[ $setting_name ] ) ) {
412 return $default_settings[ $setting_name ];
413 }
414 }
415 return false;
416 } else {
417
418 if ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
419 /**
420 * Extensions could change the extracted value, based on custom / different / specific for role settings.
421 *
422 * @param mixed - Value of the setting.
423 * @param string - The name of the setting.
424 * @param string - The role name.
425 *
426 * @since 2.0.0
427 */
428 return apply_filters( 'wp_2fa_setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
429 } else {
430 return $wp2fa_setting[ $setting_name ];
431 }
432 }
433 }
434
435 /**
436 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
437 *
438 * @param string $setting_name Settings to grab value of.
439 */
440 public static function get_wp2fa_email_templates( $setting_name = '' ) {
441
442 // If we have no setting name, return what ever is saved.
443 if ( empty( $setting_name ) ) {
444 return self::$wp_2fa_email_templates;
445 }
446
447 // If we have a saved setting, return it.
448 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
449 return self::$wp_2fa_email_templates[ $setting_name ];
450 }
451
452 // Create Login Code Message.
453 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
454
455
456 $login_code_body = '<p>' . sprintf(
457 esc_html__( 'Enter %1$s to log in.', 'wp-2fa' ),
458 '<strong>{login_code}</strong>'
459 );
460 $login_code_body .= '</p>';
461 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
462 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
463 $login_code_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
464 $login_code_body .= '</p>';
465
466 // Create User Locked Message.
467 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
468
469 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
470 $user_locked_body .= '<p>' . sprintf(
471 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' ),
472 '{user_login_name}',
473 '{site_name}'
474 );
475 $user_locked_body .= '</p>';
476 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
477 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
478 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
479 $user_locked_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
480 $user_locked_body .= '</p>';
481
482 // Create User unlocked Message.
483 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
484 $user_unlocked_body = '';
485
486 $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>';
487
488 if ( ! empty( WP2FA::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
489 $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>';
490 }
491
492 $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>';
493
494 // Array of defaults, now we have things setup above.
495 $default_settings = array(
496 'email_from_setting' => 'use-defaults',
497 'custom_from_email_address' => '',
498 'custom_from_display_name' => '',
499 'login_code_email_subject' => $login_code_subject,
500 'login_code_email_body' => $login_code_body,
501 'user_account_locked_email_subject' => $user_locked_subject,
502 'user_account_locked_email_body' => $user_locked_body,
503 'user_account_unlocked_email_subject' => $user_unlocked_subject,
504 'user_account_unlocked_email_body' => $user_unlocked_body,
505 'send_account_locked_email' => 'enable_account_locked_email',
506 'send_account_unlocked_email' => 'enable_account_unlocked_email',
507 );
508
509 /**
510 * Allows 3rd party providers to their own settings for the mail templates.
511 *
512 * @param array $default_settings - Array with the default settings.
513 *
514 * @since 2.0.0
515 */
516 $default_settings = apply_filters( 'wp_2fa_mail_default_settings', $default_settings );
517
518 return $default_settings[ $setting_name ];
519 }
520
521 /**
522 * Util which we use to replace our {strings} with actual, useful stuff.
523 *
524 * @param string $input Text we are working on.
525 * @param int|string $user_id User id, if its needed.
526 * @param string $token Login code, if its needed..
527 * @return string The output, with all the {strings} swapped out.
528 */
529 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
530
531 // Gather grace period.
532 $grace_period_string = '';
533 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
534 $grace_period_string = $override_grace_period;
535 } else {
536 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
537 $grace_period_string = DateTimeUtils::format_grace_period_expiration_string( $grace_policy );
538 }
539
540 // Setup user data.
541 if ( isset( $user_id ) && ! empty( $user_id ) ) {
542 $user = get_userdata( $user_id );
543 } else {
544 $user = wp_get_current_user();
545 }
546
547 // Setup token.
548 if ( isset( $token ) && ! empty( $token ) ) {
549 $login_code = $token;
550 } else {
551 $login_code = '';
552 }
553
554 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
555 if ( ! empty( $new_page_id ) ) {
556 $new_page_permalink = get_permalink( $new_page_id );
557 } else {
558 $new_page_permalink = '';
559 }
560
561 // These are the strings we are going to search for, as well as there respective replacements.
562 $replacements = array(
563 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
564 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
565 '{grace_period}' => sanitize_text_field( $grace_period_string ),
566 '{user_login_name}' => sanitize_text_field( $user->user_login ),
567 '{user_first_name}' => sanitize_text_field( $user->firstname ),
568 '{user_last_name}' => sanitize_text_field( $user->lastname ),
569 '{user_display_name}' => sanitize_text_field( $user->display_name ),
570 '{login_code}' => $login_code,
571 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
572 '{user_ip_address}' => RequestUtils::get_ip(),
573 );
574
575 $replacements = apply_filters(
576 'wp_2fa_replacement_email_strings',
577 $replacements
578 );
579
580 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
581 return $final_output;
582 }
583
584 /**
585 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
586 */
587 public function block_unconfigured_users_from_admin() {
588 global $pagenow;
589
590 $user = User::get_instance();
591 $is_user_instantly_enforced = $user->getEnforcedInstantly();
592 $grace_period_expiry_time = $user->getGracePeriodExpiration();
593 $time_now = time();
594 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User::is_excluded( $user->getUser()->ID ) ) {
595
596 /**
597 * We should only allow:
598 * - 2FA setup wizard in the administration
599 * - custom 2FA page if enabled and created
600 * - AJAX requests originating from these 2FA setup UIs
601 */
602 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) {
603 return;
604 }
605
606 if ( is_admin() || is_network_admin() ) {
607 $allowed_admin_page = 'profile.php';
608 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && $_GET['show'] === 'wp-2fa-setup' ) ) {
609 return;
610 }
611 }
612
613 if ( is_page() ) {
614 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user->getUser() );
615 if ( ! empty( $custom_user_page_id ) && get_the_ID() == (int) $custom_user_page_id ) {
616 return;
617 }
618 }
619
620 // force a redirect to the 2FA set-up page if it exists.
621 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user->getUser() );
622 if ( ! empty( $custom_user_page_id ) ) {
623 wp_redirect( Settings::get_custom_page_link( $user->getUser() ) );
624 exit;
625 }
626
627 // custom 2FA page is not set-up, force redirect to the wizard in administration.
628 wp_redirect( Settings::get_setup_page_link() );
629 exit;
630 }
631 }
632
633 /**
634 * Checks if user's settings hash matches the current one, and if not, updates it.
635 *
636 * @return void
637 * @since 1.7.0
638 */
639 public function update_usermeta_if_required() {
640 if ( wp_doing_ajax() || ! is_user_logged_in()) {
641 return;
642 }
643
644 // doing this invokes update of necessary user metadata in the User class.
645 User::get_instance();
646 }
647
648 /**
649 * Handles AJAX calls for sending test emails.
650 */
651 public function handle_send_test_email_ajax() {
652
653 // check user permissions
654 if ( ! current_user_can( 'manage_options' ) ) {
655 wp_send_json_error();
656 }
657
658 // check email id
659 $email_id = filter_input(INPUT_POST, 'email_id', FILTER_SANITIZE_STRING);
660 if ($email_id === null || $email_id === false) {
661 wp_send_json_error();
662 }
663
664 // check nonce
665 $nonce = filter_input(INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING);
666 if ($nonce === null || $nonce === false || ! wp_verify_nonce($nonce, 'wp-2fa-email-test-' . $email_id)) {
667 wp_send_json_error();
668 }
669
670 $user_id = get_current_user_id();
671 // Grab user data.
672 $user = get_userdata( $user_id );
673 // Grab user email.
674 $email = $user->user_email;
675
676 if ('config_test' === $email_id) {
677 $email_sent = SettingsPage::send_email(
678 $email,
679 esc_html__('Test email from WP 2FA', 'wp-2fa'),
680 esc_html__('This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa')
681 );
682 if ( $email_sent ) {
683 wp_send_json_success('Test email was successfully sent to <strong>' . $email . '</strong>' );
684 }
685
686 wp_send_json_error();
687 }
688
689 /** @var EmailTemplate[] $email_templates */
690 $email_templates = $this->settings_email->get_email_notification_definitions();
691 foreach ($email_templates as $email_template) {
692 if ($email_id === $email_template->getEmailContentId()) {
693 // send the test email
694
695
696
697 // Setup the email contents.
698 $subject = wp_strip_all_tags( WP2FA::get_wp2fa_email_templates( $email_id . '_email_subject' ) );
699 $message = wpautop( WP2FA::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id );
700
701 $email_sent = SettingsPage::send_email( $email, $subject, $message );
702 if ( $email_sent ) {
703 wp_send_json_success('Test email <strong>' . $email_template->getTitle() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
704 }
705
706 wp_send_json_error();
707 }
708 }
709 }
710
711 /**
712 * Collects all the sites from multisite WP installation
713 *
714 * @return array
715 */
716 public static function getMultiSites() {
717 if ( self::is_this_multisite() ) {
718 if ( empty( self::$sites ) ) {
719
720 self::$sites = \get_sites();
721 }
722
723 return self::$sites;
724 }
725
726 return [];
727 }
728
729 /**
730 * Returns text with the number of plugins supported
731 *
732 * @since 1.6
733 *
734 * @return string
735 */
736 public static function getNumberOfPluginsText() {
737 $methodsCount = self::$methodsCount;
738
739 if ( \class_exists('NumberFormatter') ) {
740 $number_formatter = new \NumberFormatter( get_locale(), \NumberFormatter::SPELLOUT );
741 $methodsCount = $number_formatter->format( self::$methodsCount );
742 }
743
744 return
745 sprintf(
746 \_n(
747 'There is %s method available from which you can choose for 2FA:',
748 'There are %s methods available from which you can choose for 2FA:',
749 self::$methodsCount,
750 'wp-2fa'
751 ),
752 $methodsCount
753 );
754 }
755
756 /**
757 * Returns currently stored settings
758 *
759 * @return void
760 */
761 public static function getPolicySettings() {
762 /**
763 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
764 *
765 * @param array - Value of the settings.
766 *
767 * @since 2.0.0
768 */
769 $settings = apply_filters( 'wp_2fa_policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
770
771 return $settings;
772 }
773 /**
774 * Checks the action parameter against given list of actions
775 *
776 * @return bool
777 *
778 * @since 2.0.0
779 */
780 private static function action_check() {
781 $actions_array = array(
782 'send_authentication_setup_email',
783 'validate_authcode_via_ajax',
784 'heartbeat',
785 );
786
787 /**
788 * Allows 3rd party providers to their own settings for the mail templates.
789 *
790 * @param array $actions_array - Array with the default settings.
791 *
792 * @since 2.0.0
793 */
794 $actions_array = apply_filters( 'wp_2fa_actions_check', $actions_array );
795
796 return in_array( $_REQUEST['action'], $actions_array );
797 }
798
799 /**
800 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
801 *
802 * @param array $settings
803 * @param bool $skip_option_save If true, the settings themselves are not saved. This is needed when saving settings from settings page as WordPress options API takes care of that.
804 *
805 * @since 2.0.0
806 */
807 public static function updatePluginSettings( $settings, $skip_option_save = false, $settings_name=WP_2FA_POLICY_SETTINGS_NAME ) {
808 // update local copy of settings.
809 self::$plugin_settings[ $settings_name ] = $settings;
810
811 if ( ! $skip_option_save ) {
812 // update the database option itself.
813 SettingsUtils::update_option( $settings_name, $settings );
814 }
815
816 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
817 // Create a hash for comparison when we interact with a use.
818 $settings_hash = SettingsUtils::create_settings_hash( self::getPolicySettings() );
819 SettingsUtils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
820 }
821 }
822
823 /**
824 * Getter for the TOTP secret key of the plugin for the current instance
825 *
826 * @return string
827 *
828 * @since 2.0.0
829 */
830 public static function get_secret_key() {
831 if ( null === self::$secret_key ) {
832 self::$secret_key = SettingsUtils::get_option('secret_key');
833 if ( empty( self::$secret_key ) ) {
834 self::$secret_key = base64_encode( Open_SSL::secure_random() );
835 SettingsUtils::update_option( 'secret_key', self::$secret_key );
836 }
837 }
838
839 return self::$secret_key;
840 }
841
842 /**
843 * Returns message for the WP Mail SMTP plugin usage suggestion
844 *
845 * @return string
846 *
847 * @since 2.0.0
848 */
849 public static function print_email_deliverability_message() {
850
851 return sprintf( '%1$s <a href="https://wordpress.org/plugins/wp-mail-smtp/" target="_blank">%2$s</a>.', esc_html__( 'To ensure emails are delivered so users do not have problems logging in, we recommend using the free plugin', 'wp-2fa' ), esc_html__( 'WP Mail SMTP', 'wp-2fa' ) );
852 }
853 }
854