PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.5.0
WP 2FA – Two-factor authentication for WordPress v2.5.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 2 years ago App 2 years ago Authenticator 2 years ago Shortcodes 2 years ago Utils 2 years ago class-email-template.php 2 years ago class-wp2fa.php 2 years ago index.php 5 years ago
class-wp2fa.php
1305 lines
1 <?php
2 /**
3 * Main plugin class.
4 *
5 * @package wp2fa
6 * @copyright %%YEAR%% Melapress
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_Listing;
14 use WP2FA\Admin\User_Notices;
15 use WP2FA\Admin\Settings_Page;
16 use WP2FA\Utils\Request_Utils;
17 use WP2FA\Methods\Backup_Codes;
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\Admin\Views\Grace_Period_Notifications;
30 use WP2FA\Utils\Settings_Utils;
31 use WP2FA\Admin\SettingsPages\Settings_Page_Email;
32 use WP2FA\Admin\Views\Password_Reset_2FA;
33
34 if ( ! class_exists( '\WP2FA\WP2FA' ) ) {
35 /**
36 * Main WP2FA Class.
37 */
38 class WP2FA {
39
40 /**
41 * Holds the global plugin secret key for storing the TOTP
42 *
43 * @var string
44 *
45 * @since 2.0.0
46 */
47 private static $secret_key = null;
48
49 /**
50 * Local static cache for plugins settings.
51 *
52 * @var array
53 *
54 * @since 2.0.0
55 */
56 private static $plugin_settings = array();
57
58 /**
59 * Local static cache for email template settings.
60 *
61 * @var array
62 */
63 protected static $wp_2fa_email_templates;
64
65 /**
66 * Array with all the plugin default settings.
67 *
68 * @return array
69 *
70 * @since 2.2.0
71 */
72 public static function get_default_settings() {
73 $default_settings = array(
74 'enable_totp' => 'enable_totp',
75 'enable_email' => 'enable_email',
76 'backup_codes_enabled' => 'yes',
77 'enforcement-policy' => 'do-not-enforce',
78 'excluded_users' => array(),
79 'excluded_roles' => array(),
80 'enforced_users' => array(),
81 'enforced_roles' => array(),
82 'grace-period' => 3,
83 'grace-period-denominator' => 'days',
84 'enable_destroy_session' => '',
85 'limit_access' => '',
86 'brute_force_disable' => '',
87 '2fa_settings_last_updated_by' => '',
88 '2fa_main_user' => '',
89 'grace-period-expiry-time' => '',
90 'plugin_version' => WP_2FA_VERSION,
91 'delete_data_upon_uninstall' => '',
92 'excluded_sites' => '',
93 'included_sites' => array(),
94 'create-custom-user-page' => 'no',
95 'redirect-user-custom-page' => '',
96 'redirect-user-custom-page-global' => '',
97 'custom-user-page-url' => '',
98 'custom-user-page-id' => '',
99 'hide_remove_button' => '',
100 'separate-multisite-page-url' => '',
101 'grace-policy' => 'use-grace-period',
102 'superadmins-role-add' => 'no',
103 'superadmins-role-exclude' => 'no',
104 'default-text-code-page' => '<p>' . __( 'Please enter the two-factor authentication (2FA) verification code below to login. Depending on your 2FA setup, you can get the code from the 2FA app or it was sent to you by email.', 'wp-2fa' ) . '</p><p><strong>' . __( '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' ) . '</strong></p>',
105 'default-text-pw-reset-code-page' => '<p>' . __( 'You have been sent a one-time code via email. Please enter the code below and then click Get New Password to proceed with the password reset.', 'wp-2fa' ) . '</p><br><p><strong>' . __( 'Note: If you have not received the code please click the button Resend Code. If you still do not get the code after pressing the button, please contact the website\'s administrator.', 'wp-2fa' ) . '</strong></p>',
106 'default-2fa-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
107 'default-2fa-resetup-required-notice' => '<p>' . __( 'This website\'s administrator requires you to enable two-factor authentication (2FA) {grace_period_remaining}.', 'wp-2fa' ) . '</p><br><p>' . __( 'Failing to configure 2FA within this time period will result in a locked account. For more information, please contact your website administrator.', 'wp-2fa' ) . '</p>',
108 'specify-email_hotp' => '',
109 'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
110 'method_invalid_setting' => 'login_block',
111 'enable_wizard_styling' => 'enable_wizard_styling',
112 'show_help_text' => 'show_help_text',
113 'enable_wizard_logo' => '',
114 'enable_welcome' => '',
115 'welcome' => '',
116 'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . Methods::get_number_of_methods_text(),
117 '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>',
118 'method_help_totp_intro' => '<h3>' . __( 'Setting up TOTP', 'wp-2fa' ) . '</h3>',
119 'method_help_totp_step_1' => __( 'Download and start the application of your choice', 'wp-2fa' ),
120 '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' ),
121 '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' ),
122 '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>',
123 'method_help_authy_intro' => '<h3>' . __( 'Setting up Push notifications', 'wp-2fa' ) . '</h3><p>' . __( 'To enable push notifications enter the country and cellphone number in order to use it with this account.', 'wp-2fa' ) . '</p>',
124 'method_help_twilio_intro' => '<h3>' . __( 'Setting up 2FA over SMS', 'wp-2fa' ) . '</h3><p>' . __( 'When you use 2FA over SMS to log in to this website you will receive your one-time code via an SMS on your cellphone. Therefore please enter the cellphone number of where you would like to receive the SMS below.', 'wp-2fa' ) . '</p>',
125 '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>',
126 '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>',
127 '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>',
128 '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>',
129 '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>',
130 'method_verification_twilio_pre' => '<h3>' . __( 'Almost there…', 'wp-2fa' ) . '</h3><p>' . __( 'Please type in the one-time code sent via SMS to your phone to confirm your phone number.', 'wp-2fa' ) . '</p>',
131 '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>',
132 '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>',
133 '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>',
134 '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>',
135 'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
136 'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
137 '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://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel="noopener">' . __( 'Learn more', 'wp-2fa' ) . '</a></p>',
138 'totp_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} the 2FA App', 'wp-2fa' ) . '</h3><p>' . __( 'Click the below button to {reconfigure_or_configure} 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>',
139 'hotp_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} 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>',
140 'authy_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where link should be send:', 'wp-2fa' ) . '</p>',
141 'authy_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} push notification 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>',
142 'twilio_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the phone where code should be send:', 'wp-2fa' ) . '</p>',
143 'twilio_reconfigure_intro_unavailable' => '<h3>' . __( '{reconfigure_or_configure_capitalized} SMS method', 'wp-2fa' ) . '</h3><p>' . __( 'The 2FA over SMS service you want to use is currently unavailable. Please try again later or restart the wizard to choose another method.', 'wp-2fa' ) . '</p>',
144 'oob_reconfigure_intro' => '<h3>' . __( '{reconfigure_or_configure_capitalized} link over email method', 'wp-2fa' ) . '</h3><p>' . __( 'Please select the email address where the OOB code should be sent:', 'wp-2fa' ) . '</p>',
145 'custom_css' => '',
146 'login_custom_css' => '',
147 'logo-code-page' => '',
148 'login-to-view-area' => '<p>' . __( 'You must be logged in to view this page. {login_url}', 'wp-2fa' ) . '</p>',
149 'backup_email_intro' => '<h3>' . __( 'Your login just got more secure', 'wp-2fa' ) . '</h3><p>' . __( 'Well done on configuring 2FA, your login has just got more secure. To make sure you never get locked out you are required to confirm your email address and use email as an alternative and backup 2FA method in case your primary method is unavailable. Please confirm your email address below', 'wp-2fa' ) . '</p>',
150 'user-profile-form-preamble-title' => __( 'Two-factor authentication settings', 'wp-2fa' ),
151 'user-profile-form-preamble-desc' => __( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' ),
152
153 );
154 /**
155 * Gives the ability to filter the default settings array of the plugin
156 *
157 * @param array $settings - The array with all the default settings.
158 *
159 * @since 2.0.0
160 */
161 $default_settings = apply_filters( WP_2FA_PREFIX . 'default_settings', $default_settings );
162
163 return $default_settings;
164 }
165
166 /**
167 * Fire up classes.
168 */
169 public static function init() {
170
171 self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() );
172 self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() );
173 self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
174
175 self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
176
177 /** 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) */
178 if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
179
180 if ( ! Settings_Utils::get_option( WP_2FA_SETTINGS_NAME ) ) {
181 self::update_plugin_settings( self::get_default_settings() );
182 }
183
184 // Set a flag so we know we have default values present, not custom.
185 Settings_Utils::update_option( 'default_settings_applied', true );
186 Settings_Utils::delete_option( 'wizard_not_finished' );
187 }
188
189 // Activation/Deactivation.
190 register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
191 register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
192 // Register our uninstallation hook.
193 register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
194
195
196 WP_Helper::init();
197
198 // Bootstrap.
199 Core\setup();
200 Backup_Codes::init();
201
202 if ( is_admin() ) {
203 User_Listing::init();
204 // Hide all unrelated to the plugin notices on the plugin admin pages.
205 add_action( 'admin_print_scripts', array( '\WP2FA\Admin\Helpers\WP_Helper', 'hide_unrelated_notices' ) );
206 }
207
208 Grace_Period_Notifications::init();
209 Password_Reset_2FA::init();
210
211 Shortcodes::init();
212 User_Notices::init();
213
214 self::add_actions();
215
216 // Inits all the additional free app extensions.
217 $free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' );
218
219 foreach ( $free_extensions as $extension ) {
220 if ( method_exists( $extension, 'init' ) ) {
221 call_user_func_array( array( $extension, 'init' ), array() );
222 }
223 }
224 }
225
226 /**
227 * Add our plugins actions.
228 */
229 public static function add_actions() {
230 // Plugin redirect on activation, only if we have no settings currently saved.
231 if ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) {
232 add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 );
233 }
234
235 // SettingsPage.
236 if ( WP_Helper::is_multisite() ) {
237 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu_multisite' ) );
238 add_action( 'network_admin_edit_update_wp2fa_network_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_options' ) );
239 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_email_options' ) );
240 add_action( 'network_admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_network_admin_notice' ) );
241 add_action( 'network_admin_notices', array( __CLASS__, 'wp_not_writable' ) );
242 } else {
243 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu' ) );
244 add_action( 'admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_admin_notice' ) );
245 add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) );
246 }
247 \add_action( 'wp_ajax_nopriv_set_salt_key', array( __CLASS__, 'set_salt_key' ) );
248 \add_action( 'wp_ajax_set_salt_key', array( __CLASS__, 'set_salt_key' ) );
249
250 add_action( 'wp_ajax_get_all_users', array( '\WP2FA\Admin\Settings_Page', 'get_all_users' ) );
251 add_action( 'wp_ajax_get_all_network_sites', array( '\WP2FA\Admin\Settings_Page', 'get_all_network_sites' ) );
252 add_action( 'wp_ajax_unlock_account', array( '\WP2FA\Admin\Settings_Page', 'unlock_account' ), 10, 1 );
253 add_action( 'admin_action_unlock_account', array( '\WP2FA\Admin\Settings_Page', 'unlock_account' ), 10, 1 );
254 add_action( 'admin_action_remove_user_2fa', array( '\WP2FA\Admin\Settings_Page', 'remove_user_2fa' ), 10, 1 );
255 add_action( 'wp_ajax_remove_user_2fa', array( '\WP2FA\Admin\Settings_Page', 'remove_user_2fa' ), 10, 1 );
256 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'hide_settings' ), 999 );
257 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( '\WP2FA\Admin\Settings_Page', 'add_plugin_action_links' ) );
258 add_filter( 'display_post_states', array( '\WP2FA\Admin\Settings_Page', 'add_display_post_states' ), 10, 2 );
259
260 // Setup_Wizard.
261 if ( WP_Helper::is_multisite() ) {
262 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'network_admin_menus' ), 10 );
263 add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
264 } else {
265 add_action( 'admin_menu', array( '\WP2FA\Admin\Setup_Wizard', 'admin_menus' ), 10 );
266 }
267 add_action( 'plugins_loaded', array( __CLASS__, 'add_wizard_actions' ), 10 );
268 add_action( 'wp_ajax_send_authentication_setup_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_authentication_setup_email' ) );
269 add_action( 'wp_ajax_send_backup_codes_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_backup_codes_email' ) );
270 add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Admin\Setup_Wizard', 'regenerate_authentication_key' ) );
271
272 // User_Notices.
273 add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
274 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
275 add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
276
277 // User_Profile.
278 global $pagenow;
279 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
280 add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
281 add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
282 if ( WP_Helper::is_multisite() ) {
283 add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
284 }
285 }
286 add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
287 if ( WP_Helper::is_multisite() ) {
288 add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
289 }
290 add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
291 add_action( 'wp_ajax_wp2fa_test_email', array( __CLASS__, 'handle_send_test_email_ajax' ) );
292
293 // Login.
294 add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
295 add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
296 add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
297 add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
298 add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
299
300 // Reset password.
301 add_action( 'lostpassword_post', array( '\WP2FA\Authenticator\Reset_Password', 'lostpassword_post' ), 20, 2 );
302 add_action( 'login_form_lostpassword', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ), 20 );
303 // add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ) );
304
305 /**
306 * Keep track of all the user sessions for which we need to invalidate the
307 * authentication cookies set during the initial password check.
308 */
309 add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
310 add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
311
312 // Run only after the core wp_authenticate_username_password() check.
313 add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
314 add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
315
316 // User Register.
317 add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
318
319 // Block users from admin if needed.
320 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
321 add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
322
323 // Help & Contact Us.
324 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
325
326 // phpcs:disable
327 /* @free:start */
328 // phpcs:enable
329 // Premium Features.
330 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
331 add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
332 add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
333 // phpcs:disable
334 /* @free:end */
335 // phpcs:enable
336
337 add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
338
339 \add_action( WP_2FA_PREFIX . 'user_authenticated', array( __CLASS__, 'clear_user_after_login' ), 10, 1 );
340 }
341
342 /**
343 * Add actions specific to the wizard.
344 */
345 public static function add_wizard_actions() {
346 if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'read' ) ) {
347 add_action( 'admin_init', array( '\WP2FA\Admin\Setup_Wizard', 'setup_page' ), 10 );
348 }
349 }
350
351 /**
352 * Redirect user to 1st time setup.
353 *
354 * @SuppressWarnings(PHPMD.ExitExpression)
355 */
356 public static function setup_redirect() {
357
358 // Bail early before the redirect if the user can't manage options.
359 if ( ! current_user_can( 'manage_options' ) ) {
360 return;
361 }
362
363 $registered_and_active = 'yes';
364 if ( function_exists( 'wp2fa_freemius' ) ) {
365 $registered_and_active = wp2fa_freemius()->is_registered() && wp2fa_freemius()->has_active_valid_license() ? 'yes' : 'no';
366 }
367
368 if ( Settings_Utils::get_option( 'redirect_on_activate', false ) && 'yes' === $registered_and_active ) {
369 // Delete redirect option.
370 Settings_Utils::delete_option( 'redirect_on_activate' );
371
372 Settings_Utils::update_option( 'wizard_not_finished', true );
373
374 $redirect = add_query_arg(
375 array(
376 'page' => 'wp-2fa-setup',
377 'is_initial_setup' => 'true',
378 ),
379 admin_url( 'user-edit.php' )
380 );
381
382 wp_safe_redirect( $redirect );
383 exit();
384 }
385 }
386
387 /**
388 * Return user roles.
389 *
390 * @return array User roles.
391 */
392 public static function wp_2fa_get_roles() {
393 return WP_Helper::get_roles_wp();
394 }
395
396 /**
397 * Util function to grab 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 * @param string $role - The name of the user role.
403 *
404 * @return mixed Settings value or default value.
405 */
406 public static function get_wp2fa_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false, $role = 'global' ) {
407 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
408 return self::get_wp2fa_setting_generic( WP_2FA_POLICY_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value, $role );
409 }
410
411 /**
412 * Util function to grab settings or apply defaults if no settings are saved into the db.
413 *
414 * @param string $setting_name Settings to grab value of.
415 * @param boolean $get_default_on_empty return default setting value if current one is empty.
416 * @param boolean $get_default_value return default value setting (ignore the stored ones).
417 *
418 * @return mixed Settings value or default value.
419 */
420 public static function get_wp2fa_general_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
421
422 return self::get_wp2fa_setting_generic( WP_2FA_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
423 }
424
425 /**
426 * Util function to grab white label settings or apply defaults if no settings are saved into the db.
427 *
428 * @param string $setting_name Settings to grab value of.
429 * @param boolean $get_default_on_empty return default setting value if current one is empty.
430 * @param boolean $get_default_value return default value setting (ignore the stored ones).
431 *
432 * @return string Settings value or default value.
433 */
434 public static function get_wp2fa_white_label_setting( $setting_name = '', $get_default_on_empty = false, $get_default_value = false ) {
435
436 return self::get_wp2fa_setting_generic( WP_2FA_WHITE_LABEL_SETTINGS_NAME, $setting_name, $get_default_on_empty, $get_default_value );
437 }
438
439 /**
440 * Generic method for extracting settings from the plugin
441 *
442 * @param string $wp_2fa_setting - The name of the settings type.
443 * @param string $setting_name - The name of the setting to extract.
444 * @param boolean $get_default_on_empty - Should we use default value on empty.
445 * @param boolean $get_default_value - Extract default value.
446 * @param string $role - The name of the user role.
447 *
448 * @return mixed
449 */
450 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' ) {
451 $default_settings = self::get_default_settings();
452 $role = ( is_null( $role ) || empty( $role ) ) ? 'global' : $role;
453
454 if ( true === $get_default_value ) {
455 if ( isset( $default_settings[ $setting_name ] ) ) {
456 return $default_settings[ $setting_name ];
457 }
458
459 return false;
460 }
461
462 $apply_defaults = false;
463
464 $wp2fa_setting = self::$plugin_settings[ $wp_2fa_setting ];
465
466 // If we have no setting name, return them all.
467 if ( empty( $setting_name ) ) {
468 return $wp2fa_setting;
469 }
470
471 // First lets check if any options have been saved.
472 if ( empty( $wp2fa_setting ) || ! isset( $wp2fa_setting ) ) {
473 $apply_defaults = true;
474 }
475
476 if ( $apply_defaults ) {
477 return $default_settings[ $setting_name ];
478 } elseif ( ! isset( $wp2fa_setting[ $setting_name ] ) ) {
479 if ( true === $get_default_on_empty ) {
480 if ( isset( $default_settings[ $setting_name ] ) ) {
481 return $default_settings[ $setting_name ];
482 }
483 }
484 return false;
485 } elseif ( WP_2FA_POLICY_SETTINGS_NAME === $wp_2fa_setting ) {
486
487 /**
488 * Extensions could change the extracted value, based on custom / different / specific for role settings.
489 *
490 * @param mixed - Value of the setting.
491 * @param string - The name of the setting.
492 * @param string - The role name.
493 *
494 * @since 2.0.0
495 */
496 return apply_filters( WP_2FA_PREFIX . 'setting_generic', $wp2fa_setting[ $setting_name ], $setting_name, $role );
497 } else {
498 return $wp2fa_setting[ $setting_name ];
499 }
500 }
501
502 /**
503 * Util function to grab EMAIL settings or apply defaults if no settings are saved into the db.
504 *
505 * @param string $setting_name Settings to grab value of.
506 */
507 public static function get_wp2fa_email_templates( $setting_name = '' ) {
508
509 // If we have no setting name, return what ever is saved.
510 if ( empty( $setting_name ) ) {
511 return self::$wp_2fa_email_templates;
512 }
513
514 // If we have a saved setting, return it.
515 if ( $setting_name && isset( self::$wp_2fa_email_templates[ $setting_name ] ) ) {
516 return self::$wp_2fa_email_templates[ $setting_name ];
517 }
518
519 // Create Login Code Message.
520 $login_code_subject = __( 'Your login confirmation code for {site_name}', 'wp-2fa' );
521
522 $login_code_body = '<p>' . sprintf(
523 // translators: The login code provided from the plugin.
524 esc_html__( 'Enter %1$1s to log in.', 'wp-2fa' ),
525 '<strong>{login_code}</strong>'
526 );
527 $login_code_body .= '</p>';
528 $login_code_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
529 $login_code_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
530 $login_code_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
531 $login_code_body .= '</p>';
532
533 // Create Reset PW Code Message.
534 $reset_password_code_subject = __( '2FA code for password reset', 'wp-2fa' );
535
536 $reset_password_code_body = '<p>' . esc_html__( 'Hello,', 'wp-2fa' ) . '</p>';
537
538 $reset_password_code_body = '<p>' . sprintf(
539 // translators: The login code provided from the plugin.
540 esc_html__( 'Someone from the IP address %1$1s has requested a password reset for the user %2$2s on the website %3$3s. If this was you please use the below code to proceed with the password reset:', 'wp-2fa' ),
541 '{user_ip_address}',
542 '{user_login_name}',
543 '{site_url}'
544 );
545
546 $reset_password_code_body .= '<p><strong>{login_code}</strong></p>';
547
548 $reset_password_code_body .= '</p>';
549 $reset_password_code_body .= '<p>' . esc_html__( 'If this was not you, ignore this email and contact your website administrator.', 'wp-2fa' ) . '</p>';
550
551 $login_code_setup_body = '<p>' . sprintf(
552 // translators: The login code provided from the plugin.
553 esc_html__( 'Please enter this code to confirm 2FA setup: %1$1s', 'wp-2fa' ),
554 '<strong>{login_code}</strong>'
555 );
556 $login_code_setup_body .= '</p>';
557 $login_code_setup_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
558 $login_code_setup_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
559 $login_code_setup_body .= ' <a href="hhttps://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
560 $login_code_setup_body .= '</p>';
561
562 // Create User Locked Message.
563 $user_locked_subject = __( 'Your user on {site_name} has been locked', 'wp-2fa' );
564
565 $user_locked_body = '<p>' . esc_html__( 'Hello.', 'wp-2fa' ) . '</p>';
566 $user_locked_body .= '<p>' . sprintf(
567 // translators: %1s - the name of the user
568 // translators: %2s - the name of the site.
569 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' ),
570 '{user_login_name}',
571 '{site_name}'
572 );
573 $user_locked_body .= '</p>';
574 $user_locked_body .= '<p>' . esc_html__( 'Contact your website administrator to unlock your account.', 'wp-2fa' ) . '</p>';
575 $user_locked_body .= '<p>' . esc_html__( 'Thank you.', 'wp-2fa' ) . '</p>';
576 $user_locked_body .= '<p>' . esc_html__( 'Email sent by', 'wp-2fa' );
577 $user_locked_body .= ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . esc_html__( 'WP 2FA plugin.', 'wp-2fa' ) . '</a>';
578 $user_locked_body .= '</p>';
579
580 // Create User unlocked Message.
581 $user_unlocked_subject = __( 'Your user on {site_name} has been unlocked', 'wp-2fa' );
582 $user_unlocked_body = '';
583
584 $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>';
585
586 if ( ! empty( self::get_wp2fa_setting( 'custom-user-page-id' ) ) ) {
587 $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>';
588 }
589
590 $user_unlocked_body .= '<p>' . __( 'Thank you.', 'wp-2fa' ) . '</p><p>' . __( 'Email sent by', 'wp-2fa' ) . ' <a href="https://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
591
592 // Create User backup codes Message.
593 $user_backup_codes_subject = __( '2FA backup codes for user {user_login_name} on {site_name}', 'wp-2fa' );
594 $user_backup_codes_body = '';
595
596 $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>';
597
598 $user_backup_codes_body .= '{backup_codes}';
599
600 $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://melapress.com/wordpress-2fa/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . __( 'WP 2FA plugin', 'wp-2fa' ) . '</a></p>';
601
602 // Array of defaults, now we have things setup above.
603 $default_settings = array(
604 'email_from_setting' => 'use-defaults',
605 'custom_from_email_address' => '',
606 'custom_from_display_name' => '',
607 'login_code_email_subject' => $login_code_subject,
608 'login_code_email_body' => $login_code_body,
609 'reset_password_code_email_subject' => $reset_password_code_subject,
610 'reset_password_code_email_body' => $reset_password_code_body,
611 'login_code_setup_email_body' => $login_code_setup_body,
612 'user_account_locked_email_subject' => $user_locked_subject,
613 'user_account_locked_email_body' => $user_locked_body,
614 'user_account_unlocked_email_subject' => $user_unlocked_subject,
615 'user_account_unlocked_email_body' => $user_unlocked_body,
616 'user_backup_codes_email_subject' => $user_backup_codes_subject,
617 'user_backup_codes_email_body' => $user_backup_codes_body,
618 'send_account_locked_email' => 'enable_account_locked_email',
619 'send_account_unlocked_email' => 'enable_account_unlocked_email',
620 );
621
622 /**
623 * Allows 3rd party providers to their own settings for the mail templates.
624 *
625 * @param array $default_settings - Array with the default settings.
626 *
627 * @since 2.0.0
628 */
629 $default_settings = apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
630
631 return $default_settings[ $setting_name ];
632 }
633
634 /**
635 * Util which we use to replace our {strings} with actual, useful stuff.
636 *
637 * @param string $input Text we are working on.
638 * @param int|string $user_id User id, if its needed.
639 * @param string $token Login code, if its needed..
640 * @param string $override_grace_period - Value to override grace period with.
641 *
642 * @return string The output, with all the {strings} swapped out.
643 */
644 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
645
646 $token = trim( $token );
647
648 // Gather grace period.
649 $grace_period_string = '';
650 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
651 $grace_period_string = $override_grace_period;
652 } else {
653 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
654 $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
655 }
656
657 // Setup user data.
658 if ( isset( $user_id ) && ! empty( $user_id ) ) {
659 $user = get_userdata( $user_id );
660 } else {
661 $user = wp_get_current_user();
662 }
663
664 // Setup token.
665 if ( isset( $token ) && ! empty( $token ) ) {
666 $login_code = $token;
667 } else {
668 $login_code = '';
669 }
670
671 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
672 if ( ! empty( $new_page_id ) ) {
673 $new_page_permalink = get_permalink( $new_page_id );
674 } else {
675 $new_page_id = Settings::get_custom_settings_page_id( '', $user );
676 if ( ! empty( $new_page_id ) ) {
677 $new_page_permalink = get_permalink( $new_page_id );
678 } else {
679 $new_page_permalink = '';
680 }
681 }
682
683 // These are the strings we are going to search for, as well as there respective replacements.
684 $replacements = array(
685 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
686 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
687 '{grace_period}' => sanitize_text_field( $grace_period_string ),
688 '{user_login_name}' => sanitize_text_field( $user->user_login ),
689 '{user_first_name}' => sanitize_text_field( $user->user_firstname ),
690 '{user_last_name}' => sanitize_text_field( $user->user_lastname ),
691 '{user_display_name}' => sanitize_text_field( $user->display_name ),
692 '{login_code}' => $login_code,
693 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
694 '{user_ip_address}' => Request_Utils::get_ip(),
695 );
696
697 /**
698 * 3rd party plugins could change the mail strings, or provide their own.
699 *
700 * @param array $replacements - The array with all the currently supported strings.
701 */
702 $replacements = apply_filters(
703 WP_2FA_PREFIX . 'replacement_email_strings',
704 $replacements
705 );
706
707 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
708 return $final_output;
709 }
710
711 /**
712 * Util which contextualizes the wording 'reconfigure'/'configure' as needed.
713 *
714 * @param string $input - Text we are working on.
715 * @param int|string $user_id - User id, if its needed.
716 * @param string $method_to_check - Name of the method to check for.
717 *
718 * @return string The output, with all the {strings} swapped out.
719 *
720 * @since 2.5.0
721 */
722 public static function contextual_reconfigure_text( $input = '', $user_id = '', $method_to_check = '' ) {
723
724 if ( empty( trim( $input ) ) ) {
725 return $input;
726 }
727
728 $enabled_method = User_Helper::get_enabled_method_for_user( $user_id );
729
730 $text = ( $enabled_method === $method_to_check ) ? esc_html__( 'Reconfigure', 'wp-2fa' ) : esc_html__( 'Configure', 'wp-2fa' );
731
732 $replacements = array(
733 '{reconfigure_or_configure_capitalized}' => $text,
734 '{reconfigure_or_configure}' => strtolower( $text ),
735 );
736
737 /**
738 * 3rd party plugins could change this to their own.
739 *
740 * @param array $replacements - The array with all the currently supported strings.
741 *
742 * @since 2.5.0
743 */
744 $replacements = apply_filters(
745 WP_2FA_PREFIX . 'replacement_reconfigure_strings',
746 $replacements
747 );
748
749 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
750 }
751
752 /**
753 * Util replace replace a placeholder with the actual remaining grace period for a user..
754 *
755 * @param string $input - Text we are working on.
756 * @param int $grace_expiry - Expiration time.
757 *
758 * @return string The output, with all the {strings} swapped out.
759 *
760 * @since 2.5.0
761 */
762 public static function replace_remaining_grace_period( $input = '', $grace_expiry = -1 ) {
763 if ( empty( trim( $input ) ) || empty( trim( $grace_expiry ) ) ) {
764 return $input;
765 }
766
767 $replacements = array(
768 '{grace_period_remaining}' => esc_attr( Date_Time_Utils::format_grace_period_expiration_string( null, $grace_expiry ) ),
769 );
770
771 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
772 }
773
774 /**
775 * Util which we use to replace our {strings} with actual, useful stuff.
776 *
777 * @param string $input Text we are working on.
778 * @param WP_User $user The WP User.
779 *
780 * @return string The output, with all the {strings} swapped out.
781 */
782 public static function replace_wizard_strings( $input = '', $user = false ) {
783
784 if ( ! $user ) {
785 return $input;
786 }
787
788 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
789
790 // These are the strings we are going to search for, as well as there respective replacements.
791 $replacements = array(
792 '{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
793 );
794
795 /**
796 * 3rd party plugins could change the mail strings, or provide their own.
797 *
798 * @param array $replacements - The array with all the currently supported strings.
799 */
800 $replacements = apply_filters(
801 WP_2FA_PREFIX . 'replacement_wizard_strings',
802 $replacements
803 );
804
805 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
806 return $final_output;
807 }
808
809 /**
810 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
811 *
812 * @SuppressWarnings(PHPMD.ExitExpression)
813 */
814 public static function block_unconfigured_users_from_admin() {
815 global $pagenow;
816
817 $user = User_Helper::get_user();
818 if ( 0 === $user->ID ) {
819 return;
820 }
821
822 $redirect = true;
823
824 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
825 $redirect = User_Licensing::enable_2fa_user_setting( true );
826 }
827
828 if ( $redirect ) {
829 $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
830 $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
831 $time_now = time();
832 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
833
834 $has_cap = true;
835 if ( class_exists( 'WooCommerce', false ) ) {
836
837 // Lets check if the user has the required capabilities to view the 2FA settings page (or profile page in the Admin section - dashboard).
838 $has_cap = false;
839
840 $access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' );
841
842 foreach ( $access_caps as $access_cap ) {
843 if ( current_user_can( $access_cap ) ) {
844 $has_cap = true;
845 break;
846 }
847 }
848 }
849
850 /**
851 * We should only allow:
852 * - 2FA setup wizard in the administration
853 * - custom 2FA page if enabled and created
854 * - AJAX requests originating from these 2FA setup UIs
855 */
856 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
857 return;
858 }
859
860 if ( is_admin() || is_network_admin() ) {
861 $allowed_admin_page = 'profile.php';
862 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
863 return;
864 }
865 }
866
867 if ( is_page() ) {
868 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
869 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
870 return;
871 } else {
872 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
873 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
874 return;
875 }
876 }
877 }
878
879 // force a redirect to the 2FA set-up page if it exists.
880 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
881 if ( ! empty( $custom_user_page_id ) ) {
882 wp_redirect( Settings::get_custom_page_link( $user ) );
883 exit;
884 } else {
885 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
886 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
887 wp_redirect( get_permalink( $custom_user_page_id ) );
888 exit;
889 }
890 }
891
892 // There is nowhere to redirect, so we have to fall back to the default which is the dashboard. If the user does not have the required capabilities to view the dashboard - lets stop the redirection.
893 if ( ! $has_cap ) {
894
895 // Is there WOO installed? If so, then lets try to extract the redirection rules from there.
896 if ( class_exists( 'WooCommerce', false ) ) {
897
898 // Lets check if there is a 2FA implemented within the WOOCommerce myaccount page.
899 $items = \wc_get_account_menu_items();
900
901 if ( isset( $items['wp-2fa'] ) ) {
902
903 if ( ! isset( $_GET['wp-2fa'] ) ) {
904 $url = add_query_arg(
905 array(
906 'wp-2fa' => '',
907 ),
908 get_permalink( get_option( 'woocommerce_myaccount_page_id' ) )
909 );
910
911 wp_redirect( $url );
912
913 exit;
914 }
915 }
916 }
917
918 // Nothing suitable found - notify the admin and bail.
919 $transient_name = WP_2FA_PREFIX . '_notified_admin_mail_nowhere_to_redirect_' . $user->ID;
920 if ( false === \get_transient( $transient_name ) ) {
921 $subject = sprintf(
922 // translators: The username.
923 esc_html__(
924 'User %1$s logged in without 2FA',
925 'wp-2fa'
926 ),
927 $user->user_login,
928 );
929
930 $text = sprintf(
931 // translators: The username.
932 // translators: the site name.
933 esc_html__(
934 '2FA is enforced on the user %1$s on the website %2$s. However, since the WP 2FA plugin has not been configured properly it cannot enforce the user to configure 2FA, so the user logged in without 2FA.',
935 'wp-2fa'
936 ),
937 $user->user_login,
938 get_bloginfo( 'name' )
939 );
940 $text .= '<p>' . sprintf(
941 // translators: the settings page.
942 // translators: the support e-mail.
943 esc_html__(
944 'To enforce 2FA on users logging in from non default WordPress login pages please configure the %1$s. If you need assistance, please contact us at %2$s.',
945 'wp-2fa'
946 ),
947 '<a href="' . esc_url(
948 add_query_arg(
949 array(
950 'page' => 'wp-2fa-settings',
951 'tab' => 'integrations',
952 ),
953 network_admin_url( 'admin.php' )
954 )
955 ) . '">front-end 2FA page</a>',
956 '<a href="mailto:support@melapress.com">support@melapress.com</a>'
957 ) . '</p>';
958
959 Settings_Page::send_email(
960 get_option( 'admin_email' ),
961 $subject,
962 $text
963 );
964
965 \set_transient( $transient_name, 'sent', DAY_IN_SECONDS * 2 );
966 }
967
968 return;
969 }
970
971 // custom 2FA page is not set-up, force redirect to the wizard in administration.
972 wp_redirect( Settings::get_setup_page_link() );
973 exit;
974 }
975 }
976 }
977
978 /**
979 * Handles AJAX calls for sending test emails.
980 */
981 public static function handle_send_test_email_ajax() {
982
983 // check user permissions.
984 if ( ! current_user_can( 'manage_options' ) ) {
985 wp_send_json_error();
986 }
987
988 // check email id.
989 $email_id = isset( $_POST['email_id'] ) ? sanitize_text_field( \wp_unslash( $_POST['email_id'] ) ) : null;
990 if ( null === $email_id || false === $email_id ) {
991 wp_send_json_error();
992 }
993
994 // check nonce.
995 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( \wp_unslash( $_POST['_wpnonce'] ) ) : null;
996 if ( null === $nonce || false === $nonce || ! wp_verify_nonce( $nonce, 'wp-2fa-email-test-' . $email_id ) ) {
997 wp_send_json_error();
998 }
999
1000 $user_id = get_current_user_id();
1001 // Grab user data.
1002 $user = get_userdata( $user_id );
1003 // Grab user email.
1004 $email = $user->user_email;
1005
1006 if ( 'config_test' === $email_id ) {
1007 $email_sent = Settings_Page::send_email(
1008 $email,
1009 esc_html__( 'Test email from WP 2FA', 'wp-2fa' ),
1010 esc_html__( 'This email was sent by the WP 2FA plugin to test the email delivery.', 'wp-2fa' )
1011 );
1012 if ( $email_sent ) {
1013 wp_send_json_success( 'Test email was successfully sent to <strong>' . $email . '</strong>' );
1014 }
1015
1016 wp_send_json_error();
1017 }
1018
1019 /**
1020 * All email templates
1021 *
1022 * @var Email_Template[] $email_templates
1023 */
1024 $email_templates = Settings_Page_Email::get_email_notification_definitions();
1025 foreach ( $email_templates as $email_template ) {
1026 if ( $email_id === $email_template->get_email_content_id() ) {
1027 // send the test email.
1028
1029 // Setup the email contents.
1030 $subject = wp_strip_all_tags( self::replace_email_strings( self::get_wp2fa_email_templates( $email_id . '_email_subject' ) ) );
1031 $message = wpautop( self::replace_email_strings( self::get_wp2fa_email_templates( $email_id . '_email_body' ), $user_id ) );
1032
1033 $email_sent = Settings_Page::send_email( $email, $subject, $message );
1034 if ( $email_sent ) {
1035 wp_send_json_success( 'Test email <strong>' . $email_template->get_title() . '</strong> was successfully sent to <strong>' . $email . '</strong>' );
1036 }
1037
1038 wp_send_json_error();
1039 }
1040 }
1041 }
1042
1043 /**
1044 * Returns currently stored settings
1045 *
1046 * @return array
1047 */
1048 public static function get_policy_settings() {
1049 /**
1050 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
1051 *
1052 * @param array - Value of the settings.
1053 *
1054 * @since 2.0.0
1055 */
1056 $settings = apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
1057
1058 return $settings;
1059 }
1060
1061 /**
1062 * Checks the action parameter against given list of actions
1063 *
1064 * @return bool
1065 *
1066 * @since 2.0.0
1067 */
1068 private static function action_check() {
1069 if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
1070 return false;
1071 }
1072 $actions_array = array(
1073 'send_authentication_setup_email',
1074 'validate_authcode_via_ajax',
1075 'heartbeat',
1076 'regenerate_authentication_key',
1077 'send_backup_codes_email',
1078 'register_user_twilio',
1079 );
1080
1081 /**
1082 * Allows 3rd party providers to their own settings for the mail templates.
1083 *
1084 * @param array $actions_array - Array with the default settings.
1085 *
1086 * @since 2.0.0
1087 */
1088 $actions_array = apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
1089
1090 return in_array( $_REQUEST['action'], $actions_array, true );
1091 }
1092
1093 /**
1094 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
1095 *
1096 * @param array $settings - The settings values.
1097 * @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.
1098 * @param string $settings_name - The name of the settings to extract.
1099 *
1100 * @since 2.0.0
1101 */
1102 public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
1103 // update local copy of settings.
1104 self::$plugin_settings[ $settings_name ] = $settings;
1105
1106 if ( ! $skip_option_save ) {
1107 // update the database option itself.
1108 Settings_Utils::update_option( $settings_name, $settings );
1109 }
1110
1111 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
1112 // Create a hash for comparison when we interact with a use.
1113 $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
1114 Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
1115 }
1116 }
1117
1118 /**
1119 * Getter for the TOTP secret key of the plugin for the current instance
1120 *
1121 * Note: that is legacy code and will be removed.
1122 *
1123 * @return string
1124 *
1125 * @since 2.0.0
1126 */
1127 public static function get_secret_key() {
1128 if ( null === self::$secret_key ) {
1129 if ( ! defined( File_Writer::SECRET_NAME ) ) {
1130 self::check_for_key();
1131 } else {
1132 self::$secret_key = constant( File_Writer::SECRET_NAME );
1133 }
1134 }
1135
1136 return self::$secret_key;
1137 }
1138
1139 /**
1140 * Sets the salt key into the wp-config.php file via AJAX request.
1141 *
1142 * @return void
1143 *
1144 * @since 2.4.0
1145 */
1146 public static function set_salt_key() {
1147 if ( \wp_doing_ajax() ) {
1148 if ( isset( $_REQUEST['_wpnonce'] ) ) {
1149 $nonce_check = \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_REQUEST['_wpnonce'] ) ), 'wp-2fa-set-salt-nonce' );
1150 if ( ! $nonce_check ) {
1151 \wp_send_json_error( new \WP_Error( 500, \esc_html__( 'Nonce checking failed', 'wp-2fa' ) ), 400 );
1152 } elseif ( \current_user_can( 'manage_options' ) ) {
1153 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
1154 \wp_send_json_error(
1155 new \WP_Error(
1156 500,
1157 \esc_html__(
1158 'Unable to write to wp-config.php',
1159 'wp-2fa'
1160 )
1161 ),
1162 400
1163 );
1164 } else {
1165 $secret_key = Settings_Utils::get_option( 'secret_key' );
1166 if ( ! empty( $secret_key ) ) {
1167 File_Writer::save_secret_key( $secret_key );
1168 Settings_Utils::delete_option( 'secret_key' );
1169 \wp_send_json_success(
1170 \esc_html__(
1171 'wp-config.php successfully update, global setting deleted',
1172 'wp-2fa'
1173 )
1174 );
1175 } else {
1176 \wp_send_json_error(
1177 new \WP_Error(
1178 500,
1179 \esc_html__(
1180 'Unable to find global secret key',
1181 'wp-2fa'
1182 )
1183 ),
1184 400
1185 );
1186 }
1187 }
1188 }
1189 }
1190 }
1191 }
1192
1193 /**
1194 * Checks if the wp-config.php file is writable, show notice to the admin if it is not
1195 *
1196 * @return void
1197 *
1198 * @since 2.4.0
1199 */
1200 public static function wp_not_writable() {
1201
1202 if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) {
1203 return;
1204 }
1205
1206 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
1207 $whitelist_admin_pages = array(
1208 'wp-2fa_page_wp-2fa-settings',
1209 'wp-2fa_page_wp-2fa-settings-network',
1210 'toplevel_page_wp-2fa-policies',
1211 'toplevel_page_wp-2fa-policies-network',
1212 'wp-2fa_page_wp-2fa-help-contact-us',
1213 'wp-2fa_page_wp-2fa-help-contact-us-network',
1214 'wp-2fa_page_wp-2fa-policies-account',
1215 'wp-2fa_page_wp-2fa-policies-account-network',
1216 'wp-2fa_page_wp-2fa-reports',
1217 'wp-2fa_page_wp-2fa-reports-network',
1218 );
1219 $admin_page = get_current_screen();
1220 if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
1221 ?>
1222 <div class="notice notice-warning" id="config-update-notice">
1223 <?php
1224 $message = sprintf(
1225 '<p>%1$s <a href="https://melapress.com/support/kb/wp-2fa-add-2fa-plugin-encryption-key-wp-config/?&utm_source=plugins&utm_medium=link&utm_campaign=wp2fa" noopener target="_blank">%2$s</a><br>%3$s</p>',
1226 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>' .
1227
1228 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>' .
1229
1230 esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ),
1231 esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>',
1232 esc_html__(
1233 'Once you complete any of the above, please click the button below.
1234 ',
1235 'wp-2fa'
1236 ),
1237 )
1238 ?>
1239 <?php echo $message; // phpcs:ignore ?>
1240 <p><button id="salt-update" type="button">
1241 <span><?php esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span>
1242 </button></p>
1243 </div>
1244 <script>
1245 jQuery(document).ready(function($) {
1246 $(document).on('click', '#salt-update', function( event ) {
1247 const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
1248 const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
1249 jQuery.ajax({
1250 url: ajaxURL,
1251 data: {
1252 action: 'set_salt_key',
1253 _wpnonce: nonceValue
1254 },
1255 success: function (data) {
1256 if (data.success) {
1257 jQuery('#config-update-notice .notice-dismiss').click();
1258 } else {
1259 alert(data.data);
1260 }
1261 },
1262 error: function (data) {
1263 alert(data.responseJSON.data[0].message);
1264 }
1265 });
1266 });
1267 });
1268 </script>
1269 <?php
1270 }
1271 }
1272 }
1273
1274 /**
1275 * Remove the user meta related with the code has been sent to the user.
1276 * That is so we can lower the security by giving the option not to resend codes, so eventual brute force could succeed.
1277 * The setting name - brute_force_disable
1278 *
1279 * @return void
1280 *
1281 * @since 2.5.0
1282 */
1283 public static function clear_user_after_login() {
1284 User_Helper::remove_meta( WP_2FA_PREFIX . 'code_sent' );
1285 }
1286
1287 /**
1288 * Checks and sets the global wp2fa salt
1289 *
1290 * @return void
1291 *
1292 * @since 2.4.0
1293 */
1294 private static function check_for_key() {
1295 self::$secret_key = Settings_Utils::get_option( 'secret_key' );
1296 if ( empty( self::$secret_key ) ) {
1297 self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
1298 if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
1299 Settings_Utils::update_option( 'secret_key', self::$secret_key );
1300 }
1301 }
1302 }
1303 }
1304 }
1305