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