Skip to content

Commit 2db4430

Browse files
committed
Preserve OpenSamlAssertingPartyDetails Instance
Closes gh-12667
1 parent 000b4bc commit 2db4430

File tree

6 files changed

+154
-23
lines changed

6 files changed

+154
-23
lines changed

docs/modules/ROOT/pages/servlet/saml2/metadata.adoc

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
[[servlet-saml2login-metadata]]
2-
= Producing `<saml2:SPSSODescriptor>` Metadata
2+
= Saml 2.0 Metadata
3+
4+
Spring Security can <<parsing-asserting-party-metadata,parse asserting party metadata>> to produce an `AssertingPartyDetails` instance as well as <<publishing-relying-party-metadata,publish relying party metadata>> from a `RelyingPartyRegistration` instance.
5+
6+
[[parsing-asserting-party-metadata]]
7+
== Parsing `<saml2:IDPSSODescriptor>` metadata
8+
9+
You can parse an asserting party's metadata xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[using `RelyingPartyRegistrations`].
10+
11+
When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`.
12+
This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following:
13+
14+
====
15+
.Java
16+
[source,java,role="primary"]
17+
----
18+
OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails)
19+
registration.getAssertingPartyDetails();
20+
EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor();
21+
----
22+
23+
.Kotlin
24+
[source,kotlin,role="secondary"]
25+
----
26+
val details: OpenSamlAssertingPartyDetails =
27+
registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails;
28+
val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor();
29+
----
30+
====
31+
32+
[[publishing-relying-party-metadata]]
33+
== Producing `<saml2:SPSSODescriptor>` Metadata
334

435
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
536

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.registration;
18+
19+
import java.io.InputStream;
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
23+
class OpenSamlMetadataRelyingPartyRegistrationConverter {
24+
25+
private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
26+
27+
Collection<RelyingPartyRegistration.Builder> convert(InputStream source) {
28+
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
29+
for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
30+
builders.add(new RelyingPartyRegistration.Builder(builder));
31+
}
32+
return builders;
33+
}
34+
35+
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -89,8 +89,7 @@ public List<MediaType> getSupportedMediaTypes() {
8989
@Override
9090
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
9191
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
92-
return RelyingPartyRegistration
93-
.withAssertingPartyDetails(this.converter.convert(inputMessage.getBody()).iterator().next().build());
92+
return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next());
9493
}
9594

9695
@Override

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030

3131
import org.opensaml.xmlsec.signature.support.SignatureConstants;
3232

33+
import org.springframework.core.convert.converter.Converter;
3334
import org.springframework.security.saml2.core.Saml2X509Credential;
3435
import org.springframework.util.Assert;
3536

@@ -970,6 +971,14 @@ public static final class Builder {
970971

971972
private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
972973

974+
private Builder() {
975+
976+
}
977+
978+
private Builder(AssertingPartyDetails.Builder assertingPartyDetailsBuilder) {
979+
this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder;
980+
}
981+
973982
/**
974983
* Set the asserting party's <a href=
975984
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
@@ -1032,7 +1041,7 @@ public ProviderDetails build() {
10321041

10331042
public static final class Builder {
10341043

1035-
private String registrationId;
1044+
private Converter<ProviderDetails, String> registrationId = ProviderDetails::getEntityId;
10361045

10371046
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
10381047

@@ -1052,12 +1061,17 @@ public static final class Builder {
10521061

10531062
private String nameIdFormat = null;
10541063

1055-
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
1064+
private ProviderDetails.Builder providerDetails;
10561065

10571066
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
10581067

10591068
private Builder(String registrationId) {
1060-
this.registrationId = registrationId;
1069+
this.registrationId = (party) -> registrationId;
1070+
this.providerDetails = new ProviderDetails.Builder();
1071+
}
1072+
1073+
Builder(AssertingPartyDetails.Builder builder) {
1074+
this.providerDetails = new ProviderDetails.Builder(builder);
10611075
}
10621076

10631077
/**
@@ -1066,7 +1080,7 @@ private Builder(String registrationId) {
10661080
* @return this object
10671081
*/
10681082
public Builder registrationId(String id) {
1069-
this.registrationId = id;
1083+
this.registrationId = (party) -> id;
10701084
return this;
10711085
}
10721086

@@ -1363,11 +1377,12 @@ public RelyingPartyRegistration build() {
13631377
if (this.singleLogoutServiceResponseLocation == null) {
13641378
this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation;
13651379
}
1366-
return new RelyingPartyRegistration(this.registrationId, this.entityId,
1367-
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
1368-
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
1369-
this.singleLogoutServiceBinding, this.providerDetails.build(), this.nameIdFormat, this.credentials,
1370-
this.decryptionX509Credentials, this.signingX509Credentials);
1380+
ProviderDetails party = this.providerDetails.build();
1381+
String registrationId = this.registrationId.convert(party);
1382+
return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation,
1383+
this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation,
1384+
this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBinding, party, this.nameIdFormat,
1385+
this.credentials, this.decryptionX509Credentials, this.signingX509Credentials);
13711386
}
13721387

13731388
}

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,13 +18,11 @@
1818

1919
import java.io.IOException;
2020
import java.io.InputStream;
21-
import java.util.ArrayList;
2221
import java.util.Collection;
2322

2423
import org.springframework.core.io.DefaultResourceLoader;
2524
import org.springframework.core.io.ResourceLoader;
2625
import org.springframework.security.saml2.Saml2Exception;
27-
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
2826

2927
/**
3028
* A utility class for constructing instances of {@link RelyingPartyRegistration}
@@ -36,7 +34,7 @@
3634
*/
3735
public final class RelyingPartyRegistrations {
3836

39-
private static final OpenSamlMetadataAssertingPartyDetailsConverter assertingPartyMetadataConverter = new OpenSamlMetadataAssertingPartyDetailsConverter();
37+
private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
4038

4139
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
4240

@@ -215,11 +213,7 @@ public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadat
215213
* @since 5.7
216214
*/
217215
public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
218-
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
219-
for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) {
220-
builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build()));
221-
}
222-
return builders;
216+
return relyingPartyRegistrationConverter.convert(source);
223217
}
224218

225219
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.registration;
18+
19+
import java.io.BufferedReader;
20+
import java.io.ByteArrayInputStream;
21+
import java.io.InputStream;
22+
import java.io.InputStreamReader;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.stream.Collectors;
25+
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
29+
import org.springframework.core.io.ClassPathResource;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
34+
35+
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
36+
37+
private String metadata;
38+
39+
@BeforeEach
40+
public void setup() throws Exception {
41+
ClassPathResource resource = new ClassPathResource("test-metadata.xml");
42+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
43+
this.metadata = reader.lines().collect(Collectors.joining());
44+
}
45+
}
46+
47+
// gh-12667
48+
@Test
49+
public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception {
50+
try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) {
51+
this.converter.convert(source)
52+
.forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails())
53+
.isInstanceOf(OpenSamlAssertingPartyDetails.class));
54+
}
55+
}
56+
57+
}

0 commit comments

Comments
 (0)