Skip to content

feat(admin): add SAML Settings admin UI (PR9) - #42

Merged
man4ish merged 1 commit into
mainfrom
feature/saml-admin-ui
Aug 12, 2026
Merged

feat(admin): add SAML Settings admin UI (PR9)#42
man4ish merged 1 commit into
mainfrom
feature/saml-admin-ui

Conversation

@man4ish

@man4ish man4ish commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds an organization-scoped SAML Settings admin UI on top of PR8's CRUD API (omnibioai-auth#49), reusing the exact org-picker "list → detail" navigation shape and permission model SSOSettingsPage.tsx (PR11.3) and OrganizationMFAPolicyPage.tsx (PR11.5.6) already established under the Security section — no new permission (manage_sso reused, not a new manage_saml).
  • Backend: routes_org_saml_proxy.py relays GET/POST/PATCH/DELETE /orgs/{org_id}/saml to omnibioai-auth, the same thin-relay pattern every other *_proxy.py in this repo uses — no authorization decision is made locally, that's entirely omnibioai-auth's job (require_org_permission_or_platform_admin(MANAGE_SSO), pre-existing, unmodified). Also proxies GET /auth/saml/{org_slug}/metadata — deliberately not built on the shared JSON-wrapping _proxy() helper, since that endpoint returns raw XML (application/samlmetadata+xml), not JSON.
  • Frontend: saml.ts (typed API client), SAMLSettingsPage.tsx — view/create/update/delete, a PEM certificate textarea (pre-filled on edit since it's a public cert, unlike a secret), a key/value attribute-mapping editor, a Status control, and a "Download SP Metadata" action (blob download of the existing endpoint, same mechanism compliance.ts's HIPAA report downloads already use — no client-side XML construction, no IdP metadata import, no arbitrary URL fetch).
  • Wired into navigation.ts/AdminApp.tsx as its own Security-section nav item (/security/saml/{orgId} deep-link), gated by the same hasOrganizationsAccess UX signal iam/mfa-policy already use (backend remains authoritative either way).

Key design decisions (see PR9 report for full reasoning)

  • enabled is shown informationally only, never as an actionable "disable login" control. Confirmed via PR8's own report that only status is read by the SAML login/ACS path — the UI is honest about this rather than building a toggle that would silently do nothing.
  • The Status card is the one real, working lifecycle control — labeled "SAML Login Status" / "Activate·Disable SAML Login" (not "Enabled/Disabled", which would collide with the inert enabled field), since flipping status between active/disabled is what actually gates GET /auth/saml/{org_slug}/login.
  • No break-glass override card — unlike OrganizationSSOConfig.enforced/OrganizationMFAPolicy.required, OrganizationSAMLConfig has no enforcement flag with a lockout guard for an override to suspend, so there's nothing analogous to build.
  • Metadata is download-only, not a copyable URL — the proxy path on control-center's own origin isn't the SP's real, IdP-reachable entity/ACS URL (that's already baked into the downloaded document itself via entity_id_for/acs_url_for); surfacing a second, different URL here would be confusing rather than helpful.

Test plan

  • Clean origin/main baseline — backend: 1259 passed; frontend: 495 passed (40 files); build: clean
  • This branch — backend: 1281 passed (exact +22, matching the new proxy test file); frontend: 535 passed (exact +40, matching the new component/data-layer/nav-placement tests, 42 files); build: clean; tsc -b typecheck: 0 errors
  • ruff check on all changed/new backend files: 0 genuinely new findings (confirmed by diffing main.py's pre-existing findings against clean main; the one new finding in the test file mirrors an identical, already-unfixed pattern in test_routes_org_sso_proxy.py, the file it was modeled on)
  • Security sweep: no dangerouslySetInnerHTML, no localStorage/sessionStorage persistence, no client-constructed hrefs from IdP-supplied values, org id always server/routing-resolved (never client state/payload-supplied)
  • Not merging this PR — OPEN for review per task instructions

Scope confirmation

  • Only omnibioai-control-center touched (no changes to omnibioai-auth, TES, workflow-bundles, RAG, billing, or the root repo)
  • PR10 and PR11/SLO not started

🤖 Generated with Claude Code

Adds an organization-scoped SAML admin UI on top of PR8's CRUD API
(auth#49), mirroring SSOSettingsPage.tsx (PR11.3) and
OrganizationMFAPolicyPage.tsx (PR11.5.6) as closely as the two
protocols allow: same permission (manage_sso, reused rather than a new
manage_saml), same org-picker "list -> detail" nav shape under
Security, same loading/denied/not-configured/error state machine.

Backend: routes_org_saml_proxy.py relays the /orgs/{org_id}/saml CRUD
surface to omnibioai-auth (same thin-relay pattern as every other
*_proxy.py here, no authorization decision made locally) plus GET
/auth/saml/{org_slug}/metadata -- deliberately not built on the shared
JSON-wrapping _proxy() helper, since that endpoint returns raw XML.

Frontend: saml.ts (typed API client, mirroring sso.ts/security.ts),
SAMLSettingsPage.tsx (view/create/update/delete, a PEM certificate
textarea, a key/value attribute-mapping editor, and a "Download SP
Metadata" action), wired into navigation.ts/AdminApp.tsx as its own
Security section item alongside IAM/SSO and MFA Policy.

`enabled` is shown informationally only, never as an actionable
"disable login" control -- confirmed via PR8 that only `status` is
read by the SAML login/ACS path. The Status card is labeled and gated
accordingly; no break-glass override card exists, since
OrganizationSAMLConfig has no enforcement flag for one to suspend.
@man4ish
man4ish merged commit 2c763a8 into main Aug 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant