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