瀏覽代碼

Preserve OpenSamlAssertingPartyDetails Instance

Closes gh-12667
Josh Cummings 2 年之前
父節點
當前提交
2db4430dcd

+ 32 - 1
docs/modules/ROOT/pages/servlet/saml2/metadata.adoc

@@ -1,5 +1,36 @@
 [[servlet-saml2login-metadata]]
-= Producing `<saml2:SPSSODescriptor>` Metadata
+= Saml 2.0 Metadata
+
+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.
+
+[[parsing-asserting-party-metadata]]
+== Parsing `<saml2:IDPSSODescriptor>` 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 `<saml2:SPSSODescriptor>` Metadata
 
 You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
 

+ 35 - 0
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<RelyingPartyRegistration.Builder> convert(InputStream source) {
+		Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
+		for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
+			builders.add(new RelyingPartyRegistration.Builder(builder));
+		}
+		return builders;
+	}
+
+}

+ 2 - 3
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 class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
 	@Override
 	public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> 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

+ 25 - 10
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 java.util.function.Function;
 
 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 final class RelyingPartyRegistration {
 
 			private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
 
+			private Builder() {
+
+			}
+
+			private Builder(AssertingPartyDetails.Builder assertingPartyDetailsBuilder) {
+				this.assertingPartyDetailsBuilder = assertingPartyDetailsBuilder;
+			}
+
 			/**
 			 * Set the asserting party's <a href=
 			 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
@@ -1032,7 +1041,7 @@ public final class RelyingPartyRegistration {
 
 	public static final class Builder {
 
-		private String registrationId;
+		private Converter<ProviderDetails, String> registrationId = ProviderDetails::getEntityId;
 
 		private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
 
@@ -1052,12 +1061,17 @@ public final class RelyingPartyRegistration {
 
 		private String nameIdFormat = null;
 
-		private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
+		private ProviderDetails.Builder providerDetails;
 
 		private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> 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 @@ public final class RelyingPartyRegistration {
 		 * @return this object
 		 */
 		public Builder registrationId(String id) {
-			this.registrationId = id;
+			this.registrationId = (party) -> id;
 			return this;
 		}
 
@@ -1363,11 +1377,12 @@ public final class RelyingPartyRegistration {
 			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);
 		}
 
 	}

+ 3 - 9
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 @@ package org.springframework.security.saml2.provider.service.registration;
 
 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 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
  */
 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 final class RelyingPartyRegistrations {
 	 * @since 5.7
 	 */
 	public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) {
-		Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
-		for (AssertingPartyDetails.Builder builder : assertingPartyMetadataConverter.convert(source)) {
-			builders.add(RelyingPartyRegistration.withAssertingPartyDetails(builder.build()));
-		}
-		return builders;
+		return relyingPartyRegistrationConverter.convert(source);
 	}
 
 }

+ 57 - 0
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));
+		}
+	}
+
+}