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