feat(auth): migrate dotAuth (OAuth/OIDC + SAML) from plugin to core#36228
feat(auth): migrate dotAuth (OAuth/OIDC + SAML) from plugin to core#36228swicken wants to merge 138 commits into
Conversation
Legal RiskThe following dependencies were released under a license that RecommendationWhile merging is not directly blocked, it's best to pause and consider what it means to use this license before continuing. If you are unsure, reach out to your security team or Semgrep admin to address this issue. GPL-2.0 MPL-2.0
|
Bring the dotCMS OAuth plugin into core under com.dotcms.auth.providers.oauth with a new dotOAuth app key. Wires the web interceptor, auto-login filter, REST callback, viewtools, and default YAML template. Rejects plugin REST resources whose @path collides with a migrated core resource.
…o-fixes; regenerate openapi
Absorb SAML configuration into the dotAuth portlet so admins edit OAuth and SAML through a single UI, with OAuth/SAML secrets kept in their existing AppSecrets keys. Strategy pattern via ProtocolHandler keeps DotAuthResource thin. dotsaml-config.yml removed in the final commit once the Okta E2E checkpoint passes.
…sanitization Refuse to derive OAuth callback URL from the Host header — require explicit callbackUrl configuration to prevent header injection. Add BoundedOutputStream to CircuitBreakerUrl so OIDC discovery (1MB) and SAML metadata (5MB) fetches cannot exhaust server memory. Fix sanitizeRedirect to allow colons in query strings while still blocking protocol-like colons in the path segment.
…check Front-end SAML logins redirect to content URLs and must not be denied by the back-end role gate. Restrict the no-access check to back-end login paths and reuse SamlWebUtils.isBackEndLoginPage so the back-end path set lives in one place.
- HIGH: add one-time-use replay guard for exchanged id_tokens (token-hash cache); reject re-presentation until exp. Correct misleading nonce Javadoc. - Fix clampToIdpExp dead code (claim exp is a java.util.Date, not a Number). - Bump nimbus-jose-jwt to 9.37.4 (CVE-2025-53864). - Sanitize CRLF in exchange security logs; XML-escape SP-metadata certificate. - Bound OIDCProvider outbound fetches (setMaxResponseBytes); add azp check for multi-audience id_tokens; fail closed on present-but-unverifiable at_hash. - Add dotAuth package to swagger resourcePackages (regenerate openapi.yaml); unify dotAuth tag descriptions; de-duplicate i18n keys; size session/replay caches. - FE: suppress false saved toast on failed post-save reload; remove write-to-void session-TTL/audience/responseType/pkce inputs; delete dead dot-demo. - Tests: OAuthSsrfGuard, exchange CORS/trusted-IdP branches, sanitizeRedirect vectors.
- CircuitBreakerUrl: enforce maxResponseBytes on every consumption path and fail loudly (ResponseSizeLimitExceededException) instead of returning a silently truncated body with a 2xx status - OAuthWebInterceptor: only take over logout for sessions created via OAuth; native/basic/SAML sessions keep the standard logout contract. Route the pre-redirect role gate through AuthAccessDeniedUtil so admins are treated consistently on both flow legs - OIDCProvider: don't emit a dangling '?' on the end-session logout URL - OAuthHelper: deterministic subject resolution (sub/id/user_id/oid, verified email as last resort) instead of a per-login random UUID that broke generic OAuth2 providers after the first login; refuse login when no stable identity can be established - DotSamlResource/SAMLHelper: IdP-initiated logins (no RelayState) by users without back-end access route to '/' when front-end SSO is enabled instead of being 403'd by the defaulted /dotAdmin/ path; centralize enableBackend/enableFrontend semantics in SAMLHelper - DotAuthSessionCacheImpl: make the id_token replay guard's check-then-put atomic; correct the javadoc to state the cache is node-local and document the session-affinity/distributed-provider requirement - dot-auth portlet: round-trip idpName, sPEndpointHostname, signRequests, revocationUrl and groupsUrl through fromView/toPayload so a UI save can no longer blank a working SP hostname or delete stored OAuth secrets - OIDCProviderClaimValidationTest: align the multi-audience test with the azp hardening (was failing on HEAD)
idTokenFingerprint returned null on NoSuchAlgorithmException, which made registerExchangeTokenUse permissive — skipping replay protection for that request. SHA-256 is JVM-mandatory, so this was a documented fail-open on a dead branch. Narrow the catch and throw instead, matching the existing OAuthCrypto.pkceChallengeS256 behavior.
Mirror Nimbus DefaultJWTProcessor's 60s clock-skew allowance in the explicit exp gate so it never rejects a token Nimbus already accepted.
52fc881 to
1bb0b50
Compare
🤖 Bedrock Review —
|
|
Claude finished @swicken's task in 3m 2s —— View job Pull Request Unsafe to Rollback!!! Category: H-1 — One-Way Data Migration on
Category: H-8 — New VTL Viewtools Added to
|
|
Pull Request Unsafe to Rollback!!! Category: H-1 — One-Way Data Migration on
Category: H-8 — New VTL Viewtools Added to
|
A blank callbackUrl now derives scheme://host[:port] from the request (default ports omitted), the same way SAML derives its endpoints, instead of refusing to redirect. This is required for a global config serving multiple sites, where each site's callback host differs and a single stored value can't cover them all. An explicit callbackUrl still acts as an override for proxy edge cases.
…y controls Drops the audit-log anchor in the headless section's emergency controls and its now-orphaned i18n key (dotauth.action.audit-log).
…th login The post-login redirect target now prefers dotCMS's server-side request cache (session REDIRECT_AFTER_LOGIN, set by core's login-required logic for the real protected page), falling back to the referrer param and then the request URI. Previously the interceptor captured the login-trigger URL itself, so after login the user was bounced back to /dotCMS/login (not a real page) and got a 404. sanitizeRedirect() still guards the value against open redirect. OAuth-only; no SAML code touched.
Closes #35555
Proposed Changes
Migrates the OAuth 2.0 / OIDC provider from the legacy dotOAuth plugin into core, adds SAML support as a first-class protocol alongside OAuth, introduces a headless token-exchange flow for front-end SPAs, and replaces the old dotsaml-config / dotOAuth app-key editors with a unified dotAuth portlet under Settings.
Backend (
dotCMS/src)REST API —
/v1/dotauthDotAuthResource, DTOs, wired intoDotRestApplication)..well-known/openid-configurationand returns parsed issuer, endpoints, JWKS URI, and signing algorithms.Protocol layer
DotAuthProtocolenum +ProtocolHandlerstrategy interface.OAuthProtocolHandler— extracted from the legacy plugin; hardened OIDCisscheck,algvalidation, session binding.SamlProtocolHandler— new; own secret-key set, metadata endpoint, custom attribute mapping.dotOAuth→dotAuth.Headless token exchange
/v1/dotauth/exchange) — accepts an authorization code from an SPA, validates against the OIDC provider, provisions or resolves the user, and returns a dotCMS session-ref bearer token.DotAuthSessionmodel + cache for session-ref tokens;DotAuthSessionCredentialProcessorwired into the filter chain.BuildRolesStrategy— configurable role sync (merge / replace / none) on each login.Security hardening
OAuthSsrfGuard).id_token.Portlet registration
Task260420AddDotAuthPortletToMenu) registers the dotAuth portlet in the Settings layout.portlet.xmlregistration;/apps/dotsaml-configredirects to the dotAuth portlet; dotsaml-config hidden from the Apps grid.Frontend (
core-web)libs/portlets/dot-auth(new Nx library)protocol, list + config SignalStores.Modernization
p-inputSwitch→p-toggleswitch,pTextarea+TextareaModule), Tailwind v4 var-shorthand normalization.ANGULAR_STANDARDS(signals,@if/@for, OnPush).Testing
DotAuthResource, protocol handlers, exchange guard paths, role-strategy, OIDC claim validation, SSRF guard, mapper round-trips.Rollback notes
The startup task inserts a
dotAuthportlet entry intocms_layouts_portletsand shifts existing portlet order values. On rollback to N−1 this entry becomes orphaned. Manual cleanup required:A CDN purge of the Angular admin bundle may also be needed if a caching proxy is in front of the admin UI.
Checklist
Stats
126 files changed, ~16k LOC added