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