diff --git a/wp-content/plugins/two-factor/class-two-factor-core.php b/wp-content/plugins/two-factor/class-two-factor-core.php index f1b3045d..3c0df20a 100644 --- a/wp-content/plugins/two-factor/class-two-factor-core.php +++ b/wp-content/plugins/two-factor/class-two-factor-core.php @@ -99,6 +99,8 @@ class Two_Factor_Core { add_filter( 'wp_login_errors', array( __CLASS__, 'maybe_show_reset_password_notice' ) ); add_action( 'after_password_reset', array( __CLASS__, 'clear_password_reset_notice' ) ); add_action( 'login_form_validate_2fa', array( __CLASS__, 'login_form_validate_2fa' ) ); + add_action( 'login_form_revalidate_2fa', array( __CLASS__, 'login_form_revalidate_2fa' ) ); + add_action( 'show_user_profile', array( __CLASS__, 'user_two_factor_options' ) ); add_action( 'edit_user_profile', array( __CLASS__, 'user_two_factor_options' ) ); add_action( 'personal_options_update', array( __CLASS__, 'user_two_factor_options_update' ) ); @@ -122,6 +124,8 @@ class Two_Factor_Core { // Run as late as possible to prevent other plugins from unintentionally bypassing. add_filter( 'authenticate', array( __CLASS__, 'filter_authenticate_block_cookies' ), PHP_INT_MAX ); + add_filter( 'attach_session_information', array( __CLASS__, 'filter_session_information' ), 10, 2 ); + add_action( 'admin_init', array( __CLASS__, 'trigger_user_settings_action' ) ); add_filter( 'two_factor_providers', array( __CLASS__, 'enable_dummy_method_for_debug' ) ); @@ -179,17 +183,27 @@ class Two_Factor_Core { /** * For each filtered provider, */ - foreach ( $providers as $class => $path ) { - include_once $path; + foreach ( $providers as $provider_key => $path ) { + require_once $path; + + $class = $provider_key; + + /** + * Filters the classname for a provider. The dynamic portion of the filter is the defined providers key. + * + * @param string $class The PHP Classname of the provider. + * @param string $path The provided provider path to be included. + */ + $class = apply_filters( "two_factor_provider_classname_{$provider_key}", $class, $path ); /** * Confirm that it's been successfully included before instantiating. */ if ( class_exists( $class ) ) { try { - $providers[ $class ] = call_user_func( array( $class, 'get_instance' ) ); + $providers[ $provider_key ] = call_user_func( array( $class, 'get_instance' ) ); } catch ( Exception $e ) { - unset( $providers[ $class ] ); + unset( $providers[ $provider_key ] ); } } } @@ -232,17 +246,15 @@ class Two_Factor_Core { * @return string */ protected static function get_user_settings_page_url( $user_id ) { - $page = 'user-edit.php'; - if ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) { - $page = 'profile.php'; + return self_admin_url( 'profile.php' ); } return add_query_arg( array( 'user_id' => intval( $user_id ), ), - self_admin_url( $page ) + self_admin_url( 'user-edit.php' ) ); } @@ -267,6 +279,23 @@ class Two_Factor_Core { ); } + /** + * Get the two-factor revalidate URL. + * + * @param bool $interim If the URL should load the interim login iframe modal. + * @return string + */ + public static function get_user_two_factor_revalidate_url( $interim = false ) { + $args = array( + 'action' => 'revalidate_2fa', + ); + if ( $interim ) { + $args['interim-login'] = 1; + } + + return self::login_url( $args ); + } + /** * Check if a user action is valid. * @@ -411,15 +440,51 @@ class Two_Factor_Core { $enabled_providers = self::get_enabled_providers_for_user( $user ); $configured_providers = array(); - foreach ( $providers as $classname => $provider ) { - if ( in_array( $classname, $enabled_providers, true ) && $provider->is_available_for_user( $user ) ) { - $configured_providers[ $classname ] = $provider; + foreach ( $providers as $provider_key => $provider ) { + if ( in_array( $provider_key, $enabled_providers, true ) && $provider->is_available_for_user( $user ) ) { + $configured_providers[ $provider_key ] = $provider; } } return $configured_providers; } + /** + * Fetch the provider for the request based on the user preferences. + * + * @param int|WP_User $user Optonal. User ID, or WP_User object of the the user. Defaults to current user. + * @param null|string|object $preferred_provider Optional. The name of the provider, the provider, or empty. + * @return null|object The provider + */ + public static function get_provider_for_user( $user = null, $preferred_provider = null ) { + $user = self::fetch_user( $user ); + if ( ! $user ) { + return null; + } + + // If a specific provider instance is passed, process it just as the key. + if ( $preferred_provider && $preferred_provider instanceof Two_Factor_Provider ) { + $preferred_provider = $preferred_provider->get_key(); + } + + // Default to the currently logged in provider. + if ( ! $preferred_provider && get_current_user_id() === $user->ID ) { + $session = self::get_current_user_session(); + if ( ! empty( $session['two-factor-provider'] ) ) { + $preferred_provider = $session['two-factor-provider']; + } + } + + if ( is_string( $preferred_provider ) ) { + $providers = self::get_available_providers_for_user( $user ); + if ( isset( $providers[ $preferred_provider ] ) ) { + return $providers[ $preferred_provider ]; + } + } + + return self::get_primary_provider_for_user( $user ); + } + /** * Gets the Two-Factor Auth provider for the specified|current user. * @@ -705,37 +770,38 @@ class Two_Factor_Core { * @param string $error_msg Optional. Login error message. * @param string|object $provider An override to the provider. */ - public static function login_html( $user, $login_nonce, $redirect_to, $error_msg = '', $provider = null ) { - if ( empty( $provider ) ) { - $provider = self::get_primary_provider_for_user( $user->ID ); - } elseif ( is_string( $provider ) && method_exists( $provider, 'get_instance' ) ) { - $provider = call_user_func( array( $provider, 'get_instance' ) ); + public static function login_html( $user, $login_nonce, $redirect_to, $error_msg = '', $provider = null, $action = 'validate_2fa' ) { + $provider = self::get_provider_for_user( $user, $provider ); + if ( ! $provider ) { + wp_die( __( 'Cheatin’ uh?', 'two-factor' ) ); } - $provider_class = get_class( $provider ); - + $provider_key = $provider->get_key(); $available_providers = self::get_available_providers_for_user( $user ); - $backup_providers = array_diff_key( $available_providers, array( $provider_class => null ) ); + $backup_providers = array_diff_key( $available_providers, array( $provider_key => null ) ); $interim_login = isset( $_REQUEST['interim-login'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $rememberme = intval( self::rememberme() ); if ( ! function_exists( 'login_header' ) ) { // We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file. - include_once TWO_FACTOR_DIR . 'includes/function.login-header.php'; + require_once TWO_FACTOR_DIR . 'includes/function.login-header.php'; } + // Disable the language switcher. + add_filter( 'login_display_language_dropdown', '__return_false' ); + login_header(); if ( ! empty( $error_msg ) ) { echo '
' . esc_html( $error_msg ) . '
'; - } else { + } elseif ( 'validate_2fa' === $action ) { self::maybe_show_last_login_failure_notice( $user ); } ?> -
- + + @@ -748,87 +814,60 @@ class Two_Factor_Core { authentication_page( $user ); ?>
- 'validate_2fa', - 'provider' => $backup_classname, - 'wp-auth-id' => $user->ID, - 'wp-auth-nonce' => $login_nonce, - 'redirect_to' => $redirect_to, - 'rememberme' => $rememberme, - ) + $action, + 'wp-auth-id' => $user->ID, + 'wp-auth-nonce' => $login_nonce, ); + if ( $rememberme ) { + $backup_link_args['rememberme'] = $rememberme; + } + if ( $redirect_to ) { + $backup_link_args['redirect_to'] = $redirect_to; + } + if ( $interim_login ) { + $backup_link_args['interim-login'] = 1; + } ?>
-

- - get_label() - ) - ); - ?> - +

+

-
- -
-

- - - -

-
+