feat(sso): Test Login admin flow across Custom OIDC, SAML, LDAP#28034
feat(sso): Test Login admin flow across Custom OIDC, SAML, LDAP#28034siddhant1 wants to merge 53 commits into
Conversation
…ocol selector (#27312) Backend: - Add discoveryUri and emailClaim fields to authenticationConfiguration schema - Add emailClaim priority chain in SecurityUtil (claimsMapping > emailClaim > fallback) - Add syncFieldsFromDiscoveryUri auto-sync on save (authority, clientId, callbackUrl) - Add TestLoginHandler with OIDC popup flow (initiate + callback) and LDAP inline test - Add Test Login endpoints in SystemResource (initiate, callback, LDAP) - Add SAML email attribute check before NameID fallback - Exclude test-login callback from JwtFilter Frontend: - Add protocol selector (OIDC / SAML / LDAP) replacing 8 provider cards - Add OIDC provider dropdown options with Discovery URI templates - Add TestLoginButton component (popup + postMessage listener) - Add ClaimSelector component (claims table + email claim selection) - Add AuthModeWidget replacing Public/Confidential jargon - Hide auto-derived fields (authority, publicKeyUrls, tokenValidationAlgorithm) - Wire Test Login into SSOConfigurationForm Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ields - Add test-login/initiate to JwtFilter EXCLUDED_ENDPOINTS (popup window doesn't carry JWT token — endpoint only redirects to IdP, no data exposure) - Hide authorizer fields: botPrincipals, principalDomain, enforcePrincipalDomain, enableSecureSocketConnection, useRolesFromProvider, allowedEmailRegistrationDomains, allowedDomains, defaultOAuthRole (these will be auto-filled by Test Login or moved to advanced settings) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e-validation to Test Login - Hide emailClaim, discoveryUri from form (set via Test Login, not manual input) - Hide adminPrincipals, enableSelfSignup, enableAutoRedirect (auto-filled or advanced) - Test Login now validates configuration before opening popup — calls validateSecurityConfiguration first, shows error inline if validation fails, only opens popup if config is valid Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 404 was caused by Dropwizard/Jersey rejecting void return type for @get methods. Rewritten to return proper JAX-RS Response objects: - handleInitiate returns Response.temporaryRedirect(authUrl) instead of HttpServletResponse.sendRedirect - handleCallback returns Response.ok(html, "text/html") instead of writing to HttpServletResponse directly - Removed HttpServletResponse dependency from endpoints Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved prompt to hidden in all provider-specific UI schemas (Google, Azure, Okta, Auth0/Cognito, Custom OIDC) — this is an advanced setting that rarely needs user configuration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause of 404: ConfigResource owns @path("/v1/system/config") and SystemResource is at @path("/v1/system"). When the test-login endpoints were in SystemResource at @path("/config/auth/test-login/..."), Jersey routed them to ConfigResource (which owns /config/*) instead. ConfigResource had no matching sub-path, so it returned 404. Fix: moved all test-login endpoints to ConfigResource where they belong under the /config/auth path prefix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rm data TestLoginHandler no longer depends on AuthenticationCodeFlowHandler being initialized. Instead, it: 1. Reads discoveryUri, clientId, clientSecret, scope from query params (passed by frontend from the unsaved form data) 2. Fetches OIDC discovery document directly via OIDCProviderMetadata.resolve() 3. Builds authorization URL from the discovery metadata 4. On callback, builds token request directly using Nimbus SDK (ClientSecretBasic for confidential, ClientID-only for public) 5. Stores clientId/secret/discoveryUri in HTTP session for the callback This means Test Login works BEFORE saving — user fills fields, clicks Test Login to validate, then saves. No dependency on server state. Frontend: TestLoginButton now passes form data (discoveryUri, clientId, clientSecret, scope) as query params to the initiate endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ery URI resolve() expects an Issuer (base URL) and appends .well-known/openid-configuration. We have the full discovery URI, so use parse() with direct HTTP fetch instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_mismatch Root cause: Test Login used a separate callback URL (/api/v1/system/config/ auth/test-login/callback) that wasn't registered in the IdP's OAuth app. Google (and other IdPs) reject unregistered redirect URIs with 400. Fix: Reuse the same /callback URL that's already registered in the IdP. Differentiate Test Login callbacks using state prefix "test-login:". Changes: - TestLoginHandler.handleInitiate(): uses registered callbackUrl (from form data or defaults to /callback), prefixes state with "test-login:" - AuthCallbackServlet.doGet(): checks state prefix, routes test-login callbacks to TestLoginHandler.handleCallback() - Removed separate /auth/test-login/callback endpoint from ConfigResource - Removed callback from JwtFilter excluded endpoints (servlet handles it) - Frontend passes callbackUrl from form data to initiate endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ailClaim set Bug 1: "Client ID is required" on first click — formData was missing from useCallback dependency array, causing stale closure. Added formData to deps. Bug 2: Save button now disabled for new OIDC setups until emailClaim is confirmed via Test Login → Claim Selector flow. Existing configs can still save freely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, Other) - Provider dropdown appears at top of OIDC config form - Each option shows icon, label, and IdP-specific help text - Selecting a provider pre-fills Discovery URI template and updates provider/providerName/oidcConfiguration.type in form data - Default changed from Google to Custom OIDC (generic) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. TestLoginButton: reject Discovery URIs with unresolved placeholders
({tenant-id}, {your-domain}) — show clear message to replace them
2. SystemResource.validateSecurityConfig: call syncFieldsFromDiscoveryUri
and autoPopulatePublicKeyUrlsIfNeeded BEFORE validation, so authority
and publicKeyUrls are auto-derived from discoveryUri. Fixes "Authority
is required" error when saving with discoveryUri set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…est Login
1. Restored OIDC prompt field to visible in all provider UI schemas
(was hidden, now shows in advanced config)
2. Discovery URI: only Google pre-fills a default value. Azure, Okta,
Auth0, Cognito now show empty field with format hint in help text.
Prevents users from accidentally using placeholder templates.
3. Validation before Test Login:
- Backend: validateSecurityConfiguration accepts ?context=testLogin
query param. When set, skips adminPrincipals and principalDomain
validation (these are set via Claim Selector after Test Login)
- Frontend: TestLoginButton calls validation API with context=testLogin
before opening popup. Shows validation errors inline if config invalid.
- Save button has no separate validation — Test Login already validated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The parameter was added to the outer method but not passed to the private validateAuthorizerConfiguration method where the actual adminPrincipals/principalDomain checks happen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…h method 1. Default OIDC scope changed from "openid email profile" to "openid email profile offline_access" — ensures refresh tokens for Azure/Okta/Auth0/Cognito. Google ignores unsupported scopes. No regression for existing configs (their saved scope is unchanged). 2. Scope field hidden from main form (default covers most cases) 3. OIDC prompt field hidden from main form (advanced setting) 4. Okta clientAuthenticationMethod hidden (advanced setting) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…errors on OIDC The @Valid annotation triggers Jakarta Bean Validation on the entire SecurityConfiguration object tree, including nested ldapConfiguration and samlConfiguration schemas. This causes 400 errors with messages like "userBaseDN must not be null" when validating an OIDC config. Removed @Valid — our custom validateSecurityConfiguration() handles provider-specific validation correctly (only checks relevant fields based on the provider type). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The JSON schema default was updated but 6 hardcoded fallbacks in frontend code still used "openid email profile" without offline_access. Updated: - SSOUtils.ts (2 occurrences) - AuthProvider.util.ts (3 occurrences) - TestLoginButton.component.tsx (1 occurrence) - conf/openmetadata.yaml (OIDC_SCOPE default) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uthMethod to visible 1. Popup communication: replaced window.opener.postMessage with localStorage. window.opener is null after redirect chains (initiate → IdP → callback). localStorage works across same-origin windows regardless of redirects. Popup stores result, parent reads via storage event + close-check fallback. Popup always closes itself. 2. Restored scope, prompt to visible in all 5 provider UI schemas (they were hidden but should be in advanced config = visible). 3. Restored Okta clientAuthenticationMethod to visible. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…veryUri discoveryUri is a top-level field in authenticationConfiguration — the single source of truth for both public and confidential flows. The nested oidcConfiguration.discoveryUri is synced from it on save. - Made top-level discoveryUri visible with title and placeholder - Hidden nested oidcConfiguration.discoveryUri in all 6 provider schemas (OIDC, Google, Azure, Okta, Auth0/Cognito, Custom OIDC) - One discoveryUri field visible regardless of client type Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…estLoginButton handleOidcProviderChange was writing discoveryUri to the nested oidcConfiguration.discoveryUri (hidden), not the top-level discoveryUri (visible). TestLoginButton was also checking nested first. Fixed both to use the top-level field as primary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… default Test Login now mirrors the actual OIDC flow by passing: - prompt (consent/login/none) - maxAge - clientAuthenticationMethod (client_secret_basic/client_secret_post) Backend: TestLoginHandler reads these from query params, applies to auth URL, and uses correct client auth method for token exchange. Also reverted offline_access from default scope — back to "openid email profile" everywhere (schema, SSOUtils, AuthProvider.util, TestLoginButton, openmetadata.yaml). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…normalization - backend: add normalizeForPersistence and hydrateForResponse in SystemRepository. deterministic priority-based mirroring between root and nested OIDC fields, always-overwrite derivation of authority and publicKeyUrls from discoveryUri, Azure tenant extraction across commercial/gov/china cloud variants. - backend: SystemResource calls normalizeForPersistence on validate/PUT, hydrateForResponse on GET. Legacy configs without discoveryUri are preserved unchanged — zero regression for existing customers. - ui: mirror canonical and legacy OIDC fields in form onChange so RJSF's schema required check passes before hitting the API (workaround only; backend handles partial payloads on its own). - ui: validation state machine — Validate button posts to /validate?context=testLogin, enables Test Login on success. Save gated on Test Login + claim-selector confirm. Reset only when actual validation inputs change, not post-test fields. - ui: hide Azure tenant (backend-derived). Advanced Configuration accordion collapses non-essential OIDC fields via SSO_ADVANCED_OIDC_FIELDS. - tests: SystemRepositoryNormalizeTest covers mirror directions, Azure tenant extraction (all cloud variants), legacy preservation, null safety, no-network-call on hydrate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adminPrincipals is often configured by email (e.g. "alice@company.com")
while the derived userName is typically the email local part ("alice").
The previous check only looked up userName in adminPrincipals, so a user
configured by email never got promoted to admin on login.
- SecurityUtil: new isAdminPrincipal(adminPrincipals, userName, email)
helper that matches either form.
- AuthenticationCodeFlowHandler: use helper on existing and new OIDC
user paths.
- SamlAuthServletHandler: same for existing and new SAML user paths.
- SamlAssertionConsumerServlet: same for JWT token admin flag.
LDAP intentionally unchanged — LDAP principals are usernames, not emails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two coupled changes enabling the full Test Login gated flow across both
OIDC and SAML:
## SAML Test Login
- New TestSamlHandler builds a temporary Saml2Settings from form-provided
IdP details, redirects popup to IdP with RelayState="saml-test-login:<uuid>",
and on callback verifies the SAMLResponse + flattens attributes into the
same {success, claims} payload that OIDC Test Login produces.
- New POST /v1/system/config/auth/test-login/saml-initiate endpoint using
@FormParam so Jersey reliably parses the form-urlencoded body.
- AuthCallbackServlet.doPost now routes SAML Test Login callbacks by
RelayState prefix (parallel to the existing doGet state-prefix routing
for OIDC Test Login).
- TestLoginResponses extracted from TestLoginHandler so both OIDC and SAML
use the same popup HTML / localStorage handoff.
- UI: TestLoginButton branches by provider; SAML uses a hidden-form POST
with target=_blank so the cert fits cleanly in the body.
- UI: SAML main form now shows only IdP Entity ID, SSO Login URL, X.509
Certificate, NameID Format. SP fields are surfaced in a banner and
derived server-side. SP certs, signing flags, and security options move
to an Advanced Configuration accordion via SSO_ADVANCED_SAML_FIELDS.
- ClaimSelector handles multi-valued SAML attributes (arrays joined for
display, single values stringified as before).
## OIDC validator refinement
- New SystemRepository.validateDiscoveryUriReachable runs at the top of
validateSecurityConfiguration for any OIDC provider with a discoveryUri.
Short-circuits with a single clear error ("Could not reach Discovery
URI") when the URI is unreachable, invalid JSON, missing issuer/jwks_uri,
or (for Azure) doesn't match the Azure AD URL shape.
- Removed duplicate discoveryUri fetches from Azure, Okta, Auth0, and
Cognito validators now that the upstream check handles reachability.
Each validator keeps only its provider-specific semantic checks (issuer
format, required endpoints, client credential validation).
- Improved error messages where fields are backend-derived:
Azure: "Tenant could not be determined from Discovery URI…"
Okta/Auth0/Cognito: "domain/URL could not be verified…"
Custom OIDC: "Discovery document is missing required endpoints…"
## Validation state machine (shared across OIDC and SAML)
- areMainFieldsFilled and didValidationInputsChange now branch on
provider so SAML uses IdP fields and OIDC uses discoveryUri + client
credentials. validationStatus resets only when fields that actually
affect validation change.
- Test Login and Validate button gates now accept either OIDC or SAML.
## Tests
8 new tests in SystemRepositoryNormalizeTest for the upstream discoveryUri
check (unreachable, invalid JSON, missing issuer, Azure shape mismatch,
LDAP scope skip, valid happy path, invalid URL format, legacy config
fall-through).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TestLoginHandler: PKCE code_challenge sent by default (unless disablePkce=true), code_verifier stored in session and included in token exchange. Nonce now conditional on useNonce param. customParams parsed from JSON and merged into auth URL. Session cleanup includes CODE_VERIFIER. clientAuthMethod extracted to constant. - TestSamlHandler: replaced HttpSession storage with ConcurrentHashMap keyed by RelayState UUID. Fixes "Session expired" error caused by SameSite=Lax blocking cookies on IdP's cross-origin POST callback. Entries auto-cleaned after 5 min TTL. - SystemRepository: normalizeForPersistence now populates serverUrl (derived from callbackUrl). discoveryUri mirror changed to one-way root→nested only (Option B) — legacy configs with only nested discoveryUri no longer trigger derivation on unrelated PATCHes. Removed dead autoPopulatePublicKeyUrlsIfNeeded method. - SystemResource: PATCH handler calls normalizeForPersistence after patch apply. PUT handler @Valid removed (fires before normalize, rejects partial payloads that normalize would fill). - TestLoginButton: passes disablePkce, useNonce, customParams to backend for OIDC Test Login. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch OIDC Test Login initiate from GET+query-params to POST+form-body so clientSecret stays out of URLs, server logs, and browser history. - ConfigResource: endpoint changed to POST with @FormParam for each OIDC field (same pattern as SAML initiate). - TestLoginHandler: accepts params as method arguments instead of req.getParameter() (Jersey consumes form body before handler runs). - Redirect changed from 307 (temporaryRedirect) to 302 so the browser converts POST→GET when following the redirect to the IdP. 307 preserved the POST method, causing Azure to reject with "client_id missing in body". - TestLoginButton: OIDC now uses hidden-form POST with target=_blank (same pattern as SAML). Both protocols use identical transport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LDAP pre-save Test Login so admins can verify form-provided LDAP config without requiring a saved authenticator. UI collects user credentials via an inline modal (no popup — LDAP has no external IdP to redirect to). ClaimSelector is skipped since LDAP's email attribute is explicit; principalDomain and adminPrincipal are auto-filled from the test user's resolved email. ## Backend - TestLdapHandler: stateless handler that accepts form-provided LdapConfiguration + user credentials. Admin bind → search by mailAttributeName → user bind → read mail attribute. Uses UnboundID LDAPConnection directly; no singletons, no DB writes, no user creation. Try-with-resources for connection lifecycle. - ConfigResource: POST /v1/system/config/auth/test-login/ldap-initiate (JSON body with ldapConfiguration, email, password). Inline LdapTestLoginRequest DTO. - JwtFilter: whitelisted the new endpoint. - LdapAuthenticator: admin promotion now checks both userName and email via SecurityUtil.isAdminPrincipal — matches the fix already applied to OIDC/SAML. Fixes the case where adminPrincipals is saved as a full email but LDAP login was comparing against the local-part username. ## Frontend - TestLoginButton: LDAP branch opens an Ant Design Modal with email and password inputs. Submits JSON to /ldap-initiate, calls onSuccess with derivedPrincipalDomain + suggestedAdminPrincipal. No popup. - SSOConfigurationForm: handleTestLoginSuccess branches on provider — LDAP bypasses ClaimSelector entirely and directly applies principalDomain + adminPrincipals (emailClaim is left unset so the jwtPrincipalClaims fallback picks up "email" from the OM-issued JWT). areMainFieldsFilled and didValidationInputsChange handle LDAP fields. - TestLogin.interface: ldapConfiguration added to form data type. ## Tests (44 new, 67 total) - TestLoginHandlerTest (17) — OIDC validation branches, callback validation, buildClientAuthentication helper (basic/post/fallback), buildTestLoginResult (timestamp filtering, email detection, domain derivation, null handling). - TestSamlHandlerTest (14) — SAML validation branches, RelayState routing, buildSamlSettings (IdP fields, NameID format, security flags off), buildClaimsFromAuth (single/multi-value flattening, empty/null handling). - TestLdapHandlerTest (13) — uses UnboundID InMemoryDirectoryServer (real LDAP in-process). Covers input validation, happy path, wrong admin password, user not found, wrong user password, wrong userBaseDN, wrong mailAttributeName, server unreachable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Formatting-only changes: import reordering and long argument list splitting per Google Java Format. No logic changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace antd Card/Button/Typography/Upload.Dragger in SSOConfigurationForm with @openmetadata/ui-core-components Button + FileTrigger; keep RJSF and its widgets untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ TypeScript Types Auto-UpdatedThe generated TypeScript types have been automatically updated based on JSON schema changes in this PR. |
CI UI Checkstyle failed because prettier and organize-imports produced a diff against committed SSO playwright fixtures, three SSOTestLogin spec files, and one popupLifecycle component; this commit lands the formatter's output so the tree matches the checkstyle baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| @POST | ||
| @Path("/auth/test-login/initiate") | ||
| @Consumes(MediaType.APPLICATION_FORM_URLENCODED) | ||
| @Operation( | ||
| operationId = "testLoginInitiate", |
| "v1/collate/apps/support/login", | ||
| "v1/system/config/auth/test-login/initiate", | ||
| "v1/system/config/auth/test-login/saml-initiate", | ||
| "v1/system/config/auth/test-login/ldap-initiate"); |
| static Response buildHtmlErrorResponse(String message) { | ||
| String sanitized = message.replace("'", "\\'").replace("\n", " "); | ||
| String html = | ||
| "<!DOCTYPE html><html><body>" | ||
| + "<p>Test Login Error: " | ||
| + message | ||
| + "</p>" |
| * Confidential (secret filled), the payload is just cleaned. For Public | ||
| * (secret blank), id/discoveryUri are promoted to root clientId/authority, | ||
| * the secret is dropped, and a slim oidcConfiguration with just discoveryUri | ||
| * is kept so backend validators that dereference oidcConfig.discoveryUri | ||
| * (e.g. CustomOidcValidator.extractDiscoveryUri) don't NPE. Non-OIDC |
| <span | ||
| className="tw:text-xs tw:text-tertiary tw:truncate" | ||
| title={row.value}> | ||
| {row.value || '—'} | ||
| </span> |
| <dt className="tw:text-tertiary">{t('label.refresh-token')}</dt> | ||
| <dd> | ||
| {result?.hasRefreshToken | ||
| ? `✓ ${t('label.received')}` | ||
| : `⚠ ${t('message.no-refresh-token')}`} | ||
| </dd> |
| className="tw:text-sm tw:font-medium tw:text-primary" | ||
| data-testid="email-claim-status-set"> | ||
| <code>{emailClaim}</code> | ||
| {' ✓ '} | ||
| <span className="tw:text-xs tw:text-tertiary tw:font-normal"> | ||
| {t('message.email-claim-verified')} |
| } catch { | ||
| // HEAD against ldap:// won't speak HTTP; the connection refusing here | ||
| // means the port isn't bound. Skip rather than fail. | ||
| } |
Code Review 🚫 Blocked 0 resolved / 4 findingsImplements an SSO Test Login flow for OIDC, SAML, and LDAP with gating mechanisms, but critical security findings include unauthenticated SSRF via excluded endpoints, reflected XSS in error responses, and arbitrary credential probing in the LDAP handler. 🚨 Security: Test-login endpoints bypass authentication — unauthenticated SSRF📄 openmetadata-service/src/main/java/org/openmetadata/service/security/JwtFilter.java:109-111 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/auth/TestLoginHandler.java:158-162 📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/system/ConfigResource.java:233-247 The three new test-login endpoints ( Any anonymous user can POST to The LDAP endpoint is similarly dangerous: an unauthenticated attacker can probe arbitrary LDAP servers from the application's network position. Suggested fix🚨 Security: Reflected XSS in test-login HTML error responses📄 openmetadata-service/src/main/java/org/openmetadata/service/security/auth/TestLoginResponses.java:50 📄 openmetadata-service/src/main/java/org/openmetadata/service/security/auth/TestLoginResponses.java:68-72 Both At line 72, Suggested fix
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Gitar
|
🔴 Playwright Results — 26 failure(s), 18 flaky✅ 4041 passed · ❌ 26 failed · 🟡 18 flaky · ⏭️ 86 skipped
Genuine Failures (failed on all attempts)❌
|



Summary
TestLoginHandler,TestSamlHandler,TestLdapHandler) talk to the IdP using the form's draft config so admins can validate credentials, certs, and discovery URIs without ever applying a broken config.clientId,clientSecret,idpX509Certificate,dnAdminPassword, etc.) is edited. Includes a mode=existing overlay so admins can re-verify a saved config without retyping secrets, and a claim selector that surfaces every claim the IdP returned and remembers the chosenemailClaim.sso-test-loginPlaywright project plus unit-test parity forTestLdapHandlerTest/TestLoginHandlerTest/TestSamlHandlerTest.Test plan
docker compose -f docker/development/docker-compose.yml --profile sso-test up -dbrings up mock-OIDC (:9090) and OpenLDAP (:1389)KEYCLOAK_SAML_PORT=8081 docker compose -f docker/local-sso/keycloak-saml/docker-compose.yml up -dfor the SAML fixtureSSO_USERNAME=azure.saml@openmetadata.local SSO_PASSWORD=OpenMetadata@123 KEYCLOAK_SAML_BASE_URL=http://localhost:8081 yarn playwright test --project=sso-test-login— 13 tests cover happy path / failure / mode=existing / lockout-risk gate per provider; 5× back-to-back locally showed zero flakesmvn test -pl openmetadata-service -Dtest=TestLdapHandlerTest,TestLoginHandlerTest,TestSamlHandlerTest🤖 Generated with Claude Code
Summary by Gitar
ssoAuth.tsandsso.ts.popupLifecycle.tsand UI test spec files to ensure stable test execution.This will update automatically on new commits.