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