Skip to content

fix(sso): return verify: must-redirect from unattached JSONP probe#1309

Merged
superdav42 merged 2 commits into
mainfrom
fix/sso-jsonp-broker-not-attached
May 29, 2026
Merged

fix(sso): return verify: must-redirect from unattached JSONP probe#1309
superdav42 merged 2 commits into
mainfrom
fix/sso-jsonp-broker-not-attached

Conversation

@superdav42

@superdav42 superdav42 commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Minimal replacement for #1297. Changes a single response payload in handle_broker() so the JSONP unattached probe no longer trips sso.js into the wu_sso_denied denial path on the very first subsite front-end page-load.

Bug

When a logged-in main-site user visits a subsite front-end page, sso.js runs the JSONP <script src="<broker>/sso?_jsonp=1"> attach probe. On the first hit the broker is unattached (that's the point of the probe), so handle_broker() enters the ! $broker->isAttached() JSONP branch and returns:

{ "code": 0, "message": "Broker not attached" }

sso.js sees code !== 200 and falls into the denial path (window.wu.sso_denied()), which sets wu_sso_denied=1 for 5 minutes. After that there is no path that ever surfaces the main-site session to the broker — auto-SSO silently dies before completing a single round-trip, even though the user is in fact logged in on the main site.

Fix

Return verify: 'must-redirect' instead:

-    'code'    => 0,
-    'message' => 'Broker not attached',
+    'code'   => 200,
+    'verify' => 'must-redirect',

sso.js already has the matching branch (assets/js/sso.js:93-95, present on main today):

if (payload.verify === 'must-redirect') {
    window.location.replace(`${ o.server_url }?return_url=${ encodedLocation }`);
}

That top-level navigation re-enters handle_broker() as a regular page load (not JSONP) and falls through to the non-JSONP redirect via $broker->getAttachUrl(). After #1301 that URL (/sso-grant) routes correctly to wu_sso_handle_sso_grant and completes the cookie-less SSO round-trip.

Why this is the whole fix now (vs #1297)

PR #1297 was opened against a codebase where two regressions overlapped: the JSONP code: 0 denial and the broken /sso-grant routing. Two follow-up PRs have since merged to main:

With those landed, the only remaining defect from #1297's premise is the JSONP denial payload above. The broader rewrite #1297 proposed (redirecting through /sso instead of /sso-grant, plus bundled wp-env Cypress fixture changes, sunrise.php glob fallback, and sso.js checkout-form skip) is no longer needed for this bug and is intentionally not included here. Closing #1297 in favour of this minimal change.

Tests

Two source-code regression tests added to tests/WP_Ultimo/SSO/SSO_Test.php, matching the style of the existing test_handle_broker_source_* assertions:

  • test_handle_broker_source_jsonp_unattached_returns_must_redirect — asserts the unattached-broker JSONP response now contains 'verify' => 'must-redirect' and no longer contains 'message' => 'Broker not attached'.
  • test_sso_js_handles_must_redirect_verify — asserts sso.js still recognises the must-redirect verify value.

Verification

php -l inc/sso/class-sso.php                                                  -> No syntax errors
vendor/bin/phpcs inc/sso/class-sso.php tests/WP_Ultimo/SSO/SSO_Test.php       -> clean
vendor/bin/phpstan analyse --no-progress inc/sso/class-sso.php                -> No errors
WP_TESTS_DIR=/tmp/wordpress-tests-lib vendor/bin/phpunit --filter SSO_Test    -> 74 tests, 111 assertions, 73 pass

The one failing test (test_sso_redirect_loop_counter_ignores_non_sso_requests) fails identically on unmodified main (verified via git stash + run), so it is a pre-existing issue unrelated to this PR.

Out of scope (and explicitly not included)

  • sso.js checkout_form / /register/ skip from fix(sso): restore front-end auto-SSO via main-site magic-link redirect #1297 — separate concern, should be its own PR with its own reproduction.
  • wp-env Cypress fixture rework (setup-sso-test.php, setup-checkout-form.php, 060-sso-cross-domain.spec.js) — those are CI environment workarounds, not part of this bug.
  • sunrise.php dynamic plugin-dir glob() fallback — also CI environment scoped.

Resolves the actual auto-SSO regression #1297 was trying to address.

Summary by CodeRabbit

  • Bug Fixes

    • Improved SSO authentication flow: unattached brokers now seamlessly redirect users to the attachment process instead of showing an error message.
  • Tests

    • Added validation tests to ensure proper redirect behavior and client-side handling for broker attachment.

Review Change Stack

The unattached-broker JSONP branch in handle_broker() returned
'{code: 0, message: "Broker not attached"}', which sso.js treats
as a denial (assets/js/sso.js lines 101-117). That set the
wu_sso_denied cookie for 5 minutes on the very first subsite
front-end page-load, silently disabling auto-SSO before it could
complete a single round-trip — even when the user was in fact
logged in on the main site.

The denial payload is the wrong signal here: the broker not being
attached on the JSONP probe is the expected state on the first
hit, not a failure. Return 'verify: must-redirect' instead so the
already-existing sso.js branch (assets/js/sso.js lines 93-95)
performs a top-level navigation to '<broker>/sso?return_url=...'.
That request re-enters handle_broker() as a regular page load and
falls through to the non-JSONP redirect, which after #1301 reaches
the cookie-less server handler via getAttachUrl() and completes the
SSO round-trip.

This is the minimal subset of #1297 that the post-#1301/#1302
codebase still needs. The /sso-grant routing fix (#1301) and the
scoped redirect-loop counter (#1302) already shipped, so the rest
of #1297's PHP changes are no longer required.

Regression tests in tests/WP_Ultimo/SSO/SSO_Test.php:
- test_handle_broker_source_jsonp_unattached_returns_must_redirect:
  locks in the new payload and forbids the legacy 'Broker not
  attached' denial.
- test_sso_js_handles_must_redirect_verify: confirms sso.js still
  recognises the must-redirect verify value.
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR modifies the SSO broker attachment flow: when a broker is not attached, the handle_broker() method now returns a JSONP response with code: 200 and verify: 'must-redirect' instead of an error. Two validation tests verify the PHP implementation produces the correct JSONP structure and that the JavaScript client recognizes and handles the must-redirect signal.

Changes

Unattached Broker Must-Redirect JSONP Flow

Layer / File(s) Summary
Backend JSONP Response Change
inc/sso/class-sso.php
The handle_broker() method's unattached-broker JSONP branch now returns a success payload with code: 200 and verify: 'must-redirect', replacing the previous error response. The comment clarifies JSONP limitations and client navigation expectations.
Integration Validation Tests
tests/WP_Ultimo/SSO/SSO_Test.php
Two source-inspection tests validate that the PHP implementation generates the correct must-redirect JSONP structure and that the JavaScript sso.js properly handles the verify: 'must-redirect' signal for top-level navigation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • Ultimate-Multisite/ultimate-multisite#366: Both PRs modify the SSO JSONP handling for an unattached broker in inc/sso/class-sso.php::handle_broker() and corresponding JavaScript/tests to change how the client receives and processes the response.

Suggested labels

status:available, review-feedback-scanned

Poem

🐰 A broker unattached must now redirect,
No error code, but must-redirect set,
The tests keep watch from both sides of the wire,
PHP and JavaScript in tests transpire,
One signal flows where denial once dwelt,
Hop, test, and verify—change deftly dealt! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: modifying the JSONP response from handle_broker() to return verify: must-redirect instead of a broker-not-attached error for unattached brokers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sso-jsonp-broker-not-attached

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/WP_Ultimo/SSO/SSO_Test.php (2)

725-747: ⚡ Quick win

Reconcile the now-stale companion test. This PR changes the unattached-broker JSONP payload from an error into a must-redirect success, but the section header (Lines 725-727) and test_handle_broker_source_returns_jsonp_error_for_unattached_broker still describe a "JSONP error (not redirect)". The regex only asserts that wu.sso( is present, so it keeps passing while documenting the wrong contract. Recommend updating/renaming it or removing it in favor of the new ..._returns_must_redirect test to avoid a misleading test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/SSO/SSO_Test.php` around lines 725 - 747, Update the stale
test and its surrounding docblock to match the new contract: rename
test_handle_broker_source_returns_jsonp_error_for_unattached_broker to something
like test_handle_broker_source_returns_must_redirect_for_unattached_broker (or
delete the test if duplicate with the new must-redirect test), update the
docblock header/comments (the three-line section comment and the method phpdoc)
to state "must-redirect" success for JSONP when broker is unattached, and adjust
the assertion or regex if necessary so it verifies the new behavior (e.g.,
assert the presence of the "must-redirect" payload or the specific code path
that emits wu.sso(... 'must-redirect' ...) instead of the old error
expectation).

768-768: 💤 Low value

Use single quotes for this non-interpolated regex. Flagged by static analysis since there's no variable interpolation.

♻️ Proposed change
-		if (preg_match("/isAttached\(\).*?wp_safe_redirect/s", $source, $matches)) {
+		if (preg_match('/isAttached\(\).*?wp_safe_redirect/s', $source, $matches)) {

As per coding guidelines: "Prefer single quotes for strings; use double quotes only when interpolating variables".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/SSO/SSO_Test.php` at line 768, The regex string passed to
preg_match is using double quotes unnecessarily; change the argument
"isAttached\(\).*?wp_safe_redirect/s" to use single quotes instead (i.e., update
the preg_match call in SSO_Test.php that contains
preg_match("/isAttached\(\).*?wp_safe_redirect/s", $source, $matches)) so the
pattern is single-quoted and all escapes and modifiers remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/WP_Ultimo/SSO/SSO_Test.php`:
- Around line 725-747: Update the stale test and its surrounding docblock to
match the new contract: rename
test_handle_broker_source_returns_jsonp_error_for_unattached_broker to something
like test_handle_broker_source_returns_must_redirect_for_unattached_broker (or
delete the test if duplicate with the new must-redirect test), update the
docblock header/comments (the three-line section comment and the method phpdoc)
to state "must-redirect" success for JSONP when broker is unattached, and adjust
the assertion or regex if necessary so it verifies the new behavior (e.g.,
assert the presence of the "must-redirect" payload or the specific code path
that emits wu.sso(... 'must-redirect' ...) instead of the old error
expectation).
- Line 768: The regex string passed to preg_match is using double quotes
unnecessarily; change the argument "isAttached\(\).*?wp_safe_redirect/s" to use
single quotes instead (i.e., update the preg_match call in SSO_Test.php that
contains preg_match("/isAttached\(\).*?wp_safe_redirect/s", $source, $matches))
so the pattern is single-quoted and all escapes and modifiers remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ca275ac-8e73-4ed9-a260-5d753210fcfd

📥 Commits

Reviewing files that changed from the base of the PR and between ab751e1 and bcfea3e.

📒 Files selected for processing (2)
  • inc/sso/class-sso.php
  • tests/WP_Ultimo/SSO/SSO_Test.php

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42 superdav42 merged commit 5b6876c into main May 29, 2026
8 of 11 checks passed
@superdav42 superdav42 added the review-feedback-scanned Merged PR already scanned for quality feedback label May 29, 2026
superdav42 added a commit that referenced this pull request May 29, 2026
… on anonymous /sso-grant

Anonymous visitors hitting /sso-grant via the must-redirect flow from
PR #1309 were getting a WordPress 404, then a forced wp-login.php
redirect in the first version of this fix. Both are wrong for
intentionally-anonymous broker pages (free-checkout, landing pages,
marketing pages).

This change re-uses the existing denial-signalling pattern already
implemented in handle_broker (the 'invalid' === $verify_code branch):
when handle_server runs on /sso-grant for a not-logged-in user, redirect
back to the broker's /sso URL with sso_verify=invalid appended.

handle_broker on the broker domain recognises the signal, sets
wu_sso_denied=1 on the broker domain for 5 minutes, and redirects to
the original return_url. sso.js (assets/js/sso.js:22) then sees the
cookie and skips the JSONP probe so the user browses anonymously
without further SSO disruption.

Regression test updates accordingly (renamed
test_handle_server_source_redirects_to_login_when_not_logged_in_non_jsonp
to test_handle_server_source_signals_denial_when_not_logged_in_non_jsonp)
and adds an explicit assertDoesNotMatchRegularExpression guard that
catches any future attempt to call wp_safe_redirect(wp_login_url(...))
in that branch.

Refs #1309
superdav42 added a commit that referenced this pull request May 29, 2026
… on anonymous /sso-grant (#1321)

Anonymous visitors hitting /sso-grant via the must-redirect flow from
PR #1309 were getting a WordPress 404, then a forced wp-login.php
redirect in the first version of this fix. Both are wrong for
intentionally-anonymous broker pages (free-checkout, landing pages,
marketing pages).

This change re-uses the existing denial-signalling pattern already
implemented in handle_broker (the 'invalid' === $verify_code branch):
when handle_server runs on /sso-grant for a not-logged-in user, redirect
back to the broker's /sso URL with sso_verify=invalid appended.

handle_broker on the broker domain recognises the signal, sets
wu_sso_denied=1 on the broker domain for 5 minutes, and redirects to
the original return_url. sso.js (assets/js/sso.js:22) then sees the
cookie and skips the JSONP probe so the user browses anonymously
without further SSO disruption.

Regression test updates accordingly (renamed
test_handle_server_source_redirects_to_login_when_not_logged_in_non_jsonp
to test_handle_server_source_signals_denial_when_not_logged_in_non_jsonp)
and adds an explicit assertDoesNotMatchRegularExpression guard that
catches any future attempt to call wp_safe_redirect(wp_login_url(...))
in that branch.

Refs #1309
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-feedback-scanned Merged PR already scanned for quality feedback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant