Persist third-party plugin $_POST fields across the 2FA login screen (Fixes #705) - #925
Persist third-party plugin $_POST fields across the 2FA login screen (Fixes #705)#925chetanupare wants to merge 2 commits into
Conversation
|
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 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. |
There was a problem hiding this comment.
Pull request overview
This PR improves Two-Factor’s interrupted-login flow by preserving third-party $_POST fields across the 2FA interstitial form so downstream hooks (e.g., attach_session_information) can still read plugin-injected login inputs after 2FA completes.
Changes:
- Adds a core helper to render non-blocklisted
$_POSTfields as hidden inputs on the 2FA interstitial form (including nested arrays). - Adds unit tests for the new
$_POSTpassthrough behavior. - Introduces a new session-revalidation action/API (
two_factor_revalidate_session/action_revalidate_session()), which is not described in the PR’s stated scope.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| class-two-factor-core.php | Prints custom $_POST fields into the 2FA interstitial form; adds a new session revalidation action/API. |
| tests/class-two-factor-core.php | Adds unit tests covering custom $_POST passthrough and the new revalidation redirect behavior. |
Comments suppressed due to low confidence (1)
tests/class-two-factor-core.php:2767
- This test also needs to avoid
exit;if a redirect happens unexpectedly. Usingdo_redirect_callable()lets the test assert that no redirect occurred without risking the process terminating.
// Should not throw an exception (no redirect)
$exception = false;
try {
Two_Factor_Core::action_revalidate_session( 300 );
} catch ( Two_Factor_Redirect_Exception $e ) {
$exception = true;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| $reauth_url = self::get_user_two_factor_revalidate_url(); | ||
| $reauth_url = add_query_arg( 'redirect_to', urlencode( $redirect_to ), $reauth_url ); |
| if ( in_array( $key, $blocklist, true ) ) { | ||
| continue; | ||
| } | ||
| self::print_hidden_inputs( $key, $value ); |
| private static function print_hidden_inputs( $name, $value ) { | ||
| if ( is_array( $value ) ) { | ||
| foreach ( $value as $k => $v ) { | ||
| self::print_hidden_inputs( $name . '[' . $k . ']', $v ); | ||
| } | ||
| } else { | ||
| echo '<input type="hidden" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '" />' . "\n"; | ||
| } | ||
| } |
| // Catch the redirect exception | ||
| $redirected = false; | ||
| try { | ||
| Two_Factor_Core::action_revalidate_session( 300, '/custom-redirect/' ); | ||
| } catch ( Two_Factor_Redirect_Exception $e ) { | ||
| $redirected = true; | ||
| $location = $e->getMessage(); | ||
| $this->assertStringContainsString( 'action=revalidate_2fa', $location ); | ||
| $this->assertStringContainsString( 'redirect_to=%2Fcustom-redirect%2F', $location ); | ||
| } | ||
|
|
||
| $this->assertTrue( $redirected, 'Expected wp_safe_redirect to throw Two_Factor_Redirect_Exception.' ); |
| add_action( 'admin_init', array( __CLASS__, 'trigger_user_settings_action' ) ); | ||
| add_filter( 'two_factor_providers', array( __CLASS__, 'enable_dummy_method_for_debug' ) ); | ||
|
|
||
| add_action( 'two_factor_revalidate_session', array( __CLASS__, 'action_revalidate_session' ), 10, 2 ); |
| $_POST = array( | ||
| 'pwd' => 'my_secret_password', | ||
| 'password' => 'my_other_password', | ||
| 'log' => 'admin', | ||
| 'user_pass' => 'secret', | ||
| 'rememberme' => '1', | ||
| 'custom_ok' => 'allowed', | ||
| ); |
This safely copies unknown variables into hidden fields on the 2FA interstitial screen, ensuring third-party login forms function properly without leaking standard WordPress fields or plaintext passwords.
4db3286 to
e4d19b0
Compare
|
Hi @masteradhoc , I've cleaned up this PR branch ! The unrelated hook commit is gone, the $_POST fields are now properly unslashed, and the blocklist correctly handles nested arrays now. |
What?
This PR ensures that custom $_POST fields injected by third-party plugins (e.g., JavaScript detection flags, custom checkboxes) into the WordPress login form are preserved when the Two-Factor plugin interrupts the login flow to display its 2FA challenge.
Fixes #705
Why?
Currently, when a user successfully passes the initial username/password check, Two-Factor renders the login_html() interstitial form. In doing so, it drops any non-standard $_POST variables submitted from the original wp-login.php form. When the user eventually submits their 2FA code, plugins that rely on $_POST (for instance, during the attach_session_information hook) fail to find their data.
How?
A new recursive method Two_Factor_Core::print_custom_post_fields() has been added.
Use of AI Tools
AI assistance: No
Testing Instructions
Changelog Entry
Fixed - Preserve custom third-party $_POST variables during the 2FA login challenge.