updated plugin Two Factor version 0.13.0

This commit is contained in:
KawaiiPunk 2025-04-29 21:20:04 +00:00 committed by Gitium
parent c950632407
commit a212704ec2
16 changed files with 547 additions and 231 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -50,6 +50,6 @@ class Two_Factor_Compat {
* @return boolean * @return boolean
*/ */
public function jetpack_is_sso_active() { public function jetpack_is_sso_active() {
return ( method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'sso' ) ); return ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'sso' ) );
} }
} }

View File

@ -57,7 +57,7 @@ class Two_Factor_Core {
const USER_PASSWORD_WAS_RESET_KEY = '_two_factor_password_was_reset'; const USER_PASSWORD_WAS_RESET_KEY = '_two_factor_password_was_reset';
/** /**
* URL query paramater used for our custom actions. * URL query parameter used for our custom actions.
* *
* @var string * @var string
*/ */
@ -93,8 +93,7 @@ class Two_Factor_Core {
* @since 0.1-dev * @since 0.1-dev
*/ */
public static function add_hooks( $compat ) { public static function add_hooks( $compat ) {
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) ); add_action( 'init', array( __CLASS__, 'get_providers' ) ); // @phpstan-ignore return.void
add_action( 'init', array( __CLASS__, 'get_providers' ) );
add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 ); add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );
add_filter( 'wp_login_errors', array( __CLASS__, 'maybe_show_reset_password_notice' ) ); 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( 'after_password_reset', array( __CLASS__, 'clear_password_reset_notice' ) );
@ -133,29 +132,135 @@ class Two_Factor_Core {
} }
/** /**
* Loads the plugin's text domain. * Delete all plugin data on uninstall.
* *
* Sites on WordPress 4.6+ benefit from just-in-time loading of translations. * @return void
*/ */
public static function load_textdomain() { public static function uninstall() {
load_plugin_textdomain( 'two-factor' ); // Keep this updated as user meta keys are added or removed.
$user_meta_keys = array(
self::PROVIDER_USER_META_KEY,
self::ENABLED_PROVIDERS_USER_META_KEY,
self::USER_META_NONCE_KEY,
self::USER_RATE_LIMIT_KEY,
self::USER_FAILED_LOGIN_ATTEMPTS_KEY,
self::USER_PASSWORD_WAS_RESET_KEY,
);
$option_keys = array();
$providers = self::get_default_providers();
/** This filter is documented in the get_providers() method */
$additional_providers = apply_filters( 'two_factor_providers', $providers );
// Merge them with the default providers.
if ( ! empty( $additional_providers ) ) {
$providers = array_merge( $providers, $additional_providers );
}
foreach ( self::get_providers_classes( $providers ) as $provider_class ) {
// Merge with provider-specific user meta keys.
if ( method_exists( $provider_class, 'uninstall_user_meta_keys' ) ) {
try {
$user_meta_keys = array_merge(
$user_meta_keys,
call_user_func( array( $provider_class, 'uninstall_user_meta_keys' ) )
);
} catch ( Exception $e ) {
// Do nothing.
}
}
// Merge with provider-specific option keys.
if ( method_exists( $provider_class, 'uninstall_options' ) ) {
try {
$option_keys = array_merge(
$option_keys,
call_user_func( array( $provider_class, 'uninstall_options' ) )
);
} catch ( Exception $e ) {
// Do nothing.
}
}
}
// Delete options first since that is faster.
if ( ! empty( $option_keys ) ) {
foreach ( $option_keys as $option_key ) {
delete_option( $option_key );
}
}
foreach ( $user_meta_keys as $meta_key ) {
delete_metadata( 'user', null, $meta_key, null, true );
}
} }
/** /**
* For each provider, include it and then instantiate it. * Get the registered providers of which some might not be enabled.
* *
* @since 0.1-dev * @return array List of provider keys and paths to class files.
*
* @return array
*/ */
public static function get_providers() { private static function get_default_providers() {
$providers = array( return array(
'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class-two-factor-email.php', 'Two_Factor_Email' => TWO_FACTOR_DIR . 'providers/class-two-factor-email.php',
'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class-two-factor-totp.php', 'Two_Factor_Totp' => TWO_FACTOR_DIR . 'providers/class-two-factor-totp.php',
'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f.php', 'Two_Factor_FIDO_U2F' => TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f.php',
'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class-two-factor-backup-codes.php', 'Two_Factor_Backup_Codes' => TWO_FACTOR_DIR . 'providers/class-two-factor-backup-codes.php',
'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class-two-factor-dummy.php', 'Two_Factor_Dummy' => TWO_FACTOR_DIR . 'providers/class-two-factor-dummy.php',
); );
}
/**
* Get the classnames for specific providers.
*
* @param array $providers List of paths to provider class files indexed by class names.
*
* @return array List of provider keys and classnames.
*/
private static function get_providers_classes( $providers ) {
foreach ( $providers as $provider_key => $path ) {
if ( ! empty( $path ) && is_readable( $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.
*/
if ( class_exists( $class ) ) {
$providers[ $provider_key ] = $class;
} else {
unset( $providers[ $provider_key ] );
}
}
return $providers;
}
/**
* Get all registered two-factor providers with keys as the original
* provider class names and the values as the provider class instances.
*
* @see Two_Factor_Core::get_enabled_providers_for_user()
* @see Two_Factor_Core::get_supported_providers_for_user()
*
* @since 0.1-dev
*
* @return array
*/
public static function get_providers() {
$providers = self::get_default_providers();
/** /**
* Filter the supplied providers. * Filter the supplied providers.
@ -180,37 +285,43 @@ class Two_Factor_Core {
); );
} }
/** // Map provider keys to classes so that we can instantiate them.
* For each filtered provider, $providers = self::get_providers_classes( $providers );
*/
foreach ( $providers as $provider_key => $path ) {
require_once $path;
$class = $provider_key; // TODO: Refactor this to avoid instantiating the provider instances every time this method is called.
foreach ( $providers as $provider_key => $provider_class ) {
/** try {
* Filters the classname for a provider. The dynamic portion of the filter is the defined providers key. $providers[ $provider_key ] = call_user_func( array( $provider_class, 'get_instance' ) );
* } catch ( Exception $e ) {
* @param string $class The PHP Classname of the provider. unset( $providers[ $provider_key ] );
* @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[ $provider_key ] = call_user_func( array( $class, 'get_instance' ) );
} catch ( Exception $e ) {
unset( $providers[ $provider_key ] );
}
} }
} }
return $providers; return $providers;
} }
/**
* Get providers available for user which may not be enabled or configured.
*
* @see Two_Factor_Core::get_enabled_providers_for_user()
* @see Two_Factor_Core::get_available_providers_for_user()
*
* @param WP_User|int|null $user User ID.
* @return array List of provider instances indexed by provider key.
*/
public static function get_supported_providers_for_user( $user = null ) {
$user = self::fetch_user( $user );
$providers = self::get_providers();
/**
* List of providers available to user which may not be enabled or configured.
*
* @param array $providers List of available provider instances indexed by provider key.
* @param int|WP_User $user User ID.
*/
return apply_filters( 'two_factor_providers_for_user', $providers, $user );
}
/** /**
* Enable the dummy method only during debugging. * Enable the dummy method only during debugging.
* *
@ -397,9 +508,13 @@ class Two_Factor_Core {
} }
/** /**
* Get all Two-Factor Auth providers that are enabled for the specified|current user. * Get two-factor providers that are enabled for the specified (or current) user
* but might not be configured, yet.
* *
* @param int|WP_User $user Optonal. User ID, or WP_User object of the the user. Defaults to current user. * @see Two_Factor_Core::get_supported_providers_for_user()
* @see Two_Factor_Core::get_available_providers_for_user()
*
* @param int|WP_User $user Optional. User ID, or WP_User object of the the user. Defaults to current user.
* @return array * @return array
*/ */
public static function get_enabled_providers_for_user( $user = null ) { public static function get_enabled_providers_for_user( $user = null ) {
@ -408,7 +523,7 @@ class Two_Factor_Core {
return array(); return array();
} }
$providers = self::get_providers(); $providers = self::get_supported_providers_for_user( $user );
$enabled_providers = get_user_meta( $user->ID, self::ENABLED_PROVIDERS_USER_META_KEY, true ); $enabled_providers = get_user_meta( $user->ID, self::ENABLED_PROVIDERS_USER_META_KEY, true );
if ( empty( $enabled_providers ) ) { if ( empty( $enabled_providers ) ) {
$enabled_providers = array(); $enabled_providers = array();
@ -425,10 +540,14 @@ class Two_Factor_Core {
} }
/** /**
* Get all Two-Factor Auth providers that are both enabled and configured for the specified|current user. * Get all two-factor providers that are both enabled and configured
* for the specified (or current) user.
* *
* @param int|WP_User $user Optonal. User ID, or WP_User object of the the user. Defaults to current user. * @see Two_Factor_Core::get_supported_providers_for_user()
* @return array * @see Two_Factor_Core::get_enabled_providers_for_user()
*
* @param int|WP_User $user Optional. User ID, or WP_User object of the the user. Defaults to current user.
* @return array List of provider instances.
*/ */
public static function get_available_providers_for_user( $user = null ) { public static function get_available_providers_for_user( $user = null ) {
$user = self::fetch_user( $user ); $user = self::fetch_user( $user );
@ -436,8 +555,8 @@ class Two_Factor_Core {
return array(); return array();
} }
$providers = self::get_providers(); $providers = self::get_supported_providers_for_user( $user ); // Returns full objects.
$enabled_providers = self::get_enabled_providers_for_user( $user ); $enabled_providers = self::get_enabled_providers_for_user( $user ); // Returns just the keys.
$configured_providers = array(); $configured_providers = array();
foreach ( $providers as $provider_key => $provider ) { foreach ( $providers as $provider_key => $provider ) {
@ -452,7 +571,7 @@ class Two_Factor_Core {
/** /**
* Fetch the provider for the request based on the user preferences. * 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 int|WP_User $user Optional. 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. * @param null|string|object $preferred_provider Optional. The name of the provider, the provider, or empty.
* @return null|object The provider * @return null|object The provider
*/ */
@ -470,7 +589,7 @@ class Two_Factor_Core {
// Default to the currently logged in provider. // Default to the currently logged in provider.
if ( ! $preferred_provider && get_current_user_id() === $user->ID ) { if ( ! $preferred_provider && get_current_user_id() === $user->ID ) {
$session = self::get_current_user_session(); $session = self::get_current_user_session();
if ( ! empty( $session['two-factor-provider'] ) ) { if ( ! empty( $session['two-factor-provider'] ) ) {
$preferred_provider = $session['two-factor-provider']; $preferred_provider = $session['two-factor-provider'];
} }
} }
@ -485,12 +604,31 @@ class Two_Factor_Core {
return self::get_primary_provider_for_user( $user ); return self::get_primary_provider_for_user( $user );
} }
/**
* Get the name of the primary provider selected by the user
* and enabled for the user.
*
* @param WP_User|int $user User ID or instance.
*
* @return string|null
*/
private static function get_primary_provider_key_selected_for_user( $user ) {
$primary_provider = get_user_meta( $user->ID, self::PROVIDER_USER_META_KEY, true );
$available_providers = self::get_available_providers_for_user( $user );
if ( ! empty( $primary_provider ) && ! empty( $available_providers[ $primary_provider ] ) ) {
return $primary_provider;
}
return null;
}
/** /**
* Gets the Two-Factor Auth provider for the specified|current user. * Gets the Two-Factor Auth provider for the specified|current user.
* *
* @since 0.1-dev * @since 0.1-dev
* *
* @param int|WP_User $user Optonal. User ID, or WP_User object of the the user. Defaults to current user. * @param int|WP_User $user Optional. User ID, or WP_User object of the the user. Defaults to current user.
* @return object|null * @return object|null
*/ */
public static function get_primary_provider_for_user( $user = null ) { public static function get_primary_provider_for_user( $user = null ) {
@ -499,7 +637,7 @@ class Two_Factor_Core {
return null; return null;
} }
$providers = self::get_providers(); $providers = self::get_supported_providers_for_user( $user );
$available_providers = self::get_available_providers_for_user( $user ); $available_providers = self::get_available_providers_for_user( $user );
// If there's only one available provider, force that to be the primary. // If there's only one available provider, force that to be the primary.
@ -508,7 +646,7 @@ class Two_Factor_Core {
} elseif ( 1 === count( $available_providers ) ) { } elseif ( 1 === count( $available_providers ) ) {
$provider = key( $available_providers ); $provider = key( $available_providers );
} else { } else {
$provider = get_user_meta( $user->ID, self::PROVIDER_USER_META_KEY, true ); $provider = self::get_primary_provider_key_selected_for_user( $user );
// If the provider specified isn't enabled, just grab the first one that is. // If the provider specified isn't enabled, just grab the first one that is.
if ( ! isset( $available_providers[ $provider ] ) ) { if ( ! isset( $available_providers[ $provider ] ) ) {
@ -536,7 +674,7 @@ class Two_Factor_Core {
* *
* @since 0.1-dev * @since 0.1-dev
* *
* @param int|WP_User $user Optonal. User ID, or WP_User object of the the user. Defaults to current user. * @param int|WP_User $user Optional. User ID, or WP_User object of the the user. Defaults to current user.
* @return bool * @return bool
*/ */
public static function is_user_using_two_factor( $user = null ) { public static function is_user_using_two_factor( $user = null ) {
@ -693,8 +831,8 @@ class Two_Factor_Core {
echo '<div id="login_notice" class="message"><strong>'; echo '<div id="login_notice" class="message"><strong>';
printf( printf(
_n( _n(
'WARNING: Your account has attempted to login without providing a valid two factor token. The last failed login occured %2$s ago. If this wasn\'t you, you should reset your password.', 'WARNING: Your account has attempted to login without providing a valid two factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
'WARNING: Your account has attempted to login %1$s times without providing a valid two factor token. The last failed login occured %2$s ago. If this wasn\'t you, you should reset your password.', 'WARNING: Your account has attempted to login %1$s times without providing a valid two factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
$failed_login_count, $failed_login_count,
'two-factor' 'two-factor'
), ),
@ -1171,6 +1309,7 @@ class Two_Factor_Core {
* Return a falsey value (false, 0) if you wish to never require revalidation. * Return a falsey value (false, 0) if you wish to never require revalidation.
* *
* @param int $two_factor_revalidate_time The grace time between last validation time and when it'll be accepted. Default 10 minutes (in seconds). * @param int $two_factor_revalidate_time The grace time between last validation time and when it'll be accepted. Default 10 minutes (in seconds).
* @param int $user_id The user ID.
* @param string $context The context in use, 'display' or 'save'. Save has twice the grace time. * @param string $context The context in use, 'display' or 'save'. Save has twice the grace time.
*/ */
$two_factor_revalidate_time = apply_filters( 'two_factor_revalidate_time', 10 * MINUTE_IN_SECONDS, $user_id, $context ); $two_factor_revalidate_time = apply_filters( 'two_factor_revalidate_time', 10 * MINUTE_IN_SECONDS, $user_id, $context );
@ -1409,10 +1548,14 @@ class Two_Factor_Core {
} }
// Update the session metadata with the revalidation details. // Update the session metadata with the revalidation details.
self::update_current_user_session( array( self::update_current_user_session(
'two-factor-provider' => $provider->get_key(), array(
'two-factor-login' => time(), 'two-factor-provider' => $provider->get_key(),
) ); 'two-factor-login' => time(),
)
);
do_action( 'two_factor_user_revalidated', $user, $provider );
// Must be global because that's how login_header() uses it. // Must be global because that's how login_header() uses it.
global $interim_login; global $interim_login;
@ -1444,7 +1587,7 @@ class Two_Factor_Core {
* @param object $provider The Two Factor Provider. * @param object $provider The Two Factor Provider.
* @param WP_User $user The user being authenticated. * @param WP_User $user The user being authenticated.
* @param bool $is_post_request Whether the request is a POST request. * @param bool $is_post_request Whether the request is a POST request.
* @return false|WP_Error|true WP_Error when an error occurs, true when the user is authenticated, false if no action occured. * @return false|WP_Error|true WP_Error when an error occurs, true when the user is authenticated, false if no action occurred.
*/ */
public static function process_provider( $provider, $user, $is_post_request ) { public static function process_provider( $provider, $user, $is_post_request ) {
if ( ! $provider ) { if ( ! $provider ) {
@ -1480,7 +1623,7 @@ class Two_Factor_Core {
// Ask the provider to verify the second factor. // Ask the provider to verify the second factor.
if ( true !== $provider->validate_authentication( $user ) ) { if ( true !== $provider->validate_authentication( $user ) ) {
// Store the last time a failed login occured. // Store the last time a failed login occurred.
update_user_meta( $user->ID, self::USER_RATE_LIMIT_KEY, time() ); update_user_meta( $user->ID, self::USER_RATE_LIMIT_KEY, time() );
// Store the number of failed login attempts. // Store the number of failed login attempts.
@ -1646,7 +1789,7 @@ class Two_Factor_Core {
) )
); );
login_header( __( 'Password Reset', 'two-factor' ), '', $error ); login_header( __( 'Password Reset', 'two-factor' ), '', $error );
login_footer(); login_footer();
} }
@ -1694,95 +1837,130 @@ class Two_Factor_Core {
* @param WP_User $user WP_User object of the logged-in user. * @param WP_User $user WP_User object of the logged-in user.
*/ */
public static function user_two_factor_options( $user ) { public static function user_two_factor_options( $user ) {
$notices = [];
$providers = self::get_supported_providers_for_user( $user );
wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ), array(), TWO_FACTOR_VERSION ); wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ), array(), TWO_FACTOR_VERSION );
$enabled_providers = array_keys( self::get_available_providers_for_user( $user ) ); $enabled_providers = array_keys( self::get_available_providers_for_user( $user ) );
$primary_provider = self::get_primary_provider_for_user( $user->ID );
if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) {
$primary_provider_key = $primary_provider->get_key();
} else {
$primary_provider_key = null;
}
// This is specific to the current session, not the displayed user. // This is specific to the current session, not the displayed user.
$show_2fa_options = self::current_user_can_update_two_factor_options(); $show_2fa_options = self::current_user_can_update_two_factor_options();
if ( ! $show_2fa_options ) { if ( ! $show_2fa_options ) {
$url = self::get_user_two_factor_revalidate_url(); $url = add_query_arg(
$url = add_query_arg( 'redirect_to', urlencode( self::get_user_settings_page_url( $user->ID ) . '#two-factor-options' ), $url ); 'redirect_to',
urlencode( self::get_user_settings_page_url( $user->ID ) . '#two-factor-options' ),
self::get_user_two_factor_revalidate_url()
);
printf( $notices['warning two-factor-warning-revalidate-session'] = sprintf(
'<div class="notice notice-warning inline"><p>%s</p></div>', esc_html__( 'To update your Two-Factor options, you must first revalidate your session.', 'two-factor' ) .
sprintf( ' <a class="button" href="%s">' . esc_html__( 'Revalidate now', 'two-factor' ) . '</a>',
__( 'To update your Two-Factor options, you must first revalidate your session.', 'two-factor' ) . esc_url( $url )
'<br><a class="button" href="%s">' . __( 'Revalidate now', 'two-factor' ) . '</a>',
esc_url( $url )
)
); );
} }
printf( if ( empty( $providers ) ) {
'<fieldset id="two-factor-options" %s>', $notices['notice two-factor-notice-no-providers-supported'] = esc_html__( 'No providers are available for your account.', 'two-factor' );
$show_2fa_options ? '' : 'disabled="disabled"' }
);
wp_nonce_field( 'user_two_factor_options', '_nonce_user_two_factor_options', false ); // Suggest enabling a backup method if only one method is enabled and there are more available.
if ( count( $providers ) > 1 && 1 === count( $enabled_providers ) ) {
$notices['warning two-factor-warning-suggest-backup'] = esc_html__( 'To prevent being locked out of your account, consider enabling a backup method like Recovery Codes in case you lose access to your primary authentication method.', 'two-factor' );
}
?> ?>
<input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php /* Dummy input so $_POST value is passed when no providers are enabled. */ ?>" /> <h2><?php esc_html_e( 'Two-Factor Options', 'two-factor' ); ?></h2>
<table class="form-table">
<tr>
<th>
<?php esc_html_e( 'Two-Factor Options', 'two-factor' ); ?>
</th>
<td>
<table class="two-factor-methods-table">
<thead>
<tr>
<th class="col-enabled" scope="col"><?php esc_html_e( 'Enabled', 'two-factor' ); ?></th>
<th class="col-primary" scope="col"><?php esc_html_e( 'Primary', 'two-factor' ); ?></th>
<th class="col-name" scope="col"><?php esc_html_e( 'Type', 'two-factor' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( self::get_providers() as $provider_key => $object ) : ?>
<tr>
<th scope="row"><input id="enabled-<?php echo esc_attr( $provider_key ); ?>" type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $provider_key ); ?>" <?php checked( in_array( $provider_key, $enabled_providers, true ) ); ?> /></th>
<th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $provider_key ); ?>" <?php checked( $provider_key, $primary_provider_key ); ?> /></th>
<td>
<label class="two-factor-method-label" for="enabled-<?php echo esc_attr( $provider_key ); ?>"><?php echo esc_html( $object->get_label() ); ?></label>
<?php
/**
* Fires after user options are shown.
*
* Use the {@see 'two_factor_user_options_' . $provider_key } hook instead.
*
* @deprecated 0.7.0
*
* @param WP_User $user The user.
*/
do_action_deprecated( 'two-factor-user-options-' . $provider_key, array( $user ), '0.7.0', 'two_factor_user_options_' . $provider_key );
do_action( 'two_factor_user_options_' . $provider_key, $user );
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</td>
</tr>
</table>
</fieldset>
<?php
<?php foreach ( $notices as $notice_type => $notice ) : ?>
<div class="<?php echo esc_attr( $notice_type ? 'notice inline notice-' . $notice_type : '' ); ?>">
<p><?php echo wp_kses_post( $notice ); ?></p>
</div>
<?php endforeach; ?>
<fieldset id="two-factor-options" <?php echo $show_2fa_options ? '' : 'disabled="disabled"'; ?>>
<?php
if ( $providers ) {
self::render_user_providers_form( $user, $providers );
}
?>
</fieldset>
<?php
/** /**
* Fires after the Two Factor methods table. * Fires after the Two Factor methods table.
* *
* To be used by Two Factor methods to add settings UI. * To be used by Two Factor methods to add settings UI.
* *
* @param WP_User $user The user.
* @param array $providers List of providers available to the user.
*
* @since 0.1-dev * @since 0.1-dev
*/ */
do_action( 'show_user_security_settings', $user ); do_action( 'show_user_security_settings', $user, $providers );
}
private static function render_user_providers_form( $user, $providers ) {
$primary_provider_key = self::get_primary_provider_key_selected_for_user( $user );
$enabled_providers = self::get_enabled_providers_for_user( $user );
?>
<p>
<?php esc_html_e( 'Configure a primary two-factor method along with a backup method, such as Recovery Codes, to avoid being locked out if you lose access to your primary method.', 'two-factor' ); ?>
</p>
<?php wp_nonce_field( 'user_two_factor_options', '_nonce_user_two_factor_options', false ); ?>
<input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php /* Dummy input so $_POST value is passed when no providers are enabled. */ ?>" />
<table class="form-table two-factor-methods-table" role="presentation">
<tbody>
<?php foreach ( $providers as $provider_key => $object ) : ?>
<tr>
<th><?php echo esc_html( $object->get_label() ); ?></th>
<td>
<label class="two-factor-method-label">
<input id="enabled-<?php echo esc_attr( $provider_key ); ?>" type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $provider_key ); ?>" <?php checked( in_array( $provider_key, $enabled_providers, true ) ); ?> />
<?php echo esc_html( sprintf( __( 'Enable %s', 'two-factor' ), $object->get_label() ) ); ?>
</label>
<?php
/**
* Fires after user options are shown.
*
* Use the {@see 'two_factor_user_options_' . $provider_key } hook instead.
*
* @deprecated 0.7.0
*
* @param WP_User $user The user.
*/
do_action_deprecated( 'two-factor-user-options-' . $provider_key, array( $user ), '0.7.0', 'two_factor_user_options_' . $provider_key );
do_action( 'two_factor_user_options_' . $provider_key, $user );
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<hr />
<table class="form-table two-factor-primary-method-table" role="presentation">
<tbody>
<tr>
<th><?php esc_html_e( 'Primary Method', 'two-factor' ); ?></th>
<td>
<select name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>">
<option value=""><?php echo esc_html( __( 'Default', 'two-factor' ) ); ?></option>
<?php foreach ( $providers as $provider_key => $object ) : ?>
<option value="<?php echo esc_attr( $provider_key ); ?>" <?php selected( $provider_key, $primary_provider_key ); ?> <?php disabled( ! in_array( $provider_key, $enabled_providers, true ) ); ?>>
<?php echo esc_html( $object->get_label() ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Select the primary method to use for two-factor authentication when signing into this site.', 'two-factor' ); ?></p>
</td>
</tr>
</tbody>
</table>
<?php
} }
/** /**
@ -1796,30 +1974,21 @@ class Two_Factor_Core {
* @return bool True if the provider was enabled, false otherwise. * @return bool True if the provider was enabled, false otherwise.
*/ */
public static function enable_provider_for_user( $user_id, $new_provider ) { public static function enable_provider_for_user( $user_id, $new_provider ) {
$available_providers = self::get_providers(); // Ensure the provider is even available.
if ( ! array_key_exists( $new_provider, self::get_supported_providers_for_user( $user_id ) ) ) {
if ( ! array_key_exists( $new_provider, $available_providers ) ) {
return false; return false;
} }
$user = get_userdata( $user_id ); $enabled_providers = self::get_enabled_providers_for_user( $user_id );
$enabled_providers = self::get_enabled_providers_for_user( $user );
// Check if this is enabled already.
if ( in_array( $new_provider, $enabled_providers ) ) { if ( in_array( $new_provider, $enabled_providers ) ) {
return true; return true;
} }
$enabled_providers[] = $new_provider; $enabled_providers[] = $new_provider;
$enabled = update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $enabled_providers );
// Primary provider must be enabled. return (bool) update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $enabled_providers );
$has_primary = is_object( self::get_primary_provider_for_user( $user_id ) );
if ( ! $has_primary ) {
$has_primary = update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider );
}
return $enabled && $has_primary;
} }
/** /**
@ -1836,23 +2005,27 @@ class Two_Factor_Core {
* @return bool True if the provider was disabled, false otherwise. * @return bool True if the provider was disabled, false otherwise.
*/ */
public static function disable_provider_for_user( $user_id, $provider_to_delete ) { public static function disable_provider_for_user( $user_id, $provider_to_delete ) {
$is_registered = array_key_exists( $provider_to_delete, self::get_providers() ); // Check if the provider is even enabled.
if ( ! array_key_exists( $provider_to_delete, self::get_supported_providers_for_user( $user_id ) ) ) {
if ( ! $is_registered ) {
return false; return false;
} }
$old_enabled_providers = self::get_enabled_providers_for_user( $user_id ); $enabled_providers = self::get_enabled_providers_for_user( $user_id );
$is_enabled = in_array( $provider_to_delete, $old_enabled_providers );
if ( ! $is_enabled ) { // Check if this is disabled already.
if ( ! in_array( $provider_to_delete, $enabled_providers ) ) {
return true; return true;
} }
$new_enabled_providers = array_diff( $old_enabled_providers, array( $provider_to_delete ) ); $enabled_providers = array_diff( $enabled_providers, array( $provider_to_delete ) );
$was_disabled = update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $new_enabled_providers );
return (bool) $was_disabled; // Remove this from being a primary provider, if set.
$primary_provider = self::get_primary_provider_for_user( $user_id );
if ( $primary_provider && $primary_provider->get_key() === $provider_to_delete ) {
delete_user_meta( $user_id, self::PROVIDER_USER_META_KEY );
}
return (bool) update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $enabled_providers );
} }
/** /**
@ -1877,7 +2050,7 @@ class Two_Factor_Core {
return; return;
} }
$providers = self::get_providers(); $providers = self::get_supported_providers_for_user( $user_id );
$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ]; $enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
$existing_providers = self::get_enabled_providers_for_user( $user_id ); $existing_providers = self::get_enabled_providers_for_user( $user_id );
@ -1889,6 +2062,8 @@ class Two_Factor_Core {
$new_provider = isset( $_POST[ self::PROVIDER_USER_META_KEY ] ) ? $_POST[ self::PROVIDER_USER_META_KEY ] : ''; $new_provider = isset( $_POST[ self::PROVIDER_USER_META_KEY ] ) ? $_POST[ self::PROVIDER_USER_META_KEY ] : '';
if ( ! empty( $new_provider ) && in_array( $new_provider, $enabled_providers, true ) ) { if ( ! empty( $new_provider ) && in_array( $new_provider, $enabled_providers, true ) ) {
update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider ); update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider );
} else {
delete_user_meta( $user_id, self::PROVIDER_USER_META_KEY );
} }
// Have we changed the two-factor settings for the current user? Alter their session metadata. // Have we changed the two-factor settings for the current user? Alter their session metadata.

View File

@ -501,7 +501,7 @@ class Error extends \Exception
* @param int $code * @param int $code
* @param \Exception|null $previous * @param \Exception|null $previous
*/ */
public function __construct($message, $code, \Exception $previous = null) { public function __construct($message, $code, ?\Exception $previous = null) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
} }

View File

@ -40,7 +40,7 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
add_action( 'admin_notices', array( $this, 'admin_notices' ) ); add_action( 'admin_notices', array( $this, 'admin_notices' ) );
return parent::__construct(); parent::__construct();
} }
/** /**
@ -214,6 +214,25 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
<?php <?php
} }
/**
* Get the backup code length for a user.
*
* @param WP_User $user User object.
*
* @return int Number of characters.
*/
private function get_backup_code_length( $user ) {
/**
* Customize the character count of the backup codes.
*
* @var int $code_length Length of the backup code.
* @var WP_User $user User object.
*/
$code_length = (int) apply_filters( 'two_factor_backup_code_length', 8, $user );
return $code_length;
}
/** /**
* Generates backup codes & updates the user meta. * Generates backup codes & updates the user meta.
* *
@ -239,8 +258,10 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
$codes_hashed = (array) get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true ); $codes_hashed = (array) get_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, true );
} }
$code_length = $this->get_backup_code_length( $user );
for ( $i = 0; $i < $num_codes; $i++ ) { for ( $i = 0; $i < $num_codes; $i++ ) {
$code = $this->get_code(); $code = $this->get_code( $code_length );
$codes_hashed[] = wp_hash_password( $code ); $codes_hashed[] = wp_hash_password( $code );
$codes[] = $code; $codes[] = $code;
unset( $code ); unset( $code );
@ -326,11 +347,15 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
*/ */
public function authentication_page( $user ) { public function authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php'; require_once ABSPATH . '/wp-admin/includes/template.php';
$code_length = $this->get_backup_code_length( $user );
$code_placeholder = str_repeat( 'X', $code_length );
?> ?>
<p class="two-factor-prompt"><?php esc_html_e( 'Enter a recovery code.', 'two-factor' ); ?></p><br/> <p class="two-factor-prompt"><?php esc_html_e( 'Enter a recovery code.', 'two-factor' ); ?></p>
<p> <p>
<label for="authcode"><?php esc_html_e( 'Recovery Code:', 'two-factor' ); ?></label> <label for="authcode"><?php esc_html_e( 'Recovery Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="1234 5678" data-digits="8" /> <input type="text" inputmode="numeric" name="two-factor-backup-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $code_placeholder ); ?>" data-digits="<?php echo esc_attr( $code_length ); ?>" />
</p> </p>
<?php <?php
submit_button( __( 'Submit', 'two-factor' ) ); submit_button( __( 'Submit', 'two-factor' ) );
@ -399,4 +424,15 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
// Update the backup code master list. // Update the backup code master list.
update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes ); update_user_meta( $user->ID, self::BACKUP_CODES_META_KEY, $backup_codes );
} }
/**
* Return user meta keys to delete during plugin uninstall.
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array(
self::BACKUP_CODES_META_KEY,
);
}
} }

View File

@ -21,7 +21,7 @@ class Two_Factor_Dummy extends Two_Factor_Provider {
*/ */
protected function __construct() { protected function __construct() {
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
return parent::__construct(); parent::__construct();
} }
/** /**

View File

@ -42,7 +42,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
*/ */
protected function __construct() { protected function __construct() {
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
return parent::__construct(); parent::__construct();
} }
/** /**
@ -63,6 +63,22 @@ class Two_Factor_Email extends Two_Factor_Provider {
return __( 'Send a code to your email', 'two-factor' ); return __( 'Send a code to your email', 'two-factor' );
} }
/**
* Get the email token length.
*
* @return int Email token string length.
*/
private function get_token_length() {
/**
* Number of characters in the email token.
*
* @param int $token_length Number of characters in the email token.
*/
$token_length = (int) apply_filters( 'two_factor_email_token_length', 8 );
return $token_length;
}
/** /**
* Generate the user token. * Generate the user token.
* *
@ -72,7 +88,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
* @return string * @return string
*/ */
public function generate_token( $user_id ) { public function generate_token( $user_id ) {
$token = $this->get_code(); $token = $this->get_code( $this->get_token_length() );
update_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, time() ); update_user_meta( $user_id, self::TOKEN_META_KEY_TIMESTAMP, time() );
update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) ); update_user_meta( $user_id, self::TOKEN_META_KEY, wp_hash( $token ) );
@ -146,10 +162,21 @@ class Two_Factor_Email extends Two_Factor_Provider {
* Number of seconds the token is considered valid * Number of seconds the token is considered valid
* after the generation. * after the generation.
* *
* @deprecated 0.11.0 Use {@see 'two_factor_email_token_ttl'} instead.
*
* @param integer $token_ttl Token time-to-live in seconds. * @param integer $token_ttl Token time-to-live in seconds.
* @param integer $user_id User ID. * @param integer $user_id User ID.
*/ */
return (int) apply_filters( 'two_factor_token_ttl', $token_ttl, $user_id ); $token_ttl = (int) apply_filters_deprecated( 'two_factor_token_ttl', array( $token_ttl, $user_id ), '0.11.0', 'two_factor_email_token_ttl' );
/**
* Number of seconds the token is considered valid
* after the generation.
*
* @param integer $token_ttl Token time-to-live in seconds.
* @param integer $user_id User ID.
*/
return (int) apply_filters( 'two_factor_email_token_ttl', $token_ttl, $user_id );
} }
/** /**
@ -259,12 +286,15 @@ class Two_Factor_Email extends Two_Factor_Provider {
$this->generate_and_email_token( $user ); $this->generate_and_email_token( $user );
} }
$token_length = $this->get_token_length();
$token_placeholder = str_repeat( 'X', $token_length );
require_once ABSPATH . '/wp-admin/includes/template.php'; require_once ABSPATH . '/wp-admin/includes/template.php';
?> ?>
<p class="two-factor-prompt"><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p> <p class="two-factor-prompt"><?php esc_html_e( 'A verification code has been sent to the email address associated with your account.', 'two-factor' ); ?></p>
<p> <p>
<label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label> <label for="authcode"><?php esc_html_e( 'Verification Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" name="two-factor-email-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="1234 5678" data-digits="8" /> <input type="text" inputmode="numeric" name="two-factor-email-code" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" autocomplete="one-time-code" placeholder="<?php echo esc_attr( $token_placeholder ); ?>" data-digits="<?php echo esc_attr( $token_length ); ?>" />
<?php submit_button( __( 'Log In', 'two-factor' ) ); ?> <?php submit_button( __( 'Log In', 'two-factor' ) ); ?>
</p> </p>
<p class="two-factor-email-resend"> <p class="two-factor-email-resend">
@ -287,7 +317,7 @@ class Two_Factor_Email extends Two_Factor_Provider {
* Send the email code if missing or requested. Stop the authentication * Send the email code if missing or requested. Stop the authentication
* validation if a new token has been generated and sent. * validation if a new token has been generated and sent.
* *
* @param WP_USer $user WP_User object of the logged-in user. * @param WP_User $user WP_User object of the logged-in user.
* @return boolean * @return boolean
*/ */
public function pre_process_authentication( $user ) { public function pre_process_authentication( $user ) {
@ -351,4 +381,16 @@ class Two_Factor_Email extends Two_Factor_Provider {
</div> </div>
<?php <?php
} }
/**
* Return user meta keys to delete during plugin uninstall.
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array(
self::TOKEN_META_KEY,
self::TOKEN_META_KEY_TIMESTAMP,
);
}
} }

View File

@ -164,6 +164,10 @@ class Two_Factor_FIDO_U2F_Admin {
* @param WP_User $user WP_User object of the logged-in user. * @param WP_User $user WP_User object of the logged-in user.
*/ */
public static function show_user_profile( $user ) { public static function show_user_profile( $user ) {
if ( ! Two_Factor_FIDO_U2F::is_supported_for_user( $user ) ) {
return;
}
wp_nonce_field( "user_security_keys-{$user->ID}", '_nonce_user_security_keys' ); wp_nonce_field( "user_security_keys-{$user->ID}", '_nonce_user_security_keys' );
$new_key = false; $new_key = false;
@ -230,7 +234,7 @@ class Two_Factor_FIDO_U2F_Admin {
* @static * @static
* *
* @param int $user_id User ID. * @param int $user_id User ID.
* @return false * @return void|never
*/ */
public static function catch_submission( $user_id ) { public static function catch_submission( $user_id ) {
if ( ! empty( $_REQUEST['do_new_security_key'] ) ) { if ( ! empty( $_REQUEST['do_new_security_key'] ) ) {
@ -243,7 +247,7 @@ class Two_Factor_FIDO_U2F_Admin {
Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg ); Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg );
} catch ( Exception $e ) { } catch ( Exception $e ) {
return false; return;
} }
delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY ); delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY );

View File

@ -65,7 +65,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) ); add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_options' ) );
return parent::__construct(); parent::__construct();
} }
/** /**
@ -143,7 +143,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
* @since 0.1-dev * @since 0.1-dev
* *
* @param WP_User $user WP_User object of the logged-in user. * @param WP_User $user WP_User object of the logged-in user.
* @return null * @return void
*/ */
public function authentication_page( $user ) { public function authentication_page( $user ) {
require_once ABSPATH . '/wp-admin/includes/template.php'; require_once ABSPATH . '/wp-admin/includes/template.php';
@ -165,7 +165,7 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
?> ?>
<p><?php esc_html_e( 'An error occurred while creating authentication data.', 'two-factor' ); ?></p> <p><?php esc_html_e( 'An error occurred while creating authentication data.', 'two-factor' ); ?></p>
<?php <?php
return null; return;
} }
wp_localize_script( wp_localize_script(
@ -388,4 +388,17 @@ class Two_Factor_FIDO_U2F extends Two_Factor_Provider {
return true; return true;
} }
/**
* Return user meta keys to delete during plugin uninstall.
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array(
self::REGISTERED_KEY_USER_META_KEY,
self::AUTH_DATA_USER_META_KEY,
'_two_factor_fido_u2f_register_request', // From Two_Factor_FIDO_U2F_Admin which is not loaded during uninstall.
);
}
} }

View File

@ -123,6 +123,19 @@ abstract class Two_Factor_Provider {
*/ */
abstract public function is_available_for_user( $user ); abstract public function is_available_for_user( $user );
/**
* If this provider should be available for the user.
*
* @param WP_User|int $user WP_User object, user ID or null to resolve the current user.
*
* @return bool
*/
public static function is_supported_for_user( $user = null ) {
$providers = Two_Factor_Core::get_supported_providers_for_user( $user );
return isset( $providers[ static::class ] );
}
/** /**
* Generate a random eight-digit string to send out as an auth code. * Generate a random eight-digit string to send out as an auth code.
* *
@ -165,4 +178,24 @@ abstract class Two_Factor_Provider {
return (string) $code; return (string) $code;
} }
/**
* Return the user meta keys that need to be deletated on plugin uninstall.
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array();
}
/**
* Return the option keys that need to be deleted on plugin uninstall.
*
* Note: this method doesn't have access to the instantiated provider object.
*
* @return array
*/
public static function uninstall_options() {
return array();
}
} }

View File

@ -48,7 +48,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_two_factor_options' ) ); add_action( 'two_factor_user_options_' . __CLASS__, array( $this, 'user_two_factor_options' ) );
return parent::__construct(); parent::__construct();
} }
/** /**
@ -110,7 +110,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
* Returns the name of the provider. * Returns the name of the provider.
*/ */
public function get_label() { public function get_label() {
return _x( 'Authenticator app', 'Provider Label', 'two-factor' ); return _x( 'Authenticator App', 'Provider Label', 'two-factor' );
} }
/** /**
@ -119,7 +119,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
* @since 0.9.0 * @since 0.9.0
*/ */
public function get_alternative_provider_label() { public function get_alternative_provider_label() {
return __( 'Use your authenticator app', 'two-factor' ); return __( 'Use your authenticator app for time-based one-time passwords (TOTP)', 'two-factor' );
} }
/** /**
@ -142,7 +142,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
/** /**
* Rest API endpoint for handling deactivation of TOTP. * Rest API endpoint for handling deactivation of TOTP.
* *
* @param WP_Rest_Request $request The Rest Request object. * @param WP_REST_Request $request The Rest Request object.
* @return array Success array. * @return array Success array.
*/ */
public function rest_delete_totp( $request ) { public function rest_delete_totp( $request ) {
@ -151,6 +151,10 @@ class Two_Factor_Totp extends Two_Factor_Provider {
$this->delete_user_totp_key( $user_id ); $this->delete_user_totp_key( $user_id );
if ( ! Two_Factor_Core::disable_provider_for_user( $user_id, 'Two_Factor_Totp' ) ) {
return new WP_Error( 'db_error', __( 'Unable to disable TOTP provider for this user.', 'two-factor' ), array( 'status' => 500 ) );
}
ob_start(); ob_start();
$this->user_two_factor_options( $user ); $this->user_two_factor_options( $user );
$html = ob_get_clean(); $html = ob_get_clean();
@ -164,7 +168,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
/** /**
* REST API endpoint for setting up TOTP. * REST API endpoint for setting up TOTP.
* *
* @param WP_Rest_Request $request The Rest Request object. * @param WP_REST_Request $request The Rest Request object.
* @return WP_Error|array Array of data on success, WP_Error on error. * @return WP_Error|array Array of data on success, WP_Error on error.
*/ */
public function rest_setup_totp( $request ) { public function rest_setup_totp( $request ) {
@ -203,8 +207,8 @@ class Two_Factor_Totp extends Two_Factor_Provider {
/** /**
* Generates a URL that can be used to create a QR code. * Generates a URL that can be used to create a QR code.
* *
* @param WP_User $user The user to generate a URL for. * @param WP_User $user The user to generate a URL for.
* @param string $key The secret key. * @param string $secret_key The secret key.
* *
* @return string * @return string
*/ */
@ -260,13 +264,13 @@ class Two_Factor_Totp extends Two_Factor_Provider {
* Display TOTP options on the user settings page. * Display TOTP options on the user settings page.
* *
* @param WP_User $user The current user being edited. * @param WP_User $user The current user being edited.
* @return false * @return void
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function user_two_factor_options( $user ) { public function user_two_factor_options( $user ) {
if ( ! isset( $user->ID ) ) { if ( ! isset( $user->ID ) ) {
return false; return;
} }
$key = $this->get_user_totp_key( $user->ID ); $key = $this->get_user_totp_key( $user->ID );
@ -318,6 +322,15 @@ class Two_Factor_Totp extends Two_Factor_Provider {
qr.make(); qr.make();
document.querySelector( '#two-factor-qr-code a' ).innerHTML = qr.createSvgTag( 5 ); document.querySelector( '#two-factor-qr-code a' ).innerHTML = qr.createSvgTag( 5 );
// For accessibility, markup the SVG with a title and role.
var svg = document.querySelector( '#two-factor-qr-code a svg' ),
title = document.createElement( 'title' );
svg.role = 'image';
svg.ariaLabel = <?php echo wp_json_encode( __( 'Authenticator App QR Code', 'two-factor' ) ); ?>;
title.innerText = svg.ariaLabel;
svg.appendChild( title );
}; };
// Run now if the document is loaded, otherwise on DOMContentLoaded. // Run now if the document is loaded, otherwise on DOMContentLoaded.
@ -340,13 +353,20 @@ class Two_Factor_Totp extends Two_Factor_Provider {
/* translators: Example auth code. */ /* translators: Example auth code. */
$placeholder = sprintf( __( 'eg. %s', 'two-factor' ), '123456' ); $placeholder = sprintf( __( 'eg. %s', 'two-factor' ), '123456' );
?> ?>
<input type="tel" name="two-factor-totp-authcode" id="two-factor-totp-authcode" class="input" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $placeholder ); ?>" /> <input type="text" inputmode="numeric" name="two-factor-totp-authcode" id="two-factor-totp-authcode" class="input" value="" size="20" pattern="[0-9 ]*" placeholder="<?php echo esc_attr( $placeholder ); ?>" autocomplete="off" />
</label> </label>
<input type="submit" class="button totp-submit" name="two-factor-totp-submit" value="<?php esc_attr_e( 'Submit', 'two-factor' ); ?>" /> <input type="submit" class="button totp-submit" name="two-factor-totp-submit" value="<?php esc_attr_e( 'Submit', 'two-factor' ); ?>" />
</p> </p>
<script> <script>
(function($){ (function($){
// Focus the auth code input when the checkbox is clicked.
document.getElementById('enabled-Two_Factor_Totp').addEventListener('click', function(e) {
if ( e.target.checked ) {
document.getElementById('two-factor-totp-authcode').focus();
}
});
$('.totp-submit').click( function( e ) { $('.totp-submit').click( function( e ) {
e.preventDefault(); e.preventDefault();
var key = $('#two-factor-totp-key').val(), var key = $('#two-factor-totp-key').val(),
@ -359,6 +379,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
user_id: <?php echo wp_json_encode( $user->ID ); ?>, user_id: <?php echo wp_json_encode( $user->ID ); ?>,
key: key, key: key,
code: code, code: code,
enable_provider: true,
} }
} ).fail( function( response, status ) { } ).fail( function( response, status ) {
var errorMessage = response.responseJSON.message || status, var errorMessage = response.responseJSON.message || status,
@ -370,8 +391,10 @@ class Two_Factor_Totp extends Two_Factor_Provider {
$error.find('p').text( errorMessage ); $error.find('p').text( errorMessage );
$( '#enabled-Two_Factor_Totp' ).prop( 'checked', false );
$('#two-factor-totp-authcode').val(''); $('#two-factor-totp-authcode').val('');
} ).then( function( response ) { } ).then( function( response ) {
$( '#enabled-Two_Factor_Totp' ).prop( 'checked', true );
$( '#two-factor-totp-options' ).html( response.html ); $( '#two-factor-totp-options' ).html( response.html );
} ); } );
} ); } );
@ -398,6 +421,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
user_id: <?php echo wp_json_encode( $user->ID ); ?>, user_id: <?php echo wp_json_encode( $user->ID ); ?>,
} }
} ).then( function( response ) { } ).then( function( response ) {
$( '#enabled-Two_Factor_Totp' ).prop( 'checked', false );
$( '#two-factor-totp-options' ).html( response.html ); $( '#two-factor-totp-options' ).html( response.html );
} ); } );
} ); } );
@ -666,7 +690,7 @@ class Two_Factor_Totp extends Two_Factor_Provider {
</p> </p>
<p> <p>
<label for="authcode"><?php esc_html_e( 'Authentication Code:', 'two-factor' ); ?></label> <label for="authcode"><?php esc_html_e( 'Authentication Code:', 'two-factor' ); ?></label>
<input type="text" inputmode="numeric" autocomplete="one-time-code" name="authcode" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="123 456" data-digits="<?php echo esc_attr( self::DEFAULT_DIGIT_COUNT ); ?>" /> <input type="text" inputmode="numeric" autocomplete="one-time-code" name="authcode" id="authcode" class="input authcode" value="" size="20" pattern="[0-9 ]*" placeholder="123 456" autocomplete="one-time-code" data-digits="<?php echo esc_attr( self::DEFAULT_DIGIT_COUNT ); ?>" />
</p> </p>
<script type="text/javascript"> <script type="text/javascript">
setTimeout( function(){ setTimeout( function(){
@ -762,4 +786,16 @@ class Two_Factor_Totp extends Two_Factor_Provider {
} }
return ( $a < $b ) ? -1 : 1; return ( $a < $b ) ? -1 : 1;
} }
/**
* Return user meta keys to delete during plugin uninstall.
*
* @return array
*/
public static function uninstall_user_meta_keys() {
return array(
self::SECRET_META_KEY,
self::LAST_SUCCESSFUL_LOGIN_META_KEY,
);
}
} }

View File

@ -1,12 +1,12 @@
=== Two-Factor === === Two-Factor ===
Contributors: georgestephanis, valendesigns, stevenkword, extendwings, sgrant, aaroncampbell, johnbillion, stevegrunwell, netweb, kasparsd, alihusnainarshad, passoniate Contributors: georgestephanis, valendesigns, stevenkword, extendwings, sgrant, aaroncampbell, johnbillion, stevegrunwell, netweb, kasparsd, alihusnainarshad, passoniate
Tags: two factor, two step, authentication, login, totp, fido u2f, u2f, email, backup codes, 2fa, yubikey Tags: 2fa, mfa, totp, authentication, security
Requires at least: 4.3 Tested up to: 6.7
Tested up to: 6.5 Stable tag: 0.13.0
Requires PHP: 5.6 License: GPL-2.0-or-later
Stable tag: 0.9.1 License URI: https://spdx.org/licenses/GPL-2.0-or-later.html
Enable Two-Factor Authentication using time-based one-time passwords (OTP, Google Authenticator), Universal 2nd Factor (FIDO U2F, YubiKey), email and backup verification codes. Enable Two-Factor Authentication (2FA) using time-based one-time passwords (TOTP), Universal 2nd Factor (U2F), email, and backup verification codes.
== Description == == Description ==
@ -25,9 +25,12 @@ For more history, see [this post](https://georgestephanis.wordpress.com/2013/08/
Here is a list of action and filter hooks provided by the plugin: Here is a list of action and filter hooks provided by the plugin:
- `two_factor_providers` filter overrides the available two-factor providers such as email and time-based one-time passwords. Array values are PHP classnames of the two-factor providers. - `two_factor_providers` filter overrides the available two-factor providers such as email and time-based one-time passwords. Array values are PHP classnames of the two-factor providers.
- `two_factor_providers_for_user` filter overrides the available two-factor providers for a specific user. Array values are instances of provider classes and the user object `WP_User` is available as the second argument.
- `two_factor_enabled_providers_for_user` filter overrides the list of two-factor providers enabled for a user. First argument is an array of enabled provider classnames as values, the second argument is the user ID. - `two_factor_enabled_providers_for_user` filter overrides the list of two-factor providers enabled for a user. First argument is an array of enabled provider classnames as values, the second argument is the user ID.
- `two_factor_user_authenticated` action which receives the logged in `WP_User` object as the first argument for determining the logged in user right after the authentication workflow. - `two_factor_user_authenticated` action which receives the logged in `WP_User` object as the first argument for determining the logged in user right after the authentication workflow.
- `two_factor_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated. - `two_factor_email_token_ttl` filter overrides the time interval in seconds that an email token is considered after generation. Accepts the time in seconds as the first argument and the ID of the `WP_User` object being authenticated.
- `two_factor_email_token_length` filter overrides the default 8 character count for email tokens.
- `two_factor_backup_code_length` filter overrides the default 8 character count for backup codes. Providers the `WP_User` of the associated user as the second argument.
== Frequently Asked Questions == == Frequently Asked Questions ==

View File

@ -3,19 +3,23 @@
* Two Factor * Two Factor
* *
* @package Two_Factor * @package Two_Factor
* @author Plugin Contributors * @author WordPress.org Contributors
* @copyright 2020 Plugin Contributors * @copyright 2020 Plugin Contributors
* @license GPL-2.0-or-later * @license GPL-2.0-or-later
* *
* @wordpress-plugin * @wordpress-plugin
* Plugin Name: Two Factor * Plugin Name: Two Factor
* Plugin URI: https://wordpress.org/plugins/two-factor/ * Plugin URI: https://wordpress.org/plugins/two-factor/
* Description: Two-Factor Authentication using time-based one-time passwords, Universal 2nd Factor (FIDO U2F), email and backup verification codes. * Description: Enable Two-Factor Authentication using time-based one-time passwords, Universal 2nd Factor (FIDO U2F, YubiKey), email, and backup verification codes.
* Author: Plugin Contributors * Version: 0.13.0
* Version: 0.9.1 * Requires at least: 6.3
* Author URI: https://github.com/wordpress/two-factor/graphs/contributors * Requires PHP: 7.2
* Network: True * Author: WordPress.org Contributors
* Text Domain: two-factor * Author URI: https://github.com/wordpress/two-factor/graphs/contributors
* License: GPL-2.0-or-later
* License URI: https://spdx.org/licenses/GPL-2.0-or-later.html
* Text Domain: two-factor
* Network: True
*/ */
/** /**
@ -26,7 +30,7 @@ define( 'TWO_FACTOR_DIR', plugin_dir_path( __FILE__ ) );
/** /**
* Version of the plugin. * Version of the plugin.
*/ */
define( 'TWO_FACTOR_VERSION', '0.9.1' ); define( 'TWO_FACTOR_VERSION', '0.13.0' );
/** /**
* Include the base class here, so that other plugins can also extend it. * Include the base class here, so that other plugins can also extend it.
@ -39,10 +43,13 @@ require_once TWO_FACTOR_DIR . 'providers/class-two-factor-provider.php';
require_once TWO_FACTOR_DIR . 'class-two-factor-core.php'; require_once TWO_FACTOR_DIR . 'class-two-factor-core.php';
/** /**
* A compatability layer for some of the most-used plugins out there. * A compatibility layer for some of the most-used plugins out there.
*/ */
require_once TWO_FACTOR_DIR . 'class-two-factor-compat.php'; require_once TWO_FACTOR_DIR . 'class-two-factor-compat.php';
$two_factor_compat = new Two_Factor_Compat(); $two_factor_compat = new Two_Factor_Compat();
Two_Factor_Core::add_hooks( $two_factor_compat ); Two_Factor_Core::add_hooks( $two_factor_compat );
// Delete our options and user meta during uninstall.
register_uninstall_hook( __FILE__, array( Two_Factor_Core::class, 'uninstall' ) );

View File

@ -1,41 +1,8 @@
.two-factor-methods-table {
background-color: #fff;
border: 1px solid #e5e5e5;
border-spacing: 0;
}
.two-factor-methods-table thead,
.two-factor-methods-table tfoot {
background: #fff;
}
.two-factor-methods-table thead th {
padding: 0.5em;
}
.two-factor-methods-table .col-primary,
.two-factor-methods-table .col-enabled {
width: 5%;
}
.two-factor-methods-table .col-name {
width: 90%;
}
.two-factor-methods-table tbody th {
text-align: center;
}
.two-factor-methods-table tbody th, .two-factor-methods-table tbody th,
.two-factor-methods-table tbody td { .two-factor-methods-table tbody td {
vertical-align: top; vertical-align: top;
} }
.two-factor-methods-table tbody tr:nth-child(odd) {
background-color: #f9f9f9;
}
.two-factor-methods-table .two-factor-method-label { .two-factor-methods-table .two-factor-method-label {
display: block; display: block;
font-weight: 700; font-weight: 700;