Traits
11 months ago
class-backup-codes.php
11 months ago
class-email-wizard-steps.php
11 months ago
class-email.php
11 months ago
class-totp-wizard-steps.php
11 months ago
class-totp.php
11 months ago
index.php
11 months ago
class-totp-wizard-steps.php
392 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Responsible for WP2FA user's TOTP manipulation. |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @subpackage methods-wizard |
| 7 | * @since 2.6.0 |
| 8 | * @copyright 2025 Melapress |
| 9 | * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 |
| 10 | * |
| 11 | * @see https://wordpress.org/plugins/wp-2fa/ |
| 12 | */ |
| 13 | |
| 14 | declare(strict_types=1); |
| 15 | |
| 16 | namespace WP2FA\Methods\Wizards; |
| 17 | |
| 18 | use WP2FA\WP2FA; |
| 19 | use WP2FA\Methods\TOTP; |
| 20 | use WP2FA\Utils\Settings_Utils; |
| 21 | use WP2FA\Admin\Helpers\WP_Helper; |
| 22 | use WP2FA\Admin\Views\Wizard_Steps; |
| 23 | use WP2FA\Admin\Controllers\Methods; |
| 24 | use WP2FA\Admin\Helpers\User_Helper; |
| 25 | use WP2FA\Authenticator\Authentication; |
| 26 | use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait; |
| 27 | |
| 28 | /** |
| 29 | * Class for handling totp codes. |
| 30 | * |
| 31 | * @since 2.6.0 |
| 32 | * |
| 33 | * @package WP2FA |
| 34 | */ |
| 35 | if ( ! class_exists( '\WP2FA\Methods\Wizards\TOTP_Wizard_Steps' ) ) { |
| 36 | /** |
| 37 | * TOTP code class, for handling totp (app) code generation and such. |
| 38 | * |
| 39 | * @since 2.6.0 |
| 40 | */ |
| 41 | class TOTP_Wizard_Steps extends Wizard_Steps { |
| 42 | |
| 43 | use Methods_Wizards_Trait; |
| 44 | |
| 45 | /** |
| 46 | * Keeps the main class method name, so we can call it when needed. |
| 47 | * |
| 48 | * @var string |
| 49 | * |
| 50 | * @since 2.6.0 |
| 51 | */ |
| 52 | private static $main_class = TOTP::class; |
| 53 | |
| 54 | /** |
| 55 | * The default value of the method order in the wizards. |
| 56 | * |
| 57 | * @var integer |
| 58 | * |
| 59 | * @since 2.6.0 |
| 60 | */ |
| 61 | private static $order = 1; |
| 62 | |
| 63 | /** |
| 64 | * Inits the class hooks |
| 65 | * |
| 66 | * @return void |
| 67 | * |
| 68 | * @since 2.4.0 |
| 69 | */ |
| 70 | public static function init() { |
| 71 | \add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'totp_option' ), 10, 2 ); |
| 72 | \add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'totp_modal_configure' ) ); |
| 73 | \add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'totp_re_configure' ), 10, 2 ); |
| 74 | \add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'totp_wizard_settings' ), 10, 4 ); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Shows the option to reconfigure email (if applicable) |
| 79 | * |
| 80 | * @param array $methods - Array of methods collected. |
| 81 | * @param string $role - The name of the role to show option to. |
| 82 | * |
| 83 | * @since 2.6.0 |
| 84 | * |
| 85 | * @return array |
| 86 | */ |
| 87 | public static function totp_re_configure( array $methods, string $role ): array { |
| 88 | |
| 89 | if ( ! TOTP::is_enabled( $role ) ) { |
| 90 | return $methods; |
| 91 | } |
| 92 | \ob_start(); |
| 93 | ?> |
| 94 | <div class="option-pill"> |
| 95 | <?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'totp_reconfigure_intro', true ), User_Helper::get_user_object()->ID, TOTP::METHOD_NAME ) ); ?> |
| 96 | <div class="wp2fa-setup-actions"> |
| 97 | <a href="#" class="button button-primary wp-2fa-button-primary" data-name="next_step_setting_modal_wizard" data-trigger-reset-key <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" data-next-step="2fa-wizard-totp"><?php \esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a> |
| 98 | </div> |
| 99 | </div> |
| 100 | <?php |
| 101 | $output = ob_get_contents(); |
| 102 | ob_end_clean(); |
| 103 | |
| 104 | $methods[ self::get_order( $role, $methods ) ] = array( |
| 105 | 'name' => self::$main_class::METHOD_NAME, |
| 106 | 'output' => $output, |
| 107 | ); |
| 108 | |
| 109 | return $methods; |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Shows the initial totp setup options based on enabled methods |
| 114 | * |
| 115 | * @param array $methods - Array of methods collected. |
| 116 | * @param string $role - The name of the role to show option to. |
| 117 | * |
| 118 | * @since 2.6.0 |
| 119 | * |
| 120 | * @return array |
| 121 | */ |
| 122 | public static function totp_option( array $methods, string $role ): array { |
| 123 | if ( TOTP::is_enabled( $role ) && Methods::is_method_enabled_for_role( TOTP::METHOD_NAME, $role ) ) { |
| 124 | \ob_start(); |
| 125 | ?> |
| 126 | <div class="option-pill"> |
| 127 | <label for="basic"> |
| 128 | <input id="basic" name="wp_2fa_enabled_methods" type="radio" value="totp"> |
| 129 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label', true ) ); ?> |
| 130 | <?php |
| 131 | $show = ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) && WP2FA::get_wp2fa_white_label_setting( \esc_attr( TOTP::METHOD_NAME ) . '-option-label-hint', true ) ) ?? false; |
| 132 | if ( $show ) { |
| 133 | echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( TOTP::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>'; |
| 134 | } |
| 135 | ?> |
| 136 | </label> |
| 137 | <?php |
| 138 | if ( $show ) { |
| 139 | echo '<p class="description tooltip-content-wrapper" data-' . \esc_attr( TOTP::METHOD_NAME ) . '-tooltip-content-wrapper>' . \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( \esc_attr( TOTP::METHOD_NAME ) . '-option-label-hint', true ) ) . '</p>'; |
| 140 | } |
| 141 | ?> |
| 142 | </div> |
| 143 | <?php |
| 144 | $output = ob_get_contents(); |
| 145 | ob_end_clean(); |
| 146 | |
| 147 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 148 | } |
| 149 | |
| 150 | return $methods; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Shows the TOTP modal configuration. |
| 155 | * |
| 156 | * @return void |
| 157 | * |
| 158 | * @since 2.6.0 |
| 159 | */ |
| 160 | public static function totp_modal_configure() { |
| 161 | if ( TOTP::is_enabled() ) { |
| 162 | ?> |
| 163 | <div class="wizard-step" id="2fa-wizard-totp"> |
| 164 | <fieldset> |
| 165 | <?php self::totp_configure(); ?> |
| 166 | </fieldset> |
| 167 | </div> |
| 168 | <?php |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Reconfigures the totp form |
| 174 | * |
| 175 | * @since 2.6.0 |
| 176 | * |
| 177 | * @return void |
| 178 | */ |
| 179 | public static function totp_configure() { |
| 180 | |
| 181 | if ( ! TOTP::is_enabled() ) { |
| 182 | return; |
| 183 | } |
| 184 | |
| 185 | // Regenerate the code if the method is not in use. |
| 186 | if ( TOTP::METHOD_NAME !== User_Helper::get_enabled_method_for_user() ) { |
| 187 | TOTP::remove_user_totp_key(); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Active on modal, additional attribute is required on standard HTML (check below) |
| 192 | */ |
| 193 | $add_step_attributes = 'active'; |
| 194 | |
| 195 | /** |
| 196 | * Closing div for extra modal wrappers see lines above |
| 197 | */ |
| 198 | $close_div = ''; |
| 199 | |
| 200 | $qr_code = '<img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" id="wp-2fa-totp-qrcode" />'; |
| 201 | $open30_wrapper = ' |
| 202 | <div class="mb-30 clear-both"> |
| 203 | '; |
| 204 | $open60_wrapper = ' |
| 205 | <div class="modal-60"> |
| 206 | '; |
| 207 | $open40_wrapper = ' |
| 208 | <div class="modal-40"> |
| 209 | '; |
| 210 | $close_div = ' |
| 211 | </div> |
| 212 | '; |
| 213 | |
| 214 | ?> |
| 215 | <div class="step-setting-wrapper <?php echo \esc_attr( $add_step_attributes ); ?>"> |
| 216 | <div class="mb-20"> |
| 217 | <?php echo wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_intro', true ) ); ?> |
| 218 | </div> |
| 219 | <?php echo $open30_wrapper . $open40_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 220 | |
| 221 | <div class="qr-code-wrapper"> |
| 222 | <?php echo $qr_code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 223 | </div> |
| 224 | <?php |
| 225 | echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 226 | echo $open60_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 227 | ?> |
| 228 | |
| 229 | <div class="radio-cells option-pill mb-0"> |
| 230 | <ol class="wizard-custom-counter"> |
| 231 | <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_1', true ) ); ?> |
| 232 | <?php |
| 233 | if ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) { |
| 234 | ?> |
| 235 | <span class="wizard-tooltip" data-tooltip-content="data-totp-setup-tooltip-content-wrapper">i</span> |
| 236 | <?php } ?> |
| 237 | </li> |
| 238 | <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_2', true ) ); ?> |
| 239 | <div class="app-key-wrapper"> |
| 240 | <input type="text" id="app-key-input" readonly value="<?php echo \esc_html( TOTP::get_totp_decrypted() ); ?>" class="app-key"> |
| 241 | <?php |
| 242 | if ( is_ssl() ) { |
| 243 | ?> |
| 244 | <span class="click-to-copy"><?php \esc_html_e( 'COPY', 'wp-2fa' ); ?></span> |
| 245 | <?php } ?> |
| 246 | </div> |
| 247 | </li> |
| 248 | <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_3', true ) ); ?></li> |
| 249 | </ol> |
| 250 | </div> |
| 251 | <?php |
| 252 | echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 253 | echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 254 | ?> |
| 255 | <?php if ( ! empty( WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) ) : ?> |
| 256 | <div class="tooltip-content-wrapper" data-totp-setup-tooltip-content-wrapper> |
| 257 | <p class="description"><?php \esc_html_e( 'Click on the icon of the app that you are using for a detailed guide on how to set it up.', 'wp-2fa' ); ?></p> |
| 258 | <div class="apps-wrapper"> |
| 259 | <?php foreach ( Authentication::get_apps() as $app ) { ?> |
| 260 | <a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=authentication_help#<?php echo $app['hash']; ?>" target="_blank" class="app-logo"><img src="<?php echo \esc_url( WP_2FA_URL . 'dist/images/' . $app['logo'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></a> |
| 261 | <?php } ?> |
| 262 | </div> |
| 263 | </div> |
| 264 | <?php endif; ?> |
| 265 | <div class="wp2fa-setup-actions"> |
| 266 | <button class="button wp-2fa-button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" type="button"><?php \esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></button> |
| 267 | <a class="button button-primary wp-2fa-button-secondary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a> |
| 268 | </div> |
| 269 | </div> |
| 270 | <div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>"> |
| 271 | <div class="mb-20"> |
| 272 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_totp_pre', true ) ); ?> |
| 273 | </div> |
| 274 | <fieldset> |
| 275 | <label for="2fa-totp-authcode"> |
| 276 | <?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?> |
| 277 | <input type="tel" name="wp-2fa-totp-authcode" id="wp-2fa-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/> |
| 278 | <script> |
| 279 | const totp_authcode = document.getElementById('wp-2fa-totp-authcode'); |
| 280 | totp_authcode.addEventListener('input', function() { |
| 281 | this.value = this.value.trim(); |
| 282 | }); |
| 283 | </script> |
| 284 | </label> |
| 285 | <div class="verification-response"></div> |
| 286 | </fieldset> |
| 287 | <input type="hidden" name="wp-2fa-totp-key" value="<?php echo \esc_attr( TOTP::get_totp_decrypted() ); ?>" /> |
| 288 | |
| 289 | <a href="#" class="modal__btn button button-primary wp-2fa-button-primary" data-validate-authcode-ajax <?php echo WP_Helper::create_data_nonce( 'wp-2fa-validate-authcode' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_html_e( 'Validate & Save', 'wp-2fa' ); ?></a> |
| 290 | <button class="modal__btn wp-2fa-button-secondary button button-secondary wp-2fa-button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button> |
| 291 | </div> |
| 292 | |
| 293 | <?php |
| 294 | } |
| 295 | |
| 296 | /** |
| 297 | * Settings page and first time wizard settings render |
| 298 | * |
| 299 | * @param array $methods - Array with all the methods in which we have to add this one. |
| 300 | * @param boolean $setup_wizard - Is that the first time setup wizard. |
| 301 | * @param string $data_role - Additional HTML data attribute. |
| 302 | * @param mixed $role - Name of the role. |
| 303 | * |
| 304 | * @return array - The array with the methods with all the methods wizard steps. |
| 305 | * |
| 306 | * @since 2.6.0 |
| 307 | */ |
| 308 | public static function totp_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) { |
| 309 | $name_prefix = WP_2FA_POLICY_SETTINGS_NAME; |
| 310 | $role_id = ''; |
| 311 | if ( null !== $role && '' !== trim( (string) $role ) ) { |
| 312 | $name_prefix .= "[{$role}]"; |
| 313 | $data_role = 'data-role="' . $role . '"'; |
| 314 | $role_id = '-' . $role; |
| 315 | } |
| 316 | \ob_start(); |
| 317 | ?> |
| 318 | <div id="<?php echo \esc_attr( TOTP::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper"> |
| 319 | <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 320 | <label for="totp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;"> |
| 321 | <input type="checkbox" id="totp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_totp]" value="enable_totp" |
| 322 | <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 323 | |
| 324 | |
| 325 | <?php \checked( TOTP::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, TOTP::POLICY_SETTINGS_NAME ), true ); ?> |
| 326 | |
| 327 | > |
| 328 | <?php \esc_html_e( 'One-time code via 2FA App (TOTP) - ', 'wp-2fa' ); ?><a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=totp_aplications_help" target="_blank" rel=noopener><?php \esc_html_e( 'complete list of supported 2FA apps.', 'wp-2fa' ); ?></a> |
| 329 | </label> |
| 330 | <?php |
| 331 | if ( $setup_wizard ) { |
| 332 | echo '<p class="description">'; |
| 333 | printf( |
| 334 | /* translators: link to the knowledge base website */ |
| 335 | \esc_html__( 'When using this method, users will need to configure a 2FA app to get the one-time login code. The plugin supports all standard 2FA apps. Refer to the %s for more information. Allowing users to set up a secondary 2FA method is highly recommended. You can do this in the next step of the wizard. This will allow users to log in using an alternative method should they, for example lose access to their phone.', 'wp-2fa' ), |
| 336 | '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_how_to_setup_2fa_apps" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>' |
| 337 | ); |
| 338 | echo '</p>'; |
| 339 | } |
| 340 | if ( ! $setup_wizard ) { |
| 341 | echo '<p class="description">'; |
| 342 | printf( |
| 343 | /* translators: link to the knowledge base website */ |
| 344 | \esc_html__( 'Refer to the %s for more information on how to setup these apps and which apps are supported.', 'wp-2fa' ), |
| 345 | '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_how_to_setup_2fa_apps_2" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>' |
| 346 | ); |
| 347 | echo '</p>'; |
| 348 | } |
| 349 | ?> |
| 350 | </div> |
| 351 | <?php |
| 352 | $output = ob_get_contents(); |
| 353 | ob_end_clean(); |
| 354 | |
| 355 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 356 | |
| 357 | return $methods; |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Prints the form that prompts the user to authenticate. |
| 362 | * |
| 363 | * @param \WP_User $user - \WP_User object of the logged-in user. |
| 364 | * |
| 365 | * @since 2.6.0 |
| 366 | */ |
| 367 | public static function totp_authentication_page( $user ) { |
| 368 | require_once ABSPATH . '/wp-admin/includes/template.php'; |
| 369 | ?> |
| 370 | <?php |
| 371 | if ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) { |
| 372 | echo WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 373 | } else { |
| 374 | echo WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 375 | } |
| 376 | ?> |
| 377 | <p> |
| 378 | </br> |
| 379 | <label for="authcode"><?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?></label> |
| 380 | <input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" /> |
| 381 | <script> |
| 382 | const authcode = document.getElementById('authcode'); |
| 383 | authcode.addEventListener('input', function() { |
| 384 | this.value = this.value.trim(); |
| 385 | }); |
| 386 | </script> |
| 387 | </p> |
| 388 | <?php |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 |