PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.8.0
WP 2FA – Two-factor authentication for WordPress v2.8.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 1 year ago Fly-Out 1 year ago Helpers 1 year ago Methods 1 year ago SettingsPages 1 year ago Views 1 year ago class-help-contact-us.php 1 year ago class-plugin-updated-notice.php 1 year ago class-premium-features.php 1 year ago class-settings-page.php 1 year ago class-setup-wizard.php 1 year ago class-user-listing.php 1 year ago class-user-notices.php 1 year ago class-user-profile.php 1 year ago class-user-registered.php 1 year ago index.php 1 year ago
class-user-profile.php
851 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=plugin&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 $show_enabled = true;
277
278 if ( isset( $additional_args ) && ! empty( $additional_args ) && isset( $additional_args['options'] ) && ! empty( $additional_args['options'] ) ) {
279 if ( isset( $additional_args['options']['do_not_show_enabled'] ) && 'false' !== $additional_args['options']['do_not_show_enabled'] ) {
280 $show_enabled = false;
281 }
282 }
283
284 if ( $show_enabled ) {
285
286 $form_output .= '<h3>' . \esc_html__( 'Currently configured:', 'wp-2fa' ) . '</h3>';
287
288 $form_output .= '
289 <table id="2fa-currently-configured-methods" class="form-table wp-2fa-user-profile-form" role="presentation">
290 <tbody>
291 <tr>
292 <th><label>' . \esc_html__( 'Primary method:', 'wp-2fa' ) . '</label></th>
293 <td>
294 ' . $primary_label . '
295 </td>
296 </tr>';
297
298 $form_output .= '
299 <tr>
300 <th><label>' . \esc_html__( 'Secondary method(s):', 'wp-2fa' ) . '</label></th>
301 <td>
302 ' . $backup_methods_enabled . '
303 </td>
304 </tr>';
305
306 $form_output .= '
307 </tbody>
308 </table>';
309 }
310
311 $form_output .= '<h3>' . \esc_html__( '2FA configuration:', 'wp-2fa' ) . '</h3>';
312
313 if ( User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) && isset( $enabled_methods ) && TOTP::METHOD_NAME === $enabled_methods ) {
314 $form_output .= '
315 <table id="2fa-configuration-options" class="form-table wp-2fa-user-profile-form remove-tr-padding" role="presentation">
316 <tbody>
317 <tr>
318 <th><label>' . Settings::get_providers_translate_names()[ $enabled_methods ] . '</label></th>
319 <td>
320 <details>
321 <summary class="qr-btn">' . \esc_html__( 'Show QR code', 'wp-2fa' ) . '</summary>
322 <p><img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" /></p>
323 <div class="app-key-wrapper">
324 <input type="text" id="app-key-input" readonly value="' . \esc_html( TOTP::get_totp_decrypted() ) . '" class="app-key">
325 ' .
326 ( ( is_ssl() ) ?
327 '<span class="click-to-copy">' . \esc_html__( 'COPY', 'wp-2fa' ) . '</span>' : '' ) . '
328 </div>
329 </details>
330 </td>
331 </tr>
332 </tbody>
333 </table>';
334 }
335
336 $form_output .= '
337 <table id="2fa-user-global-configuration" class="form-table wp-2fa-user-profile-form" role="presentation">
338 <tbody>
339 <tr>
340 <th><label>' . \esc_html__( '2FA Setup:', 'wp-2fa' ) . '</label></th>
341 <td>
342 ' . $form_content . '
343 </td>
344 </tr>
345 </tbody>
346 </table>';
347
348 if ( ( isset( $_GET['show'] ) && 'wp-2fa-setup' === $_GET['show'] ) || User_Helper::get_user_enforced_instantly( $user ) ) { // phpcs:ignore
349 $form_output .= '
350 <script>
351 window.addEventListener("load", function() {
352 wp2fa_fireWizard();
353 });
354 </script>
355 ';
356 }
357 }
358
359 echo $form_output; // phpcs:ignore
360
361 self::generate_inline_modals( $user_type );
362 }
363
364 /**
365 * Responsible for the building of all the modals.
366 *
367 * @param array $user_type - The WP user type.
368 *
369 * @return void
370 *
371 * @since 2.7.0
372 */
373 public static function generate_inline_modals( $user_type = array() ) {
374
375 ob_start();
376
377 $user = \wp_get_current_user();
378
379 $styling_class = ( empty( WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_styling' ) ) ) ? 'default_styling' : 'enable_styling';
380
381 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 ) ) { ?>
382 <div>
383 <div class="wp2fa-modal micromodal-slide <?php echo \esc_attr( $styling_class ); ?>" id="configure-2fa" aria-hidden="true">
384 <div class="modal__overlay" tabindex="-1">
385 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
386 <?php
387 echo Generate_Modal::generate_modal( // phpcs:ignore
388 'notify-users',
389 __( 'Are you sure?', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
390 __( 'Any unsaved changes will be lost!', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
391 array(
392 '<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>',
393 '<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>',
394 ),
395 '',
396 '430px'
397 );
398 ?>
399 <button class="modal__close modal_cancel" aria-label="Close modal"></button>
400 <main class="modal__content wp2fa-form-styles" id="modal-1-content">
401 <?php
402 $logo_url = WP2FA::get_wp2fa_white_label_setting( 'logo-code-page', false );
403 $logo_section = ( $logo_url ) ? '<p class="modal-logo-wrapper"><img style="max-height: 60px;margin: 0 auto 30px;" src="' . \esc_url( $logo_url ) . '" /></p>' : '';
404 $enable_logo = WP2FA::get_wp2fa_white_label_setting( 'enable_wizard_logo', false );
405
406 if ( $enable_logo ) {
407 echo $logo_section; // phpcs:ignore */
408 }
409
410 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 ) ) {
411
412 $available_methods = Methods::get_enabled_methods( User_Helper::get_user_role( $user ) );
413 $optional_welcome = WP2FA::get_wp2fa_white_label_setting( 'welcome', false );
414 $enable_welcome = WP2FA::get_wp2fa_white_label_setting( 'enable_welcome', false );
415
416 $intro_text = '';
417 if ( count( $available_methods[ User_Helper::get_user_role( $user ) ] ) > 1 ) {
418 $intro_text = WP2FA::replace_wizard_strings( WP2FA::get_wp2fa_white_label_setting( 'method_selection', true ), $user );
419 } elseif ( 1 === count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
420 $intro_text = WP2FA::get_wp2fa_white_label_setting( 'method_selection_single', true );
421 } else {
422 $intro_text = '<h3>' . __( 'No available 2FA methods set', 'wp-2fa' ) . '</h3><p>' . __( 'Ask your administrator to enable 2FA methods', 'wp-2fa' ) . '</p>';
423 }
424
425 if ( ! empty( $optional_welcome ) && $enable_welcome ) {
426 Wizard_Steps::optional_user_welcome_step();
427 }
428 ?>
429
430 <div class="wizard-step <?php echo ( empty( $optional_welcome ) ) ? 'active' : ''; ?>" id="choose-2fa-method">
431 <div class="mb-20"><?php echo \wp_kses_post( $intro_text ); ?></div>
432 <fieldset class="radio-cells">
433 <?php
434 /**
435 * Adds an option for external providers to add their own 2fa methods options. And sorts them (our logic).
436 *
437 * @since 2.0.0
438 */
439 \do_action( WP_2FA_PREFIX . 'methods_options' );
440 ?>
441 </fieldset>
442 <br>
443 <?php
444 if ( 0 !== count( $available_methods[ User_Helper::get_user_role( $user ) ] ) ) {
445 ?>
446 <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>
447 <?php
448 }
449 ?>
450 <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>
451 </div>
452 <?php } ?>
453
454 <?php if ( User_Utils::in_array_all( array( 'has_enabled_methods', 'viewing_own_profile' ), $user_type ) ) { ?>
455 <div class="wizard-step active">
456 <fieldset class="radio-cells max-3">
457 <?php
458 /**
459 * Add an option for external providers to add their own reconfigure methods options.
460 *
461 * @since 2.0.0
462 */
463 \do_action( WP_2FA_PREFIX . 'methods_reconfigure_options' );
464 ?>
465 </fieldset>
466 </div>
467 <?php } ?>
468
469 <?php Wizard_Steps::show_modal_methods(); ?>
470 <?php
471
472 $backup_methods = Settings::get_enabled_backup_methods_for_user_role( $user );
473
474 if ( count( $backup_methods ) > 1 ) {
475 Wizard_Steps::choose_backup_method();
476 }
477
478 /**
479 * Add an option for external providers to add their own wizard steps.
480 *
481 * @since 2.0.0
482 */
483 \do_action( WP_2FA_PREFIX . 'additional_settings_steps' );
484
485 // Create a nonce for use in ajax call to generate codes.
486 if ( Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) {
487 ?>
488 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
489 <?php Wizard_Steps::backup_codes_configure(); ?>
490 <?php Wizard_Steps::generated_backup_codes(); ?>
491 </div>
492 <?php } else { ?>
493 <div class="wizard-step" id="2fa-wizard-config-backup-codes">
494 <?php Wizard_Steps::congratulations_step(); ?>
495 </div>
496 <?php } ?>
497 </main>
498 </div>
499 </div>
500 </div>
501 </div>
502 <?php } ?>
503
504 <?php
505 /**
506 * Add an option for external providers to add their own 2fa methods options.
507 *
508 * @since 2.0.0
509 */
510 \do_action( WP_2FA_PREFIX . 'methods_wizards' );
511 ?>
512
513 <?php if ( Backup_Codes::are_backup_codes_enabled_for_role( User_Helper::get_user_role( $user ) ) ) { ?>
514 <div>
515 <div class="wp2fa-modal micromodal-slide <?php echo \esc_attr( $styling_class ); ?>" id="configure-2fa-backup-codes" aria-hidden="true">
516 <div class="modal__overlay" tabindex="-1">
517 <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
518 <button class="modal__close modal_cancel" aria-label="Close modal" data-close-2fa-modal></button>
519 <main class="modal__content wp2fa-form-styles" id="modal-1-content">
520 <?php Wizard_Steps::generated_backup_codes( true ); ?>
521 </main>
522 </div>
523 </div>
524 </div>
525 </div>
526 <?php } ?>
527 <div>
528 <?php
529
530 if ( self::can_user_remove_2fa( $user->ID ) ) :
531 echo Generate_Modal::generate_modal( // phpcs:ignore
532 'confirm-remove-2fa',
533 __( 'Remove 2FA?', 'wp-2fa' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
534 __( '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
535 array(
536 '<a href="#" class="button wp-2fa-button-primary" data-trigger-remove-2fa data-user-id="' . \esc_attr( $user->ID ) . '" ' . WP_Helper::create_data_nonce( 'wp-2fa-remove-user-2fa-nonce' ) . '>' . \esc_html__( 'Yes', 'wp-2fa' ) . '</a>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
537 '<button class="modal__btn wp-2fa-button-secondary button" data-close-2fa-modal aria-label="Close this dialog window">' . \esc_html__( 'No', 'wp-2fa' ) . '</button>',
538 )
539 );
540 endif;
541 ?>
542 </div>
543 <?php
544
545 $output = ob_get_contents();
546 ob_end_clean();
547
548 echo $output; // phpcs:ignore
549 }
550
551 /**
552 * Produces the 2FA configuration form for network users, or any user with no roles.
553 *
554 * @param string $is_shortcode - Current logic expects that to be set always.
555 * @param boolean $show_preamble - Show / hide preamble.
556 * @param array $options - Array with additional options.
557 *
558 * @return void
559 *
560 * @since 2.7.0
561 */
562 public static function inline_2fa_profile_form( $is_shortcode = 'true', $show_preamble = true, array $options = array() ) {
563
564 if ( isset( $_GET['user_id'] ) ) { // phpcs:ignore
565 $user_id = (int) $_GET['user_id']; // phpcs:ignore
566 $user = \get_user_by( 'id', $user_id );
567 } else {
568 $user = \wp_get_current_user();
569 }
570
571 // Get current user, we going to need this regardless.
572 $current_user = \wp_get_current_user();
573
574 if ( \is_multisite() ) {
575 if ( '' === trim( (string) \WP2FA\Admin\Helpers\User_Helper::get_user_role( $user ) ) ) {
576 return;
577 }
578 }
579
580 // Bail if we still dont have an object.
581 if ( ! is_a( $user, '\WP_User' ) || ! is_a( $current_user, '\WP_User' ) ) {
582 return;
583 }
584
585 $additional_args = array(
586 'is_shortcode' => $is_shortcode,
587 'show_preamble' => $show_preamble,
588 'options' => $options,
589 );
590
591 self::user_2fa_options( $user, $additional_args );
592 }
593
594 /**
595 * Add custom unlock account link to user edit admin list.
596 *
597 * @param string $actions Default actions.
598 * @param object $user_object User data.
599 * @return string Appended actions.
600 *
601 * @since 2.7.0
602 */
603 public static function user_2fa_row_actions( $actions, $user_object ) {
604 $nonce = wp_create_nonce( 'wp-2fa-unlock-account-nonce' );
605 $grace_period_expired = User_Helper::get_grace_period( $user_object );
606 $url = add_query_arg(
607 array(
608 'action' => 'unlock_account',
609 'user_id' => $user_object->ID,
610 'wp_2fa_nonce' => $nonce,
611 ),
612 admin_url( 'users.php' )
613 );
614
615 if ( $grace_period_expired ) {
616 $actions['edit_badges'] = '<a href="' . \esc_url( $url ) . '">' . \esc_html__( 'Unlock user', 'wp-2fa' ) . '</a>';
617 }
618
619 return $actions;
620 }
621
622 /**
623 * Save user profile information.
624 *
625 * @param array $input - The array with values to process.
626 *
627 * @return void
628 *
629 * @since 2.7.0
630 */
631 public static function save_user_2fa_options( $input ) {
632
633 // Ensure we have the inputs we want before we process.
634 // To avoid causing issues with the rest of the user profile.
635 if ( ! is_array( $input ) ) {
636 return;
637 }
638
639 // Assign the input to post, in case we are dealing with saving the data from another page.
640 if ( isset( $input ) ) {
641 $_POST = $input;
642 }
643
644 // Grab current user.
645 $user = wp_get_current_user();
646
647 // phpcs:disable
648 // Grab authcode and ensure its a number.
649 if ( isset( $_POST['wp-2fa-totp-authcode'] ) ) {
650 $_POST['wp-2fa-totp-authcode'] = (int) $_POST['wp-2fa-totp-authcode'];
651 }
652 if ( ( ! isset( $_POST['custom-email-address'] ) || isset( $_POST['custom-email-address'] ) && empty( $_POST['custom-email-address'] ) ) &&
653 ( ! isset( $_POST['custom-oob-email-address'] ) || isset( $_POST['custom-oob-email-address'] ) && empty( $_POST['custom-oob-email-address'] ) ) ) {
654 if ( isset( $_POST['email'] ) ) {
655 User_Helper::set_nominated_email_for_user( $_POST['email'], $user );
656 } elseif ( isset( $_POST['wp_2fa_email_address'] ) && isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
657 User_Helper::set_nominated_email_for_user( $_POST['wp_2fa_email_address'], $user );
658 } elseif ( isset( $_POST['wp_2fa_email_oob_address'] ) && isset( $_POST['wp-2fa-oob-authcode'] ) && ! empty( $_POST['wp-2fa-oob-authcode'] ) ) {
659 if ( 'use_custom_email' !== $_POST['wp_2fa_email_oob_address'] ) {
660 User_Helper::set_nominated_email_for_user( $_POST['wp_2fa_email_oob_address'], $user );
661 }
662 }
663 } elseif ( isset( $_POST['custom-email-address'] ) && ! empty( $_POST['custom-email-address'] ) ) {
664 User_Helper::set_nominated_email_for_user( $_POST['custom-email-address'], $user );
665 } elseif ( isset( $_POST['custom-oob-email-address'] ) && ! empty( $_POST['custom-oob-email-address'] ) ) {
666 User_Helper::set_nominated_email_for_user( $_POST['custom-oob-email-address'], $user );
667 }
668
669 // Check its one of our options.
670 if ( ( isset( $_POST['wp_2fa_enabled_methods'] ) && TOTP::METHOD_NAME === $_POST['wp_2fa_enabled_methods'] ) ||
671 ( isset( $_POST['wp_2fa_enabled_methods'] ) && Email::METHOD_NAME === $_POST['wp_2fa_enabled_methods'] ) ||
672 ( 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'] ) ) ) {
673 User_Helper::set_enabled_method_for_user(sanitize_text_field( wp_unslash( $_POST['wp_2fa_enabled_methods'] ) ), $user);
674 self::delete_expire_and_enforced_keys( $user->ID );
675 User_Helper::set_user_status( $user );
676 }
677
678 if ( isset( $_POST['wp-2fa-email-authcode'] ) && ! empty( $_POST['wp-2fa-email-authcode'] ) ) {
679 User_Helper::set_enabled_method_for_user( Email::METHOD_NAME, $user );
680 self::delete_expire_and_enforced_keys( $user->ID );
681 User_Helper::set_user_status( $user );
682 }
683
684 if ( isset( $_POST['wp-2fa-totp-authcode'] ) && ! empty( $_POST['wp-2fa-totp-authcode'] ) ) {
685 $totp_key = $_POST['wp-2fa-totp-key'];
686 if ( Authentication::is_valid_key( $totp_key ) ) {
687 if ( Open_SSL::is_ssl_available() ) {
688 $totp_key = Open_SSL::SECRET_KEY_PREFIX . Open_SSL::encrypt( $totp_key );
689 }
690
691 TOTP::set_user_method( $user, $totp_key );
692 }
693 }
694 // phpcs:enable
695 }
696
697 /**
698 * Utility function to remove user expiry and enforced data.
699 *
700 * @param int $user_id User id to process.
701 *
702 * @since 2.7.0
703 */
704 public static function delete_expire_and_enforced_keys( $user_id ) {
705 User_Helper::remove_user_expiry_date( $user_id );
706 User_Helper::remove_user_enforced_instantly( $user_id );
707 User_Helper::remove_grace_period( $user_id );
708 }
709
710 /**
711 * Validate a user's code when setting up 2fa via the inline form.
712 *
713 * @return void
714 *
715 * @since 2.7.0
716 */
717 public static function validate_authcode_via_ajax() {
718 check_ajax_referer( 'wp-2fa-validate-authcode' );
719
720 if ( isset( $_POST['form'] ) ) {
721 $input = wp_unslash( $_POST['form'] ); // phpcs:ignore
722 } else {
723 wp_send_json_error(
724 array(
725 'error' => \esc_html__( 'No form', 'wp-2fa' ),
726 )
727 );
728 }
729
730 $user = wp_get_current_user();
731
732 $our_errors = '';
733
734 // Grab key from the $_POST.
735 if ( isset( $input['wp-2fa-totp-key'] ) ) {
736 $current_key = sanitize_text_field( wp_unslash( $input['wp-2fa-totp-key'] ) );
737 }
738
739 // Grab authcode and ensure its a number.
740 if ( isset( $input['wp-2fa-totp-authcode'] ) ) {
741 $input['wp-2fa-totp-authcode'] = (int) $input['wp-2fa-totp-authcode'];
742 }
743
744 // Check if we are dealing with totp or email, if totp validate and store a new secret key.
745 if ( ! empty( $input['wp-2fa-totp-authcode'] ) && ! empty( $current_key ) ) {
746 if ( Authentication::is_valid_key( $current_key ) || ! is_numeric( $input['wp-2fa-totp-authcode'] ) ) {
747 if ( ! Authentication::is_valid_authcode( $current_key, sanitize_text_field( wp_unslash( $input['wp-2fa-totp-authcode'] ) ) ) ) {
748 $our_errors = \esc_html__( 'Invalid Two Factor Authentication code.', 'wp-2fa' );
749 }
750 } else {
751 $our_errors = \esc_html__( 'Invalid Two Factor Authentication secret key.', 'wp-2fa' );
752 }
753
754 // If its not totp, is it email.
755 } elseif ( ! empty( $input['wp-2fa-email-authcode'] ) ) {
756 if ( ! Authentication::validate_token( $user, sanitize_text_field( wp_unslash( $input['wp-2fa-email-authcode'] ) ) ) ) {
757 $our_errors = \esc_html__( 'Invalid Email Authentication code.', 'wp-2fa' );
758 }
759 } else {
760 $our_errors = \esc_html__( 'Please enter the code to finalize the 2FA setup.', 'wp-2fa' );
761 }
762
763 if ( ! empty( $our_errors ) ) {
764 // Send the response.
765 wp_send_json_error(
766 array(
767 'error' => $our_errors,
768 )
769 );
770 } else {
771 self::save_user_2fa_options( $input );
772 // Send the response.
773 wp_send_json_success();
774 }
775
776 wp_send_json_error(
777 array(
778 'error' => \esc_html__( 'Error processing form', 'wp-2fa' ),
779 )
780 );
781 }
782
783 /**
784 * Checks the user for remove 2FA capabilities.
785 *
786 * @param int $user_id User ID.
787 *
788 * @return bool True if the user can remove 2FA from their account.
789 *
790 * @since 2.7.0
791 */
792 public static function can_user_remove_2fa( $user_id ) {
793 // check the "Hide the Remove 2FA button" setting.
794 if ( Settings::get_role_or_default_setting( 'hide_remove_button', $user_id ) ) {
795 return false;
796 }
797
798 // check grace period policy.
799 $grace_policy = Settings::get_role_or_default_setting( 'grace-policy', $user_id );
800 if ( 'no-grace-period' === $grace_policy ) {
801 // we only need to run further checks to find out if the 2FA is enforced for the user in question if there
802 // is no grace period.
803 $enforcement_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
804
805 if ( 'all-users' === $enforcement_policy ) {
806 // enforced for all users, target user is definitely included.
807 return false;
808 }
809
810 if ( 'certain-roles-only' === $enforcement_policy && ! User_Helper::is_enforced( $user_id ) ) {
811 // Users specific role is not enforced, allow removal.
812 return true;
813 }
814
815 if ( 'do-not-enforce' !== $enforcement_policy ) {
816 // one of possible enforcement options is set, check the target user.
817 return User_Helper::is_enforced( $user_id );
818 }
819 }
820
821 return true;
822 }
823
824 /**
825 * Add script to admin footer to allow for nags to be dismissed from all admin pages.
826 *
827 * @return void
828 *
829 * @since 2.7.0
830 */
831 public static function dismiss_nag_notice() {
832 ?>
833 <script type="text/javascript">
834 jQuery( document ).on( 'click', '.dismiss-user-configure-nag', function() {
835 const thisNotice = jQuery( this ).closest( '.notice' );
836 jQuery.ajax( {
837 url: '<?php echo admin_url( 'admin-ajax.php' ); // phpcs:ignore ?>',
838 data: {
839 action: 'dismiss_nag'
840 },
841 complete: function() {
842 jQuery( thisNotice ).slideUp();
843 },
844 } );
845 } );
846 </script>
847 <?php
848 }
849 }
850 }
851