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