From 2db4430dcdb836ea5e0895c10b54dc5c92a0eac8 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 15 Feb 2023 17:53:21 -0700 Subject: [PATCH] Preserve OpenSamlAssertingPartyDetails Instance Closes gh-12667 --- .../ROOT/pages/servlet/saml2/metadata.adoc | 33 ++++++++++- ...dataRelyingPartyRegistrationConverter.java | 35 ++++++++++++ ...gistrationBuilderHttpMessageConverter.java | 5 +- .../RelyingPartyRegistration.java | 35 ++++++++---- .../RelyingPartyRegistrations.java | 12 +--- ...elyingPartyRegistrationConverterTests.java | 57 +++++++++++++++++++ 6 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java diff --git a/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc b/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc index f63e93ba604..6f69ecec4f3 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc @@ -1,5 +1,36 @@ [[servlet-saml2login-metadata]] -= Producing `` Metadata += Saml 2.0 Metadata + +Spring Security can <> to produce an `AssertingPartyDetails` instance as well as <> from a `RelyingPartyRegistration` instance. + +[[parsing-asserting-party-metadata]] +== Parsing `` metadata + +You can parse an asserting party's metadata xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistrationrepository[using `RelyingPartyRegistrations`]. + +When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`. +This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following: + +==== +.Java +[source,java,role="primary"] +---- +OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails) + registration.getAssertingPartyDetails(); +EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor(); +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +val details: OpenSamlAssertingPartyDetails = + registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails; +val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor(); +---- +==== + +[[publishing-relying-party-metadata]] +== Producing `` Metadata You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below: diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java new file mode 100644 index 00000000000..882707c4f94 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + +class OpenSamlMetadataRelyingPartyRegistrationConverter { + + private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter(); + + Collection convert(InputStream source) { + Collection builders = new ArrayList<>(); + for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) { + builders.add(new RelyingPartyRegistration.Builder(builder)); + } + return builders; + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java index 8f29e833587..1996c6246d0 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,8 +89,7 @@ public List getSupportedMediaTypes() { @Override public RelyingPartyRegistration.Builder read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - return RelyingPartyRegistration - .withAssertingPartyDetails(this.converter.convert(inputMessage.getBody()).iterator().next().build()); + return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next()); } @Override diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index 28aa8c506ce..ca158005c61 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.opensaml.xmlsec.signature.support.SignatureConstants; +import org.springframework.core.convert.converter.Converter; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.Assert; @@ -970,6 +971,14 @@ public static final class Builder { private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder(); + private Builder() { + + } + + private Builder(AssertingPartyDetails.Builder assertingPartyDetailsBuilder) { + this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder; + } + /** * Set the asserting party's EntityID. @@ -1032,7 +1041,7 @@ public ProviderDetails build() { public static final class Builder { - private String registrationId; + private Converter registrationId = ProviderDetails::getEntityId; private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}"; @@ -1052,12 +1061,17 @@ public static final class Builder { private String nameIdFormat = null; - private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder(); + private ProviderDetails.Builder providerDetails; private Collection credentials = new LinkedHashSet<>(); private Builder(String registrationId) { - this.registrationId = registrationId; + this.registrationId = (party) -> registrationId; + this.providerDetails = new ProviderDetails.Builder(); + } + + Builder(AssertingPartyDetails.Builder builder) { + this.providerDetails = new ProviderDetails.Builder(builder); } /** @@ -1066,7 +1080,7 @@ private Builder(String registrationId) { * @return this object */ public Builder registrationId(String id) { - this.registrationId = id; + this.registrationId = (party) -> id; return this; } @@ -1363,11 +1377,12 @@ public RelyingPartyRegistration build() { if (this.singleLogoutServiceResponseLocation == null) { this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation; } - return new RelyingPartyRegistration(this.registrationId, this.entityId, - this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding, - this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation, - this.singleLogoutServiceBinding, this.providerDetails.build(), this.nameIdFormat, this.credentials, - this.decryptionX509Credentials, this.signingX509Credentials); + ProviderDetails party = this.providerDetails.build(); + String registrationId = this.registrationId.convert(party); + return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation, + this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation, + this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBinding, party, this.nameIdFormat, + this.credentials, this.decryptionX509Credentials, this.signingX509Credentials); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java index 9b116331cb4..d7382eafd33 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,11 @@ import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Collection; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails; /** * A utility class for constructing instances of {@link RelyingPartyRegistration} @@ -36,7 +34,7 @@ */ public final class RelyingPartyRegistrations { - private static final OpenSamlMetadataAssertingPartyDetailsConverter assertingPartyMetadataConverter = new OpenSamlMetadataAssertingPartyDetailsConverter(); + private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter(); private static final ResourceLoader resourceLoader = new DefaultResourceLoader(); @@ -215,11 +213,7 @@ public static Collection collectionFromMetadat * @since 5.7 */ public static Collection collectionFromMetadata(InputStream source) { - Collection builders = new ArrayList<>(); - for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) { - builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build())); - } - return builders; + return relyingPartyRegistrationConverter.convert(source); } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java new file mode 100644 index 00000000000..c4021620314 --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenSamlMetadataRelyingPartyRegistrationConverterTests { + + private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter(); + + private String metadata; + + @BeforeEach + public void setup() throws Exception { + ClassPathResource resource = new ClassPathResource("test-metadata.xml"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { + this.metadata = reader.lines().collect(Collectors.joining()); + } + } + + // gh-12667 + @Test + public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception { + try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) { + this.converter.convert(source) + .forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails()) + .isInstanceOf(OpenSamlAssertingPartyDetails.class)); + } + } + +}