Pārlūkot izejas kodu

Allow Creating RelyingPartyRegistration from Metadata InputStream

Update SAML2 Login reference documentation to reflect the changes

Closes gh-9558
Marcus Hert da Coregio 4 gadi atpakaļ
vecāks
revīzija
6474a9e76e

+ 14 - 1
docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

@@ -536,7 +536,6 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
         .registrationId("my-id")
         .build();
 ----
-
 .Kotlin
 [source,kotlin,role="secondary"]
 ----
@@ -547,6 +546,20 @@ val relyingPartyRegistration = RelyingPartyRegistrations
 ----
 ====
 
+Note that you can also create a `RelyingPartyRegistration` from an arbitrary `InputStream` source.
+One such example is when the metadata is stored in a database:
+
+[source,java]
+----
+String xml = fromDatabase();
+try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
+    RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
+            .fromMetadata(source)
+            .registrationId("my-id")
+            .build();
+}
+----
+
 Though a more sophisticated setup is also possible, like so:
 
 ====

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

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -28,6 +28,7 @@ import org.springframework.security.saml2.Saml2Exception;
  *
  * @author Josh Cummings
  * @author Ryan Cassar
+ * @author Marcus da Coregio
  * @since 5.4
  */
 public final class RelyingPartyRegistrations {
@@ -73,7 +74,7 @@ public final class RelyingPartyRegistrations {
 	 */
 	public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
 		try (InputStream source = resourceLoader.getResource(metadataLocation).getInputStream()) {
-			return assertingPartyMetadataConverter.convert(source);
+			return fromMetadata(source);
 		}
 		catch (IOException ex) {
 			if (ex.getCause() instanceof Saml2Exception) {
@@ -83,4 +84,45 @@ public final class RelyingPartyRegistrations {
 		}
 	}
 
+	/**
+	 * Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0
+	 * Asserting Party (IDP) metadata.
+	 *
+	 * <p>
+	 * This method is intended for scenarios when the metadata is looked up by a separate
+	 * mechanism. One such example is when the metadata is stored in a database.
+	 * </p>
+	 *
+	 * <p>
+	 * <strong>The callers of this method are accountable for closing the
+	 * {@code InputStream} source.</strong>
+	 * </p>
+	 *
+	 * Note that by default the registrationId is set to be the given metadata location,
+	 * but this will most often not be sufficient. To complete the configuration, most
+	 * applications will also need to provide a registrationId, like so:
+	 *
+	 * <pre>
+	 *	String xml = fromDatabase();
+	 *	try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
+	 *		RelyingPartyRegistration registration = RelyingPartyRegistrations
+	 * 			.fromMetadata(source)
+	 * 			.registrationId("registration-id")
+	 * 			.build();
+	 * 	}
+	 * </pre>
+	 *
+	 * Also note that an {@code IDPSSODescriptor} typically only contains information
+	 * about the asserting party. Thus, you will need to remember to still populate
+	 * anything about the relying party, like any private keys the relying party will use
+	 * for signing AuthnRequests.
+	 * @param source the {@link InputStream} source containing the asserting party
+	 * metadata
+	 * @return the {@link RelyingPartyRegistration.Builder} for further configuration
+	 * @since 5.6
+	 */
+	public static RelyingPartyRegistration.Builder fromMetadata(InputStream source) {
+		return assertingPartyMetadataConverter.convert(source);
+	}
+
 }

+ 26 - 1
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -17,7 +17,9 @@
 package org.springframework.security.saml2.provider.service.registration;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.stream.Collectors;
 
@@ -104,4 +106,27 @@ public class RelyingPartyRegistrationsTests {
 				.isThrownBy(() -> RelyingPartyRegistrations.fromMetadataLocation("filePath"));
 	}
 
+	@Test
+	public void fromMetadataInputStreamWhenResolvableThenPopulatesBuilder() throws Exception {
+		try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes())) {
+			RelyingPartyRegistration registration = RelyingPartyRegistrations.fromMetadata(source).entityId("rp")
+					.build();
+			RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails();
+			assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth");
+			assertThat(details.getSingleSignOnServiceLocation())
+					.isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO");
+			assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
+			assertThat(details.getVerificationX509Credentials()).hasSize(1);
+			assertThat(details.getEncryptionX509Credentials()).hasSize(1);
+		}
+	}
+
+	@Test
+	public void fromMetadataInputStreamWhenEmptyThenSaml2Exception() throws Exception {
+		try (InputStream source = new ByteArrayInputStream("".getBytes())) {
+			assertThatExceptionOfType(Saml2Exception.class)
+					.isThrownBy(() -> RelyingPartyRegistrations.fromMetadata(source));
+		}
+	}
+
 }