PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 1.7.0
WP 2FA – Two-factor authentication for WordPress v1.7.0
4.1.0 4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / UserProfile.php
wp-2fa / includes / classes / Admin Last commit date
Controllers 5 years ago Views 5 years ago SettingsPage.php 5 years ago SetupWizard.php 5 years ago User.php 5 years ago UserListing.php 5 years ago UserNotices.php 5 years ago UserProfile.php 5 years ago UserRegistered.php 5 years ago index.php 5 years ago
UserProfile.php
609 lines
1 <?php // phpcs:ignore
2
3 namespace WP2FA\Admin;
4
5 use \WP2FA\Core as Core;
6 use \WP2FA\WP2FA as WP2FA;
7 use WP2FA\Utils\GenerateModal;
8 use \WP2FA\Utils\DateTimeUtils;
9 use WP2FA\Admin\Views\WizardSteps;
10 use WP2FA\Authenticator\BackupCodes;
11 use WP2FA\Admin\Controllers\Settings;
12 use WP2FA\Authenticator\Authentication;
13 use \WP2FA\Utils\UserUtils as UserUtils;
14 use WP2FA\Utils\SettingsUtils as SettingsUtils;
15
16 /**
17 * UserProfile - Class for handling user things such as profile settings and admin list views.
18 */
19 class UserProfile {
20
21 const NOTICES_META_KEY = 'wp_2fa_totp_notices';
22
23 /**
24 * Classs constructor
25 */
26 public function __construct() {
27 }
28
29 /**
30 * Add our buttons to the user profile editing screen.
31 *
32 * @param object $user User data.
33 */
34 public function user_2fa_options( $user, $additional_args = array() ) {
35
36 if ( isset( $_GET['user_id'] ) ) {
37 $user_id = (int) $_GET['user_id'];
38 $user = get_user_by( 'id', $user_id );
39 } else {
40 // Get current user, we going to need this regardless.
41 $user = wp_get_current_user();
42 }
43
44 if ( ! is_a( $user, '\WP_User' ) ) {
45 return;
46 }
47
48 // Ensure we have something in the settings.
49 if ( empty( SettingsUtils::get_option( WP_2FA_SETTINGS_NAME ) ) ) {
50 return;
51 }
52
53 $show_preamble = true;
54 if ( isset( $additional_args['show_preamble'] ) ) {
55 $show_preamble = \filter_var( $additional_args['show_preamble'], FILTER_VALIDATE_BOOLEAN );
56 }
57
58 $user_type = UserUtils::determine_user_2fa_status( $user );
59
60 $form_output = '';
61 $form_content = '';
62 $description = __( 'Add two-factor authentication to strengthen the security of your WordPress user account.', 'wp-2fa' );
63 $show_form_table = true;
64 $page_url = ( WP2FA::is_this_multisite() ) ? 'index.php' : 'options-general.php';
65
66 // Orpan user (a user with no role or capabitlies).
67 if ( in_array( 'orphan_user', $user_type, true ) ) {
68 // We want to use the same form/buttons used in the shortcode.
69 $additional_args['is_shortcode'] = true;
70
71 // Create useful message for admin.
72 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'can_manage_options' ), $user_type ) ) {
73 $description = __( 'This user is required to setup 2FA but has not yet done so.', 'wp-2fa' );
74 }
75
76 if ( UserUtils::in_array_all( array( 'user_is_excluded', 'can_manage_options' ), $user_type ) ) {
77 $description = __( 'This user is excluded from configuring 2FA.', 'wp-2fa' );
78 }
79 }
80
81 // Excluded user.
82 if ( in_array( 'user_is_excluded', $user_type, true ) ) {
83 return;
84 }
85
86 // A user viewing their own profile AND has a 2FA method configured.
87 if ( UserUtils::in_array_all( array( 'viewing_own_profile' ), $user_type ) ) {
88 if (
89 UserUtils::in_array_all( ['has_enabled_methods'], $user_type ) ||
90 UserUtils::in_array_all( ['no_required_has_enabled'], $user_type)
91 ) {
92 // Create wizard link based on which 2fa methods are allowed by admin.
93 if ( ! empty( WP2FA::get_wp2fa_setting( 'enable_totp' ) ) && ! empty( WP2FA::get_wp2fa_setting( 'enable_email' ) ) ) {
94 $setup_2fa_url = add_query_arg(
95 array(
96 'page' => 'wp-2fa-setup',
97 'current-step' => 'user_choose_2fa_method',
98 'wizard_type' => 'user_2fa_config',
99 ),
100 admin_url( $page_url )
101 );
102 } else {
103 $setup_2fa_url = add_query_arg(
104 array(
105 'page' => 'wp-2fa-setup',
106 'current-step' => 'reconfigure_method',
107 'wizard_type' => 'user_reconfigure_config',
108 ),
109 admin_url( $page_url )
110 );
111 }
112
113 // Create backup codes URL;
114 $backup_codes_url = add_query_arg(
115 array(
116 'page' => 'wp-2fa-setup',
117 'current-step' => 'backup_codes',
118 'wizard_type' => 'backup_codes_config',
119 ),
120 admin_url( $page_url )
121 );
122
123 $form_content .= '<a href="' . esc_url( $setup_2fa_url ) . '" class="button button-primary">' . __( 'Change 2FA Settings', 'wp-2fa' ) . '</a>';
124
125 if ( self::can_user_remove_2fa( $user->ID ) ) {
126 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . __( 'Remove 2FA', 'wp-2fa' ) . '</a>';
127 }
128
129 $form_content .= '<br /><br />';
130
131 if ( SettingsPage::are_backup_codes_enabled() ) {
132 $form_content .= '<a href="' . esc_url( $backup_codes_url ) . '" class="button button-primary">' . __( 'Generate backup codes', 'wp-2fa' ) . '</a>';
133
134 $codes_remaining = BackupCodes::codes_remaining_for_user( $user );
135 if ( $codes_remaining > 0 ) {
136 $form_content .= '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . __( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
137 } elseif ( 0 === $codes_remaining ) {
138 $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>';
139 }
140 }
141
142 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
143 $form_content = '<a href="#" class="button button-primary remove-2fa" data-open-configure-2fa-wizard>' . __( 'Change 2FA Settings', 'wp-2fa' ) . '</a>';
144
145 if ( self::can_user_remove_2fa( $user->ID ) ) {
146 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . __( 'Remove 2FA', 'wp-2fa' ) . '</a>';
147 }
148 if ( SettingsPage::are_backup_codes_enabled() ) {
149 $codes_remaining = BackupCodes::codes_remaining_for_user( $user );
150 if ( $codes_remaining > 0 ) {
151 $backup_codes_desc = '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . __( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
152 } elseif ( 0 === $codes_remaining ) {
153 $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>';
154 }
155
156 $form_content .= WizardSteps::getGenerateCodesLink() . $backup_codes_desc;
157 }
158 }
159 }
160
161 $showIfUserIsNotIn = [
162 'user_is_excluded',
163 'has_enabled_methods',
164 'no_required_has_enabled',
165 ];
166
167 // User viewing own profile and needs to enable 2FA.
168 if (
169 UserUtils::in_array_all( array( 'user_needs_to_setup_2fa' ), $user_type ) ||
170 UserUtils::roleIsNot( $showIfUserIsNotIn, $user_type )
171 ) {
172 $first_time_setup_url = Settings::getSetupPageLink();
173
174 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
175 $form_content .= '<a href="#" class="button button-primary" data-open-configure-2fa-wizard>' . __( 'Configure 2FA', 'wp-2fa' ) . '</a>';
176 }
177
178 if ( empty( $additional_args ) ) {
179 $form_content .= '<a href="' . esc_url( $first_time_setup_url ) . '" class="button button-primary">' . __( 'Configure Two-factor authentication (2FA)', 'wp-2fa' ) . '</a>';
180 }
181 }
182 }
183
184 // Admin viewing users profile AND user has a configured 2FA method.
185 if ( UserUtils::in_array_all( array( 'can_manage_options', 'has_enabled_methods' ), $user_type ) && ! in_array( 'viewing_own_profile', $user_type, true ) ) {
186 $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' );
187
188 $remove_users_2fa_url = add_query_arg(
189 array(
190 'action' => 'remove_user_2fa',
191 'user_id' => $user->ID,
192 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ),
193 'admin_reset' => 'yes',
194 ),
195 admin_url( 'user-edit.php' )
196 );
197
198 $form_content .= '<a href="' . esc_url( $remove_users_2fa_url ) . '" class="button button-primary">' . __( 'Reset 2FA configuration', 'wp-2fa' ) . '</a>';
199 }
200
201 // Admin viewing users profile AND users grace period has expired.
202 if ( UserUtils::in_array_all( array( 'can_manage_options', 'grace_has_expired' ), $user_type ) ) {
203 $unlock_user_url = add_query_arg(
204 array(
205 'action' => 'unlock_account',
206 'user_id' => $user->ID,
207 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-unlock-account-nonce' ),
208 ),
209 admin_url( 'user-edit.php' )
210 );
211 $form_content .= '<a href="' . esc_url( $unlock_user_url ) . '" class="button button-primary">' . __( 'Unlock user and reset the grace period', 'wp-2fa' ) . '</a>';
212 }
213
214 if ( $show_preamble ) {
215 $form_output .= '<h2>' . __( 'WP 2FA Settings', 'wp-2fa' ) . '</h2>';
216
217 if ( $description ) {
218 $form_output .= '<p class="description">' . $description . '</p>';
219 }
220 }
221
222 $form_content = apply_filters( 'wp_2fa_append_to_profile_form_content', $form_content );
223
224 if ( $show_form_table && ! empty( $form_content ) ) {
225 $form_output .= '
226 <table class="form-table wp-2fa-user-profile-form" role="presentation">
227 <tbody>
228 <tr>
229 <th><label>' . __( '2-Factor authentication', 'wp-2fa' ) . '</label></th>
230 <td>
231 ' . $form_content . '
232 </td>
233 </tr>
234 </tbody>
235 </table>';
236
237 if ( (isset($_GET['show']) && 'wp-2fa-setup' === $_GET['show']) || ( new User($user) )->getEnforcedInstantly() ) {
238 $form_output .= '
239 <script>
240 window.addEventListener("load", function(){
241 jQuery( \'.verification-response span\' ).remove();
242 jQuery( \'#configure-2fa .wizard-step.active, #configure-2fa .step-setting-wrapper.active\' ).removeClass( \'active\' );
243 jQuery( \'#configure-2fa .wizard-step:first-of-type, #configure-2fa .step-setting-wrapper:first-of-type\' ).addClass( \'active\' );
244 jQuery( \'.modal__content input:not([type="radio"]):not([type="hidden"])\' ).val( \'\' );
245 MicroModal.show( \'configure-2fa\' );
246 if ( jQuery( \'input#basic\' ).is( \':visible\' ) ) {
247 jQuery( \'input#basic\' ).click();
248 } else {
249 jQuery( \'input#geek\' ).click();
250 }
251 jQuery( \'[name="wp_2fa_enabled_methods"]\' ).change();
252
253 if ( 1 === jQuery( \'.wizard-step.active .option-pill\' ).length ) {
254 jQuery( \'.modal__btn.button.button-primary.2fa-choose-method\' ).click();
255 }
256
257 });
258 </script>
259 ';
260 }
261 }
262
263 echo $form_output;
264
265 $this->generate_inline_modals( $user_type );
266 }
267
268 public function generate_inline_modals( $user_type = array() ) {
269
270 ob_start();
271
272 $user = wp_get_current_user();
273
274 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 ) || UserUtils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) ) { ?>
275 <div>
276 <div class="wp2fa-modal micromodal-slide" id="configure-2fa" aria-hidden="true">
277 <div class="modal__overlay" tabindex="-1" data-micromodal-close>
278 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
279 <?php
280 echo GenerateModal::generate_modal(
281 'notify-users',
282 __( 'Are you sure?', 'wp-2fa' ),
283 __( 'Any unsaved changes will be lost!', 'wp-2fa' ),
284 [
285 '<button class="modal__btn button-confirm" data-micromodal-close aria-label="Close this dialog window and the wizard">'. __( 'Yes', 'wp-2fa' ) .'</button>',
286 '<button class="modal__btn button-decline" data-micromodal-close aria-label="Close this dialog window">'. __( 'No', 'wp-2fa' ) .'</button>'
287 ],
288 '',
289 '430px'
290 );?>
291 <button class="modal__close" aria-label="Close modal"></button>
292 <main class="modal__content" id="modal-1-content">
293 <?php
294 $logoSection = '<p style="text-align: center; padding:0; margin: 0;"><img style="filter: invert(76.4%); width: 50px; margin: 0 auto;" src="'.WP_2FA_URL . 'dist/images/wp-2fa-white-icon20x28.svg'.'" /></p>';
295 $logoSection = apply_filters( 'wp_fa_plugin_logo_wizard', $logoSection );
296
297
298 echo $logoSection;
299
300 if ( UserUtils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || UserUtils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) ) :
301
302 $available_methods = UserUtils::get_2fa_methods_available_to_user( $user );
303
304 $intro_text = esc_html__( 'Choose the 2FA authentication method', 'wp-2fa' );
305 if ( count( $available_methods ) > 1 ) {
306 $sub_text = WP2FA::getNumberOfPluginsText();
307 } else {
308 $sub_text = esc_html__( 'Only the below 2FA method is allowed on this website:', 'wp-2fa' );
309 }
310 ?>
311
312 <div class="wizard-step active">
313 <h3><?php echo sanitize_text_field( $intro_text ); ?></h3>
314 <p><?php echo sanitize_text_field( $sub_text ); ?></p>
315 <fieldset>
316 <?php WizardSteps::totpOption();?>
317 <?php WizardSteps::emailOption();?>
318 </fieldset>
319 <br>
320 <a href="#" class="modal__btn button button-primary 2fa-choose-method" data-name="next_step_setting_modal_wizard" data-next-step><?php esc_html_e( 'Next Step', 'wp-2fa' ); ?></a>
321 <button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
322 </div>
323 <?php endif; ?>
324
325 <?php if ( UserUtils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) { ?>
326 <div class="wizard-step active">
327 <fieldset>
328 <?php WizardSteps::totpReConfigure(); ?>
329 <?php WizardSteps::emailReConfigure();?>
330 </fieldset>
331 </div>
332 <?php } ?>
333
334 <?php WizardSteps::showModalMethods(); ?>
335 <?php
336 // Create a nonce for use in ajax call to generate codes.
337 if ( SettingsPage::are_backup_codes_enabled() ) { ?>
338 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
339 <?php WizardSteps::backupCodesConfigure() ?>
340 <?php WizardSteps::generatedBackupCodes() ?>
341 </div>
342 <?php } else { ?>
343 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
344 <?php WizardSteps::congratulationsStep();?>
345 </div>
346 <?php } ?>
347 </main>
348 </div>
349 </div>
350 </div>
351 </div>
352 <?php } ?>
353 <?php if ( SettingsPage::are_backup_codes_enabled() ) { ?>
354 <div>
355 <div class="wp2fa-modal micromodal-slide" id="configure-2fa-backup-codes" aria-hidden="true">
356 <div class="modal__overlay" tabindex="-1" data-micromodal-close>
357 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
358 <button class="modal__close" aria-label="Close modal" data-close-2fa-modal></button>
359 <main class="modal__content" id="modal-1-content">
360 <?php WizardSteps::generatedBackupCodes( true ) ?>
361 </main>
362 </div>
363 </div>
364 </div>
365 </div>
366 <?php } ?>
367 <?php
368
369 if ( self::can_user_remove_2fa( $user->ID ) ) :
370 echo GenerateModal::generate_modal(
371 'confirm-remove-2fa',
372 __( 'Remove 2FA?', 'wp-2fa' ),
373 __( 'Are you sure you want to remove two-factor authentication and lower the security of your user account?', 'wp-2fa' ),
374 array(
375 '<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>',
376 '<button class="modal__btn button" data-close-2fa-modal aria-label="Close this dialog window">' . __( 'No', 'wp-2fa' ) . '</button>',
377 )
378 );
379 endif;
380
381 $output = ob_get_contents();
382 ob_end_clean();
383
384 echo $output;
385 }
386
387 /**
388 * Produces the 2FA configuration form for network users, or any user with no roles.
389 */
390 public function inline_2fa_profile_form( $is_shortcode = '', $show_preamble = true ) {
391
392 if ( isset( $_GET['user_id'] ) ) {
393 $user_id = (int) $_GET['user_id'];
394 $user = get_user_by( 'id', $user_id );
395 } else {
396 $user = wp_get_current_user();
397 }
398
399 // Get current user, we going to need this regardless.
400 $current_user = wp_get_current_user();
401
402 // Bail if we still dont have an object.
403 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
404 return;
405 }
406
407 $additional_args = array(
408 'is_shortcode' => $is_shortcode,
409 'show_preamble' => $show_preamble,
410 );
411
412 $this->user_2fa_options( $user, $additional_args );
413 }
414
415 /**
416 * Add custom unlock account link to user edit admin list.
417 *
418 * @param string $actions Default actions.
419 * @param object $user_object User data.
420 * @return string Appended actions.
421 */
422 public function user_2fa_row_actions( $actions, $user_object ) {
423 $nonce = wp_create_nonce( 'wp-2fa-unlock-account-nonce' );
424 $grace_period_expired = get_user_meta( $user_object->ID, WP_2FA_PREFIX . 'user_grace_period_expired', true );
425 $url = add_query_arg(
426 array(
427 'action' => 'unlock_account',
428 'user_id' => $user_object->ID,
429 'wp_2fa_nonce' => $nonce,
430 ),
431 admin_url( 'users.php' )
432 );
433
434 if ( $grace_period_expired ) {
435 $actions['edit_badges'] = '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Unlock user', 'wp-2fa' ) . '</a>';
436 }
437 return $actions;
438 }
439
440 /**
441 * Save user profile information.
442 */
443 public function save_user_2fa_options( $input ) {
444
445 // Ensure we have the inputs we want before we process.
446 // To avoid causing issues with the rest of the user profile.
447 if ( ! is_array( $input ) ) {
448 return;
449 }
450
451 // Assign the input to post, in case we are dealing with saving the data from another page.
452 if ( isset( $input ) ) {
453 $_POST = $input;
454 }
455
456 // Grab current user.
457 $user = wp_get_current_user();
458
459 // Grab authcode and ensure its a number.
460 if ( isset( $_POST['wp-2fa-totp-authcode'] ) ) {
461 $_POST['wp-2fa-totp-authcode'] = (int) $_POST['wp-2fa-totp-authcode'];
462 }
463
464 if ( ! isset( $_POST['custom-email-address'] ) || isset( $_POST['custom-email-address'] ) && empty( $_POST['custom-email-address'] ) ) {
465 if ( isset( $_POST['email'] ) ) {
466 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $_POST['email'] );
467 } elseif ( isset( $_POST['wp_2fa_email_address'] ) ) {
468 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $_POST['wp_2fa_email_address'] );
469 }
470 } elseif ( isset( $_POST['custom-email-address'] ) ) {
471 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', sanitize_email( wp_unslash( $_POST['custom-email-address'] ) ) );
472 }
473
474 // Check its one of our options.
475 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'] ) {
476 update_user_meta( $user->ID, WP_2FA_PREFIX . 'enabled_methods', sanitize_text_field( wp_unslash( $_POST['wp_2fa_enabled_methods'] ) ) );
477 self::delete_expire_and_enforced_keys( $user->ID );
478 User::setUserStatus( $user );
479 }
480
481 if ( isset( $_POST['wp-2fa-email-authcode'] ) && ! empty( $_POST['wp-2fa-email-authcode'] ) ) {
482 update_user_meta( $user->ID, WP_2FA_PREFIX . 'enabled_methods', 'email' );
483 self::delete_expire_and_enforced_keys( $user->ID );
484 User::setUserStatus( $user );
485 }
486
487 if ( isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
488 update_user_meta( $user->ID, WP_2FA_PREFIX . 'enabled_methods', 'totp' );
489 self::delete_expire_and_enforced_keys( $user->ID );
490 User::setUserStatus( $user );
491 }
492 }
493
494 /**
495 * Utility function to quickly remove data via direct query.
496 *
497 * @param int $user_id User id to process.
498 */
499 public static function delete_expire_and_enforced_keys( $user_id ) {
500 global $wpdb;
501 $wpdb->query(
502 $wpdb->prepare(
503 "
504 DELETE FROM $wpdb->usermeta
505 WHERE user_id = %d
506 AND meta_key IN ( %s, %s )
507 ",
508 [
509 $user_id,
510 WP_2FA_PREFIX . 'grace_period_expiry',
511 WP_2FA_PREFIX . 'user_enforced_instantly',
512 ]
513 )
514 );
515 }
516
517 /**
518 * Validate a user's code when setting up 2fa via the inline form.
519 *
520 * @return string JSON result of validation.
521 */
522 public function validate_authcode_via_ajax() {
523 check_ajax_referer( 'wp-2fa-validate-authcode' );
524
525 if ( isset( $_POST['form'] ) ) {
526 $input = $_POST['form'];
527 } else {
528 return 'No form';
529 }
530
531 $user = wp_get_current_user();
532
533 $our_errors = '';
534
535 // Grab key from the $_POST.
536 if ( isset( $input['wp-2fa-totp-key'] ) ) {
537 $current_key = sanitize_text_field( wp_unslash( $input['wp-2fa-totp-key'] ) );
538 }
539
540 // Grab authcode and ensure its a number.
541 if ( isset( $input['wp-2fa-totp-authcode'] ) ) {
542 $input['wp-2fa-totp-authcode'] = (int) $input['wp-2fa-totp-authcode'];
543 }
544
545 // Check if we are dealing with totp or email, if totp validate and store a new secret key.
546 if ( ! empty( $input['wp-2fa-totp-authcode'] ) && ! empty( $current_key ) ) {
547 if ( Authentication::is_valid_key( $current_key ) || ! is_numeric( $input['wp-2fa-totp-authcode'] ) ) {
548 if ( ! Authentication::is_valid_authcode( $current_key, sanitize_text_field( wp_unslash( $input['wp-2fa-totp-authcode'] ) ) ) ) {
549 $our_errors = esc_html__( 'Invalid Two Factor Authentication code.', 'wp-2fa' );
550 }
551 } else {
552 $our_errors = esc_html__( 'Invalid Two Factor Authentication secret key.', 'wp-2fa' );
553 }
554
555 // If its not totp, is it email.
556 } elseif ( ! empty( $input['wp-2fa-email-authcode'] ) ) {
557 if ( ! Authentication::validate_token( $user->ID, sanitize_text_field( wp_unslash( $input['wp-2fa-email-authcode'] ) ) ) ) {
558 $our_errors = __( 'Invalid Email Authentication code.', 'wp-2fa' );
559 }
560 } else {
561 $our_errors = __( 'Please enter the code to finalize the 2FA setup.', 'wp-2fa' );
562 }
563
564 if ( ! empty( $our_errors ) ) {
565 // Send the response.
566 wp_send_json_error(
567 array(
568 'error' => $our_errors,
569 )
570 );
571 } else {
572 $this->save_user_2fa_options( $input );
573 // Send the response.
574 wp_send_json_success();
575 }
576 }
577
578 /**
579 * @param int $user_id User ID.
580 *
581 * @return bool True if the user can remove 2FA from their account.
582 */
583 public static function can_user_remove_2fa( $user_id ) {
584 // check the "Hide the Remove 2FA button" setting
585 if ( WP2FA::get_wp2fa_setting( 'hide_remove_button' ) ) {
586 return false;
587 }
588
589 // check grace period policy
590 $grace_policy = WP2FA::get_wp2fa_setting( 'grace-policy' );
591 if ( 'no-grace-period' === $grace_policy ) {
592 // we only need to run further checks to find out if the 2FA is enforced for the user in question if there
593 // is no grace period
594 $enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
595 if ( 'all-users' === $enforcement_policy ) {
596 // enforced for all users, target user is definitely included
597 return false;
598 }
599
600 if ( 'do-not-enforce' !== $enforcement_policy ) {
601 // one of possible enforcement options is set, check the target user
602 return WP2FA::isUserEnforced( $user_id );
603 }
604 }
605
606 return true;
607 }
608 }
609