Skip to content

fix: resolve SAML entity ID from metadata when external_key is null#3933

Merged
fhanik merged 3 commits into
cloudfoundry:developfrom
fhanik:pr/fix-saml-idp-resolution
Jun 6, 2026
Merged

fix: resolve SAML entity ID from metadata when external_key is null#3933
fhanik merged 3 commits into
cloudfoundry:developfrom
fhanik:pr/fix-saml-idp-resolution

Conversation

@fhanik
Copy link
Copy Markdown
Contributor

@fhanik fhanik commented Jun 4, 2026

SAML login fails for IDPs bootstrapped from uaa.yml


Problem

A SAML IDP configured in uaa.yml with a URL-type metadata location (metadataTrustCheck: true + metaDataLocation: https://...) cannot authenticate users. Two distinct failure modes are observed depending on which ACS URL the IDP targets:

Failure 1 — relying_party_registration_not_found (primary reported symptom)

When the IDP posts its SAML response to the IDP-alias ACS URL (/saml/SSO/alias/{idpAlias}), UaaRelyingPartyRegistrationResolver.resolveFromRequest performs an endsWith check against the SP entity-ID alias. Because the IDP alias does not end with the SP alias string, the resolver returns null, and Spring Security throws:

Saml2AuthenticationException{error=[relying_party_registration_not_found]
  No relying party registration found}

Failure 2 — invalid_issuer (secondary; same root cause)

When the IDP posts to the canonical SP ACS URL (/saml/SSO/alias/{spAlias}), the issuer is extracted from the SAML response and used to look up the IDP by external_key. Because external_key is null for URL-type bootstrap IDPs (see root cause below), the lookup returns nothing. The default-stub registration is returned instead, and Spring Security throws:

Saml2AuthenticationException{error=[invalid_issuer]
  Invalid issuer [https://idp.example.com/...]}

Root Cause

BootstrapSamlIdentityProviderData.setIdentityProviders only calls def.setIdpEntityId(...) for DATA (inline XML) metadata type, never for URL type. JdbcIdentityProviderProvisioning stores external_key = saml.getIdpEntityId(), so all URL-type bootstrap IDPs are persisted with external_key = null. Both lookup paths that depend on external_key therefore silently fail.


Fix

1. UaaRelyingPartyRegistrationResolver — fallback to URL alias as registration ID

When resolveFromRequest finds that the URL path segment does not end with the SP alias but a SAMLResponse parameter is present, it now falls back to using the URL path segment (the IDP alias) directly as the relyingPartyRegistrationId. ConfiguratorRelyingPartyRegistrationRepository can then find the IDP by origin key.

// Fallback for IDP-initiated SSO: when the ACS URL carries the IDP alias instead of
// the SP alias, the endsWith check does not fire and relyingPartyRegistrationId stays null.
if (relyingPartyRegistrationId == null && resolvedEntityId != null && samlResponseParameter != null) {
    relyingPartyRegistrationId = resolvedEntityId;
}

2. ConfiguratorRelyingPartyRegistrationRepository — resolve entity ID from metadata when external_key is null

In the loop that matches IDPs by entity ID, if idpEntityId (sourced from external_key) is null, the repository now resolves the entity ID on-the-fly from the stored metadata via SamlIdentityProviderConfigurator.getExtendedMetadataDelegate.

String resolvedEntityId = identityProviderDefinition.getIdpEntityId();
if (resolvedEntityId == null) {
    resolvedEntityId = configurator.getExtendedMetadataDelegate(identityProviderDefinition)
            .getAssertingPartyMetadata().getEntityId();
}
if (registrationId.equals(resolvedEntityId)) {
    return createRelyingPartyRegistration(...);
}

Changes

  • server/.../saml/UaaRelyingPartyRegistrationResolver.java — fallback registration ID resolution from URL alias
  • server/.../saml/ConfiguratorRelyingPartyRegistrationRepository.java — on-the-fly entity ID resolution from metadata when external_key is null
  • server/.../saml/Saml2TestUtils.java — new responseWithAssertions(issuer, pemKey, pemCert) overload for signing assertions with custom credentials
  • uaa/.../mock/saml/BootstrapSamlIdpSsoMockMvcTests.java — new regression test covering both failure modes

Test Plan

  • BootstrapSamlIdpSsoMockMvcTests#samlResponse_viaIdpAliasUrl_authenticatesSuccessfully — reproduces relying_party_registration_not_found; passes with fix in UaaRelyingPartyRegistrationResolver
  • BootstrapSamlIdpSsoMockMvcTests#samlResponse_viaSpAliasUrl_authenticatesSuccessfully — reproduces invalid_issuer; passes with fix in ConfiguratorRelyingPartyRegistrationRepository
  • Run full ./gradlew integrationTest to verify no regression in existing SAML flows

Made with Cursor

@fhanik fhanik requested a review from duanemay June 5, 2026 14:13
@fhanik
Copy link
Copy Markdown
Contributor Author

fhanik commented Jun 5, 2026

We need to decide if we should ingest SAML metadata when bootstrapping SAML IDP providers from uaa.yml

duanemay
duanemay previously approved these changes Jun 5, 2026
@github-project-automation github-project-automation Bot moved this from Inbox to Pending Merge | Prioritized in Foundational Infrastructure Working Group Jun 5, 2026
…available.

Fallback to previous behavior, continue startup, if metadata is not available. But log the error.
@fhanik fhanik force-pushed the pr/fix-saml-idp-resolution branch from 7a3006a to e2be18c Compare June 5, 2026 20:30
@fhanik fhanik marked this pull request as ready for review June 6, 2026 00:30
@fhanik fhanik merged commit 817b8b8 into cloudfoundry:develop Jun 6, 2026
33 of 34 checks passed
@github-project-automation github-project-automation Bot moved this from Pending Merge | Prioritized to Done in Foundational Infrastructure Working Group Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants