PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.4.0
WP 2FA – Two-factor authentication for WordPress v2.4.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 App 3 years ago Authenticator 3 years ago Cron 3 years ago Shortcodes 3 years ago Utils 3 years ago class-email-template.php 3 years ago class-wp2fa.php 3 years ago index.php 5 years ago
class-wp2fa.php
1042 lines
1 <?php
2 /**
3 * Main plugin class.
4 *
5 * @package wp2fa
6 * @copyright 2023 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\User_Notices;
16 use WP2FA\Admin\Settings_Page;
17 use WP2FA\Utils\Request_Utils;
18 use WP2FA\Shortcodes\Shortcodes;
19 use WP2FA\Utils\Date_Time_Utils;
20 use WP2FA\Authenticator\Open_SSL;
21 use WP2FA\Admin\Helpers\WP_Helper;
22 use WP2FA\Freemius\User_Licensing;
23 use WP2FA\Freemius\Freemius_Helper;
24 use WP2FA\Admin\Controllers\Methods;
25 use WP2FA\Admin\Helpers\File_Writer;
26 use WP2FA\Admin\Helpers\User_Helper;
27 use WP2FA\Admin\Controllers\Settings;
28 use WP2FA\Admin\Helpers\Classes_Helper;
29 use WP2FA\Authenticator\Backup_Codes;
30 use WP2FA\Utils\Settings_Utils as Settings_Utils;
31 use WP2FA\Admin\SettingsPages\Settings_Page_Email;
32
33 /**
34 * Main WP2FA Class.
35 */
36 class WP2FA {
37
38 /**
39 * Holds the global plugin secret key for storing the TOTP
40 *
41 * @var string
42 *
43 * @since 2.0.0
44 */
45 private static $secret_key = null;
46
47 /**
48 * Local static cache for plugins settings.
49 *
50 * @var array
51 *
52 * @since 2.0.0
53 */
54 private static $plugin_settings = array();
55
56 /**
57 * Local static cache for email template settings.
58 *
59 * @var array
60 */
61 protected static $wp_2fa_email_templates;
62
63 /**
64 * Array with all the plugin default settings.
65 *
66 * @return array
67 *
68 * @since 2.2.0
69 */
70 public static function get_default_settings() {
71 $default_settings = array(
72 'enable_totp' => 'enable_totp',
73 'enable_email' => 'enable_email',
74 'backup_codes_enabled' => 'yes',
75 'enforcement-policy' => 'do-not-enforce',
76 'excluded_users' => array(),
77 'excluded_roles' => array(),
78 'enforced_users' => array(),
79 'enforced_roles' => array(),
80 'grace-period' => 3,
81 'grace-period-denominator' => 'days',
82 'enable_destroy_session' => '',
83 'limit_access' => '',
84 '2fa_settings_last_updated_by' => '',
85 '2fa_main_user' => '',
86 'grace-period-expiry-time' => '',
87 'plugin_version' => WP_2FA_VERSION,
88 'delete_data_upon_uninstall' => '',
89 'excluded_sites' => '',
90 'included_sites' => array(),
91 'create-custom-user-page' => 'no',
92 'redirect-user-custom-page' => '',
93 'redirect-user-custom-page-global' => '',
94 'custom-user-page-url' => '',
95 'custom-user-page-id' => '',
96 'hide_remove_button' => '',
97 'grace-policy' => 'use-grace-period',
98 'superadmins-role-add' => 'no',
99 'superadmins-role-exclude' => 'no',
100 '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' ),
101 'specify-email_hotp' => '',
102 'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
103 'method_invalid_setting' => 'login_block',
104 'enable_wizard_styling' => 'enable_wizard_styling',
105 'show_help_text' => 'show_help_text',
106 'enable_wizard_logo' => '',
107 'enable_welcome' => '',
108 'welcome' => '',
109 'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . Methods::get_number_of_methods_text(),
110 '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>',
111 'method_help_totp_intro' => '<h3>' . __( 'Setting up TOTP', 'wp-2fa' ) . '</h3>',
112 'method_help_totp_step_1' => __( 'Download and start the application of your choice', 'wp-2fa' ),
113 'method_help_totp_step_2' => __( 'From within the application scan the QR code provided on the left. Otherwise, enter the following code manually in the application:', 'wp-2fa' ),
114 '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' ),
115 '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>',
116 '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>',
117 'method_help_twilio_intro' => '<h3>' . __( 'Setting up Twilio', 'wp-2fa' ) . '</h3><p>' . __( 'To enable Twiliio enter the country and cellphone number in order to use it with this account.', 'wp-2fa' ) . '</p>',
118 '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>',
119 '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>',
120 '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>',
121 '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>',
122 '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>',
123 '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>',
124 '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>',
125 '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 should now generate the list of backup method. Although this is optional, it is highly recommended to have a secondary 2FA method. This can be used as a backup should the primary 2FA method fail. This can happen if, for example, you forget your smartphone, the smartphone runs out of battery, or there are email deliverability problems.', 'wp-2fa' ) . '</p>',
126 '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>',
127 'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
128 'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
129 '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>',
130 '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>',
131 '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>',
132 'authy_reconfigure_intro' => '<h3>' . __( 'Reconfigure Authy method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where link should be send:', 'wp-2fa' ) . '</p>',
133 '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>',
134 'twilio_reconfigure_intro' => '<h3>' . __( 'Reconfigure Twilio method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where code should be send:', 'wp-2fa' ) . '</p>',
135 'twilio_reconfigure_intro_unavailable' => '<h3>' . __( 'Reconfigure Twilio 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>',
136 '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>',
137 'custom_css' => '',
138 'logo-code-page' => '',
139 );
140 /**
141 * Gives the ability to filter the default settings array of the plugin
142 *
143 * @param array $settings - The array with all the default settings.
144 *
145 * @since 2.0.0
146 */
147 $default_settings = apply_filters( WP_2FA_PREFIX . 'default_settings', $default_settings );
148
149 return $default_settings;
150 }
151
152 /**
153 * Fire up classes.
154 */
155 public static function init() {
156
157 self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() );
158 self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() );
159 self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
160
161 self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
162
163 /** 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) */
164 if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
165
166 if ( ! Settings_Utils::get_option( WP_2FA_SETTINGS_NAME ) ) {
167 self::update_plugin_settings( self::get_default_settings() );
168 }
169
170 // Set a flag so we know we have default values present, not custom.
171 Settings_Utils::update_option( 'default_settings_applied', true );
172 Settings_Utils::delete_option( 'wizard_not_finished' );
173 }
174
175 // Activation/Deactivation.
176 register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
177 register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
178 // Register our uninstallation hook.
179 register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
180
181
182 WP_Helper::init();
183
184 // Bootstrap.
185 Core\setup();
186 Backup_Codes::init();
187
188 if ( is_admin() ) {
189 User_Listing::init();
190 }
191
192 Shortcodes::init();
193 User_Notices::init();
194
195 self::add_actions();
196
197 // Inits all the additional free app extensions.
198 $free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' );
199
200 foreach ( $free_extensions as $extension ) {
201 if ( method_exists( $extension, 'init' ) ) {
202 call_user_func_array( array( $extension, 'init' ), array() );
203 }
204 }
205 }
206
207 /**
208 * Add our plugins actions.
209 */
210 public static function add_actions() {
211 // Plugin redirect on activation, only if we have no settings currently saved.
212 if ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) {
213 add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 );
214 }
215
216 // SettingsPage.
217 if ( WP_Helper::is_multisite() ) {
218 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu_multisite' ) );
219 add_action( 'network_admin_edit_update_wp2fa_network_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_options' ) );
220 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_email_options' ) );
221 add_action( 'network_admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_network_admin_notice' ) );
222 } else {
223 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu' ) );
224 add_action( 'admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_admin_notice' ) );
225 add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) );
226 }
227 \add_action( 'wp_ajax_nopriv_set_salt_key', array( __CLASS__, 'set_salt_key' ) );
228 \add_action( 'wp_ajax_set_salt_key', array( __CLASS__, 'set_salt_key' ) );
229
230 add_action( 'wp_ajax_get_all_users', array( '\WP2FA\Admin\Settings_Page', 'get_all_users' ) );
231 add_action( 'wp_ajax_get_all_network_sites', array( '\WP2FA\Admin\Settings_Page', 'get_all_network_sites' ) );
232 add_action( 'wp_ajax_unlock_account', array( '\WP2FA\Admin\Settings_Page', 'unlock_account' ), 10, 1 );
233 add_action( 'admin_action_unlock_account', array( '\WP2FA\Admin\Settings_Page', 'unlock_account' ), 10, 1 );
234 add_action( 'admin_action_remove_user_2fa', array( '\WP2FA\Admin\Settings_Page', 'remove_user_2fa' ), 10, 1 );
235 add_action( 'wp_ajax_remove_user_2fa', array( '\WP2FA\Admin\Settings_Page', 'remove_user_2fa' ), 10, 1 );
236 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'hide_settings' ), 999 );
237 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( '\WP2FA\Admin\Settings_Page', 'add_plugin_action_links' ) );
238 add_filter( 'display_post_states', array( '\WP2FA\Admin\Settings_Page', 'add_display_post_states' ), 10, 2 );
239
240 // Setup_Wizard.
241 if ( WP_Helper::is_multisite() ) {
242 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'network_admin_menus' ), 10 );
243 add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
244 } else {
245 add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
246 }
247 add_action( 'plugins_loaded', array( __CLASS__, 'add_wizard_actions' ), 10 );
248 add_action( 'wp_ajax_send_authentication_setup_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_authentication_setup_email' ) );
249 add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Admin\Setup_Wizard', 'regenerate_authentication_key' ) );
250
251 // User_Notices.
252 add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
253 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
254 add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
255
256 // User_Profile.
257 global $pagenow;
258 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
259 add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
260 add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
261 if ( WP_Helper::is_multisite() ) {
262 add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
263 }
264 }
265 add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
266 if ( WP_Helper::is_multisite() ) {
267 add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
268 }
269 add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
270 add_action( 'wp_ajax_wp2fa_test_email', array( __CLASS__, 'handle_send_test_email_ajax' ) );
271
272 // Login.
273 add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
274 add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
275 add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
276 add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
277 add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
278
279 /**
280 * Keep track of all the user sessions for which we need to invalidate the
281 * authentication cookies set during the initial password check.
282 */
283 add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
284 add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
285
286 // Run only after the core wp_authenticate_username_password() check.
287 add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
288 add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
289
290 // User Register.
291 add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
292
293 // Block users from admin if needed.
294 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
295 add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
296 // Check if usermeta is out of sync with settings.
297 add_action( $user_block_hook, array( __CLASS__, 'update_usermeta_if_required' ), 5 );
298
299 // Help & Contact Us.
300 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
301
302 // phpcs:ignore
303 /* @free:start */
304 // Premium Features.
305 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
306 add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
307 add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
308 /* @free:end */
309
310 add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
311 }
312
313 /**
314 * Add actions specific to the wizard.
315 */
316 public static function add_wizard_actions() {
317 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
318 add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 );
319 }
320 }
321
322 /**
323 * Redirect user to 1st time setup.
324 *
325 * @SuppressWarnings(PHPMD.ExitExpression)
326 */
327 public static function setup_redirect() {
328
329 // Bail early before the redirect if the user can't manage options.
330 if ( ! current_user_can( 'manage_options' ) ) {
331 return;
332 }
333
334 $registered_and_active = 'yes';
335 if ( function_exists( 'wp2fa_freemius' ) ) {
336 $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
337 }
338
339 if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
340 // Delete redirect option.
341 Settings_Utils::delete_option( 'redirect_on_activate' );
342
343 Settings_Utils::update_option( 'wizard_not_finished', true );
344
345 $redirect = add_query_arg(
346 array(
347 'page' => 'wp-2fa-setup',
348 'is_initial_setup' => 'true',
349 ),
350 admin_url( 'user-edit.php' )
351 );
352
353 wp_safe_redirect( $redirect );
354 exit();
355 }
356 }
357
358 /**
359 * Return user roles.
360 *
361 * @return array User roles.
362 */
363 public static function wp_2fa_get_roles() {
364 return WP_Helper::get_roles_wp();
365 }
366
367 /**
368 * Util function to grab settings or apply defaults if no settings are saved into the db.
369 *
370 * @param string $setting_name Settings to grab value of.
371 * @param boolean $get_default_on_empty return default setting value if current one is empty.
372 * @param boolean $get_default_value return default value setting (ignore the stored ones).
373 * @param string $role - The name of the user role.
374 *
375 * @return mixed Settings value or default value.
376 */
377 public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
378 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
379 return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role );
380 }
381
382 /**
383 * Util function to grab settings or apply defaults if no settings are saved into the db.
384 *
385 * @param string $setting_name Settings to grab value of.
386 * @param boolean $get_default_on_empty return default setting value if current one is empty.
387 * @param boolean $get_default_value return default value setting (ignore the stored ones).
388 *
389 * @return mixed Settings value or default value.
390 */
391 public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
392
393 return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
394 }
395
396 /**
397 * Util function to grab white label settings or apply defaults if no settings are saved into the db.
398 *
399 * @param string $setting_name Settings to grab value of.
400 * @param boolean $get_default_on_empty return default setting value if current one is empty.
401 * @param boolean $get_default_value return default value setting (ignore the stored ones).
402 *
403 * @return string Settings value or default value.
404 */
405 public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
406
407 return self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
408 }
409
410 /**
411 * Generic method for extracting settings from the plugin
412 *
413 * @param string $wp_2fa_setting - The name of the settings type.
414 * @param string $setting_name - The name of the setting to extract.
415 * @param boolean $get_default_on_empty - Should we use default value on empty.
416 * @param boolean $get_default_value - Extract default value.
417 * @param string $role - The name of the user role.
418 *
419 * @return mixed
420 */
421 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' ) {
422 $default_settings = self::get_default_settings();
423 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
424
425 if ( true === $get_default_value ) {
426 if ( isset( $default_settings[ $setting_name ] ) ) {
427 return $default_settings[ $setting_name ];
428 }
429
430 return false;
431 }
432
433 $apply_defaults = false;
434
435 $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
436
437 // If we have no setting name, return them all.
438 if ( empty( $setting_name ) ) {
439 return $wp2fa_setting;
440 }
441
442 // First lets check if any options have been saved.
443 if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
444 $apply_defaults = true;
445 }
446
447 if ( $apply_defaults ) {
448 return $default_settings[ $setting_name ];
449 } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
450 if ( true === $get_default_on_empty ) {
451 if ( isset( $default_settings[ $setting_name ] ) ) {
452 return $default_settings[ $setting_name ];
453 }
454 }
455 return false;
456 } else {
457
458 if ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
459 /**
460 * Extensions could change the extracted value, based on custom / different / specific for role settings.
461 *
462 * @param mixed - Value of the setting.
463 * @param string - The name of the setting.
464 * @param string - The role name.
465 *
466 * @since 2.0.0
467 */
468 return apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
469 } else {
470 return $wp2fa_setting[ $setting_name ];
471 }
472 }
473 }
474
475 /**
476 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
477 *
478 * @param string $setting_name Settings to grab value of.
479 */
480 public static function get_wp2fa_email_templates( $setting_name = '' ) {
481
482 // If we have no setting name, return what ever is saved.
483 if ( empty( $setting_name ) ) {
484 return self::$wp_2fa_email_templates;
485 }
486
487 // If we have a saved setting, return it.
488 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
489 return self::$wp_2fa_email_templates[ $setting_name ];
490 }
491
492 // Create Login Code Message.
493 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
494
495 $login_code_body = '<p>' . sprintf(
496 // translators: The login code provided from the plugin.
497 esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ),
498 '<strong>{login_code}</strong>'
499 );
500 $login_code_body .= '</p>';
501 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
502 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
503 $login_code_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
504 $login_code_body .= '</p>';
505
506 // Create User Locked Message.
507 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
508
509 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
510 $user_locked_body .= '<p>' . sprintf(
511 // translators: %1s - the name of the user
512 // translators: %2s - the name of the site.
513 esc_html__( 'Since you have not enabled two-factor authentication for the user %1$1s on the website %2$2s within the grace period, your account has been locked.', 'wp-2fa' ),
514 '{user_login_name}',
515 '{site_name}'
516 );
517 $user_locked_body .= '</p>';
518 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
519 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
520 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
521 $user_locked_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
522 $user_locked_body .= '</p>';
523
524 // Create User unlocked Message.
525 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
526 $user_unlocked_body = '';
527
528 $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>';
529
530 if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
531 $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>';
532 }
533
534 $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>';
535
536 // Array of defaults, now we have things setup above.
537 $default_settings = array(
538 'email_from_setting' => 'use-defaults',
539 'custom_from_email_address' => '',
540 'custom_from_display_name' => '',
541 'login_code_email_subject' => $login_code_subject,
542 'login_code_email_body' => $login_code_body,
543 'user_account_locked_email_subject' => $user_locked_subject,
544 'user_account_locked_email_body' => $user_locked_body,
545 'user_account_unlocked_email_subject' => $user_unlocked_subject,
546 'user_account_unlocked_email_body' => $user_unlocked_body,
547 'send_account_locked_email' => 'enable_account_locked_email',
548 'send_account_unlocked_email' => 'enable_account_unlocked_email',
549 );
550
551 /**
552 * Allows 3rd party providers to their own settings for the mail templates.
553 *
554 * @param array $default_settings - Array with the default settings.
555 *
556 * @since 2.0.0
557 */
558 $default_settings = apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
559
560 return $default_settings[ $setting_name ];
561 }
562
563 /**
564 * Util which we use to replace our {strings} with actual, useful stuff.
565 *
566 * @param string $input Text we are working on.
567 * @param int|string $user_id User id, if its needed.
568 * @param string $token Login code, if its needed..
569 * @param string $override_grace_period - Value to override grace period with.
570 *
571 * @return string The output, with all the {strings} swapped out.
572 */
573 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
574
575 // Gather grace period.
576 $grace_period_string = '';
577 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
578 $grace_period_string = $override_grace_period;
579 } else {
580 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
581 $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
582 }
583
584 // Setup user data.
585 if ( isset( $user_id ) && ! empty( $user_id ) ) {
586 $user = get_userdata( $user_id );
587 } else {
588 $user = wp_get_current_user();
589 }
590
591 // Setup token.
592 if ( isset( $token ) && ! empty( $token ) ) {
593 $login_code = $token;
594 } else {
595 $login_code = '';
596 }
597
598 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
599 if ( ! empty( $new_page_id ) ) {
600 $new_page_permalink = get_permalink( $new_page_id );
601 } else {
602 $new_page_permalink = '';
603 }
604
605 // These are the strings we are going to search for, as well as there respective replacements.
606 $replacements = array(
607 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
608 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
609 '{grace_period}' => sanitize_text_field( $grace_period_string ),
610 '{user_login_name}' => sanitize_text_field( $user->user_login ),
611 '{user_first_name}' => sanitize_text_field( $user->user_firstname ),
612 '{user_last_name}' => sanitize_text_field( $user->user_lastname ),
613 '{user_display_name}' => sanitize_text_field( $user->display_name ),
614 '{login_code}' => $login_code,
615 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
616 '{user_ip_address}' => Request_Utils::get_ip(),
617 );
618
619 /**
620 * 3rd party plugins could change the mail strings, or provide their own.
621 *
622 * @param array $replacements - The array with all the currently supported strings.
623 */
624 $replacements = apply_filters(
625 WP_2FA_PREFIX . 'replacement_email_strings',
626 $replacements
627 );
628
629 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
630 return $final_output;
631 }
632
633 /**
634 * Util which we use to replace our {strings} with actual, useful stuff.
635 *
636 * @param string $input Text we are working on.
637 * @param WP_User $user The WP User.
638 *
639 * @return string The output, with all the {strings} swapped out.
640 */
641 public static function replace_wizard_strings( $input = '', $user = false ) {
642
643 if ( ! $user ) {
644 return $input;
645 }
646
647 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
648
649 // These are the strings we are going to search for, as well as there respective replacements.
650 $replacements = array(
651 '{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
652 );
653
654 /**
655 * 3rd party plugins could change the mail strings, or provide their own.
656 *
657 * @param array $replacements - The array with all the currently supported strings.
658 */
659 $replacements = apply_filters(
660 WP_2FA_PREFIX . 'replacement_wizard_strings',
661 $replacements
662 );
663
664 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
665 return $final_output;
666 }
667
668 /**
669 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
670 *
671 * @SuppressWarnings(PHPMD.ExitExpression)
672 */
673 public static function block_unconfigured_users_from_admin() {
674 global $pagenow;
675
676 $user = User_Helper::get_user();
677 if ( 0 === $user->ID ) {
678 return;
679 }
680
681 $redirect = true;
682
683 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
684 $redirect = User_Licensing::enable_2fa_user_setting( true );
685 }
686
687 if ( $redirect ) {
688 $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
689 $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
690 $time_now = time();
691 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
692
693 /**
694 * We should only allow:
695 * - 2FA setup wizard in the administration
696 * - custom 2FA page if enabled and created
697 * - AJAX requests originating from these 2FA setup UIs
698 */
699 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
700 return;
701 }
702
703 if ( is_admin() || is_network_admin() ) {
704 $allowed_admin_page = 'profile.php';
705 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
706 return;
707 }
708 }
709
710 if ( is_page() ) {
711 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
712 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
713 return;
714 }
715 }
716
717 // force a redirect to the 2FA set-up page if it exists.
718 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
719 if ( ! empty( $custom_user_page_id ) ) {
720 wp_redirect( Settings::get_custom_page_link( $user ) );
721 exit;
722 }
723
724 // custom 2FA page is not set-up, force redirect to the wizard in administration.
725 wp_redirect( Settings::get_setup_page_link() );
726 exit;
727 }
728 }
729 }
730
731 /**
732 * Checks if user's settings hash matches the current one, and if not, updates it.
733 *
734 * @return void
735 * @since 1.7.0
736 */
737 public static function update_usermeta_if_required() {
738 if ( wp_doing_ajax() || ! is_user_logged_in() ) {
739 return;
740 }
741
742 // doing this invokes update of necessary user metadata in the User class.
743 User::get_instance();
744 }
745
746 /**
747 * Handles AJAX calls for sending test emails.
748 */
749 public static function handle_send_test_email_ajax() {
750
751 // check user permissions.
752 if ( ! current_user_can( 'manage_options' ) ) {
753 wp_send_json_error();
754 }
755
756 // check email id.
757 $email_id = isset( $_POST['email_id'] ) ? sanitize_text_field( \wp_unslash( $_POST['email_id'] ) ) : null;
758 if ( null === $email_id || false === $email_id ) {
759 wp_send_json_error();
760 }
761
762 // check nonce.
763 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : null;
764 if ( null === $nonce || false === $nonce || ! wp_verify_nonce( $nonce, 'wp-2fa-email-test-' . $email_id ) ) {
765 wp_send_json_error();
766 }
767
768 $user_id = get_current_user_id();
769 // Grab user data.
770 $user = get_userdata( $user_id );
771 // Grab user email.
772 $email = $user->user_email;
773
774 if ( 'config_test' === $email_id ) {
775 $email_sent = Settings_Page::send_email(
776 $email,
777 esc_html__( 'Test email from WP 2FA', 'wp-2fa' ),
778 esc_html__( 'This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa' )
779 );
780 if ( $email_sent ) {
781 wp_send_json_success( 'Test email was successfully sent to <strong>' . $email . '</strong>' );
782 }
783
784 wp_send_json_error();
785 }
786
787 /**
788 * All email templates
789 *
790 * @var Email_Template[] $email_templates
791 */
792 $email_templates = Settings_Page_Email::get_email_notification_definitions();
793 foreach ( $email_templates as $email_template ) {
794 if ( $email_id === $email_template->get_email_content_id() ) {
795 // send the test email.
796
797 // Setup the email contents.
798 $subject = wp_strip_all_tags( self::get_wp2fa_email_templates( $email_id . '_email_subject' ) );
799 $message = wpautop( self::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id );
800
801 $email_sent = Settings_Page::send_email( $email, $subject, $message );
802 if ( $email_sent ) {
803 wp_send_json_success( 'Test email <strong>' . $email_template->get_title() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
804 }
805
806 wp_send_json_error();
807 }
808 }
809 }
810
811 /**
812 * Returns currently stored settings
813 *
814 * @return array
815 */
816 public static function get_policy_settings() {
817 /**
818 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
819 *
820 * @param array - Value of the settings.
821 *
822 * @since 2.0.0
823 */
824 $settings = apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
825
826 return $settings;
827 }
828
829 /**
830 * Checks the action parameter against given list of actions
831 *
832 * @return bool
833 *
834 * @since 2.0.0
835 */
836 private static function action_check() {
837 if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
838 return false;
839 }
840 $actions_array = array(
841 'send_authentication_setup_email',
842 'validate_authcode_via_ajax',
843 'heartbeat',
844 'regenerate_authentication_key',
845 );
846
847 /**
848 * Allows 3rd party providers to their own settings for the mail templates.
849 *
850 * @param array $actions_array - Array with the default settings.
851 *
852 * @since 2.0.0
853 */
854 $actions_array = apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
855
856 return in_array( $_REQUEST['action'], $actions_array, true );
857 }
858
859 /**
860 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
861 *
862 * @param array $settings - The settings values.
863 * @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.
864 * @param string $settings_name - The name of the settings to extract.
865 *
866 * @since 2.0.0
867 */
868 public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
869 // update local copy of settings.
870 self::$plugin_settings[ $settings_name ] = $settings;
871
872 if ( ! $skip_option_save ) {
873 // update the database option itself.
874 Settings_Utils::update_option( $settings_name, $settings );
875 }
876
877 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
878 // Create a hash for comparison when we interact with a use.
879 $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
880 Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
881 }
882 }
883
884 /**
885 * Getter for the TOTP secret key of the plugin for the current instance
886 *
887 * Note: that is legacy code and will be removed.
888 *
889 * @return string
890 *
891 * @since 2.0.0
892 */
893 public static function get_secret_key() {
894 if ( null === self::$secret_key ) {
895 if ( ! defined( File_Writer::SECRET_NAME ) ) {
896 self::check_for_key();
897 } else {
898 self::$secret_key = constant( File_Writer::SECRET_NAME );
899 }
900 }
901
902 return self::$secret_key;
903 }
904
905 /**
906 * Sets the salt key into the wp-config.php file via AJAX request.
907 *
908 * @return void
909 *
910 * @since 2.4.0
911 */
912 public static function set_salt_key() {
913 if ( \wp_doing_ajax() ) {
914 if ( isset( $_REQUEST['_wpnonce'] ) ) {
915 $nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) ), 'wp-2fa-set-salt-nonce' );
916 if ( ! $nonce_check ) {
917 \wp_send_json_error( new \WP_Error( 500, \esc_html__( 'Nonce checking failed', 'wp-2fa' ) ), 400 );
918 } else {
919 if ( \current_user_can( 'manage_options' ) ) {
920 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
921 \wp_send_json_error(
922 new \WP_Error(
923 500,
924 \esc_html__(
925 'Unable to write to wp-config.php',
926 'wp-2fa'
927 )
928 ),
929 400
930 );
931 } else {
932 $secret_key = Settings_Utils::get_option( 'secret_key' );
933 if ( ! empty( $secret_key ) ) {
934 File_Writer::save_secret_key( $secret_key );
935 Settings_Utils::delete_option( 'secret_key' );
936 \wp_send_json_success(
937 \esc_html__(
938 'wp-config.php successfully update, global setting deleted',
939 'wp-2fa'
940 )
941 );
942 } else {
943 \wp_send_json_error(
944 new \WP_Error(
945 500,
946 \esc_html__(
947 'Unable to find global secret key',
948 'wp-2fa'
949 )
950 ),
951 400
952 );
953 }
954 }
955 }
956 }
957 }
958 }
959 }
960
961 /**
962 * Checks if the wp-config.php file is writable, show notice to the admin if it is not
963 *
964 * @return void
965 *
966 * @since 2.4.0
967 */
968 public static function wp_not_writable() {
969 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
970 $whitelist_admin_pages = array(
971 'wp-2fa_page_wp-2fa-settings',
972 'toplevel_page_wp-2fa-policies',
973 'wp-2fa_page_wp-2fa-help-contact-us',
974 'wp-2fa_page_wp-2fa-policies-account',
975 'wp-2fa_page_wp-2fa-reports',
976 );
977 $admin_page = get_current_screen();
978 if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
979 ?>
980 <div class="notice notice-warning" id="config-update-notice">
981 <?php
982 $message = sprintf(
983 '<p>%1$s <a href="mailto:support@wpwhitesecurity.com">%2$s</a><br>%3$s</p><br>',
984 esc_html__( 'WP 2FA needs to store the encryption key in the wp-config.php file for security reasons. However, it is unable to write the encryption key in the wp-config.php file. This is usually caused by restrictive permissions. Please allow the plugin / WordPress to write to the wp-config.php file, at least temporarily until the encryption key is saved in the file. Typically this can be done by changing the wp-config.php permissions to 755. If you have any questions please contact our', 'wp-2fa' ),
985 esc_html__( 'support team.', 'wp-2fa' ),
986 esc_html__( 'Note: if the plugin cannot write the encryption key to file it will store it in the database. This is not ideal in terms of security.
987 ', 'wp-2fa' ),
988 )
989 ?>
990 <?php echo $message; ?>
991 <button id="salt-update" type="button">
992 <span><?php esc_html_e( 'Write key to file now', 'wp-2fa' ); ?></span>
993 </button>
994 </div>
995 <script>
996 jQuery(document).ready(function($) {
997 $(document).on('click', '#salt-update', function( event ) {
998 const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
999 const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
1000 jQuery.ajax({
1001 url: ajaxURL,
1002 data: {
1003 action: 'set_salt_key',
1004 _wpnonce: nonceValue
1005 },
1006 success: function (data) {
1007 if (data.success) {
1008 jQuery('#config-update-notice .notice-dismiss').click();
1009 } else {
1010 alert(data.data);
1011 }
1012 },
1013 error: function (data) {
1014 alert(data.responseJSON.data[0].message);
1015 }
1016 });
1017 });
1018 });
1019 </script>
1020 <?php
1021 }
1022 }
1023 }
1024
1025 /**
1026 * Checks and sets the global wp2fa salt
1027 *
1028 * @return void
1029 *
1030 * @since 2.4.0
1031 */
1032 private static function check_for_key() {
1033 self::$secret_key = Settings_Utils::get_option( 'secret_key' );
1034 if ( empty( self::$secret_key ) ) {
1035 self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
1036 if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
1037 Settings_Utils::update_option( 'secret_key', self::$secret_key );
1038 }
1039 }
1040 }
1041 }
1042