Skip to content

Fix: SAML login fails for inline metadata containing a UTF-8 BOM (#3994)#3995

Closed
cweibel wants to merge 1 commit into
cloudfoundry:developfrom
cweibel:fix/saml-metadata-bom
Closed

Fix: SAML login fails for inline metadata containing a UTF-8 BOM (#3994)#3995
cweibel wants to merge 1 commit into
cloudfoundry:developfrom
cweibel:fix/saml-metadata-bom

Conversation

@cweibel

@cweibel cweibel commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Problem

After 79.3.079.4.0, SAML login fails for IdPs whose inline metadata (idpMetadata: <EntityDescriptor…>) begins with a UTF-8 BOM (U+FEFF). This is common for Microsoft Entra / Azure AD / ADFS federationmetadata.xml, which is served UTF-8-with-BOM and pasted verbatim into config. GET /saml2/authenticate/{origin} returns HTTP 500:

Cannot retrieve SAML trusted party for registrationId: {origin}
org.springframework.security.saml2.Saml2Exception: java.io.FileNotFoundException:
  class path resource [<BOM><?xml ...<EntityDescriptor ...>] cannot be opened because it does not exist
    at RelyingPartyRegistrations.fromMetadataLocation(...)
    at RelyingPartyRegistrationBuilder.buildRelyingPartyRegistration(...)
    at ConfiguratorRelyingPartyRegistrationRepository.createRelyingPartyRegistration(...)

Fixes #3994.

Root cause

SamlIdentityProviderDefinition.getType(String) classifies metadata as inline XML (DATA) vs URL by prefix-matching after String.trim(). String.trim() only removes characters <= U+0020, so a leading BOM survives and the startsWith("<?xml") / startsWith("<EntityDescriptor") checks fail → the value is classified UNKNOWN. RelyingPartyRegistrationBuilder then sends anything not DATA to RelyingPartyRegistrations.fromMetadataLocation(...), which treats the XML blob as a resource location and throws FileNotFoundException.

This was a latent gap in getType(); it surfaced on the login path when #3971 routed live login through SamlIdentityProviderConfigurator.resolveMetadataXml()getType(). (Distinct from the other #3971 follow-up, #3974.)

Fix

  • Strip a leading U+FEFF in SamlIdentityProviderDefinition.getType() before classification — minimal change, fixes all call sites (login, admin/validate, metadata delegate).
  • Defense-in-depth: also strip a leading BOM from the bytes returned by SamlIdentityProviderConfigurator.resolveMetadataXml() (covers URL endpoints that serve BOM-prefixed metadata).

Tests

  • SamlIdentityProviderDefinitionTests: getType() classifies BOM-prefixed inline XML as DATA and BOM-prefixed URL as URL.
  • ConfiguratorRelyingPartyRegistrationRepositoryTest: BOM-prefixed inline metadata resolves to a valid RelyingPartyRegistration.

Verification

./gradlew :cloudfoundry-identity-model:test \
  --tests "org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderDefinitionTests"
# BUILD SUCCESSFUL — 18 tests passed

./gradlew :cloudfoundry-identity-server:test \
  --tests "org.cloudfoundry.identity.uaa.provider.saml.ConfiguratorRelyingPartyRegistrationRepositoryTest"
# BUILD SUCCESSFUL — 15 tests passed

Rollback

Revert this commit; no schema, config, or API changes.

Security impact

Affects SAML IdP metadata parsing/classification only. No change to signature validation, trust, or authorization; BOM stripping is limited to a single leading U+FEFF before XML parsing (which OpenSAML already tolerates).

…ed correctly

Inline SAML IDP metadata that begins with a UTF-8 BOM (U+FEFF) -- as served
by Microsoft Entra/ADFS federationmetadata.xml and frequently pasted verbatim
into idpMetadata -- was misclassified by SamlIdentityProviderDefinition.getType().
String.trim() only strips characters <= U+0020, so the BOM survived and the
startsWith checks failed, returning UNKNOWN instead of DATA.

Since cloudfoundry#3971 routed the live login path through resolveMetadataXml() and thence
getType(), that latent gap surfaced as a login failure: RelyingPartyRegistrationBuilder
sent the non-DATA metadata to RelyingPartyRegistrations.fromMetadataLocation(),
which treated the multi-KB XML blob as a resource location and threw
FileNotFoundException -> Saml2Exception (HTTP 500 on /saml2/authenticate/{origin}).

Strip a leading BOM in getType() (fixes all call sites) and also normalize the
bytes returned by SamlIdentityProviderConfigurator.resolveMetadataXml() as
defense-in-depth for URL endpoints that serve BOM-prefixed metadata.

Add regression tests in SamlIdentityProviderDefinitionTests and
ConfiguratorRelyingPartyRegistrationRepositoryTest.

Fixes cloudfoundry#3994
@cweibel

cweibel commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #3993, which addresses the same root cause (BOM defeating DATA/URL classification in SamlIdentityProviderDefinition.getType()) and is a superset of this change — it also detects UTF-16LE/BE BOMs in resolveMetadataXml(). Deferring to the maintainer PR.

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.

SAML login fails for inline metadata containing a UTF-8 BOM (regression from #3971)

1 participant