Skip to content

Commit 7ad9ee9

Browse files
committed
Add AssertingPartyMetadataRepository
Closes gh-15394
1 parent 437a457 commit 7ad9ee9

File tree

4 files changed

+370
-2
lines changed

4 files changed

+370
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.registration;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.function.Consumer;
22+
23+
import org.springframework.security.saml2.core.Saml2X509Credential;
24+
25+
/**
26+
* An interface representing SAML 2.0 Asserting Party metadata
27+
*
28+
* @author Josh Cummings
29+
* @since 6.4
30+
*/
31+
public interface AssertingPartyMetadata {
32+
33+
/**
34+
* Get the asserting party's <a href=
35+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
36+
*
37+
* <p>
38+
* Equivalent to the value found in the asserting party's &lt;EntityDescriptor
39+
* EntityID="..."/&gt;
40+
*
41+
* <p>
42+
* This value may contain a number of placeholders, which need to be resolved before
43+
* use. They are {@code baseUrl}, {@code registrationId}, {@code baseScheme},
44+
* {@code baseHost}, and {@code basePort}.
45+
* @return the asserting party's EntityID
46+
*/
47+
String getEntityId();
48+
49+
/**
50+
* Get the WantAuthnRequestsSigned setting, indicating the asserting party's
51+
* preference that relying parties should sign the AuthnRequest before sending.
52+
* @return the WantAuthnRequestsSigned value
53+
*/
54+
boolean getWantAuthnRequestsSigned();
55+
56+
/**
57+
* Get the list of org.opensaml.saml.ext.saml2alg.SigningMethod Algorithms for this
58+
* asserting party, in preference order.
59+
*
60+
* <p>
61+
* Equivalent to the values found in &lt;SigningMethod Algorithm="..."/&gt; in the
62+
* asserting party's &lt;IDPSSODescriptor&gt;.
63+
* @return the list of SigningMethod Algorithms
64+
* @since 5.5
65+
*/
66+
List<String> getSigningAlgorithms();
67+
68+
/**
69+
* Get all verification {@link Saml2X509Credential}s associated with this asserting
70+
* party
71+
* @return all verification {@link Saml2X509Credential}s associated with this
72+
* asserting party
73+
* @since 5.4
74+
*/
75+
Collection<Saml2X509Credential> getVerificationX509Credentials();
76+
77+
/**
78+
* Get all encryption {@link Saml2X509Credential}s associated with this asserting
79+
* party
80+
* @return all encryption {@link Saml2X509Credential}s associated with this asserting
81+
* party
82+
* @since 5.4
83+
*/
84+
Collection<Saml2X509Credential> getEncryptionX509Credentials();
85+
86+
/**
87+
* Get the <a href=
88+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
89+
* Location.
90+
*
91+
* <p>
92+
* Equivalent to the value found in &lt;SingleSignOnService Location="..."/&gt; in the
93+
* asserting party's &lt;IDPSSODescriptor&gt;.
94+
* @return the SingleSignOnService Location
95+
*/
96+
String getSingleSignOnServiceLocation();
97+
98+
/**
99+
* Get the <a href=
100+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
101+
* Binding.
102+
*
103+
* <p>
104+
* Equivalent to the value found in &lt;SingleSignOnService Binding="..."/&gt; in the
105+
* asserting party's &lt;IDPSSODescriptor&gt;.
106+
* @return the SingleSignOnService Location
107+
*/
108+
Saml2MessageBinding getSingleSignOnServiceBinding();
109+
110+
/**
111+
* Get the <a href=
112+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
113+
* Location</a>
114+
*
115+
* <p>
116+
* Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the
117+
* asserting party's &lt;IDPSSODescriptor&gt;.
118+
* @return the SingleLogoutService Location
119+
* @since 5.6
120+
*/
121+
String getSingleLogoutServiceLocation();
122+
123+
/**
124+
* Get the <a href=
125+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
126+
* Response Location</a>
127+
*
128+
* <p>
129+
* Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in the
130+
* asserting party's &lt;IDPSSODescriptor&gt;.
131+
* @return the SingleLogoutService Response Location
132+
* @since 5.6
133+
*/
134+
String getSingleLogoutServiceResponseLocation();
135+
136+
/**
137+
* Get the <a href=
138+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
139+
* Binding</a>
140+
*
141+
* <p>
142+
* Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in the
143+
* asserting party's &lt;IDPSSODescriptor&gt;.
144+
* @return the SingleLogoutService Binding
145+
* @since 5.6
146+
*/
147+
Saml2MessageBinding getSingleLogoutServiceBinding();
148+
149+
Builder<?> mutate();
150+
151+
interface Builder<B extends Builder<B>> {
152+
153+
/**
154+
* Set the asserting party's <a href=
155+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
156+
* Equivalent to the value found in the asserting party's &lt;EntityDescriptor
157+
* EntityID="..."/&gt;
158+
* @param entityId the asserting party's EntityID
159+
* @return the {@link B} for further configuration
160+
*/
161+
B entityId(String entityId);
162+
163+
/**
164+
* Set the WantAuthnRequestsSigned setting, indicating the asserting party's
165+
* preference that relying parties should sign the AuthnRequest before sending.
166+
* @param wantAuthnRequestsSigned the WantAuthnRequestsSigned setting
167+
* @return the {@link B} for further configuration
168+
*/
169+
B wantAuthnRequestsSigned(boolean wantAuthnRequestsSigned);
170+
171+
/**
172+
* Apply this {@link Consumer} to the list of SigningMethod Algorithms
173+
* @param signingMethodAlgorithmsConsumer a {@link Consumer} of the list of
174+
* SigningMethod Algorithms
175+
* @return this {@link B} for further configuration
176+
* @since 5.5
177+
*/
178+
B signingAlgorithms(Consumer<List<String>> signingMethodAlgorithmsConsumer);
179+
180+
/**
181+
* Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s
182+
* @param credentialsConsumer a {@link Consumer} of the {@link List} of
183+
* {@link Saml2X509Credential}s
184+
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
185+
* @since 5.4
186+
*/
187+
B verificationX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer);
188+
189+
/**
190+
* Apply this {@link Consumer} to the list of {@link Saml2X509Credential}s
191+
* @param credentialsConsumer a {@link Consumer} of the {@link List} of
192+
* {@link Saml2X509Credential}s
193+
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
194+
* @since 5.4
195+
*/
196+
B encryptionX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer);
197+
198+
/**
199+
* Set the <a href=
200+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
201+
* Location.
202+
*
203+
* <p>
204+
* Equivalent to the value found in &lt;SingleSignOnService Location="..."/&gt; in
205+
* the asserting party's &lt;IDPSSODescriptor&gt;.
206+
* @param singleSignOnServiceLocation the SingleSignOnService Location
207+
* @return the {@link B} for further configuration
208+
*/
209+
B singleSignOnServiceLocation(String singleSignOnServiceLocation);
210+
211+
/**
212+
* Set the <a href=
213+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.5%20Endpoint">SingleSignOnService</a>
214+
* Binding.
215+
*
216+
* <p>
217+
* Equivalent to the value found in &lt;SingleSignOnService Binding="..."/&gt; in
218+
* the asserting party's &lt;IDPSSODescriptor&gt;.
219+
* @param singleSignOnServiceBinding the SingleSignOnService Binding
220+
* @return the {@link B} for further configuration
221+
*/
222+
B singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding);
223+
224+
/**
225+
* Set the <a href=
226+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
227+
* Location</a>
228+
*
229+
* <p>
230+
* Equivalent to the value found in &lt;SingleLogoutService Location="..."/&gt; in
231+
* the asserting party's &lt;IDPSSODescriptor&gt;.
232+
* @param singleLogoutServiceLocation the SingleLogoutService Location
233+
* @return the {@link B} for further configuration
234+
* @since 5.6
235+
*/
236+
B singleLogoutServiceLocation(String singleLogoutServiceLocation);
237+
238+
/**
239+
* Set the <a href=
240+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
241+
* Response Location</a>
242+
*
243+
* <p>
244+
* Equivalent to the value found in &lt;SingleLogoutService
245+
* ResponseLocation="..."/&gt; in the asserting party's &lt;IDPSSODescriptor&gt;.
246+
* @param singleLogoutServiceResponseLocation the SingleLogoutService Response
247+
* Location
248+
* @return the {@link B} for further configuration
249+
* @since 5.6
250+
*/
251+
B singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation);
252+
253+
/**
254+
* Set the <a href=
255+
* "https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf#page=7">SingleLogoutService
256+
* Binding</a>
257+
*
258+
* <p>
259+
* Equivalent to the value found in &lt;SingleLogoutService Binding="..."/&gt; in
260+
* the asserting party's &lt;IDPSSODescriptor&gt;.
261+
* @param singleLogoutServiceBinding the SingleLogoutService Binding
262+
* @return the {@link B} for further configuration
263+
* @since 5.6
264+
*/
265+
B singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding);
266+
267+
/**
268+
* Creates an immutable ProviderDetails object representing the configuration for
269+
* an Identity Provider, IDP
270+
* @return immutable ProviderDetails object
271+
*/
272+
AssertingPartyMetadata build();
273+
274+
}
275+
276+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.registration;
18+
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* A repository for retrieving SAML 2.0 Asserting Party Metadata
23+
*
24+
* @author Josh Cummings
25+
* @since 6.4
26+
* @see OpenSamlAssertingPartyMetadataRepository
27+
* @see org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations
28+
*/
29+
public interface AssertingPartyMetadataRepository extends Iterable<AssertingPartyMetadata> {
30+
31+
/**
32+
* Retrieve an {@link AssertingPartyMetadata} by its <a href=
33+
* "https://www.oasis-open.org/committees/download.php/51890/SAML%20MD%20simplified%20overview.pdf#2.9%20EntityDescriptor">EntityID</a>.
34+
* @param entityId the EntityID to lookup
35+
* @return the found {@link AssertingPartyMetadata}, or {@code null} otherwise
36+
*/
37+
@Nullable
38+
default AssertingPartyMetadata findByEntityId(String entityId) {
39+
for (AssertingPartyMetadata metadata : this) {
40+
if (metadata.getEntityId().equals(entityId)) {
41+
return metadata;
42+
}
43+
}
44+
return null;
45+
}
46+
47+
}

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,25 @@ public static Builder withAssertingPartyDetails(AssertingPartyDetails assertingP
338338
return new Builder(assertingPartyDetails.getEntityId(), assertingPartyDetails.mutate());
339339
}
340340

341+
/**
342+
* Creates a {@code RelyingPartyRegistration} {@link Builder} with a
343+
* {@code registrationId} equivalent to the asserting party entity id. Also
344+
* initializes to the contents of the given {@link AssertingPartyMetadata}.
345+
*
346+
* <p>
347+
* Presented as a convenience method when working with
348+
* {@link AssertingPartyMetadataRepository} return values. As such, only supports
349+
* {@link AssertingPartyMetadata} instances of type {@link AssertingPartyDetails}.
350+
* @param metadata the metadata used to initialize the
351+
* {@link RelyingPartyRegistration} {@link Builder}
352+
* @return {@link Builder} to create a {@link RelyingPartyRegistration} object
353+
* @since 6.4
354+
*/
355+
public static Builder withAssertingPartyMetadata(AssertingPartyMetadata metadata) {
356+
Assert.isInstanceOf(AssertingPartyDetails.class, metadata, "metadata must be of type AssertingPartyDetails");
357+
return withAssertingPartyDetails((AssertingPartyDetails) metadata);
358+
}
359+
341360
/**
342361
* Creates a {@code RelyingPartyRegistration} {@link Builder} based on an existing
343362
* object
@@ -380,7 +399,7 @@ public static Builder withRelyingPartyRegistration(RelyingPartyRegistration regi
380399
*
381400
* @since 5.4
382401
*/
383-
public static class AssertingPartyDetails {
402+
public static class AssertingPartyDetails implements AssertingPartyMetadata {
384403

385404
private final String entityId;
386405

@@ -584,7 +603,7 @@ public AssertingPartyDetails.Builder mutate() {
584603
.singleLogoutServiceBinding(this.singleLogoutServiceBinding);
585604
}
586605

587-
public static class Builder {
606+
public static class Builder implements AssertingPartyMetadata.Builder<Builder> {
588607

589608
private String entityId;
590609

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,30 @@ public void buildPreservesCredentialsOrder() {
166166
.containsExactly(encryptingCredential, altApCredential);
167167
}
168168

169+
@Test
170+
void withAssertingPartyMetadataWhenDetailsThenBuilderCopies() {
171+
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
172+
.nameIdFormat("format")
173+
.assertingPartyDetails((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
174+
.assertingPartyDetails((a) -> a.wantAuthnRequestsSigned(false))
175+
.assertingPartyDetails((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
176+
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
177+
.build();
178+
RelyingPartyRegistration copied = RelyingPartyRegistration
179+
.withAssertingPartyMetadata(registration.getAssertingPartyDetails())
180+
.registrationId(registration.getRegistrationId())
181+
.entityId(registration.getEntityId())
182+
.signingX509Credentials((c) -> c.addAll(registration.getSigningX509Credentials()))
183+
.decryptionX509Credentials((c) -> c.addAll(registration.getDecryptionX509Credentials()))
184+
.assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation())
185+
.assertionConsumerServiceBinding(registration.getAssertionConsumerServiceBinding())
186+
.singleLogoutServiceLocation(registration.getSingleLogoutServiceLocation())
187+
.singleLogoutServiceResponseLocation(registration.getSingleLogoutServiceResponseLocation())
188+
.singleLogoutServiceBindings((c) -> c.addAll(registration.getSingleLogoutServiceBindings()))
189+
.nameIdFormat(registration.getNameIdFormat())
190+
.authnRequestsSigned(registration.isAuthnRequestsSigned())
191+
.build();
192+
compareRegistrations(registration, copied);
193+
}
194+
169195
}

0 commit comments

Comments
 (0)