Skip to content

Session Log: OAuth2 authenticated path completed end-to-end; ADR 014 visibility verified; reverse proxy fixed

Date: 2026-08-20 (second session of the day; continues 2026-08-20-checkauthstatus-forces-logout-root-caused.md) Participants: ys2n, Claude Code Outcome: ADR 014's authenticated Solr proxy path now works end-to-end and is proven correct, not merely functional. Five PRs merged (#128–#132). The 4th and last OAuth2 defect is fixed and verified live; visibility filtering is verified with a positive and negative discriminator; the http:// RelayState bug is fixed. Two regression tests and two how-tos are now in the repo.

⚠ Hand-written, not machine-generated. Verifying /oauth/userinfo necessarily returns the authenticated user's OIDC profile claims, and the test identity is authmapped to a real account — so the raw transcript contains a real name and email. save-session-log.py was deliberately not run. (Yuji assessed the PII itself as not a concern; the log is still hand-written so the transcript is not republished verbatim into a public repo.)


What shipped

PR
#128 mandala_saml_oauth — exempts OAuth2 Bearer requests from checkAuthStatus()
#129 Live verification of that fix
#130 scripts/verify-oauth2-userinfo.{py,sh} + how-to
#131 reverse_proxy / trusted_host_patterns in settings.php
#132 scripts/verify-solr-proxy-visibility.py + how-to

main clean, no open PRs.

The headline result

The full chain — SAML login → OAuth2 authorization_code → Bearer /oauth/userinfo → proxy query with the Redis visibility token — works, and the right people see the right things:

anonymous uid 600
images-11-95599 (private, their collection) hidden visible
images-11-3 (private, someone else's) hidden hidden
1631777 (public) visible visible
*:* 751,032 751,566

Client fq injection is stripped; a forged sid falls back to anonymous.

Three false alarms, all mine, all now documented

This is the part worth carrying forward. Each looked like a product defect and was a testing or reasoning error:

  1. The designed fix was a no-op. The previous session's fix tested $this->account instanceof TokenAuthUserInterface. @current_user is an AccountProxy that wraps the real account, so it never matches. Caught by reading core before shipping. Detection now uses SimpleOauthRequestPolicyInterface::isOauth2Request().
  2. A post-deploy test raced the deploy. The watcher gated on the module directory appearing; deploy_backend.yml starts the container ~50s before import full site configuration enables modules, and a ServiceProvider only runs for an enabled module. The replay landed 12 seconds inside that window and reported a confident FAIL on a working fix.
  3. id is not unique in kmassets. Four documents share id 1821 — the places/subjects/terms taxonomy shadows (ADR 006) plus an audio-video asset. Using it as a fixture produced got 4, expected 0, which reads exactly like a visibility breach. The four visible documents were the public shadows; the private asset was correctly withheld.

A fourth correction, from the earlier session: the watchdog evidence blamed on checkAuthStatus() for Xiaoming's logout report was our own test traffic — every Session closed belonged to uid 600 inside our own testing windows.

Reverse proxy — what the investigation turned up

reverse_proxy/trusted_host_patterns were never configured, so Request::getUri() returned http:// and the SAML RelayState was built with the wrong scheme. Two findings beyond the obvious fix:

  • Each VPC carries two CIDR blocks, not one (staging 10.130.109.0/24 + 10.130.112.0/24; production 10.130.110.0/24 + 10.130.113.0/24), and health checks arrive from both. Listing only the primaries half-works — Drupal falls back silently rather than erroring, so the scheme would be wrong intermittently with no log line.
  • trusted_host_patterns fails closed: an unmatched Host is a 400, and the health check on / expects 200–299, so a wrong pattern set takes targets unhealthy. Patterns were tested against 27 real hostnames and 5 hostile ones before deploying. The exact health-check Host could not be captured (ALB access logs exclude health checks; no packet-capture tooling on dev-0), so the IPv4 pattern is deliberately broad and the code comment says so rather than asserting AWS behaviour as fact.

Verified after deploy: 8/8 host-header checks, RelayState now https://, both ALB targets stayed healthy, and the OAuth2 regression test still passes.

Still open

  1. NetBadge logout — the IdP advertises no SingleLogoutService and logout_goto_url is unset, so a Drupal logout never ends the NetBadge session. Needs the canonical logout URL from UVA ITS and a trusted.url.domains entry (absent entirely today). The note carries a classification flag for Yuji.
  2. 8h vs 23d session expiry — upstream MR !48 is RTBC against our 4.x branch and fixes where the user lands; whether 8h is the right lifetime is undecided.
  3. Cron re-run loop on dev-0 (26 entries/48h) — cause still uninvestigated.
  4. A one-off TypeError: Cannot assign null to property TokenAuthUser::$consumer.
  • docs/deferred/simplesamlphp-checkauthstatus-forces-logout-oauth-and-maybe-browser.md
  • docs/deferred/saml-logout-does-not-terminate-netbadge-idp-session.md
  • docs/deferred/saml-session-expires-8h-forcing-logout-mid-session.md
  • docs/dev-notes/howto-verify-oauth2-authenticated-path.md
  • docs/dev-notes/howto-verify-solr-proxy-visibility.md