Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -687,26 +687,32 @@ public static function get_available_providers_for_user( $user = null ) {
$user_providers_raw = get_user_meta( $user->ID, self::ENABLED_PROVIDERS_USER_META_KEY, true );

/**
* If the user had enabled providers, but none of them exist currently,
* if emailed codes is available force it to be on, so that deprecated
* or removed providers don't result in the two-factor requirement being
* If the user had enabled providers in user meta, but none of those
* providers are still registered, force emailed codes on when available
* so deprecated or removed providers don't result in two-factor being
* removed and 'failing open'.
*
* Possible enhancement: add a filter to change the fallback method?
* If the configured providers are still registered, an empty enabled list
* may have been returned intentionally by two_factor_enabled_providers_for_user
* and must be respected.
*/
if ( empty( $enabled_providers ) && $user_providers_raw ) {
if ( isset( $providers['Two_Factor_Email'] ) ) {
// Force Emailed codes to 'on'.
$enabled_providers[] = 'Two_Factor_Email';
} else {
return new WP_Error(
'no_available_2fa_methods',
__( 'Error: You have Two Factor method(s) enabled, but the provider(s) no longer exist. Please contact a site administrator for assistance.', 'two-factor' ),
array(
'user_providers_raw' => $user_providers_raw,
'available_providers' => array_keys( $providers ),
)
);
$still_registered = array_intersect( (array) $user_providers_raw, array_keys( $providers ) );

if ( empty( $still_registered ) ) {
if ( isset( $providers['Two_Factor_Email'] ) ) {
// Force Emailed codes to 'on'.
$enabled_providers[] = 'Two_Factor_Email';
} else {
return new WP_Error(
'no_available_2fa_methods',
__( 'Error: You have Two Factor method(s) enabled, but the provider(s) no longer exist. Please contact a site administrator for assistance.', 'two-factor' ),
array(
'user_providers_raw' => $user_providers_raw,
'available_providers' => array_keys( $providers ),
)
);
}
}
}

Expand Down
43 changes: 43 additions & 0 deletions tests/class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,49 @@ public function test_get_available_providers_for_user_with_configured_providers(
$this->assertCount( 2, $available, 'Two providers are available' );
}

/**
* Ensure an intentionally emptied provider list is respected.
*
* @covers Two_Factor_Core::get_available_providers_for_user
*/
public function test_get_available_providers_for_user_respects_filter_cleared_list() {
$user = self::factory()->user->create_and_get();

update_user_meta( $user->ID, Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY, array( 'Two_Factor_Email' ) );

$filter = function ( $enabled_providers, $user_id ) use ( $user ) {
$this->assertSame( $user->ID, $user_id, 'Filter received expected user ID' );
return array();
};

add_filter( 'two_factor_enabled_providers_for_user', $filter, 10, 2 );

try {
$this->assertEmpty(
Two_Factor_Core::get_available_providers_for_user( $user->ID ),
'No fallback provider is forced when the filter intentionally returns an empty list'
);
} finally {
remove_filter( 'two_factor_enabled_providers_for_user', $filter, 10 );
}
}

/**
* Ensure fallback still applies when configured providers are no longer registered.
*
* @covers Two_Factor_Core::get_available_providers_for_user
*/
public function test_get_available_providers_for_user_falls_back_when_configured_providers_are_missing() {
$user = self::factory()->user->create_and_get();

update_user_meta( $user->ID, Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY, array( 'Two_Factor_Missing' ) );

$available = Two_Factor_Core::get_available_providers_for_user( $user->ID );

$this->assertCount( 1, $available, 'Email fallback remains active when configured providers are missing' );
$this->assertArrayHasKey( 'Two_Factor_Email', $available, 'Emailed codes are forced on for missing configured providers' );
}

/**
* Verify process_provider() returns WP_Error when no provider is given.
*
Expand Down
Loading