Przeglądaj źródła

Add AssertingPartyMetadataRepository

Closes gh-15394
Josh Cummings 1 rok temu
rodzic
commit
7ad9ee93cf

+ 276 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/AssertingPartyMetadata.java

@@ -0,0 +1,276 @@
+/*
+ * Copyright 2002-2024 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.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.springframework.security.saml2.core.Saml2X509Credential;
+
+/**
+ * An interface representing SAML 2.0 Asserting Party metadata
+ *
+ * @author Josh Cummings
+ * @since 6.4
+ */
+public interface AssertingPartyMetadata {
+
+	/**
+	 * Get 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>.
+	 *
+	 * <p>
+	 * Equivalent to the value found in the asserting party's &lt;EntityDescriptor
+	 * EntityID="..."/&gt;
+	 *
+	 * <p>
+	 * This value may contain a number of placeholders, which need to be resolved before
+	 * use. They are {@code baseUrl}, {@code registrationId}, {@code baseScheme},
+	 * {@code baseHost}, and {@code basePort}.
+	 * @return the asserting party's EntityID
+	 */
+	String getEntityId();
+
+	/**
+	 * Get the WantAuthnRequestsSigned setting, indicating the asserting party's
+	 * preference that relying parties should sign the AuthnRequest before sending.
+	 * @return the WantAuthnRequestsSigned value
+	 */
+	boolean getWantAuthnRequestsSigned();
+
+	/**
+	 * Get the list of org.opensaml.saml.ext.saml2alg.SigningMethod Algorithms for this
+	 * asserting party, in preference order.
+	 *
+	 * <p>
+	 * Equivalent to the values found in &lt;SigningMethod Algorithm="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the list of SigningMethod Algorithms
+	 * @since 5.5
+	 */
+	List<String> getSigningAlgorithms();
+
+	/**
+	 * Get all verification {@link Saml2X509Credential}s associated with this asserting
+	 * party
+	 * @return all verification {@link Saml2X509Credential}s associated with this
+	 * asserting party
+	 * @since 5.4
+	 */
+	Collection<Saml2X509Credential> getVerificationX509Credentials();
+
+	/**
+	 * Get all encryption {@link Saml2X509Credential}s associated with this asserting
+	 * party
+	 * @return all encryption {@link Saml2X509Credential}s associated with this asserting
+	 * party
+	 * @since 5.4
+	 */
+	Collection<Saml2X509Credential> getEncryptionX509Credentials();
+
+	/**
+	 * Get the <a href=
+	 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
+	 * Location.
+	 *
+	 * <p>
+	 * Equivalent to the value found in &lt;SingleSignOnService Location="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the SingleSignOnService Location
+	 */
+	String getSingleSignOnServiceLocation();
+
+	/**
+	 * Get the <a href=
+	 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
+	 * Binding.
+	 *
+	 * <p>
+	 * Equivalent to the value found in &lt;SingleSignOnService Binding="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the SingleSignOnService Location
+	 */
+	Saml2MessageBinding getSingleSignOnServiceBinding();
+
+	/**
+	 * Get the <a href=
+	 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+	 * Location</a>
+	 *
+	 * <p>
+	 * Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the SingleLogoutService Location
+	 * @since 5.6
+	 */
+	String getSingleLogoutServiceLocation();
+
+	/**
+	 * Get the <a href=
+	 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+	 * Response Location</a>
+	 *
+	 * <p>
+	 * Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the SingleLogoutService Response Location
+	 * @since 5.6
+	 */
+	String getSingleLogoutServiceResponseLocation();
+
+	/**
+	 * Get the <a href=
+	 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+	 * Binding</a>
+	 *
+	 * <p>
+	 * Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in the
+	 * asserting party's &lt;IDPSSODescriptor&gt;.
+	 * @return the SingleLogoutService Binding
+	 * @since 5.6
+	 */
+	Saml2MessageBinding getSingleLogoutServiceBinding();
+
+	Builder<?> mutate();
+
+	interface Builder<B extends Builder<B>> {
+
+		/**
+		 * 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>.
+		 * Equivalent to the value found in the asserting party's &lt;EntityDescriptor
+		 * EntityID="..."/&gt;
+		 * @param entityId the asserting party's EntityID
+		 * @return the {@link B} for further configuration
+		 */
+		B entityId(String entityId);
+
+		/**
+		 * Set the WantAuthnRequestsSigned setting, indicating the asserting party's
+		 * preference that relying parties should sign the AuthnRequest before sending.
+		 * @param wantAuthnRequestsSigned the WantAuthnRequestsSigned setting
+		 * @return the {@link B} for further configuration
+		 */
+		B wantAuthnRequestsSigned(boolean wantAuthnRequestsSigned);
+
+		/**
+		 * Apply this {@link Consumer} to the list of SigningMethod Algorithms
+		 * @param signingMethodAlgorithmsConsumer a {@link Consumer} of the list of
+		 * SigningMethod Algorithms
+		 * @return this {@link B} for further configuration
+		 * @since 5.5
+		 */
+		B signingAlgorithms(Consumer<List<String>> signingMethodAlgorithmsConsumer);
+
+		/**
+		 * Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s
+		 * @param credentialsConsumer a {@link Consumer} of the {@link List} of
+		 * {@link Saml2X509Credential}s
+		 * @return the {@link RelyingPartyRegistration.Builder} for further configuration
+		 * @since 5.4
+		 */
+		B verificationX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer);
+
+		/**
+		 * Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s
+		 * @param credentialsConsumer a {@link Consumer} of the {@link List} of
+		 * {@link Saml2X509Credential}s
+		 * @return the {@link RelyingPartyRegistration.Builder} for further configuration
+		 * @since 5.4
+		 */
+		B encryptionX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer);
+
+		/**
+		 * Set the <a href=
+		 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
+		 * Location.
+		 *
+		 * <p>
+		 * Equivalent to the value found in &lt;SingleSignOnService Location="..."/&gt; in
+		 * the asserting party's &lt;IDPSSODescriptor&gt;.
+		 * @param singleSignOnServiceLocation the SingleSignOnService Location
+		 * @return the {@link B} for further configuration
+		 */
+		B singleSignOnServiceLocation(String singleSignOnServiceLocation);
+
+		/**
+		 * Set the <a href=
+		 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
+		 * Binding.
+		 *
+		 * <p>
+		 * Equivalent to the value found in &lt;SingleSignOnService Binding="..."/&gt; in
+		 * the asserting party's &lt;IDPSSODescriptor&gt;.
+		 * @param singleSignOnServiceBinding the SingleSignOnService Binding
+		 * @return the {@link B} for further configuration
+		 */
+		B singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding);
+
+		/**
+		 * Set the <a href=
+		 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+		 * Location</a>
+		 *
+		 * <p>
+		 * Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in
+		 * the asserting party's &lt;IDPSSODescriptor&gt;.
+		 * @param singleLogoutServiceLocation the SingleLogoutService Location
+		 * @return the {@link B} for further configuration
+		 * @since 5.6
+		 */
+		B singleLogoutServiceLocation(String singleLogoutServiceLocation);
+
+		/**
+		 * Set the <a href=
+		 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+		 * Response Location</a>
+		 *
+		 * <p>
+		 * Equivalent to the value found in &lt;SingleLogoutService
+		 * ResponseLocation="..."/&gt; in the asserting party's &lt;IDPSSODescriptor&gt;.
+		 * @param singleLogoutServiceResponseLocation the SingleLogoutService Response
+		 * Location
+		 * @return the {@link B} for further configuration
+		 * @since 5.6
+		 */
+		B singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation);
+
+		/**
+		 * Set the <a href=
+		 * "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
+		 * Binding</a>
+		 *
+		 * <p>
+		 * Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in
+		 * the asserting party's &lt;IDPSSODescriptor&gt;.
+		 * @param singleLogoutServiceBinding the SingleLogoutService Binding
+		 * @return the {@link B} for further configuration
+		 * @since 5.6
+		 */
+		B singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding);
+
+		/**
+		 * Creates an immutable ProviderDetails object representing the configuration for
+		 * an Identity Provider, IDP
+		 * @return immutable ProviderDetails object
+		 */
+		AssertingPartyMetadata build();
+
+	}
+
+}

+ 47 - 0
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/AssertingPartyMetadataRepository.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2024 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 org.springframework.lang.Nullable;
+
+/**
+ * A repository for retrieving SAML 2.0 Asserting Party Metadata
+ *
+ * @author Josh Cummings
+ * @since 6.4
+ * @see OpenSamlAssertingPartyMetadataRepository
+ * @see org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations
+ */
+public interface AssertingPartyMetadataRepository extends Iterable<AssertingPartyMetadata> {
+
+	/**
+	 * Retrieve an {@link AssertingPartyMetadata} by its <a href=
+	 * "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
+	 * @param entityId the EntityID to lookup
+	 * @return the found {@link AssertingPartyMetadata}, or {@code null} otherwise
+	 */
+	@Nullable
+	default AssertingPartyMetadata findByEntityId(String entityId) {
+		for (AssertingPartyMetadata metadata : this) {
+			if (metadata.getEntityId().equals(entityId)) {
+				return metadata;
+			}
+		}
+		return null;
+	}
+
+}

+ 21 - 2
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

@@ -338,6 +338,25 @@ public class RelyingPartyRegistration {
 		return new Builder(assertingPartyDetails.getEntityId(), assertingPartyDetails.mutate());
 	}
 
+	/**
+	 * Creates a {@code RelyingPartyRegistration} {@link Builder} with a
+	 * {@code registrationId} equivalent to the asserting party entity id. Also
+	 * initializes to the contents of the given {@link AssertingPartyMetadata}.
+	 *
+	 * <p>
+	 * Presented as a convenience method when working with
+	 * {@link AssertingPartyMetadataRepository} return values. As such, only supports
+	 * {@link AssertingPartyMetadata} instances of type {@link AssertingPartyDetails}.
+	 * @param metadata the metadata used to initialize the
+	 * {@link RelyingPartyRegistration} {@link Builder}
+	 * @return {@link Builder} to create a {@link RelyingPartyRegistration} object
+	 * @since 6.4
+	 */
+	public static Builder withAssertingPartyMetadata(AssertingPartyMetadata metadata) {
+		Assert.isInstanceOf(AssertingPartyDetails.class, metadata, "metadata must be of type AssertingPartyDetails");
+		return withAssertingPartyDetails((AssertingPartyDetails) metadata);
+	}
+
 	/**
 	 * Creates a {@code RelyingPartyRegistration} {@link Builder} based on an existing
 	 * object
@@ -380,7 +399,7 @@ public class RelyingPartyRegistration {
 	 *
 	 * @since 5.4
 	 */
-	public static class AssertingPartyDetails {
+	public static class AssertingPartyDetails implements AssertingPartyMetadata {
 
 		private final String entityId;
 
@@ -584,7 +603,7 @@ public class RelyingPartyRegistration {
 				.singleLogoutServiceBinding(this.singleLogoutServiceBinding);
 		}
 
-		public static class Builder {
+		public static class Builder implements AssertingPartyMetadata.Builder<Builder> {
 
 			private String entityId;
 

+ 26 - 0
saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java

@@ -166,4 +166,30 @@ public class RelyingPartyRegistrationTests {
 			.containsExactly(encryptingCredential, altApCredential);
 	}
 
+	@Test
+	void withAssertingPartyMetadataWhenDetailsThenBuilderCopies() {
+		RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
+			.nameIdFormat("format")
+			.assertingPartyDetails((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
+			.assertingPartyDetails((a) -> a.wantAuthnRequestsSigned(false))
+			.assertingPartyDetails((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
+			.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
+			.build();
+		RelyingPartyRegistration copied = RelyingPartyRegistration
+			.withAssertingPartyMetadata(registration.getAssertingPartyDetails())
+			.registrationId(registration.getRegistrationId())
+			.entityId(registration.getEntityId())
+			.signingX509Credentials((c) -> c.addAll(registration.getSigningX509Credentials()))
+			.decryptionX509Credentials((c) -> c.addAll(registration.getDecryptionX509Credentials()))
+			.assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation())
+			.assertionConsumerServiceBinding(registration.getAssertionConsumerServiceBinding())
+			.singleLogoutServiceLocation(registration.getSingleLogoutServiceLocation())
+			.singleLogoutServiceResponseLocation(registration.getSingleLogoutServiceResponseLocation())
+			.singleLogoutServiceBindings((c) -> c.addAll(registration.getSingleLogoutServiceBindings()))
+			.nameIdFormat(registration.getNameIdFormat())
+			.authnRequestsSigned(registration.isAuthnRequestsSigned())
+			.build();
+		compareRegistrations(registration, copied);
+	}
+
 }