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