Choose SAML party based on entity ID rather than always using first

Update `Saml2RelyingPartyRegistrationConfiguration` so that
`RelyingPartyRegistrations` uses `collectionFromMetadataLocation`
rather than `fromMetadataLocation` and searches candidates for a
matching entity ID.

Prior to this commit, it was possible for the wrong provider to be
used if multiple candidates existed in the returned metadata.

See gh-35902
pull/36620/head
Lasse Lindqvist 1 year ago committed by Phillip Webb
parent 51ee702dac
commit 864af59adc

@ -88,9 +88,14 @@ class Saml2RelyingPartyRegistrationConfiguration {
private RelyingPartyRegistration asRegistration(String id, Registration properties) {
AssertingPartyProperties assertingParty = new AssertingPartyProperties(properties, id);
boolean usingMetadata = StringUtils.hasText(assertingParty.getMetadataUri());
Builder builder = (usingMetadata)
? RelyingPartyRegistrations.fromMetadataLocation(assertingParty.getMetadataUri()).registrationId(id)
: RelyingPartyRegistration.withRegistrationId(id);
Builder builder = (usingMetadata) ? RelyingPartyRegistrations
.collectionFromMetadataLocation(properties.getAssertingparty().getMetadataUri())
.stream()
.filter(b -> entityIdsMatch(properties, b))
.findFirst()
.orElseThrow(() -> new IllegalStateException(
"No relying party with entity-id " + properties.getEntityId() + " found."))
.registrationId(id) : RelyingPartyRegistration.withRegistrationId(id);
builder.assertionConsumerServiceLocation(properties.getAcs().getLocation());
builder.assertionConsumerServiceBinding(properties.getAcs().getBinding());
builder.assertingPartyDetails(mapAssertingParty(properties, id, usingMetadata));
@ -119,6 +124,19 @@ class Saml2RelyingPartyRegistrationConfiguration {
return registration;
}
/**
* Tests if the builder would have the correct entity-id. If no entity-id is given in
* properties, any builder passes the test.
* @param properties the properties
* @param b the builder
* @return true if the builder passes the test
*/
private boolean entityIdsMatch(Registration properties, Builder b) {
RelyingPartyRegistration rpr = b.build();
return properties.getAssertingparty().getEntityId() == null
|| properties.getAssertingparty().getEntityId().equals(rpr.getAssertingPartyDetails().getEntityId());
}
private Consumer<AssertingPartyDetails.Builder> mapAssertingParty(Registration registration, String id,
boolean usingMetadata) {
return (details) -> {

@ -403,6 +403,43 @@ class Saml2RelyingPartyAutoConfigurationTests {
.run((context) -> assertThat(hasFilter(context, Saml2LogoutRequestFilter.class)).isTrue());
}
@Test
void autoconfigurationShouldWorkWithMultipleProvidersWithNoEntityId() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("saml/idp-metadata-with-multiple-providers"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl)
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyDetails().getEntityId())
.isEqualTo("https://idp.example.com/idp/shibboleth");
});
}
}
@Test
void autoconfigurationShouldWorkWithMultipleProviders() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.start();
String metadataUrl = server.url("").toString();
setupMockResponse(server, new ClassPathResource("saml/idp-metadata-with-multiple-providers"));
this.contextRunner.withPropertyValues(PREFIX + ".foo.assertingparty.metadata-uri=" + metadataUrl,
PREFIX + ".foo.assertingparty.entity-id=https://idp2.example.com/idp/shibboleth")
.run((context) -> {
assertThat(context).hasSingleBean(RelyingPartyRegistrationRepository.class);
assertThat(server.getRequestCount()).isOne();
RelyingPartyRegistrationRepository repository = context.getBean(RelyingPartyRegistrationRepository.class);
RelyingPartyRegistration registration = repository.findByRegistrationId("foo");
assertThat(registration.getAssertingPartyDetails().getEntityId())
.isEqualTo("https://idp2.example.com/idp/shibboleth");
});
}
}
private String[] getPropertyValuesWithoutSigningCredentials(boolean signRequests, boolean useDeprecated) {
String assertingParty = useDeprecated ? "identityprovider" : "assertingparty";
return new String[] {

@ -0,0 +1,86 @@
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="virtu-20230614094100" Name="virtu" validUntil="2023-07-12T06:41:00Z" xsi:schemaLocation="urn:oasis:names:tc:SAML:2.0:metadata saml-schema-metadata-2.0.xsd http://www.w3.org/2000/09/xmldsig# xmldsig-core-schema.xsd">
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
<md:EntityDescriptor entityID="https://idp2.example.com/idp/shibboleth"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB
BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe
Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t
cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP
ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS
v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN
iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece
byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz
cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v
dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX
gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w
dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW
BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu
9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL
qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU
duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU
yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p
V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e
Cq53OZt9ISjHEw==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp2.example.com/sso"/>
</md:IDPSSODescriptor>
<md:ContactPerson contactType="technical">
<md:EmailAddress>mailto:technical.contact2@example.com</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
</EntitiesDescriptor>
Loading…
Cancel
Save