PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.6.1
WP 2FA – Two-factor authentication for WordPress v2.6.1
4.1.0 4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / class-wp2fa.php
wp-2fa / includes / classes Last commit date
Admin 2 years ago App 2 years ago Authenticator 2 years ago Shortcodes 2 years ago Utils 2 years ago class-email-template.php 3 years ago class-wp2fa.php 2 years ago index.php 5 years ago
class-wp2fa.php
1190 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\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
112 'default-backup-code-page' => __( 'Enter a backup verification code.', 'wp-2fa' ),
113 'method_invalid_setting' => 'login_block',
114 'enable_wizard_styling' => 'enable_wizard_styling',
115 'show_help_text' => 'show_help_text',
116 'enable_wizard_logo' => '',
117 'enable_welcome' => '',
118 'welcome' => '',
119 'method_selection' => '<h3>' . __( 'Choose the 2FA method', 'wp-2fa' ) . '</h3>' . Methods::get_number_of_methods_text(),
120 '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>',
121 '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>',
122 '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>',
123 '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>',
124 '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>',
125 '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>',
126 '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>',
127 '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>',
128 '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>',
129 '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>',
130 '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>',
131 'backup_codes_generated' => '<h3>' . __( 'Backup codes generated', 'wp-2fa' ) . '</h3><p>' . __( 'Here are your backup codes:', 'wp-2fa' ) . '</p>',
132 'no_further_action' => '<h3>' . __( 'Congratulations! You are all set.', 'wp-2fa' ),
133 '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>',
134 '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>',
135 '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>',
136 '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>',
137 '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>',
138 '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>',
139 'custom_css' => '',
140 'login_custom_css' => '',
141 'logo-code-page' => '',
142 'login-to-view-area' => '<p>' . __( 'You must be logged in to view this page. {login_url}', 'wp-2fa' ) . '</p>',
143 '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>',
144 'user-profile-form-preamble-title' => __( 'Two-factor authentication settings', 'wp-2fa' ),
145 'user-profile-form-preamble-desc' => __( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' ),
146 'use_custom_2fa_message' => 'use-defaults',
147
148 );
149 /**
150 * Gives the ability to filter the default settings array of the plugin
151 *
152 * @param array $settings - The array with all the default settings.
153 *
154 * @since 2.0.0
155 */
156 $default_settings = apply_filters( WP_2FA_PREFIX . 'default_settings', $default_settings );
157
158 return $default_settings;
159 }
160
161 /**
162 * Inits the plugin related classes and settings
163 *
164 * @return void
165 *
166 * @since 2.6.0
167 */
168 public static function init() {
169
170 Methods_Helper::init();
171
172 self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME, array() );
173 self::$plugin_settings[ WP_2FA_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_SETTINGS_NAME, array() );
174 self::$plugin_settings[ WP_2FA_WHITE_LABEL_SETTINGS_NAME ] = Settings_Utils::get_option( WP_2FA_WHITE_LABEL_SETTINGS_NAME, array() );
175
176 self::$wp_2fa_email_templates = Settings_Utils::get_option( WP_2FA_EMAIL_SETTINGS_NAME );
177
178 /** 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) */
179 if ( Settings_Utils::get_option( 'wizard_not_finished' ) && ! isset( $_GET['is_initial_setup'] ) && ! wp_doing_ajax() && ! defined( 'DOING_CRON' ) ) {
180
181 if ( ! Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) {
182 self::update_plugin_settings( self::get_default_settings() );
183 }
184
185 // Set a flag so we know we have default values present, not custom.
186 Settings_Utils::update_option( 'default_settings_applied', true );
187 Settings_Utils::delete_option( 'wizard_not_finished' );
188 }
189
190
191 WP_Helper::init();
192
193 // Bootstrap.
194 Core\setup();
195
196 if ( is_admin() ) {
197 User_Listing::init();
198 // Hide all unrelated to the plugin notices on the plugin admin pages.
199 add_action( 'admin_print_scripts', array( '\WP2FA\Admin\Helpers\WP_Helper', 'hide_unrelated_notices' ) );
200 }
201
202 Grace_Period_Notifications::init();
203 Password_Reset_2FA::init();
204
205 Shortcodes::init();
206 User_Notices::init();
207
208 self::add_actions();
209
210 // Inits all the additional free app extensions.
211 $free_extensions = Classes_Helper::get_classes_by_namespace( 'WP2FA\\App\\' );
212
213 foreach ( $free_extensions as $extension ) {
214 if ( method_exists( $extension, 'init' ) ) {
215 call_user_func_array( array( $extension, 'init' ), array() );
216 }
217 }
218 }
219
220 /**
221 * Inits all the plugin hooks
222 *
223 * @return void
224 *
225 * @since 2.6.0
226 */
227 public static function add_actions() {
228 // Plugin redirect on activation, only if we have no settings currently saved.
229 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 ) ) {
230 add_action( 'admin_init', array( __CLASS__, 'setup_redirect' ), 10 );
231
232 if ( ! isset( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) || empty( self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] ) ) {
233 self::update_plugin_settings( self::get_default_settings() );
234 }
235 } elseif ( ! \is_array( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) {
236 Settings_Utils::delete_option( WP_2FA_POLICY_SETTINGS_NAME );
237 self::update_plugin_settings( self::get_default_settings() );
238 }
239
240 // SettingsPage.
241 if ( WP_Helper::is_multisite() ) {
242 add_action( 'network_admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu_multisite' ) );
243 add_action( 'network_admin_edit_update_wp2fa_network_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_options' ) );
244 add_action( 'network_admin_edit_update_wp2fa_network_email_options', array( '\WP2FA\Admin\Settings_Page', 'update_wp2fa_network_email_options' ) );
245 add_action( 'network_admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_network_admin_notice' ) );
246 add_action( 'network_admin_notices', array( __CLASS__, 'wp_not_writable' ) );
247 } else {
248 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'create_settings_admin_menu' ) );
249 add_action( 'admin_notices', array( '\WP2FA\Admin\Settings_Page', 'settings_saved_admin_notice' ) );
250 add_action( 'admin_notices', array( __CLASS__, 'wp_not_writable' ) );
251 }
252 add_action( 'wp_ajax_wp2fa_dismiss_notice_mail_domain', array( '\WP2FA\Admin\Settings_Page', 'dismiss_notice_mail_domain' ) );
253 \add_action( 'wp_ajax_nopriv_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
254 \add_action( 'wp_ajax_set_salt_key', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'set_salt_key' ) );
255
256 add_action( 'wp_ajax_wp_2fa_get_all_users', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_users' ) );
257 add_action( 'wp_ajax_wp_2fa_get_all_roles', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_ajax_user_roles' ) );
258 add_action( 'wp_ajax_wp_2fa_get_all_network_sites', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'get_all_network_sites' ) );
259 add_action( 'wp_ajax_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
260 add_action( 'admin_action_unlock_account', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'unlock_account' ), 10, 1 );
261 add_action( 'admin_action_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
262 add_action( 'wp_ajax_remove_user_2fa', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'remove_user_2fa' ), 10, 1 );
263 add_action( 'admin_menu', array( '\WP2FA\Admin\Settings_Page', 'hide_settings' ), 999 );
264 add_action( 'plugin_action_links_' . WP_2FA_BASE, array( '\WP2FA\Admin\Settings_Page', 'add_plugin_action_links' ) );
265 add_filter( 'display_post_states', array( '\WP2FA\Admin\Settings_Page', 'add_display_post_states' ), 10, 2 );
266 add_action( 'wp_ajax_send_authentication_setup_email', array( '\WP2FA\Admin\Setup_Wizard', 'send_authentication_setup_email' ) );
267 add_action( 'wp_ajax_send_backup_codes_email', array( '\WP2FA\Methods\Backup_Codes', 'send_backup_codes_email' ) );
268 add_action( 'wp_ajax_regenerate_authentication_key', array( '\WP2FA\Methods\TOTP', 'regenerate_authentication_key' ) );
269
270 // User_Notices.
271 add_action( 'wp_ajax_dismiss_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
272 add_action( 'wp_ajax_wp2fa_dismiss_reconfigure_nag', array( '\WP2FA\Admin\User_Notices', 'dismiss_nag' ) );
273 add_action( 'wp_logout', array( '\WP2FA\Admin\User_Notices', 'reset_nag' ), 10, 1 );
274
275 // User_Profile.
276 global $pagenow;
277 if ( 'profile.php' !== $pagenow || 'user-edit.php' !== $pagenow ) {
278 add_action( 'show_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
279 add_action( 'edit_user_profile', array( '\WP2FA\Admin\User_Profile', 'inline_2fa_profile_form' ) );
280 if ( WP_Helper::is_multisite() ) {
281 add_action( 'personal_options_update', array( '\WP2FA\Admin\User_Profile', 'save_user_2fa_options' ) );
282 }
283 }
284 add_filter( 'user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
285 if ( WP_Helper::is_multisite() ) {
286 add_filter( 'ms_user_row_actions', array( '\WP2FA\Admin\User_Profile', 'user_2fa_row_actions' ), 10, 2 );
287 }
288 add_action( 'wp_ajax_validate_authcode_via_ajax', array( '\WP2FA\Admin\User_Profile', 'validate_authcode_via_ajax' ) );
289 add_action( 'wp_ajax_wp2fa_test_email', array( '\WP2FA\Admin\Helpers\Ajax_Helper', 'handle_send_test_email_ajax' ) );
290
291 // Login.
292 add_action( 'wp_login', array( '\WP2FA\Authenticator\Login', 'wp_login' ), 20, 2 );
293 add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
294 add_action( 'login_form_validate_2fa', array( '\WP2FA\Authenticator\Login', 'login_form_validate_2fa' ) );
295 add_action( 'login_form_backup_2fa', array( '\WP2FA\Authenticator\Login', 'backup_2fa' ) );
296 add_action( 'login_enqueue_scripts', array( '\WP2FA\Authenticator\Login', 'dequeue_style' ), PHP_INT_MAX );
297
298 // Reset password.
299 add_action( 'lostpassword_post', array( '\WP2FA\Authenticator\Reset_Password', 'lostpassword_post' ), 20, 2 );
300 add_action( 'login_form_lostpassword', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ), 20 );
301 // add_action( 'wp_loaded', array( '\WP2FA\Authenticator\Reset_Password', 'login_form_validate_2fa' ) );
302
303 /**
304 * Keep track of all the user sessions for which we need to invalidate the
305 * authentication cookies set during the initial password check.
306 */
307 add_action( 'set_auth_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
308 add_action( 'set_logged_in_cookie', array( '\WP2FA\Authenticator\Login', 'collect_auth_cookie_tokens' ) );
309
310 // Run only after the core wp_authenticate_username_password() check.
311 add_filter( 'authenticate', array( '\WP2FA\Authenticator\Login', 'filter_authenticate' ), 50 );
312 add_filter( 'wp_authenticate_user', array( '\WP2FA\Authenticator\Login', 'run_authentication_check' ), 10, 2 );
313
314 // User Register.
315 add_action( 'set_user_role', array( '\WP2FA\Admin\User_Registered', 'check_user_upon_role_change' ), 10, 3 );
316
317 // Block users from admin if needed.
318 $user_block_hook = is_admin() || is_network_admin() ? 'init' : 'wp';
319 add_action( $user_block_hook, array( __CLASS__, 'block_unconfigured_users_from_admin' ), 10 );
320
321 // Help & Contact Us.
322 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( '\WP2FA\Admin\Help_Contact_Us', 'add_extra_menu_item' ) );
323
324 // phpcs:disable
325 /* @free:start */
326 // phpcs:enable
327 // Premium Features.
328 add_action( WP_2FA_PREFIX . 'after_admin_menu_created', array( 'WP2FA\Admin\Premium_Features', 'add_extra_menu_item' ) );
329 add_action( WP_2FA_PREFIX . 'before_plugin_settings', array( 'WP2FA\Admin\Premium_Features', 'add_settings_banner' ) );
330 add_action( 'admin_footer', array( 'WP2FA\Admin\Premium_Features', 'pricing_new_tab_js' ) );
331 // phpcs:disable
332 /* @free:end */
333 // phpcs:enable
334
335 add_action( 'admin_footer', array( '\WP2FA\Admin\User_Profile', 'dismiss_nag_notice' ) );
336
337 \add_action( WP_2FA_PREFIX . 'user_authenticated', array( __CLASS__, 'clear_user_after_login' ), 10, 1 );
338
339 \add_filter( 'mepr-auto-login', array( '\WP2FA\Authenticator\Login', 'mepr_login' ) );
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_subject' => $login_code_subject,
612 'login_code_setup_email_body' => $login_code_setup_body,
613 'user_account_locked_email_subject' => $user_locked_subject,
614 'user_account_locked_email_body' => $user_locked_body,
615 'user_account_unlocked_email_subject' => $user_unlocked_subject,
616 'user_account_unlocked_email_body' => $user_unlocked_body,
617 'user_backup_codes_email_subject' => $user_backup_codes_subject,
618 'user_backup_codes_email_body' => $user_backup_codes_body,
619 'send_account_locked_email' => 'enable_account_locked_email',
620 'send_account_unlocked_email' => 'enable_account_unlocked_email',
621 'send_login_code_email' => 'enable_send_login_code_email',
622 'send_reset_password_code_email' => 'enable_send_reset_password_code_email',
623 );
624
625 /**
626 * Allows 3rd party providers to their own settings for the mail templates.
627 *
628 * @param array $default_settings - Array with the default settings.
629 *
630 * @since 2.0.0
631 */
632 $default_settings = apply_filters( WP_2FA_PREFIX . 'mail_default_settings', $default_settings );
633
634 return $default_settings[ $setting_name ];
635 }
636
637 /**
638 * Util which we use to replace our {strings} with actual, useful stuff.
639 *
640 * @param string $input Text we are working on.
641 * @param int|string $user_id User id, if its needed.
642 * @param string $token Login code, if its needed..
643 * @param string $override_grace_period - Value to override grace period with.
644 *
645 * @return string The output, with all the {strings} swapped out.
646 */
647 public static function replace_email_strings( $input = '', $user_id = '', $token = '', $override_grace_period = '' ) {
648
649 $token = trim( (string) $token );
650
651 // Gather grace period.
652 $grace_period_string = '';
653 if ( isset( $override_grace_period ) && ! empty( $override_grace_period ) ) {
654 $grace_period_string = $override_grace_period;
655 } else {
656 $grace_policy = self::get_wp2fa_setting( 'grace-policy' );
657 $grace_period_string = Date_Time_Utils::format_grace_period_expiration_string( $grace_policy );
658 }
659
660 // Setup user data.
661 if ( isset( $user_id ) && ! empty( $user_id ) ) {
662 $user = get_userdata( $user_id );
663 } else {
664 $user = wp_get_current_user();
665 }
666
667 // Setup token.
668 if ( isset( $token ) && ! empty( $token ) ) {
669 $login_code = $token;
670 } else {
671 $login_code = '';
672 }
673
674 $new_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
675 if ( ! empty( $new_page_id ) ) {
676 $new_page_permalink = get_permalink( $new_page_id );
677 } else {
678 $new_page_id = Settings::get_custom_settings_page_id( '', $user );
679 if ( ! empty( $new_page_id ) ) {
680 $new_page_permalink = get_permalink( $new_page_id );
681 } else {
682 $new_page_permalink = '';
683 }
684 }
685
686 // These are the strings we are going to search for, as well as there respective replacements.
687 $replacements = array(
688 '{site_url}' => esc_url( get_bloginfo( 'url' ) ),
689 '{site_name}' => sanitize_text_field( get_bloginfo( 'name' ) ),
690 '{grace_period}' => sanitize_text_field( $grace_period_string ),
691 '{user_login_name}' => sanitize_text_field( $user->user_login ),
692 '{user_first_name}' => sanitize_text_field( $user->user_firstname ),
693 '{user_last_name}' => sanitize_text_field( $user->user_lastname ),
694 '{user_display_name}' => sanitize_text_field( $user->display_name ),
695 '{login_code}' => $login_code,
696 '{2fa_settings_page_url}' => esc_url( $new_page_permalink ),
697 '{user_ip_address}' => Request_Utils::get_ip(),
698 );
699
700 /**
701 * 3rd party plugins could change the mail strings, or provide their own.
702 *
703 * @param array $replacements - The array with all the currently supported strings.
704 */
705 $replacements = apply_filters(
706 WP_2FA_PREFIX . 'replacement_email_strings',
707 $replacements
708 );
709
710 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
711 return $final_output;
712 }
713
714 /**
715 * Util which contextualizes the wording 'reconfigure'/'configure' as needed.
716 *
717 * @param string $input - Text we are working on.
718 * @param int|string $user_id - User id, if its needed.
719 * @param string $method_to_check - Name of the method to check for.
720 *
721 * @return string The output, with all the {strings} swapped out.
722 *
723 * @since 2.5.0
724 */
725 public static function contextual_reconfigure_text( $input = '', $user_id = '', $method_to_check = '' ) {
726
727 if ( empty( trim( (string) $input ) ) ) {
728 return $input;
729 }
730
731 $enabled_method = User_Helper::get_enabled_method_for_user( $user_id );
732
733 $text = ( $enabled_method === $method_to_check ) ? esc_html__( 'Reconfigure', 'wp-2fa' ) : esc_html__( 'Configure', 'wp-2fa' );
734
735 $replacements = array(
736 '{reconfigure_or_configure_capitalized}' => $text,
737 '{reconfigure_or_configure}' => strtolower( $text ),
738 );
739
740 /**
741 * 3rd party plugins could change this to their own.
742 *
743 * @param array $replacements - The array with all the currently supported strings.
744 *
745 * @since 2.5.0
746 */
747 $replacements = apply_filters(
748 WP_2FA_PREFIX . 'replacement_reconfigure_strings',
749 $replacements
750 );
751
752 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
753 }
754
755 /**
756 * Util replace replace a placeholder with the actual remaining grace period for a user..
757 *
758 * @param string $input - Text we are working on.
759 * @param int $grace_expiry - Expiration time.
760 *
761 * @return string The output, with all the {strings} swapped out.
762 *
763 * @since 2.5.0
764 */
765 public static function replace_remaining_grace_period( $input = '', $grace_expiry = -1 ) {
766 if ( empty( trim( (string) $input ) ) || empty( trim( (string) $grace_expiry ) ) ) {
767 return $input;
768 }
769
770 $replacements = array(
771 '{grace_period_remaining}' => esc_attr( Date_Time_Utils::format_grace_period_expiration_string( null, $grace_expiry ) ),
772 );
773
774 return str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
775 }
776
777 /**
778 * Util which we use to replace our {strings} with actual, useful stuff.
779 *
780 * @param string $input Text we are working on.
781 * @param WP_User $user The WP User.
782 *
783 * @return string The output, with all the {strings} swapped out.
784 */
785 public static function replace_wizard_strings( $input = '', $user = false ) {
786
787 if ( ! $user ) {
788 return $input;
789 }
790
791 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
792
793 // These are the strings we are going to search for, as well as there respective replacements.
794 $replacements = array(
795 '{available_methods_count}' => count( $available_methods[ User_Helper::get_user_role( $user ) ] ),
796 );
797
798 /**
799 * 3rd party plugins could change the mail strings, or provide their own.
800 *
801 * @param array $replacements - The array with all the currently supported strings.
802 */
803 $replacements = apply_filters(
804 WP_2FA_PREFIX . 'replacement_wizard_strings',
805 $replacements
806 );
807
808 $final_output = str_replace( array_keys( $replacements ), array_values( $replacements ), $input );
809 return $final_output;
810 }
811
812 /**
813 * If a user is trying to access anywhere other than the 2FA config area, this blocks them.
814 *
815 * @SuppressWarnings(PHPMD.ExitExpression)
816 */
817 public static function block_unconfigured_users_from_admin() {
818 global $pagenow;
819
820 $user = User_Helper::get_user();
821 if ( 0 === $user->ID ) {
822 return;
823 }
824
825 $redirect = true;
826
827 if ( class_exists( '\WP2FA\Freemius\User_Licensing' ) ) {
828 $redirect = User_Licensing::enable_2fa_user_setting( true );
829 }
830
831
832 if ( $redirect ) {
833 $is_user_instantly_enforced = User_Helper::get_user_enforced_instantly();
834 $grace_period_expiry_time = (int) User_Helper::get_user_expiry_date();
835 $time_now = time();
836 if ( $is_user_instantly_enforced && ! empty( $grace_period_expiry_time ) && $grace_period_expiry_time < $time_now && ! User_Helper::is_excluded( $user->ID ) ) {
837
838 $has_cap = true;
839 if ( class_exists( 'WooCommerce', false ) ) {
840
841 // Lets check if the user has the required capabilities to view the 2FA settings page (or profile page in the Admin section - dashboard).
842 $has_cap = false;
843
844 $access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' );
845
846 foreach ( $access_caps as $access_cap ) {
847 if ( current_user_can( $access_cap ) ) {
848 $has_cap = true;
849 break;
850 }
851 }
852 }
853
854 /**
855 * We should only allow:
856 * - 2FA setup wizard in the administration
857 * - custom 2FA page if enabled and created
858 * - AJAX requests originating from these 2FA setup UIs
859 */
860 if ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && self::action_check() ) { // phpcs:ignore
861 return;
862 }
863
864 if ( is_admin() || is_network_admin() ) {
865 $allowed_admin_page = 'profile.php';
866 if ( $pagenow === $allowed_admin_page && ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) ) { // phpcs:ignore
867 return;
868 }
869 }
870
871 if ( is_page() ) {
872 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
873 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
874 return;
875 } else {
876 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
877 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
878 return;
879 }
880 }
881 }
882
883 // force a redirect to the 2FA set-up page if it exists.
884 $custom_user_page_id = Settings::get_role_or_default_setting( 'custom-user-page-id', $user );
885 if ( ! empty( $custom_user_page_id ) ) {
886 wp_redirect( Settings::get_custom_page_link( $user ) );
887 exit;
888 } else {
889 $custom_user_page_id = Settings::get_custom_settings_page_id( '', $user );
890 if ( ! empty( $custom_user_page_id ) && get_the_ID() === (int) $custom_user_page_id ) {
891 wp_redirect( get_permalink( $custom_user_page_id ) );
892 exit;
893 }
894 }
895
896 // 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.
897 if ( ! $has_cap ) {
898
899 // Is there WOO installed? If so, then lets try to extract the redirection rules from there.
900 if ( class_exists( 'WooCommerce', false ) ) {
901
902 // Lets check if there is a 2FA implemented within the WOOCommerce myaccount page.
903 $items = \wc_get_account_menu_items();
904
905 if ( isset( $items['wp-2fa'] ) ) {
906
907 if ( ! isset( $_GET['wp-2fa'] ) ) {
908 $url = add_query_arg(
909 array(
910 'wp-2fa' => '',
911 ),
912 get_permalink( get_option( 'woocommerce_myaccount_page_id' ) )
913 );
914
915 wp_redirect( $url );
916
917 exit;
918 }
919 }
920 }
921
922 // Nothing suitable found - notify the admin and bail.
923 $transient_name = WP_2FA_PREFIX . '_notified_admin_mail_nowhere_to_redirect_' . $user->ID;
924 if ( false === \get_transient( $transient_name ) ) {
925 $subject = sprintf(
926 // translators: The username.
927 esc_html__(
928 'User %1$s logged in without 2FA',
929 'wp-2fa'
930 ),
931 $user->user_login,
932 );
933
934 $text = sprintf(
935 // translators: The username.
936 // translators: the site name.
937 esc_html__(
938 '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.',
939 'wp-2fa'
940 ),
941 $user->user_login,
942 get_bloginfo( 'name' )
943 );
944 $text .= '<p>' . sprintf(
945 // translators: the settings page.
946 // translators: the support e-mail.
947 esc_html__(
948 '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.',
949 'wp-2fa'
950 ),
951 '<a href="' . esc_url(
952 add_query_arg(
953 array(
954 'page' => 'wp-2fa-settings',
955 'tab' => 'integrations',
956 ),
957 network_admin_url( 'admin.php' )
958 )
959 ) . '">front-end 2FA page</a>',
960 '<a href="mailto:support@melapress.com">support@melapress.com</a>'
961 ) . '</p>';
962
963 Settings_Page::send_email(
964 get_option( 'admin_email' ),
965 $subject,
966 $text
967 );
968
969 \set_transient( $transient_name, 'sent', DAY_IN_SECONDS * 2 );
970 }
971
972 return;
973 }
974
975 // custom 2FA page is not set-up, force redirect to the wizard in administration.
976 wp_redirect( Settings::get_setup_page_link() );
977 exit;
978 }
979 }
980 }
981
982 /**
983 * Returns currently stored settings
984 *
985 * @return array
986 */
987 public static function get_policy_settings() {
988 /**
989 * Extensions could change the stored settings value, based on custom / different / specific for role settings.
990 *
991 * @param array - Value of the settings.
992 *
993 * @since 2.0.0
994 */
995 $settings = apply_filters( WP_2FA_PREFIX . 'policy_settings', self::$plugin_settings[ WP_2FA_POLICY_SETTINGS_NAME ] );
996
997 return $settings;
998 }
999
1000 /**
1001 * Checks the action parameter against given list of actions
1002 *
1003 * @return bool
1004 *
1005 * @since 2.0.0
1006 */
1007 private static function action_check() {
1008 if ( ! isset( $_REQUEST['action'] ) ) { //phpcs:ignore -- No nonce - that is not needed here
1009 return false;
1010 }
1011 $actions_array = array(
1012 'send_authentication_setup_email',
1013 'validate_authcode_via_ajax',
1014 'heartbeat',
1015 'regenerate_authentication_key',
1016 'send_backup_codes_email',
1017 'register_user_twilio',
1018 );
1019
1020 /**
1021 * Allows 3rd party providers to their own settings for the mail templates.
1022 *
1023 * @param array $actions_array - Array with the default settings.
1024 *
1025 * @since 2.0.0
1026 */
1027 $actions_array = apply_filters( WP_2FA_PREFIX . 'actions_check', $actions_array );
1028
1029 return in_array( $_REQUEST['action'], $actions_array, true );
1030 }
1031
1032 /**
1033 * Updates the plugin settings, the settings hash in the database as well as a local (cached) copy of the settings.
1034 *
1035 * @param array $settings - The settings values.
1036 * @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.
1037 * @param string $settings_name - The name of the settings to extract.
1038 *
1039 * @since 2.0.0
1040 */
1041 public static function update_plugin_settings( $settings, $skip_option_save = false, $settings_name = WP_2FA_POLICY_SETTINGS_NAME ) {
1042 // update local copy of settings.
1043 self::$plugin_settings[ $settings_name ] = $settings;
1044
1045 if ( ! $skip_option_save ) {
1046 // update the database option itself.
1047 Settings_Utils::update_option( $settings_name, $settings );
1048 }
1049
1050 if ( WP_2FA_POLICY_SETTINGS_NAME === $settings_name ) {
1051 // Create a hash for comparison when we interact with a use.
1052 $settings_hash = Settings_Utils::create_settings_hash( self::get_policy_settings() );
1053 Settings_Utils::update_option( WP_2FA_PREFIX . 'settings_hash', $settings_hash );
1054 }
1055 }
1056
1057 /**
1058 * Getter for the ecret key of the plugin for the current instance
1059 *
1060 * Note: that is legacy code and will be removed.
1061 *
1062 * @return string
1063 *
1064 * @since 2.0.0
1065 */
1066 public static function get_secret_key() {
1067 if ( null === self::$secret_key ) {
1068 if ( ! defined( File_Writer::SECRET_NAME ) ) {
1069 self::check_for_key();
1070 } else {
1071 self::$secret_key = constant( File_Writer::SECRET_NAME );
1072 }
1073 }
1074
1075 return self::$secret_key;
1076 }
1077
1078 /**
1079 * Checks if the wp-config.php file is writable, show notice to the admin if it is not
1080 *
1081 * @return void
1082 *
1083 * @since 2.4.0
1084 */
1085 public static function wp_not_writable() {
1086
1087 if ( ! \defined( 'WP2FA_SECRET_IS_IN_DB' ) || true !== WP2FA_SECRET_IS_IN_DB ) {
1088 return;
1089 }
1090
1091 if ( ! File_Writer::can_write_to_file( File_Writer::get_wp_config_file_path() ) ) {
1092 $whitelist_admin_pages = array(
1093 'wp-2fa_page_wp-2fa-settings',
1094 'wp-2fa_page_wp-2fa-settings-network',
1095 'toplevel_page_wp-2fa-policies',
1096 'toplevel_page_wp-2fa-policies-network',
1097 'wp-2fa_page_wp-2fa-help-contact-us',
1098 'wp-2fa_page_wp-2fa-help-contact-us-network',
1099 'wp-2fa_page_wp-2fa-policies-account',
1100 'wp-2fa_page_wp-2fa-policies-account-network',
1101 'wp-2fa_page_wp-2fa-reports',
1102 'wp-2fa_page_wp-2fa-reports-network',
1103 );
1104 $admin_page = get_current_screen();
1105 if ( in_array( $admin_page->base, $whitelist_admin_pages ) ) {
1106 ?>
1107 <div class="notice notice-warning" id="config-update-notice">
1108 <?php
1109 $message = sprintf(
1110 '<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>',
1111 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>' .
1112
1113 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>' .
1114
1115 esc_html__( 'Option B) Add the encryption key to the wp-config.php file yourself by ', 'wp-2fa' ),
1116 esc_html__( 'following these instructions.', 'wp-2fa' ) . '<br>',
1117 esc_html__(
1118 'Once you complete any of the above, please click the button below.
1119 ',
1120 'wp-2fa'
1121 ),
1122 )
1123 ?>
1124 <?php echo $message; // phpcs:ignore ?>
1125 <p><button id="salt-update" type="button">
1126 <span><?php esc_html_e( 'Write key to file now / Check for the key in file', 'wp-2fa' ); ?></span>
1127 </button></p>
1128 </div>
1129 <script>
1130 jQuery(document).ready(function($) {
1131 $(document).on('click', '#salt-update', function( event ) {
1132 const ajaxURL = (typeof wp2faWizardData != "undefined") ? wp2faWizardData.ajaxURL : ajaxurl;
1133 const nonceValue = '<?php echo \esc_attr( \wp_create_nonce( 'wp-2fa-set-salt-nonce' ) ); ?>';
1134 jQuery.ajax({
1135 url: ajaxURL,
1136 data: {
1137 action: 'set_salt_key',
1138 _wpnonce: nonceValue
1139 },
1140 success: function (data) {
1141 if (data.success) {
1142 jQuery('#config-update-notice .notice-dismiss').click();
1143 } else {
1144 alert(data.data);
1145 }
1146 },
1147 error: function (data) {
1148 alert(data.responseJSON.data[0].message);
1149 }
1150 });
1151 });
1152 });
1153 </script>
1154 <?php
1155 }
1156 }
1157 }
1158
1159 /**
1160 * Remove the user meta related with the code has been sent to the user.
1161 * That is so we can lower the security by giving the option not to resend codes, so eventual brute force could succeed.
1162 * The setting name - brute_force_disable
1163 *
1164 * @return void
1165 *
1166 * @since 2.5.0
1167 */
1168 public static function clear_user_after_login() {
1169 User_Helper::remove_meta( WP_2FA_PREFIX . 'code_sent' );
1170 }
1171
1172 /**
1173 * Checks and sets the global wp2fa salt
1174 *
1175 * @return void
1176 *
1177 * @since 2.4.0
1178 */
1179 private static function check_for_key() {
1180 self::$secret_key = Settings_Utils::get_option( 'secret_key' );
1181 if ( empty( self::$secret_key ) ) {
1182 self::$secret_key = base64_encode( Open_SSL::secure_random() ); // phpcs:ignore
1183 if ( ! File_Writer::save_secret_key( self::$secret_key ) ) {
1184 Settings_Utils::update_option( 'secret_key', self::$secret_key );
1185 }
1186 }
1187 }
1188 }
1189 }
1190