PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.4.2
WP 2FA – Two-factor authentication for WordPress v2.4.2
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 2 years ago Shortcodes 3 years ago Utils 3 years ago class-email-template.php 3 years ago class-wp2fa.php 2 years ago index.php 5 years ago
class-wp2fa.php
1069 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_send_backup_codes_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_backup_codes_email' ) );
250 add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Admin\Setup_Wizard', 'regenerate_authentication_key' ) );
251
252 // User_Notices.
253 add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
254 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
255 add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
256
257 // User_Profile.
258 global $pagenow;
259 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
260 add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
261 add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
262 if ( WP_Helper::is_multisite() ) {
263 add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
264 }
265 }
266 add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
267 if ( WP_Helper::is_multisite() ) {
268 add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
269 }
270 add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
271 add_action( 'wp_ajax_wp2fa_test_email', array( __CLASS__, 'handle_send_test_email_ajax' ) );
272
273 // Login.
274 add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
275 add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
276 add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
277 add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
278 add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
279
280 /**
281 * Keep track of all the user sessions for which we need to invalidate the
282 * authentication cookies set during the initial password check.
283 */
284 add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
285 add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
286
287 // Run only after the core wp_authenticate_username_password() check.
288 add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
289 add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
290
291 // User Register.
292 add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
293
294 // Block users from admin if needed.
295 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
296 add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
297 // Check if usermeta is out of sync with settings.
298 add_action( $user_block_hook, array( __CLASS__, 'update_usermeta_if_required' ), 5 );
299
300 // Help & Contact Us.
301 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
302
303 // phpcs:ignore
304 /* @free:start */
305 // Premium Features.
306 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
307 add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
308 add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
309 /* @free:end */
310
311 add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
312 }
313
314 /**
315 * Add actions specific to the wizard.
316 */
317 public static function add_wizard_actions() {
318 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
319 add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 );
320 }
321 }
322
323 /**
324 * Redirect user to 1st time setup.
325 *
326 * @SuppressWarnings(PHPMD.ExitExpression)
327 */
328 public static function setup_redirect() {
329
330 // Bail early before the redirect if the user can't manage options.
331 if ( ! current_user_can( 'manage_options' ) ) {
332 return;
333 }
334
335 $registered_and_active = 'yes';
336 if ( function_exists( 'wp2fa_freemius' ) ) {
337 $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
338 }
339
340 if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
341 // Delete redirect option.
342 Settings_Utils::delete_option( 'redirect_on_activate' );
343
344 Settings_Utils::update_option( 'wizard_not_finished', true );
345
346 $redirect = add_query_arg(
347 array(
348 'page' => 'wp-2fa-setup',
349 'is_initial_setup' => 'true',
350 ),
351 admin_url( 'user-edit.php' )
352 );
353
354 wp_safe_redirect( $redirect );
355 exit();
356 }
357 }
358
359 /**
360 * Return user roles.
361 *
362 * @return array User roles.
363 */
364 public static function wp_2fa_get_roles() {
365 return WP_Helper::get_roles_wp();
366 }
367
368 /**
369 * Util function to grab settings or apply defaults if no settings are saved into the db.
370 *
371 * @param string $setting_name Settings to grab value of.
372 * @param boolean $get_default_on_empty return default setting value if current one is empty.
373 * @param boolean $get_default_value return default value setting (ignore the stored ones).
374 * @param string $role - The name of the user role.
375 *
376 * @return mixed Settings value or default value.
377 */
378 public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
379 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
380 return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role );
381 }
382
383 /**
384 * Util function to grab settings or apply defaults if no settings are saved into the db.
385 *
386 * @param string $setting_name Settings to grab value of.
387 * @param boolean $get_default_on_empty return default setting value if current one is empty.
388 * @param boolean $get_default_value return default value setting (ignore the stored ones).
389 *
390 * @return mixed Settings value or default value.
391 */
392 public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
393
394 return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
395 }
396
397 /**
398 * Util function to grab white label settings or apply defaults if no settings are saved into the db.
399 *
400 * @param string $setting_name Settings to grab value of.
401 * @param boolean $get_default_on_empty return default setting value if current one is empty.
402 * @param boolean $get_default_value return default value setting (ignore the stored ones).
403 *
404 * @return string Settings value or default value.
405 */
406 public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
407
408 return self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
409 }
410
411 /**
412 * Generic method for extracting settings from the plugin
413 *
414 * @param string $wp_2fa_setting - The name of the settings type.
415 * @param string $setting_name - The name of the setting to extract.
416 * @param boolean $get_default_on_empty - Should we use default value on empty.
417 * @param boolean $get_default_value - Extract default value.
418 * @param string $role - The name of the user role.
419 *
420 * @return mixed
421 */
422 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' ) {
423 $default_settings = self::get_default_settings();
424 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
425
426 if ( true === $get_default_value ) {
427 if ( isset( $default_settings[ $setting_name ] ) ) {
428 return $default_settings[ $setting_name ];
429 }
430
431 return false;
432 }
433
434 $apply_defaults = false;
435
436 $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
437
438 // If we have no setting name, return them all.
439 if ( empty( $setting_name ) ) {
440 return $wp2fa_setting;
441 }
442
443 // First lets check if any options have been saved.
444 if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
445 $apply_defaults = true;
446 }
447
448 if ( $apply_defaults ) {
449 return $default_settings[ $setting_name ];
450 } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
451 if ( true === $get_default_on_empty ) {
452 if ( isset( $default_settings[ $setting_name ] ) ) {
453 return $default_settings[ $setting_name ];
454 }
455 }
456 return false;
457 } else {
458
459 if ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
460 /**
461 * Extensions could change the extracted value, based on custom / different / specific for role settings.
462 *
463 * @param mixed - Value of the setting.
464 * @param string - The name of the setting.
465 * @param string - The role name.
466 *
467 * @since 2.0.0
468 */
469 return apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
470 } else {
471 return $wp2fa_setting[ $setting_name ];
472 }
473 }
474 }
475
476 /**
477 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
478 *
479 * @param string $setting_name Settings to grab value of.
480 */
481 public static function get_wp2fa_email_templates( $setting_name = '' ) {
482
483 // If we have no setting name, return what ever is saved.
484 if ( empty( $setting_name ) ) {
485 return self::$wp_2fa_email_templates;
486 }
487
488 // If we have a saved setting, return it.
489 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
490 return self::$wp_2fa_email_templates[ $setting_name ];
491 }
492
493 // Create Login Code Message.
494 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
495
496 $login_code_body = '<p>' . sprintf(
497 // translators: The login code provided from the plugin.
498 esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ),
499 '<strong>{login_code}</strong>'
500 );
501 $login_code_body .= '</p>';
502 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
503 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
504 $login_code_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
505 $login_code_body .= '</p>';
506
507 // Create User Locked Message.
508 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
509
510 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
511 $user_locked_body .= '<p>' . sprintf(
512 // translators: %1s - the name of the user
513 // translators: %2s - the name of the site.
514 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' ),
515 '{user_login_name}',
516 '{site_name}'
517 );
518 $user_locked_body .= '</p>';
519 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
520 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
521 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
522 $user_locked_body .= ' <a href="https://www.wpwhitesecurity.com/wordpress-plugins/wp-2fa/" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
523 $user_locked_body .= '</p>';
524
525 // Create User unlocked Message.
526 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
527 $user_unlocked_body = '';
528
529 $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>';
530
531 if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
532 $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>';
533 }
534
535 $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>';
536
537 // Create User backup codes Message.
538 $user_backup_codes_subject = __( '2FA backup codes for user {user_login_name} on {site_name}', 'wp-2fa' );
539 $user_backup_codes_body = '';
540
541 $user_backup_codes_body .= '<p>' . __( 'Hello,', 'wp-2fa' ) . '</p><p>' . esc_html__( 'Below please find the 2FA backup codes for your user', 'wp-2fa' ) . ' <strong>{user_login_name}</strong> ' . esc_html__( 'on the website', 'wp-2fa' ) . ' <strong>{site_name}</strong>. ' . __( 'The website\'s URL is', 'wp-2fa' ) . ' {site_url} </p>';
542
543 $user_backup_codes_body .= '{backup_codes}';
544
545 $user_backup_codes_body .= '<p>' . __( 'Thank you for enabling 2FA on your account and helping us keeping the website secure.', '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>';
546
547 // Array of defaults, now we have things setup above.
548 $default_settings = array(
549 'email_from_setting' => 'use-defaults',
550 'custom_from_email_address' => '',
551 'custom_from_display_name' => '',
552 'login_code_email_subject' => $login_code_subject,
553 'login_code_email_body' => $login_code_body,
554 'user_account_locked_email_subject' => $user_locked_subject,
555 'user_account_locked_email_body' => $user_locked_body,
556 'user_account_unlocked_email_subject' => $user_unlocked_subject,
557 'user_account_unlocked_email_body' => $user_unlocked_body,
558 'user_backup_codes_email_subject' => $user_backup_codes_subject,
559 'user_backup_codes_email_body' => $user_backup_codes_body,
560 'send_account_locked_email' => 'enable_account_locked_email',
561 'send_account_unlocked_email' => 'enable_account_unlocked_email',
562 );
563
564 /**
565 * Allows 3rd party providers to their own settings for the mail templates.
566 *
567 * @param array $default_settings - Array with the default settings.
568 *
569 * @since 2.0.0
570 */
571 $default_settings = apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
572
573 return $default_settings[ $setting_name ];
574 }
575
576 /**
577 * Util which we use to replace our {strings} with actual, useful stuff.
578 *
579 * @param string $input Text we are working on.
580 * @param int|string $user_id User id, if its needed.
581 * @param string $token Login code, if its needed..
582 * @param string $override_grace_period - Value to override grace period with.
583 *
584 * @return string The output, with all the {strings} swapped out.
585 */
586 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
587
588 // Gather grace period.
589 $grace_period_string = '';
590 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
591 $grace_period_string = $override_grace_period;
592 } else {
593 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
594 $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
595 }
596
597 // Setup user data.
598 if ( isset( $user_id ) && ! empty( $user_id ) ) {
599 $user = get_userdata( $user_id );
600 } else {
601 $user = wp_get_current_user();
602 }
603
604 // Setup token.
605 if ( isset( $token ) && ! empty( $token ) ) {
606 $login_code = $token;
607 } else {
608 $login_code = '';
609 }
610
611 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
612 if ( ! empty( $new_page_id ) ) {
613 $new_page_permalink = get_permalink( $new_page_id );
614 } else {
615 $new_page_permalink = '';
616 }
617
618 // These are the strings we are going to search for, as well as there respective replacements.
619 $replacements = array(
620 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
621 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
622 '{grace_period}' => sanitize_text_field( $grace_period_string ),
623 '{user_login_name}' => sanitize_text_field( $user->user_login ),
624 '{user_first_name}' => sanitize_text_field( $user->user_firstname ),
625 '{user_last_name}' => sanitize_text_field( $user->user_lastname ),
626 '{user_display_name}' => sanitize_text_field( $user->display_name ),
627 '{login_code}' => $login_code,
628 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
629 '{user_ip_address}' => Request_Utils::get_ip(),
630 );
631
632 /**
633 * 3rd party plugins could change the mail strings, or provide their own.
634 *
635 * @param array $replacements - The array with all the currently supported strings.
636 */
637 $replacements = apply_filters(
638 WP_2FA_PREFIX . 'replacement_email_strings',
639 $replacements
640 );
641
642 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
643 return $final_output;
644 }
645
646 /**
647 * Util which we use to replace our {strings} with actual, useful stuff.
648 *
649 * @param string $input Text we are working on.
650 * @param WP_User $user The WP User.
651 *
652 * @return string The output, with all the {strings} swapped out.
653 */
654 public static function replace_wizard_strings( $input = '', $user = false ) {
655
656 if ( ! $user ) {
657 return $input;
658 }
659
660 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
661
662 // These are the strings we are going to search for, as well as there respective replacements.
663 $replacements = array(
664 '{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
665 );
666
667 /**
668 * 3rd party plugins could change the mail strings, or provide their own.
669 *
670 * @param array $replacements - The array with all the currently supported strings.
671 */
672 $replacements = apply_filters(
673 WP_2FA_PREFIX . 'replacement_wizard_strings',
674 $replacements
675 );
676
677 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
678 return $final_output;
679 }
680
681 /**
682 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
683 *
684 * @SuppressWarnings(PHPMD.ExitExpression)
685 */
686 public static function block_unconfigured_users_from_admin() {
687 global $pagenow;
688
689 $user = User_Helper::get_user();
690 if ( 0 === $user->ID ) {
691 return;
692 }
693
694 $redirect = true;
695
696 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
697 $redirect = User_Licensing::enable_2fa_user_setting( true );
698 }
699
700 if ( $redirect ) {
701 $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
702 $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
703 $time_now = time();
704 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
705
706 /**
707 * We should only allow:
708 * - 2FA setup wizard in the administration
709 * - custom 2FA page if enabled and created
710 * - AJAX requests originating from these 2FA setup UIs
711 */
712 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
713 return;
714 }
715
716 if ( is_admin() || is_network_admin() ) {
717 $allowed_admin_page = 'profile.php';
718 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
719 return;
720 }
721 }
722
723 if ( is_page() ) {
724 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
725 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
726 return;
727 }
728 }
729
730 // force a redirect to the 2FA set-up page if it exists.
731 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
732 if ( ! empty( $custom_user_page_id ) ) {
733 wp_redirect( Settings::get_custom_page_link( $user ) );
734 exit;
735 }
736
737 // custom 2FA page is not set-up, force redirect to the wizard in administration.
738 wp_redirect( Settings::get_setup_page_link() );
739 exit;
740 }
741 }
742 }
743
744 /**
745 * Checks if user's settings hash matches the current one, and if not, updates it.
746 *
747 * @return void
748 * @since 1.7.0
749 */
750 public static function update_usermeta_if_required() {
751 if ( wp_doing_ajax() || ! is_user_logged_in() ) {
752 return;
753 }
754
755 // doing this invokes update of necessary user metadata in the User class.
756 User::get_instance();
757 }
758
759 /**
760 * Handles AJAX calls for sending test emails.
761 */
762 public static function handle_send_test_email_ajax() {
763
764 // check user permissions.
765 if ( ! current_user_can( 'manage_options' ) ) {
766 wp_send_json_error();
767 }
768
769 // check email id.
770 $email_id = isset( $_POST['email_id'] ) ? sanitize_text_field( \wp_unslash( $_POST['email_id'] ) ) : null;
771 if ( null === $email_id || false === $email_id ) {
772 wp_send_json_error();
773 }
774
775 // check nonce.
776 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : null;
777 if ( null === $nonce || false === $nonce || ! wp_verify_nonce( $nonce, 'wp-2fa-email-test-' . $email_id ) ) {
778 wp_send_json_error();
779 }
780
781 $user_id = get_current_user_id();
782 // Grab user data.
783 $user = get_userdata( $user_id );
784 // Grab user email.
785 $email = $user->user_email;
786
787 if ( 'config_test' === $email_id ) {
788 $email_sent = Settings_Page::send_email(
789 $email,
790 esc_html__( 'Test email from WP 2FA', 'wp-2fa' ),
791 esc_html__( 'This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa' )
792 );
793 if ( $email_sent ) {
794 wp_send_json_success( 'Test email was successfully sent to <strong>' . $email . '</strong>' );
795 }
796
797 wp_send_json_error();
798 }
799
800 /**
801 * All email templates
802 *
803 * @var Email_Template[] $email_templates
804 */
805 $email_templates = Settings_Page_Email::get_email_notification_definitions();
806 foreach ( $email_templates as $email_template ) {
807 if ( $email_id === $email_template->get_email_content_id() ) {
808 // send the test email.
809
810 // Setup the email contents.
811 $subject = wp_strip_all_tags( self::get_wp2fa_email_templates( $email_id . '_email_subject' ) );
812 $message = wpautop( self::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id );
813
814 $email_sent = Settings_Page::send_email( $email, $subject, $message );
815 if ( $email_sent ) {
816 wp_send_json_success( 'Test email <strong>' . $email_template->get_title() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
817 }
818
819 wp_send_json_error();
820 }
821 }
822 }
823
824 /**
825 * Returns currently stored settings
826 *
827 * @return array
828 */
829 public static function get_policy_settings() {
830 /**
831 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
832 *
833 * @param array - Value of the settings.
834 *
835 * @since 2.0.0
836 */
837 $settings = apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
838
839 return $settings;
840 }
841
842 /**
843 * Checks the action parameter against given list of actions
844 *
845 * @return bool
846 *
847 * @since 2.0.0
848 */
849 private static function action_check() {
850 if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
851 return false;
852 }
853 $actions_array = array(
854 'send_authentication_setup_email',
855 'validate_authcode_via_ajax',
856 'heartbeat',
857 'regenerate_authentication_key',
858 'send_backup_codes_email',
859 'register_user_twilio',
860 );
861
862 /**
863 * Allows 3rd party providers to their own settings for the mail templates.
864 *
865 * @param array $actions_array - Array with the default settings.
866 *
867 * @since 2.0.0
868 */
869 $actions_array = apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
870
871 return in_array( $_REQUEST['action'], $actions_array, true );
872 }
873
874 /**
875 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
876 *
877 * @param array $settings - The settings values.
878 * @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.
879 * @param string $settings_name - The name of the settings to extract.
880 *
881 * @since 2.0.0
882 */
883 public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
884 // update local copy of settings.
885 self::$plugin_settings[ $settings_name ] = $settings;
886
887 if ( ! $skip_option_save ) {
888 // update the database option itself.
889 Settings_Utils::update_option( $settings_name, $settings );
890 }
891
892 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
893 // Create a hash for comparison when we interact with a use.
894 $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
895 Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
896 }
897 }
898
899 /**
900 * Getter for the TOTP secret key of the plugin for the current instance
901 *
902 * Note: that is legacy code and will be removed.
903 *
904 * @return string
905 *
906 * @since 2.0.0
907 */
908 public static function get_secret_key() {
909 if ( null === self::$secret_key ) {
910 if ( ! defined( File_Writer::SECRET_NAME ) ) {
911 self::check_for_key();
912 } else {
913 self::$secret_key = constant( File_Writer::SECRET_NAME );
914 }
915 }
916
917 return self::$secret_key;
918 }
919
920 /**
921 * Sets the salt key into the wp-config.php file via AJAX request.
922 *
923 * @return void
924 *
925 * @since 2.4.0
926 */
927 public static function set_salt_key() {
928 if ( \wp_doing_ajax() ) {
929 if ( isset( $_REQUEST['_wpnonce'] ) ) {
930 $nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) ), 'wp-2fa-set-salt-nonce' );
931 if ( ! $nonce_check ) {
932 \wp_send_json_error( new \WP_Error( 500, \esc_html__( 'Nonce checking failed', 'wp-2fa' ) ), 400 );
933 } else {
934 if ( \current_user_can( 'manage_options' ) ) {
935 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
936 \wp_send_json_error(
937 new \WP_Error(
938 500,
939 \esc_html__(
940 'Unable to write to wp-config.php',
941 'wp-2fa'
942 )
943 ),
944 400
945 );
946 } else {
947 $secret_key = Settings_Utils::get_option( 'secret_key' );
948 if ( ! empty( $secret_key ) ) {
949 File_Writer::save_secret_key( $secret_key );
950 Settings_Utils::delete_option( 'secret_key' );
951 \wp_send_json_success(
952 \esc_html__(
953 'wp-config.php successfully update, global setting deleted',
954 'wp-2fa'
955 )
956 );
957 } else {
958 \wp_send_json_error(
959 new \WP_Error(
960 500,
961 \esc_html__(
962 'Unable to find global secret key',
963 'wp-2fa'
964 )
965 ),
966 400
967 );
968 }
969 }
970 }
971 }
972 }
973 }
974 }
975
976 /**
977 * Checks if the wp-config.php file is writable, show notice to the admin if it is not
978 *
979 * @return void
980 *
981 * @since 2.4.0
982 */
983 public static function wp_not_writable() {
984
985 if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) {
986 return;
987 }
988
989 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
990 $whitelist_admin_pages = array(
991 'wp-2fa_page_wp-2fa-settings',
992 'toplevel_page_wp-2fa-policies',
993 'wp-2fa_page_wp-2fa-help-contact-us',
994 'wp-2fa_page_wp-2fa-policies-account',
995 'wp-2fa_page_wp-2fa-reports',
996 );
997 $admin_page = get_current_screen();
998 if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
999 ?>
1000 <div class="notice notice-warning" id="config-update-notice">
1001 <?php
1002 $message = sprintf(
1003 '<p>%1$s <a href="https://wp2fa.io/support/kb/add-2fa-plugin-encryption-key-wp-config" noopener target="_blank">%2$s</a><br>%3$s</p>',
1004 esc_html__( 'For security reasons WP 2FA needs to store the private key in the wp-config.php file. However, it is unable to. This can happen because of restrictive permissions, or the file is not in the default location. To fix this you can:', 'wp-2fa' ) . '<br><br>' .
1005
1006 esc_html__( 'Option A) allow the plugin to write to the wp-config.php file temporarily by changing the wp-config.php permissions to 755. Once ready, click the button to proceed.', 'wp-2fa' ) . '<br>' .
1007
1008 esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ),
1009 esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>',
1010 esc_html__(
1011 'Once you complete any of the above, please click the button below.
1012 ',
1013 'wp-2fa'
1014 ),
1015 )
1016 ?>
1017 <?php echo $message; ?>
1018 <p><button id="salt-update" type="button">
1019 <span><?php esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span>
1020 </button></p>
1021 </div>
1022 <script>
1023 jQuery(document).ready(function($) {
1024 $(document).on('click', '#salt-update', function( event ) {
1025 const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
1026 const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
1027 jQuery.ajax({
1028 url: ajaxURL,
1029 data: {
1030 action: 'set_salt_key',
1031 _wpnonce: nonceValue
1032 },
1033 success: function (data) {
1034 if (data.success) {
1035 jQuery('#config-update-notice .notice-dismiss').click();
1036 } else {
1037 alert(data.data);
1038 }
1039 },
1040 error: function (data) {
1041 alert(data.responseJSON.data[0].message);
1042 }
1043 });
1044 });
1045 });
1046 </script>
1047 <?php
1048 }
1049 }
1050 }
1051
1052 /**
1053 * Checks and sets the global wp2fa salt
1054 *
1055 * @return void
1056 *
1057 * @since 2.4.0
1058 */
1059 private static function check_for_key() {
1060 self::$secret_key = Settings_Utils::get_option( 'secret_key' );
1061 if ( empty( self::$secret_key ) ) {
1062 self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
1063 if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
1064 Settings_Utils::update_option( 'secret_key', self::$secret_key );
1065 }
1066 }
1067 }
1068 }
1069