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