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