From 6474a9e76e19cdecf9e6af79fa6afec55a0110f7 Mon Sep 17 00:00:00 2001 From: Marcus Hert da Coregio Date: Fri, 14 May 2021 13:46:29 -0300 Subject: [PATCH] Allow Creating RelyingPartyRegistration from Metadata InputStream Update SAML2 Login reference documentation to reflect the changes Closes gh-9558 --- .../_includes/servlet/saml2/saml2-login.adoc | 15 +++++- .../RelyingPartyRegistrations.java | 46 ++++++++++++++++++- .../RelyingPartyRegistrationsTests.java | 27 ++++++++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc index 85ba3533bb..0bfe7b8a14 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc +++ b/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: ==== 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 d80c0fe0a2..2ed5246f64 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-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. + * + *

+ * 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. + *

+ * + *

+ * The callers of this method are accountable for closing the + * {@code InputStream} source. + *

+ * + * 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: + * + *
+	 *	String xml = fromDatabase();
+	 *	try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
+	 *		RelyingPartyRegistration registration = RelyingPartyRegistrations
+	 * 			.fromMetadata(source)
+	 * 			.registrationId("registration-id")
+	 * 			.build();
+	 * 	}
+	 * 
+ * + * 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); + } + } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java index 8a070f941e..9b74aa64a0 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java +++ b/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)); + } + } + }