Procházet zdrojové kódy

Preserve OpenSamlAssertingPartyDetails Instance

Closes gh-12667
Josh Cummings před 2 roky
rodič
revize
e28ea6dbad

+ 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

+ 17 - 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.
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
 
 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;
 import org.springframework.util.CollectionUtils;
@@ -737,7 +738,7 @@ public final class RelyingPartyRegistration {
 
 	public static final class Builder {
 
-		private String registrationId;
+		private Converter<AssertingPartyDetails, String> registrationId = AssertingPartyDetails::getEntityId;
 
 		private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
 
@@ -757,10 +758,15 @@ public final class RelyingPartyRegistration {
 
 		private String nameIdFormat = null;
 
-		private AssertingPartyDetails.Builder assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
+		private AssertingPartyDetails.Builder assertingPartyDetailsBuilder;
 
 		private Builder(String registrationId) {
-			this.registrationId = registrationId;
+			this.registrationId = (party) -> registrationId;
+			this.assertingPartyDetailsBuilder = new AssertingPartyDetails.Builder();
+		}
+
+		Builder(AssertingPartyDetails.Builder builder) {
+			this.assertingPartyDetailsBuilder = builder;
 		}
 
 		/**
@@ -769,7 +775,7 @@ public final class RelyingPartyRegistration {
 		 * @return this object
 		 */
 		public Builder registrationId(String id) {
-			this.registrationId = id;
+			this.registrationId = (party) -> id;
 			return this;
 		}
 
@@ -967,11 +973,12 @@ public final class RelyingPartyRegistration {
 				this.singleLogoutServiceBindings.add(Saml2MessageBinding.POST);
 			}
 
-			return new RelyingPartyRegistration(this.registrationId, this.entityId,
-					this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
-					this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
-					this.singleLogoutServiceBindings, this.assertingPartyDetailsBuilder.build(), this.nameIdFormat,
-					this.decryptionX509Credentials, this.signingX509Credentials);
+			AssertingPartyDetails party = this.assertingPartyDetailsBuilder.build();
+			String registrationId = this.registrationId.convert(party);
+			return new RelyingPartyRegistration(registrationId, this.entityId, this.assertionConsumerServiceLocation,
+					this.assertionConsumerServiceBinding, this.singleLogoutServiceLocation,
+					this.singleLogoutServiceResponseLocation, this.singleLogoutServiceBindings, party,
+					this.nameIdFormat, 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));
+		}
+	}
+
+}