diff --git a/AGENTS.md b/AGENTS.md index 4b2cc1de..d38fc01b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,9 +47,10 @@ The plugin follows a provider pattern. `Two_Factor_Core` owns the login intercep ### Core Files -- **`two-factor.php`** — Entry point. Defines `TWO_FACTOR_DIR` and `TWO_FACTOR_VERSION`, loads all core files, instantiates `Two_Factor_Compat`, and calls `Two_Factor_Core::add_hooks()`. +- **`two-factor.php`** — Entry point. Defines `TWO_FACTOR_DIR` and `TWO_FACTOR_VERSION`, loads all core files (including the settings class), instantiates `Two_Factor_Compat`, and calls `Two_Factor_Core::add_hooks()`. Also registers the wp-admin settings page (Settings → Two-Factor) and the filter callbacks that enforce the site-wide enabled-providers option (`two_factor_filter_enabled_providers`, `two_factor_filter_enabled_providers_for_user`). - **`class-two-factor-core.php`** — Central class. Owns the login flow, user meta, nonce management, rate limiting, session tracking, REST API endpoints, and the user profile settings UI. - **`class-two-factor-compat.php`** — Compatibility shims for third-party plugins (currently: Jetpack SSO). New integrations go here; the goal is to avoid any plugin-specific logic outside this file. +- **`settings/class-two-factor-settings.php`** — `Two_Factor_Settings`, renders the site-wide settings screen (Settings → Two-Factor, requires `manage_options`) where admins choose which providers are available on the site. Saving writes the `two_factor_enabled_providers` option; enforcement happens via the filters registered in `two-factor.php`, not in this class. - **`providers/class-two-factor-provider.php`** — Abstract base class all providers extend. Defines the required interface: `get_label()`, `is_available_for_user()`, `authentication_page()`, `validate_authentication()`, and optional hooks for REST routes, settings UI, and uninstall cleanup. - **`providers/`** — Concrete providers: `class-two-factor-totp.php`, `class-two-factor-email.php`, `class-two-factor-backup-codes.php`, `class-two-factor-dummy.php`. - **`includes/`** — Custom `login_header()` and `login_footer()` template functions that replace the WordPress core versions with additional filter hooks. Excluded from PHPCS because they intentionally deviate from core function signatures. Do not modify files in includes/ directly. They are intentionally kept close to WordPress core function signatures to ease future merging into Core. Any functional changes should go through the filter hooks they expose instead. @@ -97,6 +98,12 @@ New providers should follow this pattern rather than registering hooks from outs | `USER_FAILED_LOGIN_ATTEMPTS_KEY` | `_two_factor_failed_login_attempts` | Failed attempt count | | `USER_PASSWORD_WAS_RESET_KEY` | `_two_factor_password_was_reset` | Flags compromised-password reset | +### Key Site Options (constants on `Two_Factor_Core`) + +| Constant | Option Key | Purpose | +|---|---|---| +| `ENABLED_PROVIDERS_OPTION_KEY` | `two_factor_enabled_providers` | Provider class names enabled site-wide via the settings page; never saved means all providers are allowed. | + ### REST API Namespace: `two-factor/1.0` (constant `Two_Factor_Core::REST_NAMESPACE`). Each provider that exposes REST endpoints registers its own routes in `register_rest_routes()` called from its constructor. @@ -104,6 +111,7 @@ Namespace: `two-factor/1.0` (constant `Two_Factor_Core::REST_NAMESPACE`). Each p ## Code Standards - PHP 7.2+ compatibility required; enforced by PHPCompatibilityWP. +- WordPress 6.9+ required. - Follows WordPress coding standards (WPCS) and WordPress-VIP-Go rules. - `includes/` is excluded from PHPCS — those files intentionally override core functions. - The codebase does not fully pass all PHPCS checks (known issue [#437](https://github.com/WordPress/two-factor/issues/437)). Do not treat existing violations as license to introduce new ones. diff --git a/TESTS.md b/TESTS.md index 924c8713..65f6e900 100644 --- a/TESTS.md +++ b/TESTS.md @@ -24,7 +24,7 @@ Pass PHPUnit arguments through the `composer` wrapper: ```bash # Run a single test class -npm run composer -- test -- --filter Tests_Two_Factor_Core +npm run composer -- test -- --filter Test_ClassTwoFactorCore # Run a single test method npm run composer -- test -- --filter test_create_login_nonce @@ -49,7 +49,7 @@ Smoke tests that the plugin loaded correctly: the `TWO_FACTOR_DIR` constant is d ### Core — `tests/class-two-factor-core.php` -**Class:** `Tests_Two_Factor_Core` · **Group:** `core` +**Class:** `Test_ClassTwoFactorCore` · **Group:** `core` The largest test file. Covers the full authentication lifecycle managed by `Two_Factor_Core`: - Hook registration (`add_hooks`) @@ -58,10 +58,11 @@ The largest test file. Covers the full authentication lifecycle managed by `Two_ - Login nonce creation, verification, and deletion - Rate limiting (`get_user_time_delay`, `is_user_rate_limited`) - Session management: two-factor factored vs. non-factored sessions, session destruction on 2FA enable/disable, revalidation -- Password reset flow (compromise detection, email notifications, reset notices) +- Password reset flow (compromise detection, email notifications, reset notices and their nonce validation) - REST API permission callbacks (`rest_api_can_edit_user_and_update_two_factor_options`) - User settings actions (`trigger_user_settings_action`, `current_user_can_update_two_factor_options`) -- Uninstall cleanup +- Profile settings UI output (`user_two_factor_options`), including recovery-codes wording with and without the Backup Codes provider +- Uninstall cleanup (user meta and site-wide options) - Filter hooks (`two_factor_providers`, `two_factor_primary_provider_for_user`, `two_factor_user_api_login_enable`) ### Provider Base Class — `tests/providers/class-two-factor-provider.php` @@ -158,3 +159,4 @@ Tests `Two_Factor_Dummy_Secure` (a fixture that always _fails_ authentication, u - **`tests/bootstrap.php`** — Locates the WordPress test library (via `WP_TESTS_DIR` env var, relative path, or `/tmp/wordpress-tests-lib`), loads the plugin via `muplugins_loaded`, then boots the WP test environment. - **`tests/class-two-factor-dummy-secure.php`** — Defines `Two_Factor_Dummy_Secure`, a test-only provider class that spoofs the key of `Two_Factor_Dummy` but always fails `validate_authentication`. Used by `Tests_Two_Factor_Dummy_Secure` and some core tests. +- **`Two_Factor_Redirect_Exception`** — Defined in `tests/class-two-factor-core.php`. The core tests' render helper intercepts `wp_redirect` and throws this exception, so code paths that redirect and `exit` can be tested without terminating the test process.