PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.5.2
WP 2FA – Two-factor authentication for WordPress v1.5.2
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 / Admin / UserProfile.php
wp-2fa / includes / classes / Admin Last commit date
SettingsPage.php 5 years ago SetupWizard.php 5 years ago UserNotices.php 5 years ago UserProfile.php 5 years ago UserRegistered.php 5 years ago
UserProfile.php
815 lines
1 <?php // phpcs:ignore
2
3 namespace WP2FA\Admin;
4
5 use \WP2FA\Authenticator\Authentication as Authentication;
6 use \WP2FA\Utils\DateTimeUtils;
7 use \WP2FA\WP2FA as WP2FA;
8 use \WP2FA\Core as Core;
9 use \WP2FA\Authenticator\BackupCodes as BackupCodes;
10 use \WP2FA\Utils\GenerateModal as GenerateModal;
11 use \WP2FA\Utils\UserUtils as UserUtils;
12
13 /**
14 * UserProfile - Class for handling user things such as profile settings and admin list views.
15 */
16 class UserProfile {
17
18 const NOTICES_META_KEY = 'wp_2fa_totp_notices';
19
20 /**
21 * Classs constructor
22 */
23 public function __construct() {
24 }
25
26 /**
27 * Add our buttons to the user profile editing screen.
28 *
29 * @param object $user User data.
30 */
31 public function user_2fa_options( $user, $additional_args = array() ) {
32
33 if ( isset( $_GET['user_id'] ) ) {
34 $user_id = (int) $_GET['user_id'];
35 $user = get_user_by( 'id', $user_id );
36 } else {
37 $user = wp_get_current_user();
38 }
39
40 // Get current user, we going to need this regardless.
41 $current_user = wp_get_current_user();
42
43 // Bail if we still dont have an object.
44 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
45 return;
46 }
47
48 $user_type = UserUtils::determine_user_2fa_status( $user );
49
50 $form_output = '';
51 $form_content = '';
52 $description = __( 'Add two-factor authentication to strengthen the security of your WordPress user account.', 'wp-2fa' );
53 $show_form_table = true;
54 $page_url = ( WP2FA::is_this_multisite() ) ? 'index.php' : 'options-general.php';
55
56 // Orpan user (a user with no role or capabitlies).
57 if ( in_array( 'orphan_user', $user_type, true ) ) {
58 // We want to use the same form/buttons used in the shortcode.
59 $additional_args['is_shortcode'] = true;
60
61 // Create useful message for admin.
62 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'can_manage_options' ), $user_type ) ) {
63 $description = __( 'This user is required to setup 2FA but has not yet done so.', 'wp-2fa' );
64 }
65
66 if ( UserUtils::in_array_all( array( 'user_is_excluded', 'can_manage_options' ), $user_type ) ) {
67 $description = __( 'This user is excluded from configuring 2FA.', 'wp-2fa' );
68 }
69 }
70
71 // Excluded user.
72 if ( in_array( 'user_is_excluded', $user_type, true ) ) {
73 $description = __( 'Your user / role is not permitted to configure 2FA. Contact your administrator for more information.', 'wp-2fa' );
74 $show_form_table = false;
75 }
76
77 // A user viewing their own profile AND has a 2FA method configured.
78 if ( UserUtils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) {
79 // Create wizard link based on which 2fa methods are allowed by admin.
80 if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_totp' ) ) && ! empty( WP2FA::get_wp2fa_setting( 'enable_email' ) ) ) {
81 $setup_2fa_url = add_query_arg(
82 array(
83 'page' => 'wp-2fa-setup',
84 'current-step' => 'user_choose_2fa_method',
85 'wizard_type' => 'user_2fa_config',
86 ),
87 admin_url( $page_url )
88 );
89 } else {
90 $setup_2fa_url = add_query_arg(
91 array(
92 'page' => 'wp-2fa-setup',
93 'current-step' => 'reconfigure_method',
94 'wizard_type' => 'user_reconfigure_config',
95 ),
96 admin_url( $page_url )
97 );
98 }
99
100 // Create remove 2fa link.
101 $remove_2fa_url = add_query_arg(
102 array(
103 'action' => 'remove_user_2fa',
104 'user_id' => $user->ID,
105 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ),
106 ),
107 admin_url( 'user-edit.php' )
108 );
109
110 // Create backup codes URL;
111 $backup_codes_url = add_query_arg(
112 array(
113 'page' => 'wp-2fa-setup',
114 'current-step' => 'backup_codes',
115 'wizard_type' => 'backup_codes_config',
116 ),
117 admin_url( $page_url )
118 );
119
120 $form_content .= '<a href="' . esc_url( $setup_2fa_url ) . '" class="button button-primary">' . __( 'Change 2FA Settings', 'wp-2fa' ) . '</a>';
121
122 if ( self::can_user_remove_2fa( $user->ID ) ) {
123 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . __( 'Remove 2FA', 'wp-2fa' ) . '</a>';
124 }
125
126 $form_content .= '<br /><br />';
127
128 $form_content .= '<a href="' . esc_url( $backup_codes_url ) . '" class="button button-primary">' . __( 'Generate backup codes', 'wp-2fa' ) . '</a>';
129
130 $codes_remaining = BackupCodes::codes_remaining_for_user( $user );
131 if ( $codes_remaining > 0 ) {
132 $form_content .= '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . __( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
133 } elseif ( 0 === $codes_remaining ) {
134 $form_content .= '<a class="learn_more_link" href="https://www.wpwhitesecurity.com/2fa-backup-codes/?utm_source=plugin&utm_medium=referral&utm_campaign=wp2fa&utm_content=settings+pages" target="_blank">' . __( 'Learn more.', 'wp-2fa' ) . '</a>';
135 }
136
137 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
138 $form_content = '<a href="#" class="button button-primary remove-2fa" data-open-configure-2fa-wizard>' . __( 'Change 2FA Settings', 'wp-2fa' ) . '</a>';
139
140 if ( self::can_user_remove_2fa( $user->ID ) ) {
141 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . __( 'Remove 2FA', 'wp-2fa' ) . '</a>';
142 }
143
144 $codes_remaining = BackupCodes::codes_remaining_for_user( $user );
145 if ( $codes_remaining > 0 ) {
146 $backup_codes_desc = '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . __( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
147 } elseif ( 0 === $codes_remaining ) {
148 $backup_codes_desc = '<a class="learn_more_link" href="https://www.wpwhitesecurity.com/2fa-backup-codes/?utm_source=plugin&utm_medium=referral&utm_campaign=wp2fa&utm_content=settings+pages" target="_blank">' . __( 'Learn more.', 'wp-2fa' ) . '</a>';
149 }
150
151 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show( \'configure-2fa-backup-codes\' );">' . __( 'Generate Backup Codes', 'wp-2fa' ) . '</a>' . $backup_codes_desc;
152 }
153 }
154
155 // User viewing own profile and needs to enable 2FA.
156 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) ) {
157 $first_time_setup_url = add_query_arg(
158 array(
159 'page' => 'wp-2fa-setup',
160 ),
161 admin_url( $page_url )
162 );
163
164 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
165 $form_content .= '<a href="#" class="button button-primary" data-open-configure-2fa-wizard>' . __( 'Configure 2FA', 'wp-2fa' ) . '</a>';
166 }
167
168 if ( empty( $additional_args ) ) {
169 $form_content .= '<a href="' . esc_url( $first_time_setup_url ) . '" class="button button-primary">' . __( 'Configure Two-factor authentication (2FA)', 'wp-2fa' ) . '</a>';
170 }
171 }
172
173 // Admin viewing users profile AND user has a configured 2FA method.
174 if ( UserUtils::in_array_all( array( 'can_manage_options', 'has_enabled_methods' ), $user_type ) && ! in_array( 'viewing_own_profile', $user_type, true ) ) {
175 $description = __( 'The user has already configured 2FA. When you reset the user\'s current 2FA configuration, the user can log back in with just the username and password.', 'wp-2fa' );
176
177 $remove_users_2fa_url = add_query_arg(
178 array(
179 'action' => 'remove_user_2fa',
180 'user_id' => $user->ID,
181 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ),
182 'admin_reset' => 'yes',
183 ),
184 admin_url( 'user-edit.php' )
185 );
186
187 $form_content .= '<a href="' . esc_url( $remove_users_2fa_url ) . '" class="button button-primary">' . __( 'Reset 2FA configuration', 'wp-2fa' ) . '</a>';
188 }
189
190 // Admin viewing users profile AND users grace period has expired.
191 if ( UserUtils::in_array_all( array( 'can_manage_options', 'grace_has_expired' ), $user_type ) ) {
192 $unlock_user_url = add_query_arg(
193 array(
194 'action' => 'unlock_account',
195 'user_id' => $user->ID,
196 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-unlock-account-nonce' ),
197 ),
198 admin_url( 'user-edit.php' )
199 );
200 $form_content .= '<a href="' . esc_url( $unlock_user_url ) . '" class="button button-primary">' . __( 'Unlock user and reset the grace period', 'wp-2fa' ) . '</a>';
201 }
202
203 $form_output .= '<h2>' . __( 'WP 2FA Settings', 'wp-2fa' ) . '</h2>';
204
205 if ( $description ) {
206 $form_output .= '<p class="description">' . $description . '</p>';
207 }
208
209 $form_content = apply_filters( 'wp_2fa_append_to_profile_form_content', $form_content );
210
211 if ( $show_form_table && ! empty( $form_content ) ) {
212 $form_output .= '
213 <table class="form-table wp-2fa-user-profile-form" role="presentation">
214 <tbody>
215 <tr>
216 <th><label>' . __( '2-Factor authentication', 'wp-2fa' ) . '</label></th>
217 <td>
218 ' . $form_content . '
219 </td>
220 </tr>
221 </tbody>
222 </table>
223 ';
224 }
225
226 echo $form_output;
227
228 $this->generate_inline_modals( $user_type );
229 }
230
231 public function generate_inline_modals( $user_type = array() ) {
232
233 ob_start();
234
235 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || UserUtils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) : ?>
236 <div>
237 <div class="wp2fa-modal micromodal-slide" id="configure-2fa" aria-hidden="true">
238 <div class="modal__overlay" tabindex="-1" data-micromodal-close>
239 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
240 <button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
241 <main class="modal__content" id="modal-1-content">
242 <?php
243 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) ) :
244
245 $user = wp_get_current_user();
246 $enabled_method = get_user_meta( $user->ID, 'wp_2fa_enabled_methods', true );
247
248 // Grab key from user meta.
249 $key = Authentication::get_user_totp_key( $user->ID );
250
251 // If no key is present, lets make one.
252 if ( empty( $key ) ) {
253 $key = Authentication::generate_key();
254 $update = update_user_meta( $user->ID, 'wp_2fa_totp_key', $key );
255 }
256 $setupnonce = wp_create_nonce( 'wp-2fa-send-setup-email' );
257 $validate_nonce = wp_create_nonce( 'wp-2fa-validate-authcode' );
258
259 // Setup site information, used when generating our QR code.
260 $site_name = get_bloginfo( 'name', 'display' );
261 $totp_title = apply_filters( 'wp_2fa_totp_title', $site_name . ':' . $user->user_login, $user );
262 $available_methods = UserUtils::get_2fa_methods_available_to_user( $user );
263
264 if ( count( $available_methods ) > 1 ) {
265 $intro_text = esc_html__( 'Choose the 2FA authentication method', 'wp-2fa' );
266 $sub_text = esc_html__( 'There are two methods available from which you can choose for 2FA:', 'wp-2fa' );
267 } else {
268 $intro_text = esc_html__( 'Choose the 2FA authentication method', 'wp-2fa' );
269 $sub_text = esc_html__( 'Only the below 2FA method is allowed on this website:', 'wp-2fa' );
270 }
271 ?>
272
273 <div class="wizard-step active">
274 <h3><?php echo sanitize_text_field( $intro_text ); ?></h3>
275 <p><?php echo sanitize_text_field( $sub_text ); ?></p>
276 <fieldset>
277 <?php if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_totp' ) ) ) { ?>
278 <div class="option-pill">
279 <label for="basic">
280 <input id="basic" name="wp_2fa_enabled_methods" type="radio" value="totp" checked>
281 <?php esc_html_e( 'One-time code generated with your app of choice (most reliable and secure)', 'wp-2fa' ); ?>
282 </label>
283 <?php
284 printf( '<p class="description">%1$s <a href="https://www.wpwhitesecurity.com/support/kb/configuring-2fa-apps/" target="_blank">%2$s</a> %3$s</p>', esc_html__( 'Note: This method requires you to install one of the following 2FA apps: Google Authenticator, FreeOTP, Microsoft Authenticator, Duo Security, Authy, LastPass and Okta Verify. All of these apps are free and can be downloaded from the Google Play and Apple Appstore. Read our guides on', 'wp-2fa' ), esc_html__( 'our knowledge base', 'wp-2fa' ), esc_html__( 'for more information on how to setup these apps.', 'wp-2fa' ) );
285 ?>
286 </div>
287 <?php } ?>
288 <?php if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_email' ) ) ) { ?>
289 <div class="option-pill">
290 <label for="geek">
291 <input id="geek" name="wp_2fa_enabled_methods" type="radio" value="email">
292 <?php esc_html_e( 'One-time code sent to you over email', 'wp-2fa' ); ?>
293 </label>
294 </div>
295 <?php } ?>
296 </fieldset>
297 <br>
298 <a href="#" class="modal__btn button button-primary 2fa-choose-method" name="next_step_setting_modal_wizard" data-next-step><?php esc_html_e( 'Next Step', 'wp-2fa' ); ?></a>
299 <button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Close', 'wp-2fa' ); ?></button>
300 </div>
301 <?php endif; ?>
302
303 <?php if ( UserUtils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) : ?>
304 <?php
305 // Grab current user
306 $user = wp_get_current_user();
307
308 // Grab key from user meta
309 $key = Authentication::get_user_totp_key( $user->ID );
310
311 // If no key is present, lets make one
312 if ( empty( $key ) ) {
313 $key = Authentication::generate_key();
314 $update = update_user_meta( $user->ID, 'wp_2fa_totp_key', $key );
315 }
316
317 // Setup site information, used when generating our QR code
318 $site_name = get_bloginfo( 'name', 'display' );
319 $totp_title = apply_filters( 'wp_2fa_totp_title', $site_name . ':' . $user->user_login, $user );
320
321 // Now lets grab the users enabled 2fa methods.
322 $selected_method = get_user_meta( $user->ID, 'wp_2fa_enabled_methods', true );
323
324 // Create a nonce incase we want to reset the key
325 $nonce = wp_create_nonce( 'wp-2fa-backup-codes-generate-json-' . $user->ID );
326 $validate_nonce = wp_create_nonce( 'wp-2fa-validate-authcode' );
327
328 $wizard_steps = array();
329 ?>
330
331 <div class="wizard-step active">
332 <fieldset>
333 <?php if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_totp' ) ) ) { ?>
334 <div class="option-pill">
335 <h3>
336 <?php esc_html_e( 'Reconfigure the 2FA App', 'wp-2fa' ); ?>
337 </h3>
338 <p>
339 <?php esc_html_e( 'Click the below button to reconfigure the current 2FA method. Note that once reset you will have to re-scan the QR code on all devices you want this to work on because the previous codes will stop working.', 'wp-2fa' ); ?>
340 </p>
341 <div class="wp2fa-setup-actions">
342 <a href="#" class="button button-primary" name="next_step_setting_modal_wizard" data-trigger-reset-key data-nonce="<?php echo esc_attr( $nonce ); ?>" data-user-id="<?php echo esc_attr( $user->ID ); ?>" data-next-step="2fa-wizard-totp"><?php esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a>
343 </div>
344 </div>
345 <?php } ?>
346 <?php
347 if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_email' ) ) ) {
348 $setupnonce = wp_create_nonce( 'wp-2fa-send-setup-email' );
349 ?>
350 <div class="option-pill">
351 <h3><?php esc_html_e( 'Reconfigure email', 'wp-2fa' ); ?></h3>
352 <p>
353 <?php esc_html_e( 'Please select the email address where the one-time code should be sent:', 'wp-2fa' ); ?>
354 </p>
355 <div class="wp2fa-setup-actions">
356 <a class="button button-primary" name="next_step_setting_modal_wizard" value="<?php esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-user-id="<?php echo esc_attr( $user->ID ); ?>" data-nonce="<?php echo esc_attr( $setupnonce ); ?>" data-next-step="2fa-wizard-email"><?php esc_html_e( 'Change email address', 'wp-2fa' ); ?></a>
357 </div>
358 </div>
359 <?php } ?>
360 </fieldset>
361 </div>
362 <?php endif; ?>
363
364 <div class="wizard-step" id="2fa-wizard-totp">
365 <fieldset>
366
367 <div class="step-setting-wrapper active">
368 <h3><?php esc_html_e( 'Setup the 2FA method', 'wp-2fa' ); ?></h3>
369 <div class="mb-30 clear-both">
370 <div class="modal-50">
371 <div class="option-pill">
372 <ol>
373 <li><?php esc_html_e( 'Download the app of your choice', 'wp-2fa' ); ?></li>
374 <li><?php esc_html_e( 'Click the plus sign (add new icon)', 'wp-2fa' ); ?></li>
375 <li class="hide-on-mobile"><?php esc_html_e( 'Select \'Scan a barcode\'', 'wp-2fa' ); ?></li>
376 <li class="hide-on-mobile"><?php esc_html_e( 'Scan the QR code to the right.', 'wp-2fa' ); ?></li>
377 <li class="show-on-mobile"><?php esc_html_e( 'Select "Enter a provided key" and type in the key below.', 'wp-2fa' ); ?></li>
378 </ol>
379 <p class="hide-on-mobile"><?php esc_html_e( 'Otherwise, select Enter a provided key and type in the key below:', 'wp-2fa' ); ?></p>
380 <code class="app-key"><?php echo esc_html( $key ); ?></code>
381 </div>
382 </div>
383 <div class="modal-50">
384 <div class="qr-code-wrapper">
385 <img class="qr-code" src="<?php echo esc_url( Authentication::get_google_qr_code( $totp_title, $key, $site_name ) ); ?>" id="wp-2fa-totp-qrcode" />
386 </div>
387 </div>
388 </div>
389 <h4 class="app-links-title"><?php esc_html_e( 'For detailed guides for your desired app, click below.', 'wp-2fa' ); ?></h4>
390 <div class="apps-wrapper">
391 <?php foreach ( Authentication::getApps() as $app ) : ?>
392 <a href="https://www.wpwhitesecurity.com/support/kb/configuring-2fa-apps/#<?php echo $app['hash']; ?>" target="_blank" class="app-logo"><img src="<?php echo esc_url( WP_2FA_URL . '/dist/images/' . $app['logo'] ); ?>"></a>
393 <?php endforeach; ?>
394 </div>
395 <div class="wp2fa-setup-actions">
396 <br>
397 <a class="button button-primary" name="next_step_setting"><?php esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></a>
398 </div>
399 </div>
400
401 <div class="step-setting-wrapper">
402 <h3><?php esc_html_e( 'Almost there…', 'wp-2fa' ); ?></h3>
403 <p><?php esc_html_e( 'Please type in the one-time code from your Google Authenticator app to finalize the setup.', 'wp-2fa' ); ?></p>
404 <fieldset>
405 <label for="2fa-totp-authcode">
406 <input type="tel" name="wp-2fa-totp-authcode" id="wp-2fa-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" placeholder="<?php esc_html_e( 'Authentication Code', 'wp-2fa' ); ?>" />
407 </label>
408 <div class="verification-response"></div>
409 </fieldset>
410 <input type="hidden" name="wp-2fa-totp-key" value="<?php echo esc_attr( $key ); ?>" />
411 <br>
412 <a href="#" class="modal__btn button button-primary" data-validate-authcode-ajax data-nonce="<?php echo esc_attr( $validate_nonce ); ?>"><?php esc_html_e( 'Validate & Save Configuration', 'wp-2fa' ); ?></a>
413 <button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
414 </div>
415
416 </fieldset>
417 </div>
418
419 <div class="wizard-step" id="2fa-wizard-email">
420 <fieldset>
421 <div class="step-setting-wrapper active">
422 <h3><?php esc_html_e( 'Setup the 2FA method', 'wp-2fa' ); ?></h3>
423 <p>
424 <?php esc_html_e( 'Please select the email address where the one-time code should be sent:', 'wp-2fa' ); ?>
425 </p>
426 <fieldset>
427 <label for="use_wp_email">
428 <input type="radio" name="wp_2fa_email_address" id="use_wp_email" value="<?php echo esc_attr( $user->user_email ); ?>" checked>
429 <span><?php esc_html_e( 'Use my WordPress user email (', 'wp-2fa' ); ?><small><?php echo esc_attr( $user->user_email ); ?></small><?php esc_html_e( ')', 'wp-2fa' ); ?></span>
430 </label>
431 <label for="use_custom_email">
432 <input type="radio" name="wp_2fa_email_address" id="use_custom_email" value="use_custom_email">
433 <span><?php esc_html_e( 'Use a different email address:', 'wp-2fa' ); ?></span><br>
434 <input type="email" name="custom-email-address" id="custom-email-address" class="input wide" value="" placeholder="<?php esc_html_e( 'Email address', 'wp-2fa' ); ?>"/>
435 </label>
436 </fieldset>
437 <p class="description"><?php esc_html_e( 'Note: you should be able to access the mailbox of the email address to complete the following step.', 'wp-2fa' ); ?></p>
438 <div class="wp2fa-setup-actions">
439 <?php $setupnonce = wp_create_nonce( 'wp-2fa-send-setup-email' ); ?>
440 <a class="button button-primary" name="next_step_setting" value="<?php esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" data-trigger-setup-email data-user-id="<?php echo esc_attr( $user->ID ); ?>" data-nonce="<?php echo esc_attr( $setupnonce ); ?>"><?php esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></a>
441 </div>
442 </div>
443
444 <div class="step-setting-wrapper" id="2fa-wizard-email">
445 <h4><?php esc_html_e( 'Almost there…', 'wp-2fa' ); ?></h4>
446 <p><?php esc_html_e( 'Please type in the one-time code sent to your email address to finalize the setup.', 'wp-2fa' ); ?></p>
447 <fieldset>
448 <label for="2fa-email-authcode">
449 <input type="tel" name="wp-2fa-email-authcode" id="wp-2fa-email-authcode" class="input" value="" size="20" pattern="[0-9]*" placeholder="<?php esc_html_e( 'Authentication Code:', 'wp-2fa' ); ?>"/>
450 </label>
451 <div class="verification-response"></div>
452 </fieldset>
453 <input type="hidden" name="wp-2fa-totp-key" value="<?php echo esc_attr( $key ); ?>" />
454
455 <a href="#" class="modal__btn modal__btn-primary button button-primary" data-validate-authcode-ajax data-nonce="<?php echo esc_attr( $validate_nonce ); ?>"><?php esc_html_e( 'Validate & Save Configuration', 'wp-2fa' ); ?></a>
456 <button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
457 </div>
458 </fieldset>
459 </div>
460
461 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
462 <?php
463 // Grab current user.
464 $user = wp_get_current_user();
465 // Create a nonce for use in ajax call to generate codes.
466 $nonce = wp_create_nonce( 'wp-2fa-backup-codes-generate-json-' . $user->ID );
467 ?>
468 <div class="step-setting-wrapper active">
469 <h3><?php esc_html_e( 'Your login just got more secure', 'wp-2fa' ); ?></h3>
470 <p><?php esc_html_e( 'Congratulations! You have enabled two-factor authentication for your user. You’ve just helped towards making this website more secure!', 'wp-2fa' ); ?></p>
471 <?php if ( in_array( 'user_needs_to_setup_backup_codes', $user_type, true ) ) { ?>
472 <p><?php esc_html_e( 'You can exit this wizard now or continue to create backup codes.', 'wp-2fa' ); ?></p>
473 <?php } ?>
474 <div class="wp2fa-setup-actions">
475 <?php if ( in_array( 'user_needs_to_setup_backup_codes', $user_type, true ) ) { ?>
476 <button class="button button-primary" name="next_step_setting" value="<?php esc_attr_e( 'Generate backup codes', 'wp-2fa' ); ?>" data-trigger-generate-backup-codes data-nonce="<?php echo esc_attr( $nonce ); ?>" data-user-id="<?php echo esc_attr( $user->ID ); ?>">
477 <?php esc_html_e( 'Generate backup codes', 'wp-2fa' ); ?>
478 </button>
479 <a href="#" class="button button-secondary" name="save_step" data-close-2fa-modal value="<?php esc_attr_e( 'I’ll generate them later', 'wp-2fa' ); ?>">
480 <?php esc_html_e( 'I’ll generate them later', 'wp-2fa' ); ?>
481 </a>
482 <?php } else { ?>
483 <a href="#" class="button button-secondary" name="save_step" data-close-2fa-modal>
484 <?php esc_html_e( 'Close wizard', 'wp-2fa' ); ?>
485 </a>
486 <?php } ?>
487 </div>
488 </div>
489
490 <div class="step-setting-wrapper align-center">
491 <h3><?php esc_html_e( 'Backup codes generated', 'wp-2fa' ); ?></h3>
492 <p><?php esc_html_e( 'Here are your backup codes:', 'wp-2fa' ); ?></p>
493 <code id="backup-codes-wrapper"></code>
494 <div class="wp2fa-setup-actions">
495 <button class="button button-primary" type="submit" value="<?php esc_attr_e( 'Download', 'wp-2fa' ); ?>" data-trigger-backup-code-download data-user="<?php echo esc_attr( $user->display_name ); ?>" data-website-url="<?php echo esc_attr( get_home_url() ); ?>">
496 <?php esc_html_e( 'Download', 'wp-2fa' ); ?>
497 </button>
498 <button class="button button-secondary" type="submit" value="<?php esc_attr_e( 'Print', 'wp-2fa' ); ?>" data-trigger-print data-nonce="<?php echo esc_attr( $nonce ); ?>" data-user-id="<?php echo esc_attr( $user->display_name ); ?>" data-website-url="<?php echo esc_attr( get_home_url() ); ?>">
499 <?php esc_html_e( 'Print', 'wp-2fa' ); ?>
500 </button>
501 <button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Close wizard & refresh', 'wp-2fa' ); ?></button>
502 </div>
503 </div>
504 </div>
505
506 </main>
507 </div>
508 </div>
509 </div>
510 </div>
511 <?php endif; ?>
512
513 <div>
514 <div class="wp2fa-modal micromodal-slide" id="configure-2fa-backup-codes" aria-hidden="true">
515 <div class="modal__overlay" tabindex="-1" data-micromodal-close>
516 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
517 <button class="modal__close" aria-label="Close modal" data-close-2fa-modal></button>
518 <main class="modal__content" id="modal-1-content">
519 <?php
520 // Grab current user.
521 $user = wp_get_current_user();
522 // Create a nonce for use in ajax call to generate codes.
523 $nonce = wp_create_nonce( 'wp-2fa-backup-codes-generate-json-' . $user->ID );
524 ?>
525 <div class="step-setting-wrapper active">
526 <h3><?php esc_html_e( 'Generate backup codes', 'wp-2fa' ); ?></h3>
527 <p><?php esc_html_e( 'It is recommended to generate and print some backup codes in case you lose access to your primary 2FA method. ', 'wp-2fa' ); ?></p>
528
529 <div class="wp2fa-setup-actions">
530 <button class="button button-primary" name="next_step_setting" value="<?php esc_attr_e( 'Generate backup codes', 'wp-2fa' ); ?>" data-trigger-generate-backup-codes data-nonce="<?php echo esc_attr( $nonce ); ?>" data-user-id="<?php echo esc_attr( $user->ID ); ?>">
531 <?php esc_html_e( 'Generate backup codes', 'wp-2fa' ); ?>
532 </button>
533 <a href="#" class="button" data-close-2fa-modal>
534 <?php esc_html_e( 'I’ll generate them later', 'wp-2fa' ); ?>
535 </a>
536 </div>
537 </div>
538
539 <div class="step-setting-wrapper align-center">
540 <h3><?php esc_html_e( 'Backup codes generated', 'wp-2fa' ); ?></h3>
541 <p><?php esc_html_e( 'Here are your backup codes:', 'wp-2fa' ); ?></p>
542 <code id="backup-codes-wrapper"></code>
543 <div class="wp2fa-setup-actions">
544 <button class="button button-primary" type="submit" value="<?php esc_attr_e( 'Download', 'wp-2fa' ); ?>" data-trigger-backup-code-download data-user="<?php echo esc_attr( $user->display_name ); ?>" data-website-url="<?php echo esc_attr( get_home_url() ); ?>">
545 <?php esc_html_e( 'Download', 'wp-2fa' ); ?>
546 </button>
547 <button class="button button-secondary" type="submit" value="<?php esc_attr_e( 'Print', 'wp-2fa' ); ?>" data-trigger-print data-nonce="<?php echo esc_attr( $nonce ); ?>" data-user-id="<?php echo esc_attr( $user->display_name ); ?>" data-website-url="<?php echo esc_attr( get_home_url() ); ?>">
548 <?php esc_html_e( 'Print', 'wp-2fa' ); ?>
549 </button>
550 </div>
551 </div>
552
553 </main>
554 </div>
555 </div>
556 </div>
557 </div>
558 <?php
559
560 if ( self::can_user_remove_2fa( $user->ID ) ) :
561 echo GenerateModal::generate_modal(
562 'confirm-remove-2fa',
563 __( 'Remove 2FA?', 'wp-2fa' ),
564 __( 'Are you sure you want to remove two-factor authentication and lower the security of your user account?', 'wp-2fa' ),
565 array(
566 '<a href="#" class="modal__btn modal__btn-primary button button-primary" data-trigger-remove-2fa data-user-id="'. esc_attr( $user->ID ) .'" data-nonce="' . wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ) . '">' . __( 'Yes', 'wp-2fa' ) . '</a>',
567 '<button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window">' . __( 'No', 'wp-2fa' ) . '</button>',
568 )
569 );
570 endif;
571
572 $output = ob_get_contents();
573 ob_end_clean();
574
575 echo $output;
576 }
577
578 /**
579 * Produces the 2FA configuration form for network users, or any user with no roles.
580 */
581 public function inline_2fa_profile_form( $is_shortcode = '', $show_preamble = true ) {
582
583 if ( isset( $_GET['user_id'] ) ) {
584 $user_id = (int) $_GET['user_id'];
585 $user = get_user_by( 'id', $user_id );
586 } else {
587 $user = wp_get_current_user();
588 }
589
590 // Get current user, we going to need this regardless.
591 $current_user = wp_get_current_user();
592
593 // Bail if we still dont have an object.
594 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
595 return;
596 }
597
598 $user_type = UserUtils::determine_user_2fa_status( $user );
599
600 $additional_args = array(
601 'is_shortcode' => ( $is_shortcode ) ? true : false,
602 'show_preamble' => $show_preamble,
603 );
604
605 $this->user_2fa_options( $user, $additional_args );
606 }
607
608 /**
609 * Add custom unlock account link to user edit admin list.
610 *
611 * @param string $actions Default actions.
612 * @param object $user_object User data.
613 * @return string Appended actions.
614 */
615 public function user_2fa_row_actions( $actions, $user_object ) {
616 $nonce = wp_create_nonce( 'wp-2fa-unlock-account-nonce' );
617 $grace_period_expired = get_user_meta( $user_object->ID, 'wp_2fa_user_grace_period_expired', true );
618 $url = add_query_arg(
619 array(
620 'action' => 'unlock_account',
621 'user_id' => $user_object->ID,
622 'wp_2fa_nonce' => $nonce,
623 ),
624 admin_url( 'users.php' )
625 );
626
627 if ( $grace_period_expired ) {
628 $actions['edit_badges'] = '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Unlock user', 'wp-2fa' ) . '</a>';
629 }
630 return $actions;
631 }
632
633 /**
634 * Save user profile information.
635 */
636 public function save_user_2fa_options( $input ) {
637
638 // Ensure we have the inputs we want before we process.
639 // To avoid causing issues with the rest of the user profile.
640 if ( ! is_array( $input ) ) {
641 return;
642 }
643
644 // Assign the input to post, in case we are dealing with saving the data from another page.
645 if ( isset( $input ) ) {
646 $_POST = $input;
647 } else {
648 $_POST = $_POST;
649 }
650
651 // Grab current user.
652 $user = wp_get_current_user();
653
654 // Setup some empty arrays which will may fill later, should an error arise along the way.
655 $notices = array();
656 $errors = array();
657
658 // Grab key from the $_POST.
659 if ( isset( $_POST['wp-2fa-totp-key'] ) ) {
660 $current_key = sanitize_text_field( wp_unslash( $_POST['wp-2fa-totp-key'] ) );
661 }
662
663 // Grab authcode and ensure its a number.
664 if ( isset( $_POST['wp-2fa-totp-authcode'] ) ) {
665 $_POST['wp-2fa-totp-authcode'] = (int) $_POST['wp-2fa-totp-authcode'];
666 }
667
668 if ( ! isset( $_POST['custom-email-address'] ) || isset( $_POST['custom-email-address'] ) && empty( $_POST['custom-email-address'] ) ) {
669 if ( isset( $_POST['email'] ) ) {
670 update_user_meta( $user->ID, 'wp_2fa_nominated_email_address', $_POST['email'] );
671 } elseif ( isset( $_POST['wp_2fa_email_address'] ) ) {
672 update_user_meta( $user->ID, 'wp_2fa_nominated_email_address', $_POST['wp_2fa_email_address'] );
673 }
674 } elseif ( isset( $_POST['custom-email-address'] ) ) {
675 update_user_meta( $user->ID, 'wp_2fa_nominated_email_address', sanitize_email( wp_unslash( $_POST['custom-email-address'] ) ) );
676 }
677
678 // Now lets grab the users enabled 2fa methods.
679 $get_array = filter_input_array( INPUT_GET );
680 $selected_method = sanitize_text_field( $get_array['enabled_methods'] );
681 // Check its one of our options.
682 if ( isset( $_POST['wp_2fa_enabled_methods'] ) && 'totp' === $_POST['wp_2fa_enabled_methods'] || isset( $_POST['wp_2fa_enabled_methods'] ) && 'email' === $_POST['wp_2fa_enabled_methods'] ) {
683 update_user_meta( $user->ID, 'wp_2fa_enabled_methods', sanitize_text_field( wp_unslash( $_POST['wp_2fa_enabled_methods'] ) ) );
684 self::delete_expire_and_enforced_keys( $user->ID );
685 }
686
687 if ( isset( $_POST['wp-2fa-email-authcode'] ) && ! empty( $_POST['wp-2fa-email-authcode'] ) ) {
688 update_user_meta( $user->ID, 'wp_2fa_enabled_methods', 'email' );
689 self::delete_expire_and_enforced_keys( $user->ID );
690 }
691
692 if ( isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
693 update_user_meta( $user->ID, 'wp_2fa_enabled_methods', 'totp' );
694 self::delete_expire_and_enforced_keys( $user->ID );
695 }
696 }
697
698 /**
699 * Utility function to quickly remove data via direct query.
700 *
701 * @param int $user_id User id to process.
702 */
703 public static function delete_expire_and_enforced_keys( $user_id ) {
704 global $wpdb;
705 $wpdb->query(
706 $wpdb->prepare(
707 "
708 DELETE FROM $wpdb->usermeta
709 WHERE user_id = %d
710 AND meta_key IN ( %s, %s )
711 ",
712 [
713 $user_id,
714 'wp_2fa_grace_period_expiry',
715 'wp_2fa_user_enforced_instantly',
716 ]
717 )
718 );
719 }
720
721 /**
722 * Validate a user's code when setting up 2fa via the inline form.
723 *
724 * @return json result of validation.
725 */
726 public function validate_authcode_via_ajax() {
727 check_ajax_referer( 'wp-2fa-validate-authcode' );
728
729 if ( isset( $_POST['form'] ) ) {
730 $input = $_POST['form'];
731 } else {
732 return 'No form';
733 }
734
735 $user = wp_get_current_user();
736
737 // Setup some empty arrays which will may fill later, should an error arise along the way.
738 $notices = array();
739 $our_errors = '';
740
741 // Grab key from the $_POST.
742 if ( isset( $input['wp-2fa-totp-key'] ) ) {
743 $current_key = sanitize_text_field( wp_unslash( $input['wp-2fa-totp-key'] ) );
744 }
745
746 // Grab authcode and ensure its a number.
747 if ( isset( $input['wp-2fa-totp-authcode'] ) ) {
748 $input['wp-2fa-totp-authcode'] = (int) $input['wp-2fa-totp-authcode'];
749 }
750
751 // Check if we are dealing with totp or email, if totp validate and store a new secret key.
752 if ( ! empty( $input['wp-2fa-totp-authcode'] ) && ! empty( $current_key ) ) {
753 if ( Authentication::is_valid_key( $current_key ) || ! is_numeric( $input['wp-2fa-totp-authcode'] ) ) {
754 if ( ! Authentication::is_valid_authcode( $current_key, sanitize_text_field( wp_unslash( $input['wp-2fa-totp-authcode'] ) ) ) ) {
755 $our_errors = esc_html__( 'Invalid Two Factor Authentication code.', 'wp-2fa' );
756 }
757 } else {
758 $our_errors = esc_html__( 'Invalid Two Factor Authentication secret key.', 'wp-2fa' );
759 }
760
761 // If its not totp, is it email.
762 } elseif ( ! empty( $input['wp-2fa-email-authcode'] ) ) {
763 if ( ! Authentication::validate_token( $user->ID, sanitize_text_field( wp_unslash( $input['wp-2fa-email-authcode'] ) ) ) ) {
764 $our_errors = __( 'Invalid Email Authentication code.', 'wp-2fa' );
765 }
766 } else {
767 $our_errors = __( 'Please enter the code to finalize the 2FA setup.', 'wp-2fa' );
768 }
769
770 if ( ! empty( $our_errors ) ) {
771 // Send the response.
772 wp_send_json_error(
773 array(
774 'error' => $our_errors,
775 )
776 );
777 } else {
778 $this->save_user_2fa_options( $input );
779 // Send the response.
780 wp_send_json_success();
781 }
782 }
783
784 /**
785 * @param int $user_id User ID.
786 *
787 * @return bool True if the user can remove 2FA from their account.
788 */
789 public static function can_user_remove_2fa( $user_id ) {
790 // check the "Hide the Remove 2FA button" setting
791 if ( WP2FA::get_wp2fa_setting( 'hide_remove_button' ) ) {
792 return false;
793 }
794
795 // check grace period policy
796 $grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
797 if ( 'no-grace-period' === $grace_policy ) {
798 // we only need to run further checks to find out if the 2FA is enforced for the user in question if there
799 // is no grace period
800 $enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
801 if ( 'all-users' === $enforcement_policy ) {
802 // enforced for all users, target user is definitely included
803 return false;
804 }
805
806 if ( 'do-not-enforce' !== $enforcement_policy ) {
807 // one of possible enforcement options is set, check the target user
808 return Authentication::is_user_eligible_for_2fa( $user_id );
809 }
810 }
811
812 return true;
813 }
814 }
815