Skip to content

Commit e8c93fd

Browse files
committed
Move Saml Class
Closes gh-14628
1 parent 008296c commit e8c93fd

File tree

2 files changed

+194
-142
lines changed

2 files changed

+194
-142
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,166 +16,33 @@
1616

1717
package org.springframework.security.saml2.provider.service.metadata;
1818

19-
import java.io.UnsupportedEncodingException;
20-
import java.net.URLEncoder;
21-
import java.nio.charset.StandardCharsets;
22-
import java.util.Collections;
23-
import java.util.LinkedHashMap;
24-
import java.util.Map;
25-
import java.util.UUID;
26-
27-
import jakarta.servlet.http.HttpServletRequest;
28-
29-
import org.springframework.security.saml2.Saml2Exception;
3019
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
3120
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
32-
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
33-
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
34-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
35-
import org.springframework.security.web.util.matcher.OrRequestMatcher;
3621
import org.springframework.security.web.util.matcher.RequestMatcher;
37-
import org.springframework.util.Assert;
3822

3923
/**
4024
* An implementation of {@link Saml2MetadataResponseResolver} that identifies which
4125
* {@link RelyingPartyRegistration}s to use with a {@link RequestMatcher}
4226
*
4327
* @author Josh Cummings
4428
* @since 6.1
29+
* @deprecated Please use
30+
* {@link org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver}
4531
*/
46-
public final class RequestMatcherMetadataResponseResolver implements Saml2MetadataResponseResolver {
47-
48-
private static final String DEFAULT_METADATA_FILENAME = "saml-{registrationId}-metadata.xml";
49-
50-
private RequestMatcher matcher = new OrRequestMatcher(
51-
new AntPathRequestMatcher("/saml2/service-provider-metadata/{registrationId}"),
52-
new AntPathRequestMatcher("/saml2/metadata/{registrationId}"),
53-
new AntPathRequestMatcher("/saml2/metadata"));
54-
55-
private String filename = DEFAULT_METADATA_FILENAME;
56-
57-
private final RelyingPartyRegistrationRepository registrations;
58-
59-
private final Saml2MetadataResolver metadata;
32+
@Deprecated
33+
public final class RequestMatcherMetadataResponseResolver extends
34+
org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver {
6035

6136
/**
62-
* Construct a {@link RequestMatcherMetadataResponseResolver}
37+
* Construct a
38+
* {@link org.springframework.security.saml2.provider.service.web.metadata.RequestMatcherMetadataResponseResolver}
6339
* @param registrations the source for relying party metadata
6440
* @param metadata the strategy for converting {@link RelyingPartyRegistration}s into
6541
* metadata
6642
*/
6743
public RequestMatcherMetadataResponseResolver(RelyingPartyRegistrationRepository registrations,
6844
Saml2MetadataResolver metadata) {
69-
Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null");
70-
Assert.notNull(metadata, "saml2MetadataResolver cannot be null");
71-
this.registrations = registrations;
72-
this.metadata = metadata;
73-
}
74-
75-
/**
76-
* Construct and serialize a relying party's SAML 2.0 metadata based on the given
77-
* {@link HttpServletRequest}. Uses the configured {@link RequestMatcher} to identify
78-
* the metadata request, including looking for any indicated {@code registrationId}.
79-
*
80-
* <p>
81-
* If a {@code registrationId} is found in the request, it will attempt to use that,
82-
* erroring if no {@link RelyingPartyRegistration} is found.
83-
*
84-
* <p>
85-
* If no {@code registrationId} is found in the request, it will attempt to show all
86-
* {@link RelyingPartyRegistration}s in an {@code <md:EntitiesDescriptor>}. To
87-
* exercise this functionality, the provided
88-
* {@link RelyingPartyRegistrationRepository} needs to implement {@link Iterable}.
89-
* @param request the HTTP request
90-
* @return a {@link Saml2MetadataResponse} instance
91-
* @throws Saml2Exception if the {@link RequestMatcher} specifies a non-existent
92-
* {@code registrationId}
93-
*/
94-
@Override
95-
public Saml2MetadataResponse resolve(HttpServletRequest request) {
96-
RequestMatcher.MatchResult result = this.matcher.matcher(request);
97-
if (!result.isMatch()) {
98-
return null;
99-
}
100-
String registrationId = result.getVariables().get("registrationId");
101-
Saml2MetadataResponse response = responseByRegistrationId(request, registrationId);
102-
if (response != null) {
103-
return response;
104-
}
105-
if (this.registrations instanceof Iterable<?>) {
106-
Iterable<RelyingPartyRegistration> registrations = (Iterable<RelyingPartyRegistration>) this.registrations;
107-
return responseByIterable(request, registrations);
108-
}
109-
return null;
110-
}
111-
112-
private Saml2MetadataResponse responseByRegistrationId(HttpServletRequest request, String registrationId) {
113-
if (registrationId == null) {
114-
return null;
115-
}
116-
RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId);
117-
if (registration == null) {
118-
throw new Saml2Exception("registration not found");
119-
}
120-
return responseByIterable(request, Collections.singleton(registration));
121-
}
122-
123-
private Saml2MetadataResponse responseByIterable(HttpServletRequest request,
124-
Iterable<RelyingPartyRegistration> registrations) {
125-
Map<String, RelyingPartyRegistration> results = new LinkedHashMap<>();
126-
for (RelyingPartyRegistration registration : registrations) {
127-
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
128-
String entityId = uriResolver.resolve(registration.getEntityId());
129-
results.computeIfAbsent(entityId, (e) -> {
130-
String ssoLocation = uriResolver.resolve(registration.getAssertionConsumerServiceLocation());
131-
String sloLocation = uriResolver.resolve(registration.getSingleLogoutServiceLocation());
132-
String sloResponseLocation = uriResolver.resolve(registration.getSingleLogoutServiceResponseLocation());
133-
return registration.mutate()
134-
.entityId(entityId)
135-
.assertionConsumerServiceLocation(ssoLocation)
136-
.singleLogoutServiceLocation(sloLocation)
137-
.singleLogoutServiceResponseLocation(sloResponseLocation)
138-
.build();
139-
});
140-
}
141-
String metadata = this.metadata.resolve(results.values());
142-
String value = (results.size() == 1) ? results.values().iterator().next().getRegistrationId()
143-
: UUID.randomUUID().toString();
144-
String fileName = this.filename.replace("{registrationId}", value);
145-
try {
146-
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
147-
return new Saml2MetadataResponse(metadata, encodedFileName);
148-
}
149-
catch (UnsupportedEncodingException ex) {
150-
throw new Saml2Exception(ex);
151-
}
152-
}
153-
154-
/**
155-
* Use this {@link RequestMatcher} to identity which requests to generate metadata
156-
* for. By default, matches {@code /saml2/metadata},
157-
* {@code /saml2/metadata/{registrationId}}, {@code /saml2/service-provider-metadata},
158-
* and {@code /saml2/service-provider-metadata/{registrationId}}
159-
* @param requestMatcher the {@link RequestMatcher} to use
160-
*/
161-
public void setRequestMatcher(RequestMatcher requestMatcher) {
162-
Assert.notNull(requestMatcher, "requestMatcher cannot be empty");
163-
this.matcher = requestMatcher;
164-
}
165-
166-
/**
167-
* Sets the metadata filename template. If it contains the {@code {registrationId}}
168-
* placeholder, it will be resolved as a random UUID if there are multiple
169-
* {@link RelyingPartyRegistration}s. Otherwise, it will be replaced by the
170-
* {@link RelyingPartyRegistration}'s id.
171-
*
172-
* <p>
173-
* The default value is {@code saml-{registrationId}-metadata.xml}
174-
* @param metadataFilename metadata filename, must contain a {registrationId}
175-
*/
176-
public void setMetadataFilename(String metadataFilename) {
177-
Assert.hasText(metadataFilename, "metadataFilename cannot be empty");
178-
this.filename = metadataFilename;
45+
super(registrations, metadata);
17946
}
18047

18148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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.web.metadata;
18+
19+
import java.io.UnsupportedEncodingException;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Collections;
23+
import java.util.LinkedHashMap;
24+
import java.util.Map;
25+
import java.util.UUID;
26+
27+
import jakarta.servlet.http.HttpServletRequest;
28+
29+
import org.springframework.security.saml2.Saml2Exception;
30+
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver;
31+
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
32+
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver;
33+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
34+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
35+
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
36+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
37+
import org.springframework.security.web.util.matcher.OrRequestMatcher;
38+
import org.springframework.security.web.util.matcher.RequestMatcher;
39+
import org.springframework.util.Assert;
40+
41+
/**
42+
* An implementation of {@link Saml2MetadataResponseResolver} that identifies which
43+
* {@link RelyingPartyRegistration}s to use with a {@link RequestMatcher}
44+
*
45+
* @author Josh Cummings
46+
* @since 6.1
47+
*/
48+
public class RequestMatcherMetadataResponseResolver implements Saml2MetadataResponseResolver {
49+
50+
private static final String DEFAULT_METADATA_FILENAME = "saml-{registrationId}-metadata.xml";
51+
52+
private RequestMatcher matcher = new OrRequestMatcher(
53+
new AntPathRequestMatcher("/saml2/service-provider-metadata/{registrationId}"),
54+
new AntPathRequestMatcher("/saml2/metadata/{registrationId}"),
55+
new AntPathRequestMatcher("/saml2/metadata"));
56+
57+
private String filename = DEFAULT_METADATA_FILENAME;
58+
59+
private final RelyingPartyRegistrationRepository registrations;
60+
61+
private final Saml2MetadataResolver metadata;
62+
63+
/**
64+
* Construct a
65+
* {@link org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver}
66+
* @param registrations the source for relying party metadata
67+
* @param metadata the strategy for converting {@link RelyingPartyRegistration}s into
68+
* metadata
69+
*/
70+
public RequestMatcherMetadataResponseResolver(RelyingPartyRegistrationRepository registrations,
71+
Saml2MetadataResolver metadata) {
72+
Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null");
73+
Assert.notNull(metadata, "saml2MetadataResolver cannot be null");
74+
this.registrations = registrations;
75+
this.metadata = metadata;
76+
}
77+
78+
/**
79+
* Construct and serialize a relying party's SAML 2.0 metadata based on the given
80+
* {@link HttpServletRequest}. Uses the configured {@link RequestMatcher} to identify
81+
* the metadata request, including looking for any indicated {@code registrationId}.
82+
*
83+
* <p>
84+
* If a {@code registrationId} is found in the request, it will attempt to use that,
85+
* erroring if no {@link RelyingPartyRegistration} is found.
86+
*
87+
* <p>
88+
* If no {@code registrationId} is found in the request, it will attempt to show all
89+
* {@link RelyingPartyRegistration}s in an {@code <md:EntitiesDescriptor>}. To
90+
* exercise this functionality, the provided
91+
* {@link RelyingPartyRegistrationRepository} needs to implement {@link Iterable}.
92+
* @param request the HTTP request
93+
* @return a {@link Saml2MetadataResponse} instance
94+
* @throws Saml2Exception if the {@link RequestMatcher} specifies a non-existent
95+
* {@code registrationId}
96+
*/
97+
@Override
98+
public Saml2MetadataResponse resolve(HttpServletRequest request) {
99+
RequestMatcher.MatchResult result = this.matcher.matcher(request);
100+
if (!result.isMatch()) {
101+
return null;
102+
}
103+
String registrationId = result.getVariables().get("registrationId");
104+
Saml2MetadataResponse response = responseByRegistrationId(request, registrationId);
105+
if (response != null) {
106+
return response;
107+
}
108+
if (this.registrations instanceof Iterable<?>) {
109+
Iterable<RelyingPartyRegistration> registrations = (Iterable<RelyingPartyRegistration>) this.registrations;
110+
return responseByIterable(request, registrations);
111+
}
112+
return null;
113+
}
114+
115+
private Saml2MetadataResponse responseByRegistrationId(HttpServletRequest request, String registrationId) {
116+
if (registrationId == null) {
117+
return null;
118+
}
119+
RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId);
120+
if (registration == null) {
121+
throw new Saml2Exception("registration not found");
122+
}
123+
return responseByIterable(request, Collections.singleton(registration));
124+
}
125+
126+
private Saml2MetadataResponse responseByIterable(HttpServletRequest request,
127+
Iterable<RelyingPartyRegistration> registrations) {
128+
Map<String, RelyingPartyRegistration> results = new LinkedHashMap<>();
129+
for (RelyingPartyRegistration registration : registrations) {
130+
RelyingPartyRegistrationPlaceholderResolvers.UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers
131+
.uriResolver(request, registration);
132+
String entityId = uriResolver.resolve(registration.getEntityId());
133+
results.computeIfAbsent(entityId, (e) -> {
134+
String ssoLocation = uriResolver.resolve(registration.getAssertionConsumerServiceLocation());
135+
String sloLocation = uriResolver.resolve(registration.getSingleLogoutServiceLocation());
136+
String sloResponseLocation = uriResolver.resolve(registration.getSingleLogoutServiceResponseLocation());
137+
return registration.mutate()
138+
.entityId(entityId)
139+
.assertionConsumerServiceLocation(ssoLocation)
140+
.singleLogoutServiceLocation(sloLocation)
141+
.singleLogoutServiceResponseLocation(sloResponseLocation)
142+
.build();
143+
});
144+
}
145+
String metadata = this.metadata.resolve(results.values());
146+
String value = (results.size() == 1) ? results.values().iterator().next().getRegistrationId()
147+
: UUID.randomUUID().toString();
148+
String fileName = this.filename.replace("{registrationId}", value);
149+
try {
150+
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
151+
return new Saml2MetadataResponse(metadata, encodedFileName);
152+
}
153+
catch (UnsupportedEncodingException ex) {
154+
throw new Saml2Exception(ex);
155+
}
156+
}
157+
158+
/**
159+
* Use this {@link RequestMatcher} to identity which requests to generate metadata
160+
* for. By default, matches {@code /saml2/metadata},
161+
* {@code /saml2/metadata/{registrationId}}, {@code /saml2/service-provider-metadata},
162+
* and {@code /saml2/service-provider-metadata/{registrationId}}
163+
* @param requestMatcher the {@link RequestMatcher} to use
164+
*/
165+
public void setRequestMatcher(RequestMatcher requestMatcher) {
166+
Assert.notNull(requestMatcher, "requestMatcher cannot be empty");
167+
this.matcher = requestMatcher;
168+
}
169+
170+
/**
171+
* Sets the metadata filename template. If it contains the {@code {registrationId}}
172+
* placeholder, it will be resolved as a random UUID if there are multiple
173+
* {@link RelyingPartyRegistration}s. Otherwise, it will be replaced by the
174+
* {@link RelyingPartyRegistration}'s id.
175+
*
176+
* <p>
177+
* The default value is {@code saml-{registrationId}-metadata.xml}
178+
* @param metadataFilename metadata filename, must contain a {registrationId}
179+
*/
180+
public void setMetadataFilename(String metadataFilename) {
181+
Assert.hasText(metadataFilename, "metadataFilename cannot be empty");
182+
this.filename = metadataFilename;
183+
}
184+
185+
}

0 commit comments

Comments
 (0)