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