Skip to content
Open
50 changes: 38 additions & 12 deletions class-two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -695,18 +695,44 @@ public static function get_available_providers_for_user( $user = null ) {
* Possible enhancement: add a filter to change the fallback method?
*/
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 ),
)
);
// Determine whether the filter intentionally cleared the list, or
// whether the providers are genuinely missing/removed. Only apply
// the fallback filter in the former case — if providers no longer
// exist, the fail-safe always applies to prevent failing open.
$unfiltered = array_intersect( (array) $user_providers_raw, array_keys( $providers ) );

/**
* Filters whether the email provider fallback is applied when a user's
* enabled provider list resolves to empty but they have providers configured
* in user meta. Return false to disable the fallback and allow an empty
* provider list to pass through — for example, to bypass two-factor for
* trusted IP addresses.
*
* This filter only runs when the configured providers still exist. If
* providers are genuinely missing or removed, the fail-safe always applies
* regardless of this filter.
*
* @since 0.17.0
*
* @param bool $apply_fallback Whether to apply the email fallback. Default true.
* @param int $user_id The user ID.
*/
$apply_fallback = empty( $unfiltered ) || apply_filters( 'two_factor_email_fallback_enabled', true, $user->ID );

if ( $apply_fallback ) {
if ( isset( $providers['Two_Factor_Email'] ) ) {
// Force Emailed codes to 'on'.
$enabled_providers[] = 'Two_Factor_Email';

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new two_factor_email_fallback_enabled behavior changes the outcome of get_available_providers_for_user() when a valid provider list is intentionally cleared via two_factor_enabled_providers_for_user, but there’s no unit test coverage validating (1) fallback remains enabled by default and (2) the fallback can be disabled via the new filter while still preserving the existing fail-safe when providers are genuinely missing. Please add/extend tests (likely in tests/class-two-factor-core.php, near test_deprecated_provider_for_user) to cover these cases so regressions are caught.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nimesh-xecurify would you be able to support on unit test extension here?

} 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
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ 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_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_email_fallback_enabled` filter controls whether the email provider fallback is applied when a user's enabled provider list is empty but providers are configured in user meta. Return `false` to disable the fallback (e.g. to bypass two-factor for trusted IP addresses). The user ID is available as the second argument.
- `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_api_login_enable` filter restricts authentication for REST API and XML-RPC to application passwords only. Provides the user ID as the second argument.
- `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.
Expand Down
Loading