PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.4.2
WP 2FA – Two-factor authentication for WordPress v2.4.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 / class-user-profile.php
wp-2fa / includes / classes / Admin Last commit date
Controllers 3 years ago Helpers 3 years ago SettingsPages 3 years ago Views 3 years ago class-help-contact-us.php 3 years ago class-premium-features.php 3 years ago class-settings-page.php 3 years ago class-settingspage.php 3 years ago class-setup-wizard.php 3 years ago class-user-listing.php 3 years ago class-user-notices.php 3 years ago class-user-profile.php 3 years ago class-user-registered.php 3 years ago class-user.php 3 years ago index.php 5 years ago
class-user-profile.php
817 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's profile settings.
4 *
5 * @package wp2fa
6 * @subpackage user-utils
7 * @copyright 2023 WP White Security
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 namespace WP2FA\Admin;
13
14 use \WP2FA\WP2FA as WP2FA;
15 use WP2FA\Admin\Settings_Page;
16 use WP2FA\Utils\Generate_Modal;
17 use WP2FA\Authenticator\Open_SSL;
18 use WP2FA\Admin\Helpers\WP_Helper;
19 use WP2FA\Admin\Views\Wizard_Steps;
20 use WP2FA\Admin\Controllers\Methods;
21 use WP2FA\Admin\Helpers\User_Helper;
22 use WP2FA\Admin\Controllers\Settings;
23 use WP2FA\Authenticator\Backup_Codes;
24 use WP2FA\Authenticator\Authentication;
25 use \WP2FA\Utils\User_Utils as User_Utils;
26 use WP2FA\Utils\Settings_Utils as Settings_Utils;
27
28 /**
29 * User_Profile class responsible for the profile page operations
30 *
31 * @since 2.4.0
32 */
33 if ( ! class_exists( '\WP2FA\Admin\User_Profile' ) ) {
34 /**
35 * User_Profile - Class for handling user things such as profile settings and admin list views.
36 */
37 class User_Profile {
38
39 /**
40 * Add our buttons to the user profile editing screen.
41 *
42 * @param object $user User data.
43 * @param array $additional_args - Array with extra parameters for the method.
44 */
45 public static function user_2fa_options( $user, $additional_args = array() ) {
46
47 if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
48 $user_id = (int) $_GET['user_id']; // phpcs:ignore
49 $user = get_user_by( 'id', $user_id );
50 } else {
51 // Get current user, we're going to need this regardless.
52 $user = wp_get_current_user();
53 }
54
55 if ( ! is_a( $user, '\WP_User' ) ) {
56 return;
57 }
58
59 // Ensure we have something in the settings.
60 if ( empty( Settings_Utils::get_option( WP_2FA_POLICY_SETTINGS_NAME ) ) ) {
61 return;
62 }
63
64 $show_preamble = true;
65 if ( isset( $additional_args['show_preamble'] ) ) {
66 $show_preamble = \filter_var( $additional_args['show_preamble'], FILTER_VALIDATE_BOOLEAN );
67 }
68
69 $user_type = User_Utils::determine_user_2fa_status( $user );
70
71 $form_output = '';
72 $form_content = '';
73 $description = esc_html__( 'Add two-factor authentication to strengthen the security of your user account.', 'wp-2fa' );
74 $show_form_table = true;
75 $page_url = ( WP_Helper::is_multisite() ) ? 'index.php' : 'options-general.php';
76
77 // Orphan user (a user with no role or capabilities).
78 if ( in_array( 'orphan_user', $user_type, true ) ) {
79 // We want to use the same form/buttons used in the shortcode.
80 $additional_args['is_shortcode'] = true;
81
82 // Create useful message for admin.
83 if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'can_manage_options' ), $user_type ) ) {
84 $description = esc_html__( 'This user is required to setup 2FA but has not yet done so.', 'wp-2fa' );
85 }
86
87 if ( User_Utils::in_array_all( array( 'user_is_excluded', 'can_manage_options' ), $user_type ) ) {
88 $description = esc_html__( 'This user is excluded from configuring 2FA.', 'wp-2fa' );
89 }
90 }
91
92 // Excluded user.
93 if ( in_array( 'user_is_excluded', $user_type, true ) ) {
94 return;
95 }
96
97 // A user viewing their own profile AND has a 2FA method configured.
98 if ( User_Utils::in_array_all( array( 'viewing_own_profile' ), $user_type ) ) {
99 if (
100 User_Utils::in_array_all( array( 'has_enabled_methods' ), $user_type ) ||
101 User_Utils::in_array_all( array( 'no_required_has_enabled' ), $user_type )
102 ) {
103 // Create wizard link based on which 2fa methods are allowed by admin.
104 if ( ! empty( Settings::get_role_or_default_setting( 'enable_totp', $user ) ) && ! empty( Settings::get_role_or_default_setting( 'enable_email', $user ) ) ) {
105 $setup_2fa_url = add_query_arg(
106 array(
107 'page' => 'wp-2fa-setup',
108 'current-step' => 'user_choose_2fa_method',
109 'wizard_type' => 'user_2fa_config',
110 ),
111 admin_url( $page_url )
112 );
113 } else {
114 $setup_2fa_url = add_query_arg(
115 array(
116 'page' => 'wp-2fa-setup',
117 'current-step' => 'reconfigure_method',
118 'wizard_type' => 'user_reconfigure_config',
119 ),
120 admin_url( $page_url )
121 );
122 }
123
124 // Create backup codes URL.
125 $backup_codes_url = add_query_arg(
126 array(
127 'page' => 'wp-2fa-setup',
128 'current-step' => 'backup_codes',
129 'wizard_type' => 'backup_codes_config',
130 ),
131 admin_url( $page_url )
132 );
133
134 /**
135 * Gives the ability to remove the user's settings.
136 *
137 * @param bool - The status of the settings.
138 *
139 * @since 2.2.2
140 */
141 $show_enable2fa = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting', true );
142
143 /**
144 * Gives the ability to change the user profile description message.
145 *
146 * @param bool - The status of the settings.
147 *
148 * @since 2.4.0
149 */
150 $description = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting_description', $description );
151
152 if ( $show_enable2fa ) {
153 $form_content .= '<a href="' . esc_url( $setup_2fa_url ) . '" class="button button-primary">' . esc_html__( 'Change 2FA Settings', 'wp-2fa' ) . '</a>';
154 }
155
156 if ( self::can_user_remove_2fa( $user->ID ) ) {
157 $form_content .= '<a href="#" class="button button-primary remove-2fa" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . esc_html__( 'Remove 2FA', 'wp-2fa' ) . '</a>';
158 }
159
160 $form_content .= '<br /><br />';
161
162 if ( Settings_Page::are_backup_codes_enabled( User_Helper::get_user_role( $user ) ) ) {
163 $form_content .= '<a href="' . esc_url( $backup_codes_url ) . '" class="button button-primary">' . esc_html__( 'Generate list of Backup Codes', 'wp-2fa' ) . '</a>';
164
165 $codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
166 if ( $codes_remaining > 0 ) {
167 $form_content .= '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . esc_html__( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
168 } elseif ( 0 === $codes_remaining ) {
169 $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">' . esc_html__( 'Learn more about backup codes', 'wp-2fa' ) . '</a>';
170 }
171 }
172
173 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
174 $form_content = '';
175
176 /**
177 * Gives the ability to remove the user's settings.
178 *
179 * @param bool - The status of the settings.
180 *
181 * @since 2.2.2
182 */
183 $show_enable2fa = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting', true );
184
185 /**
186 * Gives the ability to change the user profile description message.
187 *
188 * @param bool - The status of the settings.
189 *
190 * @since 2.4.0
191 */
192 $description = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting_description', $description );
193
194 $styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
195
196 if ( $show_enable2fa ) {
197 $form_content = '<a href="#" class="button button-primary remove-2fa ' . esc_attr__( $styling_class ) . '" data-open-configure-2fa-wizard>' . esc_html__( 'Change 2FA settings', 'wp-2fa' ) . '</a>';
198 }
199
200 if ( self::can_user_remove_2fa( $user->ID ) ) {
201 $form_content .= '<a href="#" class="button button-primary remove-2fa ' . esc_attr__( $styling_class ) . '" onclick="MicroModal.show(\'confirm-remove-2fa\');">' . esc_html__( 'Remove 2FA', 'wp-2fa' ) . '</a>';
202 }
203 if ( Settings_Page::are_backup_codes_enabled( User_Helper::get_user_role( $user ) ) ) {
204 $form_content .= '</td><tr><th class="backup-methods-label">';
205 $codes_remaining = Backup_Codes::codes_remaining_for_user( $user );
206 if ( $codes_remaining > 0 ) {
207 $backup_codes_desc = '<span class="description mt-5px">' . esc_attr( (int) $codes_remaining ) . ' ' . esc_html__( 'unused backup codes remaining.', 'wp-2fa' ) . '</span>';
208 } elseif ( 0 === $codes_remaining ) {
209 $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">' . esc_html__( 'Learn more about backup codes', 'wp-2fa' ) . '</a>';
210 }
211
212 $form_content .= Wizard_Steps::get_generate_codes_link() . $backup_codes_desc;
213
214 /**
215 * Add an option for external providers to add their own user form buttons.
216 *
217 * @since 2.0.0
218 */
219 $form_content = apply_filters( WP_2FA_PREFIX . 'additional_form_buttons', $form_content );
220
221 $form_content .= '</th></tr>';
222 }
223 }
224 }
225
226 $show_if_user_is_not_in = array(
227 'user_is_excluded',
228 'has_enabled_methods',
229 'no_required_has_enabled',
230 );
231
232 // User viewing own profile and needs to enable 2FA.
233 if (
234 User_Utils::in_array_all( array( 'user_needs_to_setup_2fa' ), $user_type ) ||
235 User_Utils::role_is_not( $show_if_user_is_not_in, $user_type )
236 ) {
237 $first_time_setup_url = Settings::get_setup_page_link();
238
239 /**
240 * Gives the ability to remove the user's settings.
241 *
242 * @param bool - The status of the settings.
243 *
244 * @since 2.2.2
245 */
246 $show_enable2fa = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting', true );
247
248 /**
249 * Gives the ability to change the user profile description message.
250 *
251 * @param bool - The status of the settings.
252 *
253 * @since 2.4.0
254 */
255 $description = \apply_filters( WP_2FA_PREFIX . 'enable_2fa_user_setting_description', $description );
256
257 $styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
258
259 if ( $show_enable2fa ) {
260
261 if ( isset( $additional_args['is_shortcode'] ) && $additional_args['is_shortcode'] ) {
262 $form_content .= '<a href="#" class="button button-primary ' . esc_attr__( $styling_class ) . '" data-open-configure-2fa-wizard>' . esc_html__( 'Configure 2FA', 'wp-2fa' ) . '</a>';
263 }
264
265 if ( empty( $additional_args ) ) {
266 $form_content .= '<a href="' . esc_url( $first_time_setup_url ) . '" class="button button-primary ' . esc_attr__( $styling_class ) . '">' . esc_html__( 'Configure Two-factor authentication (2FA)', 'wp-2fa' ) . '</a>';
267 }
268 }
269 }
270 }
271
272 // Admin viewing users profile AND user has a configured 2FA method.
273 if ( User_Utils::in_array_all( array( 'can_manage_options', 'has_enabled_methods' ), $user_type ) && ! in_array( 'viewing_own_profile', $user_type, true ) ) {
274 $description = esc_html__( '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' );
275
276 $remove_users_2fa_url = add_query_arg(
277 array(
278 'action' => 'remove_user_2fa',
279 'user_id' => $user->ID,
280 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ),
281 'admin_reset' => 'yes',
282 ),
283 admin_url( 'user-edit.php' )
284 );
285
286 $form_content .= '<a href="' . esc_url( $remove_users_2fa_url ) . '" class="button button-primary">' . esc_html__( 'Reset 2FA configuration', 'wp-2fa' ) . '</a>';
287 }
288
289 // Admin viewing users profile AND users grace period has expired.
290 if ( User_Utils::in_array_all( array( 'can_manage_options', 'grace_has_expired' ), $user_type ) ) {
291 $unlock_user_url = add_query_arg(
292 array(
293 'action' => 'unlock_account',
294 'user_id' => $user->ID,
295 'wp_2fa_nonce' => wp_create_nonce( 'wp-2fa-unlock-account-nonce' ),
296 ),
297 admin_url( 'user-edit.php' )
298 );
299 $form_content .= '<a href="' . esc_url( $unlock_user_url ) . '" class="button button-primary">' . esc_html__( 'Unlock user and reset the grace period', 'wp-2fa' ) . '</a>';
300 }
301
302 if ( $show_preamble ) {
303 $form_output .= '<h2>' . esc_html__( 'Two-factor authentication settings', 'wp-2fa' ) . '</h2>';
304
305 if ( $description ) {
306 $form_output .= '<p class="description">' . $description . '</p>';
307 }
308 }
309 /**
310 * Gives the ability to add more content to the profile page.
311 *
312 * @param string $form_content - The parsed HTML of the form.
313 */
314 $form_content = apply_filters( WP_2FA_PREFIX . 'append_to_profile_form_content', $form_content );
315
316 if ( $show_form_table && ! empty( $form_content ) ) {
317 $form_output .= '
318 <table class="form-table wp-2fa-user-profile-form" role="presentation">
319 <tbody>
320 <tr>
321 <th><label>' . esc_html__( '2FA Setup:', 'wp-2fa' ) . '</label></th>
322 <td>
323 ' . $form_content . '
324 </td>
325 </tr>
326 </tbody>
327 </table>';
328
329 if ( ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) || User_Helper::get_user_enforced_instantly( $user ) ) { // phpcs:ignore
330 $form_output .= '
331 <script>
332 window.addEventListener("load", function() {
333 wp2fa_fireWizard();
334 });
335 </script>
336 ';
337 }
338 }
339
340 echo $form_output; // phpcs:ignore
341
342 self::generate_inline_modals( $user_type );
343 }
344
345 /**
346 * Responsible for the building of all the modals.
347 *
348 * @param array $user_type - The WP user type.
349 *
350 * @return void
351 */
352 public static function generate_inline_modals( $user_type = array() ) {
353
354 ob_start();
355
356 $user = wp_get_current_user();
357
358 $styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
359
360 if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( User_Helper::USER_UNDETERMINED_STATUS, 'viewing_own_profile' ), $user_type ) ) { ?>
361 <div>
362 <div class="wp2fa-modal micromodal-slide <?php echo esc_attr( $styling_class ); ?>" id="configure-2fa" aria-hidden="true">
363 <div class="modal__overlay" tabindex="-1">
364 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
365 <?php
366 echo Generate_Modal::generate_modal( // phpcs:ignore
367 'notify-users',
368 __( 'Are you sure?', 'wp-2fa' ),
369 __( 'Any unsaved changes will be lost!', 'wp-2fa' ),
370 array(
371 '<button class="button wp-2fa-button-primary button-primary button-confirm" aria-label="Close this dialog window and the wizard">' . esc_html__( 'Yes', 'wp-2fa' ) . '</button>',
372 '<button class="button wp-2fa-button-secondary button-secondary button-decline" data-micromodal-close aria-label="Close this dialog window">' . esc_html__( 'No', 'wp-2fa' ) . '</button>',
373 ),
374 '',
375 '430px'
376 );
377 ?>
378 <button class="modal__close" aria-label="Close modal"></button>
379 <main class="modal__content wp2fa-form-styles" id="modal-1-content">
380 <?php
381 $logo_url = WP2FA::get_wp2fa_white_label_setting( 'logo-code-page', false );
382 $logo_section = ( $logo_url ) ? '<p class="modal-logo-wrapper"><img style="max-height: 60px;margin: 0 auto 30px;" src="' . esc_url( $logo_url ) . '" /></p>' : '';
383 $enable_logo = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_logo', false );
384
385 if ( $enable_logo ) {
386 echo $logo_section; // phpcs:ignore */
387 }
388
389 if ( User_Utils::in_array_all( array( 'user_needs_to_setup_2fa', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( 'no_required_not_enabled', 'viewing_own_profile' ), $user_type ) || User_Utils::in_array_all( array( User_Helper::USER_UNDETERMINED_STATUS, 'viewing_own_profile' ), $user_type ) ) {
390
391 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
392 $optional_welcome = WP2FA::get_wp2fa_white_label_setting( 'welcome', false );
393 $enable_welcome = WP2FA::get_wp2fa_white_label_setting( 'enable_welcome', false );
394
395 $intro_text = '';
396 if ( count( $available_methods[ User_Helper::get_user_role( $user ) ] ) > 1 ) {
397 $intro_text = WP2FA::replace_wizard_strings( WP2FA::get_wp2fa_white_label_setting( 'method_selection', true ), $user );
398 } elseif ( 1 === count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
399 $intro_text = WP2FA::get_wp2fa_white_label_setting( 'method_selection_single', true );
400 } else {
401 $intro_text = '<h3>' . __( 'No available 2FA methods set', 'wp-2fa' ) . '</h3><p>' . __( 'Ask your administrator to enable 2FA methods', 'wp-2fa' ) . '</p>';
402 }
403
404 if ( ! empty( $optional_welcome ) && $enable_welcome ) {
405 Wizard_Steps::optional_user_welcome_step();
406 }
407 ?>
408
409 <div class="wizard-step <?php echo ( empty( $optional_welcome ) ) ? 'active' : ''; ?>" id="choose-2fa-method">
410 <div class="mb-20"><?php echo wp_kses_post( $intro_text ); ?></div>
411 <fieldset class="radio-cells">
412 <?php Wizard_Steps::totp_option(); ?>
413 <?php Wizard_Steps::email_option(); ?>
414
415 <?php
416 /**
417 * Add an option for external providers to add their own 2fa methods options.
418 *
419 * @since 2.0.0
420 */
421 do_action( WP_2FA_PREFIX . 'methods_options' );
422 ?>
423 </fieldset>
424 <br>
425 <?php
426 if ( 0 !== count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
427 ?>
428 <a href="#" class="button wp-2fa-button-primary button-primary 2fa-choose-method" data-name="next_step_setting_modal_wizard" data-next-step><?php esc_html_e( 'Next Step', 'wp-2fa' ); ?></a>
429 <?php
430 }
431 ?>
432 <button class="button wp-2fa-button-secondary button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
433 </div>
434 <?php } ?>
435
436 <?php if ( User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) { ?>
437 <div class="wizard-step active">
438 <fieldset class="radio-cells max-3">
439 <?php Wizard_Steps::totp_re_configure(); ?>
440 <?php Wizard_Steps::email_re_configure(); ?>
441 <?php
442 /**
443 * Add an option for external providers to add their own reconfigure methods options.
444 *
445 * @since 2.0.0
446 */
447 do_action( WP_2FA_PREFIX . 'methods_reconfigure_options' );
448 ?>
449 </fieldset>
450 </div>
451 <?php } ?>
452
453 <?php Wizard_Steps::show_modal_methods(); ?>
454 <?php
455
456 $backup_methods = Settings::get_enabled_backup_methods_for_user_role( $user );
457
458 if ( count( $backup_methods ) > 1 ) {
459 Wizard_Steps::choose_backup_method();
460 }
461
462 /**
463 * Add an option for external providers to add their own wizard steps.
464 *
465 * @since 2.0.0
466 */
467 do_action( WP_2FA_PREFIX . 'additional_settings_steps' );
468
469 // Create a nonce for use in ajax call to generate codes.
470 if ( Settings_Page::are_backup_codes_enabled( User_Helper::get_user_role( $user ) ) ) {
471 ?>
472 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
473 <?php Wizard_Steps::backup_codes_configure(); ?>
474 <?php Wizard_Steps::generated_backup_codes(); ?>
475 </div>
476 <?php } else { ?>
477 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
478 <?php Wizard_Steps::congratulations_step(); ?>
479 </div>
480 <?php } ?>
481 </main>
482 </div>
483 </div>
484 </div>
485 </div>
486 <?php } ?>
487
488 <?php
489 /**
490 * Add an option for external providers to add their own 2fa methods options.
491 *
492 * @since 2.0.0
493 */
494 do_action( WP_2FA_PREFIX . 'methods_wizards' );
495 ?>
496
497 <?php if ( Settings_Page::are_backup_codes_enabled( User_Helper::get_user_role( $user ) ) ) { ?>
498 <div>
499 <div class="wp2fa-modal micromodal-slide <?php echo esc_attr( $styling_class ); ?>" id="configure-2fa-backup-codes" aria-hidden="true">
500 <div class="modal__overlay" tabindex="-1">
501 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
502 <button class="modal__close" aria-label="Close modal" data-close-2fa-modal></button>
503 <main class="modal__content wp2fa-form-styles" id="modal-1-content">
504 <?php Wizard_Steps::generated_backup_codes( true ); ?>
505 </main>
506 </div>
507 </div>
508 </div>
509 </div>
510 <?php } ?>
511 <div>
512 <?php
513
514 if ( self::can_user_remove_2fa( $user->ID ) ) :
515 echo Generate_Modal::generate_modal( // phpcs:ignore
516 'confirm-remove-2fa',
517 __( 'Remove 2FA?', 'wp-2fa' ),
518 __( 'Are you sure you want to remove two-factor authentication and lower the security of your user account?', 'wp-2fa' ),
519 array(
520 '<a href="#" class="button wp-2fa-button-primary button-confirm" data-trigger-remove-2fa data-user-id="' . esc_attr( $user->ID ) . '" data-nonce="' . wp_create_nonce( 'wp-2fa-remove-user-2fa-nonce' ) . '">' . esc_html__( 'Yes', 'wp-2fa' ) . '</a>',
521 '<button class="modal__btn wp-2fa-button-secondary button button-decline" data-close-2fa-modal aria-label="Close this dialog window">' . esc_html__( 'No', 'wp-2fa' ) . '</button>',
522 )
523 );
524 endif;
525 ?>
526 </div>
527 <?php
528
529 $output = ob_get_contents();
530 ob_end_clean();
531
532 echo $output; // phpcs:ignore
533 }
534
535 /**
536 * Produces the 2FA configuration form for network users, or any user with no roles.
537 *
538 * @param string $is_shortcode - Is it called from the shortcode.
539 * @param boolean $show_preamble - Show / hide preamble.
540 *
541 * @return void
542 */
543 public static function inline_2fa_profile_form( $is_shortcode = '', $show_preamble = true ) {
544
545 if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
546 $user_id = (int) $_GET['user_id']; // phpcs:ignore
547 $user = get_user_by( 'id', $user_id );
548 } else {
549 $user = wp_get_current_user();
550 }
551
552 // Get current user, we going to need this regardless.
553 $current_user = wp_get_current_user();
554
555 if ( \is_multisite() ) {
556 if ( '' === trim( \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ) ) ) {
557 return;
558 }
559 }
560
561 // Bail if we still dont have an object.
562 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
563 return;
564 }
565
566 $additional_args = array(
567 'is_shortcode' => $is_shortcode,
568 'show_preamble' => $show_preamble,
569 );
570
571 self::user_2fa_options( $user, $additional_args );
572 }
573
574 /**
575 * Add custom unlock account link to user edit admin list.
576 *
577 * @param string $actions Default actions.
578 * @param object $user_object User data.
579 * @return string Appended actions.
580 */
581 public static function user_2fa_row_actions( $actions, $user_object ) {
582 $nonce = wp_create_nonce( 'wp-2fa-unlock-account-nonce' );
583 $grace_period_expired = User_Helper::get_grace_period( $user_object );
584 $url = add_query_arg(
585 array(
586 'action' => 'unlock_account',
587 'user_id' => $user_object->ID,
588 'wp_2fa_nonce' => $nonce,
589 ),
590 admin_url( 'users.php' )
591 );
592
593 if ( $grace_period_expired ) {
594 $actions['edit_badges'] = '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Unlock user', 'wp-2fa' ) . '</a>';
595 }
596 return $actions;
597 }
598
599 /**
600 * Save user profile information.
601 *
602 * @param array $input - The array with values to process.
603 *
604 * @return void
605 */
606 public static function save_user_2fa_options( $input ) {
607
608 // Ensure we have the inputs we want before we process.
609 // To avoid causing issues with the rest of the user profile.
610 if ( ! is_array( $input ) ) {
611 return;
612 }
613
614 // Assign the input to post, in case we are dealing with saving the data from another page.
615 if ( isset( $input ) ) {
616 $_POST = $input;
617 }
618
619 // Grab current user.
620 $user = wp_get_current_user();
621
622 // phpcs:disable
623 // Grab authcode and ensure its a number.
624 if ( isset( $_POST['wp-2fa-totp-authcode'] ) ) {
625 $_POST['wp-2fa-totp-authcode'] = (int) $_POST['wp-2fa-totp-authcode'];
626 }
627 if ( ( ! isset( $_POST['custom-email-address'] ) || isset( $_POST['custom-email-address'] ) && empty( $_POST['custom-email-address'] ) ) &&
628 ( ! isset( $_POST['custom-oob-email-address'] ) || isset( $_POST['custom-oob-email-address'] ) && empty( $_POST['custom-oob-email-address'] ) ) ) {
629 if ( isset( $_POST['email'] ) ) {
630 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $_POST['email'] );
631 } elseif ( isset( $_POST['wp_2fa_email_address'] ) && isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
632 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $_POST['wp_2fa_email_address'] );
633 } elseif ( isset( $_POST['wp_2fa_email_oob_address'] ) && isset( $_POST['wp-2fa-oob-authcode'] ) && ! empty( $_POST['wp-2fa-oob-authcode'] ) ) {
634 if ( 'use_custom_email' !== $_POST['wp_2fa_email_oob_address'] ) {
635 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $_POST['wp_2fa_email_oob_address'] );
636 } else {
637 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', $user->user_email );
638 }
639 }
640 } elseif ( isset( $_POST['custom-email-address'] ) && ! empty( $_POST['custom-email-address'] ) ) {
641 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', sanitize_email( wp_unslash( $_POST['custom-email-address'] ) ) );
642 } elseif ( isset( $_POST['custom-oob-email-address'] ) && ! empty( $_POST['custom-oob-email-address'] ) ) {
643 update_user_meta( $user->ID, WP_2FA_PREFIX . 'nominated_email_address', sanitize_email( wp_unslash( $_POST['custom-oob-email-address'] ) ) );
644 }
645
646 // Check its one of our options.
647 if ( ( isset( $_POST['wp_2fa_enabled_methods'] ) && 'totp' === $_POST['wp_2fa_enabled_methods'] ) ||
648 ( isset( $_POST['wp_2fa_enabled_methods'] ) && 'email' === $_POST['wp_2fa_enabled_methods'] ) ||
649 ( isset( $_POST['wp_2fa_enabled_methods'] ) && 'oob' === $_POST['wp_2fa_enabled_methods'] ) ) {
650 User_Helper::set_enabled_method_for_user(sanitize_text_field( wp_unslash( $_POST['wp_2fa_enabled_methods'] ) ), $user);
651 self::delete_expire_and_enforced_keys( $user->ID );
652 User_Helper::set_user_status( $user );
653 }
654
655 if ( isset( $_POST['wp-2fa-email-authcode'] ) && ! empty( $_POST['wp-2fa-email-authcode'] ) ) {
656 User_Helper::set_enabled_method_for_user( 'email', $user );
657 self::delete_expire_and_enforced_keys( $user->ID );
658 User_Helper::set_user_status( $user );
659 }
660
661 if ( isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
662 User_Helper::set_enabled_method_for_user( 'totp', $user );
663
664 $totp_key = $_POST['wp-2fa-totp-key'];
665 if ( Authentication::is_valid_key( $totp_key ) ) {
666 if ( Open_SSL::is_ssl_available() ) {
667 $totp_key = Open_SSL::SECRET_KEY_PREFIX . Open_SSL::encrypt( $totp_key );
668 }
669 User_Helper::set_user_totp_key( $totp_key, $user );
670 self::delete_expire_and_enforced_keys( $user->ID );
671 User_Helper::set_user_status( $user );
672 }
673 }
674 // phpcs:enable
675 }
676
677 /**
678 * Utility function to remove user expiry and enforced data.
679 *
680 * @param int $user_id User id to process.
681 */
682 public static function delete_expire_and_enforced_keys( $user_id ) {
683 User_Helper::remove_user_expiry_date( $user_id );
684 User_Helper::remove_user_enforced_instantly( $user_id );
685 User_Helper::remove_grace_period( $user_id );
686 }
687
688 /**
689 * Validate a user's code when setting up 2fa via the inline form.
690 *
691 * @return void
692 */
693 public static function validate_authcode_via_ajax() {
694 check_ajax_referer( 'wp-2fa-validate-authcode' );
695
696 if ( isset( $_POST['form'] ) ) {
697 $input = wp_unslash( $_POST['form'] ); // phpcs:ignore
698 } else {
699 wp_send_json_error(
700 array(
701 'error' => esc_html__( 'No form', 'wp-2fa' ),
702 )
703 );
704 }
705
706 $user = wp_get_current_user();
707
708 $our_errors = '';
709
710 // Grab key from the $_POST.
711 if ( isset( $input['wp-2fa-totp-key'] ) ) {
712 $current_key = sanitize_text_field( wp_unslash( $input['wp-2fa-totp-key'] ) );
713 }
714
715 // Grab authcode and ensure its a number.
716 if ( isset( $input['wp-2fa-totp-authcode'] ) ) {
717 $input['wp-2fa-totp-authcode'] = (int) $input['wp-2fa-totp-authcode'];
718 }
719
720 // Check if we are dealing with totp or email, if totp validate and store a new secret key.
721 if ( ! empty( $input['wp-2fa-totp-authcode'] ) && ! empty( $current_key ) ) {
722 if ( Authentication::is_valid_key( $current_key ) || ! is_numeric( $input['wp-2fa-totp-authcode'] ) ) {
723 if ( ! Authentication::is_valid_authcode( $current_key, sanitize_text_field( wp_unslash( $input['wp-2fa-totp-authcode'] ) ) ) ) {
724 $our_errors = esc_html__( 'Invalid Two Factor Authentication code.', 'wp-2fa' );
725 }
726 } else {
727 $our_errors = esc_html__( 'Invalid Two Factor Authentication secret key.', 'wp-2fa' );
728 }
729
730 // If its not totp, is it email.
731 } elseif ( ! empty( $input['wp-2fa-email-authcode'] ) ) {
732 if ( ! Authentication::validate_token( $user, sanitize_text_field( wp_unslash( $input['wp-2fa-email-authcode'] ) ) ) ) {
733 $our_errors = esc_html__( 'Invalid Email Authentication code.', 'wp-2fa' );
734 }
735 } else {
736 $our_errors = esc_html__( 'Please enter the code to finalize the 2FA setup.', 'wp-2fa' );
737 }
738
739 if ( ! empty( $our_errors ) ) {
740 // Send the response.
741 wp_send_json_error(
742 array(
743 'error' => $our_errors,
744 )
745 );
746 } else {
747 self::save_user_2fa_options( $input );
748 // Send the response.
749 wp_send_json_success();
750 }
751
752 wp_send_json_error(
753 array(
754 'error' => esc_html__( 'Error processing form', 'wp-2fa' ),
755 )
756 );
757 }
758
759 /**
760 * Checks the user for remove 2FA capabilities.
761 *
762 * @param int $user_id User ID.
763 *
764 * @return bool True if the user can remove 2FA from their account.
765 */
766 public static function can_user_remove_2fa( $user_id ) {
767 // check the "Hide the Remove 2FA button" setting.
768 if ( Settings::get_role_or_default_setting( 'hide_remove_button', $user_id ) ) {
769 return false;
770 }
771
772 // check grace period policy.
773 $grace_policy = Settings::get_role_or_default_setting( 'grace-policy', $user_id );
774 if ( 'no-grace-period' === $grace_policy ) {
775 // we only need to run further checks to find out if the 2FA is enforced for the user in question if there
776 // is no grace period.
777 $enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
778 if ( 'all-users' === $enforcement_policy ) {
779 // enforced for all users, target user is definitely included.
780 return false;
781 }
782
783 if ( 'do-not-enforce' !== $enforcement_policy ) {
784 // one of possible enforcement options is set, check the target user.
785 return User_Helper::is_enforced( $user_id );
786 }
787 }
788
789 return true;
790 }
791
792 /**
793 * Add script to admin footer to allow for nags to be dismissed from all admin pages.
794 *
795 * @return void
796 */
797 public static function dismiss_nag_notice() {
798 ?>
799 <script type="text/javascript">
800 jQuery( document ).on( 'click', '.dismiss-user-configure-nag', function() {
801 const thisNotice = jQuery( this ).closest( '.notice' );
802 jQuery.ajax( {
803 url: '<?php echo admin_url( 'admin-ajax.php' ); // phpcs:ignore ?>',
804 data: {
805 action: 'dismiss_nag'
806 },
807 complete: function() {
808 jQuery( thisNotice ).slideUp();
809 },
810 } );
811 } );
812 </script>
813 <?php
814 }
815 }
816 }
817