Fix: respect intentional empty provider list from two_factor_enabled_providers_for_user filter#882
Fix: respect intentional empty provider list from two_factor_enabled_providers_for_user filter#882masteradhoc wants to merge 2 commits intoWordPress:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new hook to let integrators intentionally bypass the “email fallback” behavior when two_factor_enabled_providers_for_user clears a user’s enabled provider list, while preserving the existing fail-safe when providers genuinely disappear.
Changes:
- Updates
Two_Factor_Core::get_available_providers_for_user()to distinguish “providers missing” vs “providers intentionally cleared” and introduces thetwo_factor_email_fallback_enabledfilter. - Documents the new filter in
readme.txt.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
class-two-factor-core.php |
Adds logic + a new filter to optionally disable the email fallback when enabled providers are intentionally cleared. |
readme.txt |
Documents the new two_factor_email_fallback_enabled filter and its purpose. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $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'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@nimesh-xecurify would you be able to support on unit test extension here?
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @abovowebdevelopment, @sirolf. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
What?
Fixes #871
Adds a new
two_factor_email_fallback_enabledfilter that allows the email provider fallback to be disabled when a user's provider list is intentionally emptied via thetwo_factor_enabled_providers_for_userfilter.Why?
Since 0.16.0, the fail-safe behaviour in
get_available_providers_for_user()forcesTwo_Factor_Emailon whenever the resolved provider list is empty but the user has providers stored in user meta. This was introduced to prevent "failing open" when providers are removed or deprecated — which is correct and should be kept.However, it also breaks the legitimate use case of using
two_factor_enabled_providers_for_userto intentionally return an empty array (e.g. to bypass 2FA for users connecting from a trusted IP address). Previously this worked; 0.16.0 made it impossible without a workaround.How?
Before applying the email fallback, we now compute
$unfiltered— the intersection of the raw user meta providers with the currently available providers. This tells us whether the provider list became empty because providers genuinely disappeared, or because a filter cleared it intentionally:$unfilteredis empty, the providers are genuinely missing/removed — the fail-safe applies unconditionally, as before.$unfilteredis not empty, the filter cleared a valid list — the newtwo_factor_email_fallback_enabledfilter is consulted, defaulting totrueso existing behaviour is unchanged for anyone not using the filter.The new filter is also documented in
readme.txt.Use of AI Tools
AI assistance: Yes
Tool(s): Claude (claude.ai)
Model(s): Claude Sonnet 4.6
Used for: Identifying the root cause, drafting the implementation.
Testing Instructions
Two_Factor_Emailenabled.functions.php:Screenshots or screencast
N/A — no UI changes.
Changelog Entry