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