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 4a23a6f127..be2fd9ba05 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 @@ -1053,279 +1053,20 @@ filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET")); [[servlet-saml2login-logout]] === Performing Single Logout -Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout. +Spring Security does not yet support single logout. -Briefly, there are two use cases Spring Security supports: - -* **RP-Initiated** - Your application has an endpoint that, when POSTed to, will logout the user and send a `saml2:LogoutRequest` to the asserting party. -Thereafter, the asserting party will send back a `saml2:LogoutResponse` and allow your application to respond -* **AP-Initiated** - Your application has an endpoint that will receive a `saml2:LogoutRequest` from the asserting party. -Your application will complete its logout at that point and then send a `saml2:LogoutResponse` to the asserting party. - -[NOTE] -In the **AP-Initiated** scenario, any local redirection that your application would do post-logout is rendered moot. -Once your application sends a `saml2:LogoutResponse`, it no longer has control of the browser. - -=== Minimal Configuration for Single Logout - -To use Spring Security's SAML 2.0 Single Logout feature, you will need the following things: - -* First, the asserting party must support SAML 2.0 Single Logout -* Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint -* Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s - -==== RP-Initiated Single Logout - -Given those, then for RP-initiated Single Logout, you can begin from the initial minimal example and add the following configuration: +Generally speaking, though, you can achieve this by creating and registering a custom `LogoutSuccessHandler` and `RequestMatcher`: [source,java] ---- -@Value("${private.key}") RSAPrivateKey key; -@Value("${public.certificate}") X509Certificate certificate; - -@Bean -RelyingPartyRegistrationRepository registrations() { - RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations - .fromMetadataLocation("https://ap.example.org/metadata") - .registrationId("id") - .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") - .signingX509Credentials((signing) -> signing.add(Saml2X509Credential.signing(key, certificate))) <1> - .build(); - return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration); -} - -@Bean -SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception { - RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations); - LogoutHandler logoutResponseHandler = logoutResponseHandler(registrationResolver); - LogoutSuccessHandler logoutRequestSuccessHandler = logoutRequestSuccessHandler(registrationResolver); - - http - .authorizeRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .saml2Login(withDefaults()) - .logout((logout) -> logout - .logoutUrl("/saml2/logout") - .logoutSuccessHandler(successHandler)) - .addFilterBefore(new Saml2LogoutResponseFilter(logoutHandler), CsrfFilter.class); - - return http.build(); -} - -private LogoutSuccessHandler logoutRequestSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <2> - OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver(registrationResolver); - return new Saml2LogoutRequestSuccessHandler(logoutRequestResolver); -} - -private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <3> - return new OpenSamlLogoutResponseHandler(relyingPartyRegistrationResolver); -} ----- -<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <> -<2> - Second, supply a `LogoutSuccessHandler` for initiating Single Logout, sending a `saml2:LogoutRequest` to the asserting party -<3> - Third, supply the `LogoutHandler` s needed to handle the `saml2:LogoutResponse` s sent from the asserting party. - -==== Runtime Expectations for RP-Initiated - -Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO. -Your application will then do the following: - -1. Logout the user and invalidate the session -2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `` based on the <> associated with the currently logged-in user. -3. Send a redirect or post to the asserting party based on the <> -4. Deserialize, verify, and process the `` sent by the asserting party -5. Redirect to any configured successful logout endpoint - -[TIP] -If your asserting party does not send `` s when logout is complete, the asserting party can still send a `POST /saml2/logout` and then there is no need to configure the `Saml2LogoutResponseHandler`. - -==== AP-Initiated Single Logout - -Instead of RP-initiated Single Logout, you can again begin from the initial minimal example and add the following configuration to achieve AP-initiated Single Logout: - -[source,java] ----- -@Value("${private.key}") RSAPrivateKey key; -@Value("${public.certificate}") X509Certificate certificate; - -@Bean -RelyingPartyRegistrationRepository registrations() { - RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations - .fromMetadataLocation("https://ap.example.org/metadata") - .registrationId("id") - .signingX509Credentials((signing) -> signing.add(Saml2X509Credential.signing(key, certificate))) <1> - .build(); - return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistration); -} - -@Bean -SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception { - RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations); - LogoutHandler logoutRequestHandler = logoutRequestHandler(registrationResolver); - LogoutSuccessHandler logoutResponseSuccessHandler = logoutResponseSuccessHandler(registrationResolver); - - http - .authorizeRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .saml2Login(withDefaults()) - .addFilterBefore(new Saml2LogoutRequestFilter(logoutResponseSuccessHandler, logoutRequestHandler), CsrfFilter.class); - - return http.build(); -} - -private LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { <2> - return new CompositeLogoutHandler( - new OpenSamlLogoutRequestHandler(relyingPartyRegistrationResolver), - new SecurityContextLogoutHandler(), - new LogoutSuccessEventPublishingLogoutHandler()); -} - -private LogoutSuccessHandler logoutSuccessHandler(RelyingPartyRegistrationResolver registrationResolver) { <3> - OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver(registrationResolver); - return new Saml2LogoutResponseSuccessHandler(logoutResponseResolver); -} ----- -<1> - First, add your signing key to the `RelyingPartyRegistration` instance or to <> -<2> - Second, supply the `LogoutHandler` needed to handle the `saml2:LogoutRequest` s sent from the asserting party. -<3> - Third, supply a `LogoutSuccessHandler` for completing Single Logout, sending a `saml2:LogoutResponse` to the asserting party - -==== Runtime Expectations for AP-Initiated - -Given the above configuration, an asserting party can send a `POST /logout/saml2` to your application that includes a `` -Also, your application can participate in an AP-initated logout when the asserting party sends a `` to `/logout/saml2/slo`: - -1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `` sent by the asserting party -2. Logout the user and invalidate the session -3. Create, sign, and serialize a `` based on the <> associated with the just logged-out user -4. Send a redirect or post to the asserting party based on the <> - -[TIP] -If your asserting party does not expect you do send a `` s when logout is complete, you may not need to configure a `LogoutSuccessHandler` - -[NOTE] -In the event that you need to support both logout flows, you can combine the above to configurations. - -=== Configuring Logout Endpoints - -There are three default endpoints that Spring Security's SAML 2.0 Single Logout support exposes: -* `/logout` - the endpoint for initiating single logout with an asserting party -* `/logout/saml2/slo` - the endpoint for receiving logout requests or responses from an asserting party - -Because the user is already logged in, the `registrationId` is already known. -For this reason, `+{registrationId}+` is not part of these URLs by default. - -These URLs are customizable in the DSL. - -For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to `GET /SLOService.saml2`. -To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so: - -[source,java] ----- -Saml2LogoutResponseFilter filter = new Saml2LogoutResponseFilter(logoutHandler); -filter.setLogoutRequestMatcher(new AntPathRequestMatcher("/SLOService.saml2", "GET")); http // ... - .addFilterBefore(filter, CsrfFilter.class); + .logout(logout -> logout + .logoutSuccessHandler(myCustomSuccessHandler()) + .logoutRequestMatcher(myRequestMatcher()) + ) ---- -=== Customizing `` Resolution +The success handler will send logout requests to the asserting party. -It's common to need to set other values in the `` than the defaults that Spring Security provides. - -By default, Spring Security will issue a `` and supply: - -* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation` -* The `ID` attribute - a GUID -* The `` element - from `RelyingPartyRegistration#getEntityId` -* The `` element - from `Authentication#getName` - -To add other values, you can use delegation, like so: - -[source,java] ----- -OpenSamlLogoutRequestResolver delegate = new OpenSamlLogoutRequestResolver(registrationResolver); -return (request, response, authentication) -> { - OpenSamlLogoutRequestBuilder builder = delegate.resolveLogoutRequest(request, response, authentication); <1> - builder.name(((Saml2AuthenticatedPrincipal) authentication.getPrincipal()).getFirstAttribute("CustomAttribute")); <2> - builder.logoutRequest((logoutRequest) -> logoutRequest.setIssueInstant(DateTime.now())); - return builder.logoutRequest(); <3> -}; ----- -<1> - Spring Security applies default values to a `` -<2> - Your application specifies customizations -<3> - You complete the invocation by calling `request()` - -[NOTE] -Support for OpenSAML 4 is coming. -In anticipation of that, `OpenSamlLogoutRequestResolver` does not add an `IssueInstant`. -Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it. - -=== Customizing `` Resolution - -It's common to need to set other values in the `` than the defaults that Spring Security provides. - -By default, Spring Security will issue a `` and supply: - -* The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation` -* The `ID` attribute - a GUID -* The `` element - from `RelyingPartyRegistration#getEntityId` -* The `` element - `SUCCESS` - -To add other values, you can use delegation, like so: - -[source,java] ----- -OpenSamlLogoutResponseResolver delegate = new OpenSamlLogoutResponseResolver(registrationResolver); -return (request, response, authentication) -> { - OpenSamlLogoutResponseBuilder builder = delegate.resolveLogoutResponse(request, response, authentication); <1> - if (checkOtherPrevailingConditions()) { - builder.status(StatusCode.PARTIAL_LOGOUT); <2> - } - builder.logoutResponse((logoutResponse) -> logoutResponse.setIssueInstant(DateTime.now())); - return builder.logoutResponse(); <3> -}; ----- -<1> - Spring Security applies default values to a `` -<2> - Your application specifies customizations -<3> - You complete the invocation by calling `response()` - -[NOTE] -Support for OpenSAML 4 is coming. -In anticipation of that, `OpenSamlLogoutResponseResolver` does not add an `IssueInstant`. -Once OpenSAML 4 support is added, the default will be able to appropriate negotiate that datatype change, meaning you will no longer have to set it. - -=== Customizing `` Validation - -To customize validation, you can implement your own `LogoutHandler`. -At this point, the validation is minimal, so you may be able to first delegate to the default `LogoutHandler` like so: - -[source,java] ----- -LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { - OpenSamlLogoutRequestHandler delegate = new OpenSamlLogoutRequestHandler(registrationResolver); - return (request, response, authentication) -> { - delegate.logout(request, response, authentication); // verify signature, issuer, destination, and principal name - LogoutRequest logoutRequest = // ... parse using OpenSAML - // perform custom validation - } -} ----- - -=== Customizing `` Validation - -To customize validation, you can implement your own `LogoutHandler`. -At this point, the validation is minimal, so you may be able to first delegate to the default `LogoutHandler` like so: - -[source,java] ----- -LogoutHandler logoutHandler(RelyingPartyRegistrationResolver registrationResolver) { - OpenSamlLogoutResponseHandler delegate = new OpenSamlLogoutResponseHandler(registrationResolver); - return (request, response, authentication) -> { - delegate.logout(request, response, authentication); // verify signature, issuer, destination, and status - LogoutResponse logoutResponse = // ... parse using OpenSAML - // perform custom validation - } -} ----- +The request matcher will detect logout requests from the asserting party. diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java index be4e3f6c36..c5cfda1a47 100644 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java +++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/core/Saml2ErrorCodes.java @@ -37,13 +37,6 @@ public interface Saml2ErrorCodes { */ String MALFORMED_RESPONSE_DATA = "malformed_response_data"; - /** - * Request is invalid in a general way. - * - * @since 5.5 - */ - String INVALID_REQUEST = "invalid_request"; - /** * Response is invalid in a general way. * diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequest.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequest.java deleted file mode 100644 index 535281f273..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequest.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2002-2020 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.authentication.logout; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; - -/** - * A class that represents a signed and serialized SAML 2.0 Logout Request - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutRequest implements Serializable { - - private final String location; - - private final Saml2MessageBinding binding; - - private final Map parameters; - - private final String id; - - private final String relyingPartyRegistrationId; - - private Saml2LogoutRequest(String location, Saml2MessageBinding binding, Map parameters, String id, - String relyingPartyRegistrationId) { - this.location = location; - this.binding = binding; - this.parameters = Collections.unmodifiableMap(new HashMap<>(parameters)); - this.id = id; - this.relyingPartyRegistrationId = relyingPartyRegistrationId; - } - - /** - * The unique identifier for this Logout Request - * @return the Logout Request identifier - */ - public String getId() { - return this.id; - } - - /** - * Get the location of the asserting party's SingleLogoutService - * @return the SingleLogoutService location - */ - public String getLocation() { - return this.location; - } - - /** - * Get the binding for the asserting party's SingleLogoutService - * @return the SingleLogoutService binding - */ - public Saml2MessageBinding getBinding() { - return this.binding; - } - - /** - * Get the signed and serialized <saml2:LogoutRequest> payload - * @return the signed and serialized <saml2:LogoutRequest> payload - */ - public String getSamlRequest() { - return this.parameters.get("SAMLRequest"); - } - - /** - * The relay state associated with this Logout Request - * @return the relay state - */ - public String getRelayState() { - return this.parameters.get("RelayState"); - } - - /** - * Get the {@code name} parameter - * - * Useful when specifying additional query parameters for the Logout Request - * @param name the parameter's name - * @return the parameter's value - */ - public String getParameter(String name) { - return this.parameters.get(name); - } - - /** - * Get all parameters - * - * Useful when specifying additional query parameters for the Logout Request - * @return - */ - public Map getParameters() { - return this.parameters; - } - - /** - * The identifier for the {@link RelyingPartyRegistration} associated with this Logout - * Request - * @return the {@link RelyingPartyRegistration} id - */ - public String getRelyingPartyRegistrationId() { - return this.relyingPartyRegistrationId; - } - - /** - * Create a {@link Builder} instance from this {@link RelyingPartyRegistration} - * - * Specifically, this will pull the SingleLogoutService - * location and binding from the {@link RelyingPartyRegistration} - * @param registration the {@link RelyingPartyRegistration} to use - * @return the {@link Builder} for further configurations - */ - public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) { - return new Builder(registration); - } - - public static final class Builder { - - private final RelyingPartyRegistration registration; - - private Map parameters = new HashMap<>(); - - private String id; - - private Builder(RelyingPartyRegistration registration) { - this.registration = registration; - } - - /** - * Use this signed and serialized and Base64-encoded <saml2:LogoutRequest> - * - * Note that if using the Redirect binding, the value should be - * {@link java.util.zip.DeflaterOutputStream deflated} and then Base64-encoded. - * - * It should not be URL-encoded as this will be done when the request is sent - * @param samlRequest the <saml2:LogoutRequest> to use - * @return the {@link Builder} for further configurations - * @see Saml2LogoutRequestResolver - */ - public Builder samlRequest(String samlRequest) { - this.parameters.put("SAMLRequest", samlRequest); - return this; - } - - /** - * Use this value for the relay state when sending the Logout Request to the - * asserting party - * - * It should not be URL-encoded as this will be done when the request is sent - * @param relayState the relay state - * @return the {@link Builder} for further configurations - */ - public Builder relayState(String relayState) { - this.parameters.put("RelayState", relayState); - return this; - } - - /** - * This is the unique id used in the {@link #samlRequest} - * @param id the Logout Request id - * @return the {@link Builder} for further configurations - */ - public Builder id(String id) { - this.id = id; - return this; - } - - /** - * Use this {@link Consumer} to modify the set of query parameters - * - * No parameter should be URL-encoded as this will be done when the request is - * sent - * @param parametersConsumer the {@link Consumer} - * @return the {@link Builder} for further configurations - */ - public Builder parameters(Consumer> parametersConsumer) { - parametersConsumer.accept(this.parameters); - return this; - } - - /** - * Build the {@link Saml2LogoutRequest} - * @return a constructed {@link Saml2LogoutRequest} - */ - public Saml2LogoutRequest build() { - return new Saml2LogoutRequest(this.registration.getAssertingPartyDetails().getSingleLogoutServiceLocation(), - this.registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(), this.parameters, - this.id, this.registration.getRegistrationId()); - } - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponse.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponse.java deleted file mode 100644 index 65e9e12646..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponse.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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. - * 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.authentication.logout; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; - -/** - * A class that represents a signed and serialized SAML 2.0 Logout Response - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutResponse { - - private final String location; - - private final Saml2MessageBinding binding; - - private final Map parameters; - - private Saml2LogoutResponse(String location, Saml2MessageBinding binding, Map parameters) { - this.location = location; - this.binding = binding; - this.parameters = Collections.unmodifiableMap(new HashMap<>(parameters)); - } - - /** - * Get the response location of the asserting party's SingleLogoutService - * @return the SingleLogoutService response location - */ - public String getResponseLocation() { - return this.location; - } - - /** - * Get the binding for the asserting party's SingleLogoutService - * @return the SingleLogoutService binding - */ - public Saml2MessageBinding getBinding() { - return this.binding; - } - - /** - * Get the signed and serialized <saml2:LogoutResponse> payload - * @return the signed and serialized <saml2:LogoutResponse> payload - */ - public String getSamlResponse() { - return this.parameters.get("SAMLResponse"); - } - - /** - * The relay state associated with this Logout Request - * @return the relay state - */ - public String getRelayState() { - return this.parameters.get("RelayState"); - } - - /** - * Get the {@code name} parameter, a short-hand for - * getParameters().get(name) - * - * - * Useful when specifying additional query parameters for the Logout Response - * @param name the parameter's name - * @return the parameter's value - */ - public String getParameter(String name) { - return this.parameters.get(name); - } - - /** - * Get all parameters - * - * Useful when specifying additional query parameters for the Logout Response - * @return - */ - public Map getParameters() { - return this.parameters; - } - - /** - * Create a {@link Saml2LogoutResponse.Builder} instance from this - * {@link RelyingPartyRegistration} - * - * Specifically, this will pull the SingleLogoutService - * response location and binding from the {@link RelyingPartyRegistration} - * @param registration the {@link RelyingPartyRegistration} to use - * @return the {@link Saml2LogoutResponse.Builder} for further configurations - */ - public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) { - return new Builder(registration); - } - - public static final class Builder { - - private String location; - - private Saml2MessageBinding binding; - - private Map parameters = new HashMap<>(); - - private Builder(RelyingPartyRegistration registration) { - this.location = registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation(); - this.binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(); - } - - /** - * Use this signed and serialized and Base64-encoded <saml2:LogoutResponse> - * - * Note that if using the Redirect binding, the value should be - * {@link java.util.zip.DeflaterOutputStream deflated} and then Base64-encoded. - * - * It should not be URL-encoded as this will be done when the response is sent - * @param samlResponse the <saml2:LogoutResponse> to use - * @return the {@link Builder} for further configurations - * @see Saml2LogoutResponseResolver - */ - public Builder samlResponse(String samlResponse) { - this.parameters.put("SAMLResponse", samlResponse); - return this; - } - - /** - * Use this value for the relay state when sending the Logout Request to the - * asserting party - * - * It should not be URL-encoded as this will be done when the response is sent - * @param relayState the relay state - * @return the {@link Builder} for further configurations - */ - public Builder relayState(String relayState) { - this.parameters.put("RelayState", relayState); - return this; - } - - /** - * Use this {@link Consumer} to modify the set of query parameters - * - * No parameter should be URL-encoded as this will be done when the response is - * sent, though any signature specified should be Base64-encoded - * @param parametersConsumer the {@link Consumer} - * @return the {@link Builder} for further configurations - */ - public Builder parameters(Consumer> parametersConsumer) { - parametersConsumer.accept(this.parameters); - return this; - } - - /** - * Build the {@link Saml2LogoutResponse} - * @return a constructed {@link Saml2LogoutResponse} - */ - public Saml2LogoutResponse build() { - return new Saml2LogoutResponse(this.location, this.binding, this.parameters); - } - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java index 1f0d5c19af..edcd9c35c6 100644 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java +++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java @@ -32,7 +32,6 @@ import org.opensaml.saml.saml2.metadata.AssertionConsumerService; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.KeyDescriptor; import org.opensaml.saml.saml2.metadata.SPSSODescriptor; -import org.opensaml.saml.saml2.metadata.SingleLogoutService; import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller; import org.opensaml.security.credential.UsageType; import org.opensaml.xmlsec.signature.KeyInfo; @@ -86,7 +85,6 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver { spSsoDescriptor.getKeyDescriptors() .addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION)); spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration)); - spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration)); return spSsoDescriptor; } @@ -125,14 +123,6 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver { return assertionConsumerService; } - private SingleLogoutService buildSingleLogoutService(RelyingPartyRegistration registration) { - SingleLogoutService singleLogoutService = build(SingleLogoutService.DEFAULT_ELEMENT_NAME); - singleLogoutService.setLocation(registration.getSingleLogoutServiceLocation()); - singleLogoutService.setResponseLocation(registration.getSingleLogoutServiceResponseLocation()); - singleLogoutService.setBinding(registration.getSingleLogoutServiceBinding().getUrn()); - return singleLogoutService; - } - @SuppressWarnings("unchecked") private T build(QName elementName) { XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java index 1b0eb0e35a..c7f04d90f4 100644 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java +++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java @@ -34,7 +34,6 @@ import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.Extensions; import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; import org.opensaml.saml.saml2.metadata.KeyDescriptor; -import org.opensaml.saml.saml2.metadata.SingleLogoutService; import org.opensaml.saml.saml2.metadata.SingleSignOnService; import org.opensaml.security.credential.UsageType; import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; @@ -106,10 +105,6 @@ class OpenSamlAssertingPartyMetadataConverter { builder.assertingPartyDetails( (party) -> party.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm()))); } - if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) { - throw new Saml2Exception( - "Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests"); - } for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) { Saml2MessageBinding binding; if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { @@ -124,27 +119,10 @@ class OpenSamlAssertingPartyMetadataConverter { builder.assertingPartyDetails( (party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation()) .singleSignOnServiceBinding(binding)); - break; + return builder; } - for (SingleLogoutService singleLogoutService : idpssoDescriptor.getSingleLogoutServices()) { - Saml2MessageBinding binding; - if (singleLogoutService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { - binding = Saml2MessageBinding.POST; - } - else if (singleLogoutService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) { - binding = Saml2MessageBinding.REDIRECT; - } - else { - continue; - } - String responseLocation = (singleLogoutService.getResponseLocation() == null) - ? singleLogoutService.getLocation() : singleLogoutService.getResponseLocation(); - builder.assertingPartyDetails( - (party) -> party.singleLogoutServiceLocation(singleLogoutService.getLocation()) - .singleLogoutServiceResponseLocation(responseLocation).singleLogoutServiceBinding(binding)); - break; - } - return builder; + throw new Saml2Exception( + "Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests"); } private List certificates(KeyDescriptor keyDescriptor) { diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index e43ac6f3a7..320bfe35b5 100644 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -81,12 +81,6 @@ public final class RelyingPartyRegistration { private final Saml2MessageBinding assertionConsumerServiceBinding; - private final String singleLogoutServiceLocation; - - private final String singleLogoutServiceResponseLocation; - - private final Saml2MessageBinding singleLogoutServiceBinding; - private final ProviderDetails providerDetails; private final List credentials; @@ -96,9 +90,7 @@ public final class RelyingPartyRegistration { private final Collection signingX509Credentials; private RelyingPartyRegistration(String registrationId, String entityId, String assertionConsumerServiceLocation, - Saml2MessageBinding assertionConsumerServiceBinding, String singleLogoutServiceLocation, - String singleLogoutServiceResponseLocation, Saml2MessageBinding singleLogoutServiceBinding, - ProviderDetails providerDetails, + Saml2MessageBinding assertionConsumerServiceBinding, ProviderDetails providerDetails, Collection credentials, Collection decryptionX509Credentials, Collection signingX509Credentials) { @@ -126,9 +118,6 @@ public final class RelyingPartyRegistration { this.entityId = entityId; this.assertionConsumerServiceLocation = assertionConsumerServiceLocation; this.assertionConsumerServiceBinding = assertionConsumerServiceBinding; - this.singleLogoutServiceLocation = singleLogoutServiceLocation; - this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation; - this.singleLogoutServiceBinding = singleLogoutServiceBinding; this.providerDetails = providerDetails; this.credentials = Collections.unmodifiableList(new LinkedList<>(credentials)); this.decryptionX509Credentials = Collections.unmodifiableList(new LinkedList<>(decryptionX509Credentials)); @@ -188,51 +177,6 @@ public final class RelyingPartyRegistration { return this.assertionConsumerServiceBinding; } - /** - * Get the SingleLogoutService - * Binding. - * - *

- * Equivalent to the value found in <SingleLogoutService Binding="..."/> in the - * relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Binding - * @since 5.5 - */ - public Saml2MessageBinding getSingleLogoutServiceBinding() { - return this.singleLogoutServiceBinding; - } - - /** - * Get the SingleLogoutService - * Location. - * - *

- * Equivalent to the value found in <SingleLogoutService Location="..."/> in the - * relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Location - * @since 5.5 - */ - public String getSingleLogoutServiceLocation() { - return this.singleLogoutServiceLocation; - } - - /** - * Get the SingleLogoutService - * Response Location. - * - *

- * Equivalent to the value found in <SingleLogoutService - * ResponseLocation="..."/> in the relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Response Location - * @since 5.5 - */ - public String getSingleLogoutServiceResponseLocation() { - return this.singleLogoutServiceResponseLocation; - } - /** * Get the {@link Collection} of decryption {@link Saml2X509Credential}s associated * with this relying party @@ -420,9 +364,6 @@ public final class RelyingPartyRegistration { .decryptionX509Credentials((c) -> c.addAll(registration.getDecryptionX509Credentials())) .assertionConsumerServiceLocation(registration.getAssertionConsumerServiceLocation()) .assertionConsumerServiceBinding(registration.getAssertionConsumerServiceBinding()) - .singleLogoutServiceLocation(registration.getSingleLogoutServiceLocation()) - .singleLogoutServiceResponseLocation(registration.getSingleLogoutServiceResponseLocation()) - .singleLogoutServiceBinding(registration.getSingleLogoutServiceBinding()) .assertingPartyDetails((assertingParty) -> assertingParty .entityId(registration.getAssertingPartyDetails().getEntityId()) .wantAuthnRequestsSigned(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) @@ -435,13 +376,7 @@ public final class RelyingPartyRegistration { .singleSignOnServiceLocation( registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()) .singleSignOnServiceBinding( - registration.getAssertingPartyDetails().getSingleSignOnServiceBinding()) - .singleLogoutServiceLocation( - registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()) - .singleLogoutServiceResponseLocation( - registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()) - .singleLogoutServiceBinding( - registration.getAssertingPartyDetails().getSingleLogoutServiceBinding())); + registration.getAssertingPartyDetails().getSingleSignOnServiceBinding())); } private static Saml2X509Credential fromDeprecated( @@ -510,17 +445,10 @@ public final class RelyingPartyRegistration { private final Saml2MessageBinding singleSignOnServiceBinding; - private final String singleLogoutServiceLocation; - - private final String singleLogoutServiceResponseLocation; - - private final Saml2MessageBinding singleLogoutServiceBinding; - private AssertingPartyDetails(String entityId, boolean wantAuthnRequestsSigned, List signingAlgorithms, Collection verificationX509Credentials, Collection encryptionX509Credentials, String singleSignOnServiceLocation, - Saml2MessageBinding singleSignOnServiceBinding, String singleLogoutServiceLocation, - String singleLogoutServiceResponseLocation, Saml2MessageBinding singleLogoutServiceBinding) { + Saml2MessageBinding singleSignOnServiceBinding) { Assert.hasText(entityId, "entityId cannot be null or empty"); Assert.notEmpty(signingAlgorithms, "signingAlgorithms cannot be empty"); Assert.notNull(verificationX509Credentials, "verificationX509Credentials cannot be null"); @@ -544,9 +472,6 @@ public final class RelyingPartyRegistration { this.encryptionX509Credentials = encryptionX509Credentials; this.singleSignOnServiceLocation = singleSignOnServiceLocation; this.singleSignOnServiceBinding = singleSignOnServiceBinding; - this.singleLogoutServiceLocation = singleLogoutServiceLocation; - this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation; - this.singleLogoutServiceBinding = singleLogoutServiceBinding; } /** @@ -640,48 +565,6 @@ public final class RelyingPartyRegistration { return this.singleSignOnServiceBinding; } - /** - * Get the SingleLogoutService - * Location. - * - *

- * Equivalent to the value found in <SingleLogoutService Location="..."/> in - * the asserting party's <IDPSSODescriptor>. - * @return the SingleLogoutService Location - */ - public String getSingleLogoutServiceLocation() { - return this.singleLogoutServiceLocation; - } - - /** - * Get the SingleLogoutService - * ResponseLocation. - * - *

- * Equivalent to the value found in <SingleLogoutService Location="..."/> in - * the asserting party's <IDPSSODescriptor>. - * @return the SingleLogoutService Response Location - */ - public String getSingleLogoutServiceResponseLocation() { - return this.singleLogoutServiceResponseLocation; - } - - /** - * Get the SingleLogoutService - * Binding. - * - *

- * Equivalent to the value found in <SingleLogoutService Binding="..."/> in - * the asserting party's <IDPSSODescriptor>. - * @return the SingleLogoutService Binding - */ - public Saml2MessageBinding getSingleLogoutServiceBinding() { - return this.singleLogoutServiceBinding; - } - public static final class Builder { private String entityId; @@ -698,12 +581,6 @@ public final class RelyingPartyRegistration { private Saml2MessageBinding singleSignOnServiceBinding = Saml2MessageBinding.REDIRECT; - private String singleLogoutServiceLocation; - - private String singleLogoutServiceResponseLocation; - - private Saml2MessageBinding singleLogoutServiceBinding = Saml2MessageBinding.REDIRECT; - /** * Set the asserting party's EntityID. @@ -800,55 +677,6 @@ public final class RelyingPartyRegistration { return this; } - /** - * Set the SingleLogoutService - * Location. - * - *

- * Equivalent to the value found in <SingleLogoutService - * Location="..."/> in the asserting party's <IDPSSODescriptor>. - * @return the SingleLogoutService Location - * @since 5.5 - */ - public Builder singleLogoutServiceLocation(String singleLogoutServiceLocation) { - this.singleLogoutServiceLocation = singleLogoutServiceLocation; - return this; - } - - /** - * Set the SingleLogoutService - * Response Location. - * - *

- * Equivalent to the value found in <SingleLogoutService - * ResponseLocation="..."/> in the asserting party's - * <IDPSSODescriptor>. - * @return the SingleLogoutService Response Location - * @since 5.5 - */ - public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation) { - this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation; - return this; - } - - /** - * Set the SingleLogoutService - * Binding. - * - *

- * Equivalent to the value found in <SingleLogoutService Binding="..."/> - * in the asserting party's <IDPSSODescriptor>. - * @return the SingleLogoutService Binding - * @since 5.5 - */ - public Builder singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding) { - this.singleLogoutServiceBinding = singleLogoutServiceBinding; - return this; - } - /** * Creates an immutable ProviderDetails object representing the configuration * for an Identity Provider, IDP @@ -861,9 +689,7 @@ public final class RelyingPartyRegistration { return new AssertingPartyDetails(this.entityId, this.wantAuthnRequestsSigned, signingAlgorithms, this.verificationX509Credentials, this.encryptionX509Credentials, - this.singleSignOnServiceLocation, this.singleSignOnServiceBinding, - this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation, - this.singleLogoutServiceBinding); + this.singleSignOnServiceLocation, this.singleSignOnServiceBinding); } } @@ -1004,12 +830,6 @@ public final class RelyingPartyRegistration { private Saml2MessageBinding assertionConsumerServiceBinding = Saml2MessageBinding.POST; - private String singleLogoutServiceLocation; - - private String singleLogoutServiceResponseLocation; - - private Saml2MessageBinding singleLogoutServiceBinding = Saml2MessageBinding.POST; - private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder(); private Collection credentials = new HashSet<>(); @@ -1113,54 +933,6 @@ public final class RelyingPartyRegistration { return this; } - /** - * Set the SingleLogoutService - * Binding. - * - *

- * Equivalent to the value found in <SingleLogoutService Binding="..."/> in - * the relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Binding - * @since 5.5 - */ - public Builder singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding) { - this.singleLogoutServiceBinding = singleLogoutServiceBinding; - return this; - } - - /** - * Set the SingleLogoutService - * Location. - * - *

- * Equivalent to the value found in <SingleLogoutService Location="..."/> in - * the relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Location - * @since 5.5 - */ - public Builder singleLogoutServiceLocation(String singleLogoutServiceLocation) { - this.singleLogoutServiceLocation = singleLogoutServiceLocation; - return this; - } - - /** - * Set the SingleLogoutService - * Response Location. - * - *

- * Equivalent to the value found in <SingleLogoutService - * ResponseLocation="..."/> in the relying party's <SPSSODescriptor>. - * @return the SingleLogoutService Response Location - * @since 5.5 - */ - public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation) { - this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation; - return this; - } - /** * Apply this {@link Consumer} to further configure the Asserting Party details * @param assertingPartyDetails The {@link Consumer} to apply @@ -1303,14 +1075,10 @@ public final class RelyingPartyRegistration { for (Saml2X509Credential credential : this.providerDetails.assertingPartyDetailsBuilder.encryptionX509Credentials) { this.credentials.add(toDeprecated(credential)); } - if (this.singleLogoutServiceResponseLocation == null) { - this.singleLogoutServiceResponseLocation = this.singleLogoutServiceLocation; - } return new RelyingPartyRegistration(this.registrationId, this.entityId, this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding, - this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation, - this.singleLogoutServiceBinding, this.providerDetails.build(), this.credentials, - this.decryptionX509Credentials, this.signingX509Credentials); + this.providerDetails.build(), this.credentials, this.decryptionX509Credentials, + this.signingX509Credentials); } } diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java index f6f61b53ee..052a4e82cb 100644 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java +++ b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolver.java @@ -41,7 +41,8 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Josh Cummings * @since 5.4 */ -public final class DefaultRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver { +public final class DefaultRelyingPartyRegistrationResolver + implements Converter, RelyingPartyRegistrationResolver { private static final char PATH_DELIMITER = '/'; @@ -55,6 +56,14 @@ public final class DefaultRelyingPartyRegistrationResolver implements RelyingPar this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository; } + /** + * {@inheritDoc} + */ + @Override + public RelyingPartyRegistration convert(HttpServletRequest request) { + return resolve(request, null); + } + /** * {@inheritDoc} */ @@ -77,14 +86,9 @@ public final class DefaultRelyingPartyRegistrationResolver implements RelyingPar String relyingPartyEntityId = templateResolver.apply(relyingPartyRegistration.getEntityId()); String assertionConsumerServiceLocation = templateResolver .apply(relyingPartyRegistration.getAssertionConsumerServiceLocation()); - String singleLogoutServiceLocation = templateResolver - .apply(relyingPartyRegistration.getSingleLogoutServiceLocation()); - String singleLogoutServiceResponseLocation = templateResolver - .apply(relyingPartyRegistration.getSingleLogoutServiceResponseLocation()); return RelyingPartyRegistration.withRelyingPartyRegistration(relyingPartyRegistration) .entityId(relyingPartyEntityId).assertionConsumerServiceLocation(assertionConsumerServiceLocation) - .singleLogoutServiceLocation(singleLogoutServiceLocation) - .singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation).build(); + .build(); } private Function templateResolver(String applicationUri, RelyingPartyRegistration relyingParty) { @@ -92,9 +96,6 @@ public final class DefaultRelyingPartyRegistrationResolver implements RelyingPar } private static String resolveUrlTemplate(String template, String baseUrl, RelyingPartyRegistration relyingParty) { - if (template == null) { - return null; - } String entityId = relyingParty.getAssertingPartyDetails().getEntityId(); String registrationId = relyingParty.getRegistrationId(); Map uriVariables = new HashMap<>(); diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepository.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepository.java deleted file mode 100644 index 69a4ef370b..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepository.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.util.Assert; - -/** - * An implementation of an {@link Saml2LogoutRequestRepository} that stores - * {@link Saml2LogoutRequest} in the {@code HttpSession}. - * - * @author Josh Cummings - * @since 5.5 - * @see Saml2LogoutRequestRepository - * @see Saml2LogoutRequest - */ -public final class HttpSessionLogoutRequestRepository implements Saml2LogoutRequestRepository { - - private static final String DEFAULT_LOGOUT_REQUEST_ATTR_NAME = HttpSessionLogoutRequestRepository.class.getName() - + ".LOGOUT_REQUEST"; - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutRequest loadLogoutRequest(HttpServletRequest request) { - Assert.notNull(request, "request cannot be null"); - String stateParameter = this.getStateParameter(request); - if (stateParameter == null) { - return null; - } - Map logoutRequests = this.getLogoutRequests(request); - return logoutRequests.get(stateParameter); - } - - /** - * {@inheritDoc} - */ - @Override - public void saveLogoutRequest(Saml2LogoutRequest logoutRequest, HttpServletRequest request, - HttpServletResponse response) { - Assert.notNull(request, "request cannot be null"); - Assert.notNull(response, "response cannot be null"); - if (logoutRequest == null) { - removeLogoutRequest(request, response); - return; - } - String state = logoutRequest.getRelayState(); - Assert.hasText(state, "logoutRequest.state cannot be empty"); - Map logoutRequests = this.getLogoutRequests(request); - logoutRequests.put(state, logoutRequest); - request.getSession().setAttribute(DEFAULT_LOGOUT_REQUEST_ATTR_NAME, logoutRequests); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutRequest removeLogoutRequest(HttpServletRequest request, HttpServletResponse response) { - Assert.notNull(request, "request cannot be null"); - Assert.notNull(response, "response cannot be null"); - String stateParameter = getStateParameter(request); - if (stateParameter == null) { - return null; - } - Map logoutRequests = getLogoutRequests(request); - Saml2LogoutRequest originalRequest = logoutRequests.remove(stateParameter); - if (!logoutRequests.isEmpty()) { - request.getSession().setAttribute(DEFAULT_LOGOUT_REQUEST_ATTR_NAME, logoutRequests); - } - else { - request.getSession().removeAttribute(DEFAULT_LOGOUT_REQUEST_ATTR_NAME); - } - return originalRequest; - } - - private String getStateParameter(HttpServletRequest request) { - return request.getParameter("RelayState"); - } - - private Map getLogoutRequests(HttpServletRequest request) { - HttpSession session = request.getSession(false); - Map logoutRequests = (session != null) - ? (Map) session.getAttribute(DEFAULT_LOGOUT_REQUEST_ATTR_NAME) : null; - if (logoutRequests == null) { - return new HashMap<>(); - } - return logoutRequests; - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandler.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandler.java deleted file mode 100644 index 3b8ae4a43f..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandler.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import net.shibboleth.utilities.java.support.xml.ParserPool; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.impl.LogoutRequestUnmarshaller; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.http.HttpMethod; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlVerificationUtils.VerifierPartial; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.util.Assert; - -/** - * A {@link LogoutHandler} that handles SAML 2.0 Logout Requests received from a SAML 2.0 - * Asserting Party. - * - * @author Josh Cummings - * @since 5.5 - */ -public final class OpenSamlLogoutRequestHandler implements LogoutHandler { - - static { - OpenSamlInitializationService.initialize(); - } - - private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; - - private final ParserPool parserPool; - - private final LogoutRequestUnmarshaller unmarshaller; - - /** - * Constructs a {@link OpenSamlLogoutRequestHandler} from the provided parameters - * @param relyingPartyRegistrationResolver the - * {@link RelyingPartyRegistrationResolver} from which to derive the - * {@link RelyingPartyRegistration} - */ - public OpenSamlLogoutRequestHandler(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.parserPool = registry.getParserPool(); - this.unmarshaller = (LogoutRequestUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() - .getUnmarshaller(LogoutRequest.DEFAULT_ELEMENT_NAME); - } - - /** - * Processes the SAML 2.0 Logout Request received from the SAML 2.0 Asserting Party. - * - * By default, verifies the signature, validates the issuer, destination, and user - * identifier. - * - * If any processing step fails, a {@link Saml2Exception} is thrown, stopping the - * logout process - * @param request the HTTP request - * @param response the HTTP response - * @param authentication the current principal details - */ - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - String serialized = request.getParameter("SAMLRequest"); - Assert.notNull(serialized, "SAMLRequest cannot be null"); - byte[] b = Saml2Utils.samlDecode(serialized); - serialized = inflateIfRequired(request, b); - RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, - getRegistrationId(authentication)); - Assert.notNull(registration, "Failed to lookup RelyingPartyRegistration for request"); - LogoutRequest logoutRequest = parse(serialized); - Saml2ResponseValidatorResult result = verifySignature(request, logoutRequest, registration); - result = result.concat(validateRequest(logoutRequest, registration, authentication)); - if (result.hasErrors()) { - throw new Saml2Exception("Failed to validate LogoutRequest: " + result.getErrors().iterator().next()); - } - request.setAttribute(LogoutRequest.class.getName(), logoutRequest); - } - - private String getRegistrationId(Authentication authentication) { - if (authentication instanceof Saml2Authentication) { - return ((Saml2Authentication) authentication).getRelyingPartyRegistrationId(); - } - return null; - } - - private String inflateIfRequired(HttpServletRequest request, byte[] b) { - if (HttpMethod.GET.matches(request.getMethod())) { - return Saml2Utils.samlInflate(b); - } - return new String(b, StandardCharsets.UTF_8); - } - - private LogoutRequest parse(String request) throws Saml2Exception { - try { - Document document = this.parserPool - .parse(new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (LogoutRequest) this.unmarshaller.unmarshall(element); - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize LogoutRequest", ex); - } - } - - private Saml2ResponseValidatorResult verifySignature(HttpServletRequest request, LogoutRequest logoutRequest, - RelyingPartyRegistration registration) { - VerifierPartial partial = OpenSamlVerificationUtils.verifySignature(logoutRequest, registration); - if (logoutRequest.isSigned()) { - return partial.post(logoutRequest.getSignature()); - } - return partial.redirect(request, "SAMLRequest"); - } - - private Saml2ResponseValidatorResult validateRequest(LogoutRequest request, RelyingPartyRegistration registration, - Authentication authentication) { - Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success(); - return result.concat(validateIssuer(request, registration)).concat(validateDestination(request, registration)) - .concat(validateName(request, authentication)); - } - - private Saml2ResponseValidatorResult validateIssuer(LogoutRequest request, RelyingPartyRegistration registration) { - if (request.getIssuer() == null) { - return Saml2ResponseValidatorResult - .failure(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse")); - } - String issuer = request.getIssuer().getValue(); - if (!issuer.equals(registration.getAssertingPartyDetails().getEntityId())) { - return Saml2ResponseValidatorResult.failure( - new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer")); - } - return Saml2ResponseValidatorResult.success(); - } - - private Saml2ResponseValidatorResult validateDestination(LogoutRequest request, - RelyingPartyRegistration registration) { - if (request.getDestination() == null) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to find destination in LogoutResponse")); - } - String destination = request.getDestination(); - if (!destination.equals(registration.getSingleLogoutServiceLocation())) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to match destination to configured destination")); - } - return Saml2ResponseValidatorResult.success(); - } - - private Saml2ResponseValidatorResult validateName(LogoutRequest request, Authentication authentication) { - if (authentication == null) { - return Saml2ResponseValidatorResult.success(); - } - NameID nameId = request.getNameID(); - if (nameId == null) { - return Saml2ResponseValidatorResult.failure( - new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest")); - } - String name = nameId.getValue(); - if (!name.equals(authentication.getName())) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST, - "Failed to match subject in LogoutRequest with currently logged in user")); - } - return Saml2ResponseValidatorResult.success(); - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolver.java deleted file mode 100644 index 43135b36c9..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolver.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; - -import javax.servlet.http.HttpServletRequest; - -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.impl.IssuerBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutRequestBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutRequestMarshaller; -import org.opensaml.saml.saml2.core.impl.NameIDBuilder; -import org.w3c.dom.Element; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Requests with - * OpenSAML - * - * Note that there are {@link Saml2LogoutRequestResolver} implements that are targeted for - * OpenSAML 3 and OpenSAML 4 via {@code OpenSaml3LogoutRequestResolver} and - * {@code OpenSaml4LogoutRequestResolver} - * - * @author Josh Cummings - * @since 5.5 - */ -public final class OpenSamlLogoutRequestResolver implements Saml2LogoutRequestResolver { - - private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; - - /** - * Construct a {@link OpenSamlLogoutRequestResolver} using the provided parameters - * @param relyingPartyRegistrationResolver the - * {@link RelyingPartyRegistrationResolver} for selecting the - * {@link RelyingPartyRegistration} - */ - public OpenSamlLogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Request. - * - * By default, includes a {@code NameID} based on the {@link Authentication} instance - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public OpenSamlLogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, - Authentication authentication) { - Assert.notNull(authentication, "Failed to lookup logged-in user for formulating LogoutRequest"); - RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, - getRegistrationId(authentication)); - Assert.notNull(registration, "Failed to lookup RelyingPartyRegistration for formulating LogoutRequest"); - return new OpenSamlLogoutRequestBuilder(registration) - .destination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()) - .issuer(registration.getEntityId()).name(authentication.getName()); - } - - private String getRegistrationId(Authentication authentication) { - if (authentication instanceof Saml2Authentication) { - return ((Saml2Authentication) authentication).getRelyingPartyRegistrationId(); - } - return null; - } - - /** - * A builder, useful for overriding any aspects of the SAML 2.0 Logout Request that - * the resolver supplied. - * - * The request returned from the {@link #logoutRequest()} method is signed and - * serialized. It will at minimum include an {@code ID} and a {@code RelayState}, - * though note that callers should also provide an {@code IssueInstant}. For your - * convenience, {@link OpenSamlLogoutRequestResolver} also sets some default values. - * - * This builder is specifically handy for getting access to the underlying - * {@link LogoutRequest} to make changes before it gets signed and serialized - * - * @see OpenSamlLogoutRequestResolver#resolveLogoutRequest - */ - public static final class OpenSamlLogoutRequestBuilder - implements Saml2LogoutRequestBuilder { - - static { - OpenSamlInitializationService.initialize(); - } - - private final LogoutRequestMarshaller marshaller; - - private final IssuerBuilder issuerBuilder; - - private final NameIDBuilder nameIdBuilder; - - private final RelyingPartyRegistration registration; - - private final LogoutRequest logoutRequest; - - private String relayState; - - /** - * Construct a {@link OpenSamlLogoutRequestBuilder} using the provided parameters - * @param registration the {@link RelyingPartyRegistration} to use - */ - public OpenSamlLogoutRequestBuilder(RelyingPartyRegistration registration) { - Assert.notNull(registration, "registration cannot be null"); - this.registration = registration; - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.marshaller = (LogoutRequestMarshaller) registry.getMarshallerFactory() - .getMarshaller(LogoutRequest.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.marshaller, "logoutRequestMarshaller must be configured in OpenSAML"); - LogoutRequestBuilder logoutRequestBuilder = (LogoutRequestBuilder) registry.getBuilderFactory() - .getBuilder(LogoutRequest.DEFAULT_ELEMENT_NAME); - Assert.notNull(logoutRequestBuilder, "logoutRequestBuilder must be configured in OpenSAML"); - this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.issuerBuilder, "issuerBuilder must be configured in OpenSAML"); - this.nameIdBuilder = (NameIDBuilder) registry.getBuilderFactory().getBuilder(NameID.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.nameIdBuilder, "nameIdBuilder must be configured in OpenSAML"); - this.logoutRequest = logoutRequestBuilder.buildObject(); - } - - /** - * {@inheritDoc} - */ - @Override - public OpenSamlLogoutRequestBuilder name(String name) { - NameID nameId = this.nameIdBuilder.buildObject(); - nameId.setValue(name); - this.logoutRequest.setNameID(nameId); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public OpenSamlLogoutRequestBuilder relayState(String relayState) { - this.relayState = relayState; - return this; - } - - /** - * Mutate the {@link LogoutRequest} using the provided {@link Consumer} - * @param request the Logout Request {@link Consumer} to use - * @return the {@link OpenSamlLogoutRequestBuilder} for further customizations - */ - public OpenSamlLogoutRequestBuilder logoutRequest(Consumer request) { - request.accept(this.logoutRequest); - return this; - } - - private OpenSamlLogoutRequestBuilder destination(String destination) { - this.logoutRequest.setDestination(destination); - return this; - } - - private OpenSamlLogoutRequestBuilder issuer(String issuer) { - Issuer iss = this.issuerBuilder.buildObject(); - iss.setValue(issuer); - this.logoutRequest.setIssuer(iss); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutRequest logoutRequest() { - if (this.logoutRequest.getID() == null) { - this.logoutRequest.setID("LR" + UUID.randomUUID()); - } - if (this.relayState == null) { - this.relayState = UUID.randomUUID().toString(); - } - Saml2LogoutRequest.Builder result = Saml2LogoutRequest.withRelyingPartyRegistration(this.registration) - .id(this.logoutRequest.getID()); - if (this.registration.getAssertingPartyDetails() - .getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) { - String xml = serialize(OpenSamlSigningUtils.sign(this.logoutRequest, this.registration)); - return result.samlRequest(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build(); - } - else { - String xml = serialize(this.logoutRequest); - String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); - result.samlRequest(deflatedAndEncoded); - Map parameters = OpenSamlSigningUtils.sign(this.registration) - .param("SAMLRequest", deflatedAndEncoded).param("RelayState", this.relayState).parameters(); - return result.parameters((params) -> params.putAll(parameters)).build(); - } - } - - private String serialize(LogoutRequest logoutRequest) { - try { - Element element = this.marshaller.marshall(logoutRequest); - return SerializeSupport.nodeToString(element); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandler.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandler.java deleted file mode 100644 index 896849dab4..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandler.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import net.shibboleth.utilities.java.support.xml.ParserPool; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.saml2.core.LogoutResponse; -import org.opensaml.saml.saml2.core.StatusCode; -import org.opensaml.saml.saml2.core.impl.LogoutResponseUnmarshaller; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.http.HttpMethod; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlVerificationUtils.VerifierPartial; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.util.Assert; - -/** - * A {@link LogoutHandler} that handles SAML 2.0 Logout Responses received from a SAML 2.0 - * Asserting Party. - * - * @author Josh Cummings - * @since 5.5 - */ -public final class OpenSamlLogoutResponseHandler implements LogoutHandler { - - static { - OpenSamlInitializationService.initialize(); - } - - private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; - - private final ParserPool parserPool; - - private final LogoutResponseUnmarshaller unmarshaller; - - private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); - - /** - * Constructs a {@link OpenSamlLogoutResponseHandler} from the provided parameters - * @param relyingPartyRegistrationResolver the - * {@link RelyingPartyRegistrationResolver} from which to derive the - * {@link RelyingPartyRegistration} - */ - public OpenSamlLogoutResponseHandler(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.parserPool = registry.getParserPool(); - this.unmarshaller = (LogoutResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() - .getUnmarshaller(LogoutResponse.DEFAULT_ELEMENT_NAME); - } - - /** - * Processes the SAML 2.0 Logout Response received from the SAML 2.0 Asserting Party. - * - * By default, verifies the signature, validates the issuer, destination, and status. - * - * If any processing step fails, a {@link Saml2Exception} is thrown, stopping the - * logout process - * @param request the HTTP request - * @param response the HTTP response - * @param authentication the current principal details - */ - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - String serialized = request.getParameter("SAMLResponse"); - Assert.notNull(serialized, "SAMLResponse cannot be null"); - byte[] b = Saml2Utils.samlDecode(serialized); - serialized = inflateIfRequired(request, b); - Saml2LogoutRequest logoutRequest = this.logoutRequestRepository.removeLogoutRequest(request, response); - if (logoutRequest == null) { - throw new Saml2Exception("Failed to find associated LogoutRequest"); - } - RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, - logoutRequest.getRelyingPartyRegistrationId()); - LogoutResponse logoutResponse = parse(serialized); - Saml2ResponseValidatorResult result = verifySignature(request, logoutResponse, registration) - .concat(validateRequest(logoutResponse, registration)) - .concat(validateLogoutRequest(logoutResponse, logoutRequest.getId())); - if (result.hasErrors()) { - throw new Saml2Exception("Failed to validate LogoutResponse: " + result.getErrors().iterator().next()); - } - } - - /** - * Use this {@link Saml2LogoutRequestRepository} for looking up the associated logout - * request. - * @param logoutRequestRepository the {@link Saml2LogoutRequestRepository} to use - */ - public void setLogoutRequestRepository(Saml2LogoutRequestRepository logoutRequestRepository) { - Assert.notNull(logoutRequestRepository, "logoutRequestRepository cannot be null"); - this.logoutRequestRepository = logoutRequestRepository; - } - - private String inflateIfRequired(HttpServletRequest request, byte[] b) { - if (HttpMethod.GET.matches(request.getMethod())) { - return Saml2Utils.samlInflate(b); - } - return new String(b, StandardCharsets.UTF_8); - } - - private LogoutResponse parse(String response) throws Saml2Exception { - try { - Document document = this.parserPool - .parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (LogoutResponse) this.unmarshaller.unmarshall(element); - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize LogoutResponse", ex); - } - } - - private Saml2ResponseValidatorResult verifySignature(HttpServletRequest request, LogoutResponse response, - RelyingPartyRegistration registration) { - VerifierPartial partial = OpenSamlVerificationUtils.verifySignature(response, registration); - if (response.isSigned()) { - return partial.post(response.getSignature()); - } - return partial.redirect(request, "SAMLResponse"); - } - - private Saml2ResponseValidatorResult validateRequest(LogoutResponse response, - RelyingPartyRegistration registration) { - Saml2ResponseValidatorResult result = Saml2ResponseValidatorResult.success(); - return result.concat(validateIssuer(response, registration)).concat(validateDestination(response, registration)) - .concat(validateStatus(response)); - } - - private Saml2ResponseValidatorResult validateIssuer(LogoutResponse response, - RelyingPartyRegistration registration) { - if (response.getIssuer() == null) { - return Saml2ResponseValidatorResult - .failure(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse")); - } - String issuer = response.getIssuer().getValue(); - if (!issuer.equals(registration.getAssertingPartyDetails().getEntityId())) { - return Saml2ResponseValidatorResult.failure( - new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer")); - } - return Saml2ResponseValidatorResult.success(); - } - - private Saml2ResponseValidatorResult validateDestination(LogoutResponse response, - RelyingPartyRegistration registration) { - if (response.getDestination() == null) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to find destination in LogoutResponse")); - } - String destination = response.getDestination(); - if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to match destination to configured destination")); - } - return Saml2ResponseValidatorResult.success(); - } - - private Saml2ResponseValidatorResult validateStatus(LogoutResponse response) { - if (response.getStatus() == null) { - return Saml2ResponseValidatorResult.success(); - } - if (response.getStatus().getStatusCode() == null) { - return Saml2ResponseValidatorResult.success(); - } - if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) { - return Saml2ResponseValidatorResult.success(); - } - if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) { - return Saml2ResponseValidatorResult.success(); - } - return Saml2ResponseValidatorResult - .failure(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed")); - } - - private Saml2ResponseValidatorResult validateLogoutRequest(LogoutResponse response, String id) { - if (response.getInResponseTo() == null) { - return Saml2ResponseValidatorResult.success(); - } - if (response.getInResponseTo().equals(id)) { - return Saml2ResponseValidatorResult.success(); - } - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, - "LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest")); - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolver.java deleted file mode 100644 index 496d3377d5..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolver.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.util.UUID; -import java.util.function.Consumer; - -import javax.servlet.http.HttpServletRequest; - -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.LogoutResponse; -import org.opensaml.saml.saml2.core.Status; -import org.opensaml.saml.saml2.core.StatusCode; -import org.opensaml.saml.saml2.core.impl.IssuerBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutResponseBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutResponseMarshaller; -import org.opensaml.saml.saml2.core.impl.StatusBuilder; -import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder; -import org.w3c.dom.Element; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlSigningUtils.QueryParametersPartial; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Responses with - * OpenSAML - * - * Note that there are {@link Saml2LogoutRequestResolver} implements that are targeted for - * OpenSAML 3 and OpenSAML 4 via {@code OpenSaml3LogoutResponseResolver} and - * {@code OpenSaml4LogoutResponseResolver} - * - * @author Josh Cummings - * @since 5.5 - */ -public final class OpenSamlLogoutResponseResolver implements Saml2LogoutResponseResolver { - - private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver; - - /** - * Construct a {@link OpenSamlLogoutResponseResolver} using the provided parameters - * @param relyingPartyRegistrationResolver the - * {@link RelyingPartyRegistrationResolver} for selecting the - * {@link RelyingPartyRegistration} - */ - public OpenSamlLogoutResponseResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver; - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Response. - * - * By default, includes a {@code RelayState} based on the {@link HttpServletRequest} - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. The - * logout response is also marked as {@code SUCCESS}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public OpenSamlLogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, - Authentication authentication) { - LogoutRequest logoutRequest = (LogoutRequest) request.getAttribute(LogoutRequest.class.getName()); - if (logoutRequest == null) { - throw new Saml2Exception("Failed to find associated LogoutRequest"); - } - RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, - getRegistrationId(authentication)); - Assert.notNull(registration, "Failed to lookup RelyingPartyRegistration for request"); - return new OpenSamlLogoutResponseBuilder(registration) - .destination(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()) - .issuer(registration.getEntityId()).status(StatusCode.SUCCESS) - .relayState(request.getParameter("RelayState")).inResponseTo(logoutRequest.getID()); - } - - private String getRegistrationId(Authentication authentication) { - if (authentication instanceof Saml2Authentication) { - return ((Saml2Authentication) authentication).getRelyingPartyRegistrationId(); - } - return null; - } - - /** - * A builder, useful for overriding any aspects of the SAML 2.0 Logout Response that - * the resolver supplied. - * - * The request returned from the {@link #logoutResponse()} method is signed and - * serialized. It will at minimum include an {@code ID}, though note that callers - * should include an {@code InResponseTo} and {@code IssueInstant}. For your - * convenience, {@link OpenSamlLogoutResponseResolver} also sets some default values. - * - * This builder is specifically handy for getting access to the underlying - * {@link LogoutResponse} to make changes before it gets signed and serialized - * - * @see OpenSamlLogoutResponseResolver#resolveLogoutResponse - */ - public static final class OpenSamlLogoutResponseBuilder - implements Saml2LogoutResponseBuilder { - - static { - OpenSamlInitializationService.initialize(); - } - - private final LogoutResponseMarshaller marshaller; - - private final LogoutResponseBuilder logoutResponseBuilder; - - private final IssuerBuilder issuerBuilder; - - private final StatusBuilder statusBuilder; - - private final StatusCodeBuilder statusCodeBuilder; - - private final RelyingPartyRegistration registration; - - private final LogoutResponse logoutResponse; - - private String relayState; - - /** - * Construct a {@link OpenSamlLogoutResponseBuilder} using the provided parameters - * @param registration the {@link RelyingPartyRegistration} to use - */ - public OpenSamlLogoutResponseBuilder(RelyingPartyRegistration registration) { - Assert.notNull(registration, "registration cannot be null"); - this.registration = registration; - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.marshaller = (LogoutResponseMarshaller) registry.getMarshallerFactory() - .getMarshaller(LogoutResponse.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.marshaller, "logoutResponseMarshaller must be configured in OpenSAML"); - this.logoutResponseBuilder = (LogoutResponseBuilder) registry.getBuilderFactory() - .getBuilder(LogoutResponse.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.logoutResponseBuilder, "logoutResponseBuilder must be configured in OpenSAML"); - this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.issuerBuilder, "issuerBuilder must be configured in OpenSAML"); - this.statusBuilder = (StatusBuilder) registry.getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.statusBuilder, "statusBuilder must be configured in OpenSAML"); - this.statusCodeBuilder = (StatusCodeBuilder) registry.getBuilderFactory() - .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME); - Assert.notNull(this.statusCodeBuilder, "statusCodeBuilder must be configured in OpenSAML"); - this.logoutResponse = this.logoutResponseBuilder.buildObject(); - } - - /** - * {@inheritDoc} - */ - @Override - public OpenSamlLogoutResponseBuilder inResponseTo(String inResponseTo) { - this.logoutResponse.setInResponseTo(inResponseTo); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public OpenSamlLogoutResponseBuilder status(String status) { - StatusCode code = this.statusCodeBuilder.buildObject(); - code.setValue(status); - Status s = this.statusBuilder.buildObject(); - s.setStatusCode(code); - this.logoutResponse.setStatus(s); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public OpenSamlLogoutResponseBuilder relayState(String relayState) { - this.relayState = relayState; - return this; - } - - /** - * Mutate the {@link LogoutResponse} using the provided {@link Consumer} - * @param response the Logout Response {@link Consumer} to use - * @return the {@link OpenSamlLogoutResponseBuilder} for further customizations - */ - public OpenSamlLogoutResponseBuilder logoutResponse(Consumer response) { - response.accept(this.logoutResponse); - return this; - } - - private OpenSamlLogoutResponseBuilder destination(String destination) { - this.logoutResponse.setDestination(destination); - return this; - } - - private OpenSamlLogoutResponseBuilder issuer(String issuer) { - Issuer iss = this.issuerBuilder.buildObject(); - iss.setValue(issuer); - this.logoutResponse.setIssuer(iss); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutResponse logoutResponse() { - Saml2LogoutResponse.Builder result = Saml2LogoutResponse.withRelyingPartyRegistration(this.registration); - if (this.logoutResponse.getID() == null) { - this.logoutResponse.setID("LR" + UUID.randomUUID()); - } - if (this.registration.getAssertingPartyDetails() - .getSingleLogoutServiceBinding() == Saml2MessageBinding.POST) { - String xml = serialize(OpenSamlSigningUtils.sign(this.logoutResponse, this.registration)); - return result.samlResponse(Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8))).build(); - } - else { - String xml = serialize(this.logoutResponse); - String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml)); - result.samlResponse(deflatedAndEncoded); - QueryParametersPartial partial = OpenSamlSigningUtils.sign(this.registration).param("SAMLResponse", - deflatedAndEncoded); - if (this.relayState != null) { - partial.param("RelayState", this.relayState); - } - return result.parameters((params) -> params.putAll(partial.parameters())).build(); - } - } - - private String serialize(LogoutResponse logoutResponse) { - try { - Element element = this.marshaller.marshall(logoutResponse); - return SerializeSupport.nodeToString(element); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlSigningUtils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlSigningUtils.java deleted file mode 100644 index 12ad6769b1..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlSigningUtils.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * Utility methods for signing SAML components with OpenSAML - * - * For internal use only. - * - * @author Josh Cummings - */ -final class OpenSamlSigningUtils { - - static String serialize(XMLObject object) { - try { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - Element element = marshaller.marshall(object); - return SerializeSupport.nodeToString(element); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - static O sign(O object, RelyingPartyRegistration relyingPartyRegistration) { - SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration); - try { - SignatureSupport.signObject(object, parameters); - return object; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - static QueryParametersPartial sign(RelyingPartyRegistration registration) { - return new QueryParametersPartial(registration); - } - - private static SignatureSigningParameters resolveSigningParameters( - RelyingPartyRegistration relyingPartyRegistration) { - List credentials = resolveSigningCredentials(relyingPartyRegistration); - List algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - CriteriaSet criteria = new CriteriaSet(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(algorithms); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private static List resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setEntityId(relyingPartyRegistration.getEntityId()); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - static class QueryParametersPartial { - - final RelyingPartyRegistration registration; - - final Map components = new LinkedHashMap<>(); - - QueryParametersPartial(RelyingPartyRegistration registration) { - this.registration = registration; - } - - QueryParametersPartial param(String key, String value) { - this.components.put(key, value); - return this; - } - - Map parameters() { - SignatureSigningParameters parameters = resolveSigningParameters(this.registration); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put("SigAlg", algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put("Signature", b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - } - - private OpenSamlSigningUtils() { - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlVerificationUtils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlVerificationUtils.java deleted file mode 100644 index 0555c74657..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlVerificationUtils.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; - -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.web.util.UriUtils; - -/** - * Utility methods for verifying SAML component signatures with OpenSAML - * - * For internal use only. - * - * @author Josh Cummings - */ - -final class OpenSamlVerificationUtils { - - static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRegistration registration) { - return new VerifierPartial(object, registration); - } - - static VerifierPartial verifySignature(RequestAbstractType object, RelyingPartyRegistration registration) { - return new VerifierPartial(object, registration); - } - - static class VerifierPartial { - - private final String id; - - private final CriteriaSet criteria; - - private final SignatureTrustEngine trustEngine; - - VerifierPartial(StatusResponseType object, RelyingPartyRegistration registration) { - this.id = object.getID(); - this.criteria = verificationCriteria(object.getIssuer()); - this.trustEngine = trustEngine(registration); - } - - VerifierPartial(RequestAbstractType object, RelyingPartyRegistration registration) { - this.id = object.getID(); - this.criteria = verificationCriteria(object.getIssuer()); - this.trustEngine = trustEngine(registration); - } - - Saml2ResponseValidatorResult redirect(HttpServletRequest request, String objectParameterName) { - RedirectSignature signature = new RedirectSignature(request, objectParameterName); - if (signature.getAlgorithm() == null) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + this.id + "]")); - } - if (!signature.hasSignature()) { - return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + this.id + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = signature.getAlgorithm(); - try { - if (!this.trustEngine.validate(signature.getSignature(), signature.getContent(), algorithmUri, - this.criteria, null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + this.id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + this.id + "]: ")); - } - return Saml2ResponseValidatorResult.failure(errors); - } - - Saml2ResponseValidatorResult post(Signature signature) { - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + this.id + "]: ")); - } - - try { - if (!this.trustEngine.validate(signature, this.criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + this.id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + this.id + "]: ")); - } - - return Saml2ResponseValidatorResult.failure(errors); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - CriteriaSet criteria = new CriteriaSet(); - criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue()))); - criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS))); - criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - return criteria; - } - - private SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) { - Set credentials = new HashSet<>(); - Collection keys = registration.getAssertingPartyDetails() - .getVerificationX509Credentials(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(registration.getAssertingPartyDetails().getEntityId()); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private static class RedirectSignature { - - private final HttpServletRequest request; - - private final String objectParameterName; - - RedirectSignature(HttpServletRequest request, String objectParameterName) { - this.request = request; - this.objectParameterName = objectParameterName; - } - - String getAlgorithm() { - return this.request.getParameter("SigAlg"); - } - - byte[] getContent() { - if (this.request.getParameter("RelayState") != null) { - return String.format("%s=%s&RelayState=%s&SigAlg=%s", this.objectParameterName, - UriUtils.encode(this.request.getParameter(this.objectParameterName), - StandardCharsets.ISO_8859_1), - UriUtils.encode(this.request.getParameter("RelayState"), StandardCharsets.ISO_8859_1), - UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1)) - .getBytes(StandardCharsets.UTF_8); - } - else { - return String - .format("%s=%s&SigAlg=%s", this.objectParameterName, - UriUtils.encode(this.request.getParameter(this.objectParameterName), - StandardCharsets.ISO_8859_1), - UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1)) - .getBytes(StandardCharsets.UTF_8); - } - } - - byte[] getSignature() { - return Saml2Utils.samlDecode(this.request.getParameter("Signature")); - } - - boolean hasSignature() { - return this.request.getParameter("Signature") != null; - } - - } - - } - - private OpenSamlVerificationUtils() { - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java deleted file mode 100644 index 43cc633007..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A filter for handling logout requests in the form of a <saml2:LogoutRequest> sent - * from the asserting party. - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutRequestFilter extends OncePerRequestFilter { - - private static final String DEFAULT_LOGOUT_ENDPOINT = "/logout/saml2/slo"; - - private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher(DEFAULT_LOGOUT_ENDPOINT); - - private final LogoutHandler logoutHandler; - - private final LogoutSuccessHandler logoutSuccessHandler; - - /** - * Constructs a {@link Saml2LogoutResponseFilter} for accepting SAML 2.0 Logout - * Requests from the asserting party - * @param logoutSuccessHandler the success handler to be run after the logout request - * passes validation and other logout operations succeed. This success handler will - * typically be one that issues a SAML 2.0 Logout Response to the asserting party, - * like {@link Saml2LogoutResponseSuccessHandler} - * @param logoutHandler the handler for handling the logout request, may be a - * {@link org.springframework.security.web.authentication.logout.CompositeLogoutHandler} - * that handles other logout concerns - */ - public Saml2LogoutRequestFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler logoutHandler) { - this.logoutSuccessHandler = logoutSuccessHandler; - this.logoutHandler = logoutHandler; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws ServletException, IOException { - - if (!this.logoutRequestMatcher.matches(request)) { - chain.doFilter(request, response); - return; - } - - if (request.getParameter("SAMLRequest") == null) { - chain.doFilter(request, response); - return; - } - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - this.logoutHandler.logout(request, response, authentication); - this.logoutSuccessHandler.onLogoutSuccess(request, response, authentication); - } - - public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) { - Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null"); - this.logoutRequestMatcher = logoutRequestMatcher; - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestRepository.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestRepository.java deleted file mode 100644 index 9b9b1c5ad4..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestRepository.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; - -/** - * Implementations of this interface are responsible for the persistence of - * {@link Saml2LogoutRequest} between requests. - * - *

- * Used by the {@link Saml2LogoutRequestSuccessHandler} for persisting the Logout Request - * before it initiates the SAML 2.0 SLO flow. As well, used by - * {@link OpenSamlLogoutResponseHandler} for resolving the Logout Request associated with - * that Logout Response. - * - * @author Josh Cummings - * @since 5.5 - * @see Saml2LogoutRequest - * @see HttpSessionLogoutRequestRepository - */ -public interface Saml2LogoutRequestRepository { - - /** - * Returns the {@link Saml2LogoutRequest} associated to the provided - * {@code HttpServletRequest} or {@code null} if not available. - * @param request the {@code HttpServletRequest} - * @return the {@link Saml2LogoutRequest} or {@code null} if not available - */ - Saml2LogoutRequest loadLogoutRequest(HttpServletRequest request); - - /** - * Persists the {@link Saml2LogoutRequest} associating it to the provided - * {@code HttpServletRequest} and/or {@code HttpServletResponse}. - * @param logoutRequest the {@link Saml2LogoutRequest} - * @param request the {@code HttpServletRequest} - * @param response the {@code HttpServletResponse} - */ - void saveLogoutRequest(Saml2LogoutRequest logoutRequest, HttpServletRequest request, HttpServletResponse response); - - /** - * Removes and returns the {@link Saml2LogoutRequest} associated to the provided - * {@code HttpServletRequest} and {@code HttpServletResponse} or if not available - * returns {@code null}. - * @param request the {@code HttpServletRequest} - * @param response the {@code HttpServletResponse} - * @return the {@link Saml2LogoutRequest} or {@code null} if not available - */ - Saml2LogoutRequest removeLogoutRequest(HttpServletRequest request, HttpServletResponse response); - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.java deleted file mode 100644 index cc3258b8c2..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -/** - * Creates a signed SAML 2.0 Logout Request based on information from the - * {@link HttpServletRequest} and current {@link Authentication}. - * - * The returned logout request is suitable for sending to the asserting party based on, - * for example, the location and binding specified in - * {@link RelyingPartyRegistration#getAssertingPartyDetails()}. - * - * @author Josh Cummings - * @since 5.5 - * @see RelyingPartyRegistration - */ -public interface Saml2LogoutRequestResolver { - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Request. - * - * By default, includes a {@code NameID} based on the {@link Authentication} instance. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - Saml2LogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, Authentication authentication); - - /** - * A partial application, useful for overriding any aspects of the SAML 2.0 Logout - * Request that the resolver supplied. - * - * The request returned from the {@link #logoutRequest()} method is signed and - * serialized - */ - interface Saml2LogoutRequestBuilder

> { - - /** - * Use the given name in the SAML 2.0 Logout Request - * @param name the name to use - * @return the {@link Saml2LogoutRequestBuilder} for further customizations - */ - P name(String name); - - /** - * Use this relay state when sending the logout response - * @param relayState the relay state to use - * @return the {@link Saml2LogoutRequestBuilder} for further customizations - */ - P relayState(String relayState); - - /** - * Return a signed and serialized SAML 2.0 Logout Request and associated signed - * request parameters - * @return a signed and serialized SAML 2.0 Logout Request - */ - Saml2LogoutRequest logoutRequest(); - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandler.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandler.java deleted file mode 100644 index be851211e7..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandler.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.util.HtmlUtils; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * A success handler for issuing a SAML 2.0 Logout Response in response to the SAML 2.0 - * Logout Request that the SAML 2.0 Asserting Party sent - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutRequestSuccessHandler implements LogoutSuccessHandler { - - private final Saml2LogoutRequestResolver logoutRequestResolver; - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); - - /** - * Constructs a {@link Saml2LogoutRequestSuccessHandler} using the provided parameters - * @param logoutRequestResolver the {@link Saml2LogoutRequestResolver} to use - */ - public Saml2LogoutRequestSuccessHandler(Saml2LogoutRequestResolver logoutRequestResolver) { - this.logoutRequestResolver = logoutRequestResolver; - } - - /** - * Produce and send a SAML 2.0 Logout Response based on the SAML 2.0 Logout Request - * received from the asserting party - * @param request the HTTP request - * @param response the HTTP response - * @param authentication the current principal details - * @throws IOException when failing to write to the response - */ - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException { - Saml2LogoutRequestResolver.Saml2LogoutRequestBuilder builder = this.logoutRequestResolver - .resolveLogoutRequest(request, authentication); - if (builder == null) { - return; - } - Saml2LogoutRequest logoutRequest = builder.logoutRequest(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, response); - if (logoutRequest.getBinding() == Saml2MessageBinding.REDIRECT) { - doRedirect(request, response, logoutRequest); - } - else { - doPost(response, logoutRequest); - } - } - - public void setLogoutRequestRepository(Saml2LogoutRequestRepository logoutRequestRepository) { - Assert.notNull(logoutRequestRepository, "logoutRequestRepository cannot be null"); - this.logoutRequestRepository = logoutRequestRepository; - } - - private void doRedirect(HttpServletRequest request, HttpServletResponse response, Saml2LogoutRequest logoutRequest) - throws IOException { - String location = logoutRequest.getLocation(); - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location); - addParameter("SAMLRequest", logoutRequest, uriBuilder); - addParameter("RelayState", logoutRequest, uriBuilder); - addParameter("SigAlg", logoutRequest, uriBuilder); - addParameter("Signature", logoutRequest, uriBuilder); - this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString()); - } - - private void addParameter(String name, Saml2LogoutRequest logoutRequest, UriComponentsBuilder builder) { - Assert.hasText(name, "name cannot be empty or null"); - if (StringUtils.hasText(logoutRequest.getParameter(name))) { - builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1), - UriUtils.encode(logoutRequest.getParameter(name), StandardCharsets.ISO_8859_1)); - } - } - - private void doPost(HttpServletResponse response, Saml2LogoutRequest logoutRequest) throws IOException { - String html = createSamlPostRequestFormData(logoutRequest); - response.setContentType(MediaType.TEXT_HTML_VALUE); - response.getWriter().write(html); - } - - private String createSamlPostRequestFormData(Saml2LogoutRequest logoutRequest) { - String location = logoutRequest.getLocation(); - String samlRequest = logoutRequest.getSamlRequest(); - String relayState = logoutRequest.getRelayState(); - StringBuilder html = new StringBuilder(); - html.append("\n"); - html.append("\n").append(" \n"); - html.append(" \n"); - html.append(" \n"); - html.append(" \n"); - html.append("

\n"); - html.append(" Note: Since your browser does not support JavaScript,\n"); - html.append(" you must press the Continue button once to proceed.\n"); - html.append("

\n"); - html.append(" \n"); - html.append(" \n"); - html.append("
\n"); - html.append("
\n"); - html.append(" \n"); - if (StringUtils.hasText(relayState)) { - html.append(" \n"); - } - html.append("
\n"); - html.append(" \n"); - html.append("
\n"); - html.append(" \n"); - html.append(" \n"); - html.append(""); - return html.toString(); - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.java deleted file mode 100644 index 5fedb0c7a4..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A filter for handling a <saml2:LogoutResponse> sent from the asserting party. A - * <saml2:LogoutResponse> is sent in response to a <saml2:LogoutRequest> - * already sent by the relying party. - * - * Note that before a <saml2:LogoutRequest> is sent, the user is logged out. Given - * that, this implementation should not use any {@link LogoutHandler} or - * {@link LogoutSuccessHandler} that rely on the user being logged in. - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutResponseFilter extends OncePerRequestFilter { - - private static final String DEFAULT_LOGOUT_ENDPOINT = "/logout/saml2/slo"; - - private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher(DEFAULT_LOGOUT_ENDPOINT); - - private final LogoutHandler logoutHandler; - - private LogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); - - /** - * Constructs a {@link Saml2LogoutResponseFilter} for accepting SAML 2.0 Logout - * Responses from the asserting party - * @param logoutHandler the handlers for handling the logout response - */ - public Saml2LogoutResponseFilter(LogoutHandler logoutHandler) { - this.logoutHandler = new CompositeLogoutHandler(logoutHandler); - } - - /** - * {@inheritDoc} - */ - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws ServletException, IOException { - - if (!this.logoutRequestMatcher.matches(request)) { - chain.doFilter(request, response); - return; - } - - if (request.getParameter("SAMLResponse") == null) { - chain.doFilter(request, response); - return; - } - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - this.logoutHandler.logout(request, response, authentication); - this.logoutSuccessHandler.onLogoutSuccess(request, response, authentication); - } - - /** - * Use this {@link RequestMatcher} for requests - * - * This is handy when your asserting party needs it to be a specific endpoint instead - * of the default. - * @param logoutRequestMatcher the {@link RequestMatcher} to use - */ - public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) { - Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null"); - this.logoutRequestMatcher = logoutRequestMatcher; - } - - /** - * Use this {@link LogoutSuccessHandler} when complete - * - * Note that when a <saml2:LogoutResponse> is received, the end user is already - * logged out. Any {@link LogoutSuccessHandler} used here should not rely on the - * {@link Authentication}. {@link SimpleUrlLogoutSuccessHandler} is an example of - * this. - * @param logoutSuccessHandler the {@link LogoutSuccessHandler} to use - * @see SimpleUrlLogoutSuccessHandler - */ - public void setLogoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) { - Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); - this.logoutSuccessHandler = logoutSuccessHandler; - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java deleted file mode 100644 index bfd1fb7490..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -/** - * Creates a signed SAML 2.0 Logout Response based on information from the - * {@link HttpServletRequest} and current {@link Authentication}. - * - * The returned logout response is suitable for sending to the asserting party based on, - * for example, the location and binding specified in - * {@link RelyingPartyRegistration#getAssertingPartyDetails()}. - * - * @author Josh Cummings - * @since 5.5 - * @see RelyingPartyRegistration - */ -public interface Saml2LogoutResponseResolver { - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Response. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout - * Response that the resolver supplied - */ - Saml2LogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, Authentication authentication); - - /** - * A partial application, useful for overriding any aspects of the SAML 2.0 Logout - * Response that the resolver supplied. - * - * The response returned from the {@link #logoutResponse()} method is signed and - * serialized - */ - interface Saml2LogoutResponseBuilder

> { - - /** - * Use this value as the {@code InResponseTo} identifier for the associated SAML - * 2.0 Logout Request - * @param name the logout request identifier - * @return the {@link Saml2LogoutResponseBuilder} for further customizations - */ - P inResponseTo(String name); - - /** - * Use this status code in the logout response. - * - * The default is {@code SUCCESS}. - * @param status the status code to use - * @return the {@link Saml2LogoutResponseBuilder} for further customizations - */ - P status(String status); - - /** - * Use this relay state when sending the logout response - * @param relayState the relay state to use - * @return the {@link Saml2LogoutResponseBuilder} for further customizations - */ - P relayState(String relayState); - - /** - * Return a signed and serialized SAML 2.0 Logout Response and associated signed - * request parameters - * @return a signed and serialized SAML 2.0 Logout Response - */ - Saml2LogoutResponse logoutResponse(); - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandler.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandler.java deleted file mode 100644 index 48586e1814..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandler.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.util.HtmlUtils; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * A success handler for issuing a SAML 2.0 Logout Response in response to the SAML 2.0 - * Logout Request that the SAML 2.0 Asserting Party sent - * - * @author Josh Cummings - * @since 5.5 - */ -public final class Saml2LogoutResponseSuccessHandler implements LogoutSuccessHandler { - - private final Saml2LogoutResponseResolver logoutResponseResolver; - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - /** - * Constructs a {@link Saml2LogoutResponseSuccessHandler} using the provided - * parameters - * @param logoutResponseResolver the {@link Saml2LogoutResponseResolver} to use - */ - public Saml2LogoutResponseSuccessHandler(Saml2LogoutResponseResolver logoutResponseResolver) { - this.logoutResponseResolver = logoutResponseResolver; - } - - /** - * Produce and send a SAML 2.0 Logout Response based on the SAML 2.0 Logout Request - * received from the asserting party - * @param request the HTTP request - * @param response the HTTP response - * @param authentication the current principal details - * @throws IOException when failing to write to the response - */ - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException { - Saml2LogoutResponse logoutResponse = this.logoutResponseResolver.resolveLogoutResponse(request, authentication) - .logoutResponse(); - if (logoutResponse.getBinding() == Saml2MessageBinding.REDIRECT) { - doRedirect(request, response, logoutResponse); - } - else { - doPost(response, logoutResponse); - } - } - - private void doRedirect(HttpServletRequest request, HttpServletResponse response, - Saml2LogoutResponse logoutResponse) throws IOException { - String location = logoutResponse.getResponseLocation(); - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location); - addParameter("SAMLResponse", logoutResponse, uriBuilder); - addParameter("RelayState", logoutResponse, uriBuilder); - addParameter("SigAlg", logoutResponse, uriBuilder); - addParameter("Signature", logoutResponse, uriBuilder); - this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString()); - } - - private void addParameter(String name, Saml2LogoutResponse logoutResponse, UriComponentsBuilder builder) { - Assert.hasText(name, "name cannot be empty or null"); - if (StringUtils.hasText(logoutResponse.getParameter(name))) { - builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1), - UriUtils.encode(logoutResponse.getParameter(name), StandardCharsets.ISO_8859_1)); - } - } - - private void doPost(HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException { - String html = createSamlPostRequestFormData(logoutResponse); - response.setContentType(MediaType.TEXT_HTML_VALUE); - response.getWriter().write(html); - } - - private String createSamlPostRequestFormData(Saml2LogoutResponse logoutResponse) { - String location = logoutResponse.getResponseLocation(); - String samlRequest = logoutResponse.getSamlResponse(); - String relayState = logoutResponse.getRelayState(); - StringBuilder html = new StringBuilder(); - html.append("\n"); - html.append("\n").append(" \n"); - html.append(" \n"); - html.append(" \n"); - html.append(" \n"); - html.append("

\n"); - html.append(" Note: Since your browser does not support JavaScript,\n"); - html.append(" you must press the Continue button once to proceed.\n"); - html.append("

\n"); - html.append(" \n"); - html.append(" \n"); - html.append("
\n"); - html.append("
\n"); - html.append(" \n"); - if (StringUtils.hasText(relayState)) { - html.append(" \n"); - } - html.append("
\n"); - html.append(" \n"); - html.append("
\n"); - html.append(" \n"); - html.append(" \n"); - html.append(""); - return html.toString(); - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RequestAttributeNames.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RequestAttributeNames.java deleted file mode 100644 index 405401af54..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RequestAttributeNames.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -/** - * Attribute names for coordinating between SAML 2.0 Logout components. - * - * For internal use only. - * - * @author Josh Cummings - */ - -final class Saml2RequestAttributeNames { - - static final String LOGOUT_REQUEST_ID = Saml2RequestAttributeNames.class.getName() + "_LOGOUT_REQUEST_ID"; - - private Saml2RequestAttributeNames() { - - } - -} diff --git a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2Utils.java b/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2Utils.java deleted file mode 100644 index c23d004432..0000000000 --- a/saml2/saml2-service-provider/core/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2Utils.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterOutputStream; - -import org.apache.commons.codec.binary.Base64; - -import org.springframework.security.saml2.Saml2Exception; - -/** - * Utility methods for working with serialized SAML messages. - * - * For internal use only. - * - * @author Josh Cummings - */ -final class Saml2Utils { - - private static Base64 BASE64 = new Base64(0, new byte[] { '\n' }); - - private Saml2Utils() { - } - - static String samlEncode(byte[] b) { - return BASE64.encodeAsString(b); - } - - static byte[] samlDecode(String s) { - return BASE64.decode(s); - } - - static byte[] samlDeflate(String s) { - try { - ByteArrayOutputStream b = new ByteArrayOutputStream(); - DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(Deflater.DEFLATED, true)); - deflater.write(s.getBytes(StandardCharsets.UTF_8)); - deflater.finish(); - return b.toByteArray(); - } - catch (IOException ex) { - throw new Saml2Exception("Unable to deflate string", ex); - } - } - - static String samlInflate(byte[] b) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true)); - iout.write(b); - iout.finish(); - return new String(out.toByteArray(), StandardCharsets.UTF_8); - } - catch (IOException ex) { - throw new Saml2Exception("Unable to inflate string", ex); - } - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java index 7acb769f4a..d7a1fcdc34 100644 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/authentication/TestOpenSamlObjects.java @@ -54,8 +54,6 @@ import org.opensaml.saml.saml2.core.EncryptedAssertion; import org.opensaml.saml.saml2.core.EncryptedAttribute; import org.opensaml.saml.saml2.core.EncryptedID; import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.LogoutResponse; import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.Response; import org.opensaml.saml.saml2.core.Status; @@ -65,10 +63,6 @@ import org.opensaml.saml.saml2.core.SubjectConfirmation; import org.opensaml.saml.saml2.core.SubjectConfirmationData; import org.opensaml.saml.saml2.core.impl.AttributeBuilder; import org.opensaml.saml.saml2.core.impl.AttributeStatementBuilder; -import org.opensaml.saml.saml2.core.impl.IssuerBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutRequestBuilder; -import org.opensaml.saml.saml2.core.impl.LogoutResponseBuilder; -import org.opensaml.saml.saml2.core.impl.NameIDBuilder; import org.opensaml.saml.saml2.core.impl.StatusBuilder; import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder; import org.opensaml.saml.saml2.encryption.Encrypter; @@ -89,7 +83,6 @@ import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; public final class TestOpenSamlObjects { @@ -100,7 +93,7 @@ public final class TestOpenSamlObjects { private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias"; - public static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; + private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; private static String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; @@ -228,7 +221,7 @@ public final class TestOpenSamlObjects { return signable; } - public static T signed(T signable, Saml2X509Credential credential, String entityId) { + static T signed(T signable, Saml2X509Credential credential, String entityId) { return signed(signable, credential, entityId, SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); } @@ -349,41 +342,6 @@ public final class TestOpenSamlObjects { return status; } - public static LogoutRequest assertingPartyLogoutRequest(RelyingPartyRegistration registration) { - LogoutRequestBuilder logoutRequestBuilder = new LogoutRequestBuilder(); - LogoutRequest logoutRequest = logoutRequestBuilder.buildObject(); - logoutRequest.setID("id"); - NameIDBuilder nameIdBuilder = new NameIDBuilder(); - NameID nameId = nameIdBuilder.buildObject(); - nameId.setValue("user"); - logoutRequest.setNameID(nameId); - IssuerBuilder issuerBuilder = new IssuerBuilder(); - Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); - logoutRequest.setIssuer(issuer); - logoutRequest.setDestination(registration.getSingleLogoutServiceLocation()); - return logoutRequest; - } - - public static LogoutResponse assertingPartyLogoutResponse(RelyingPartyRegistration registration) { - LogoutResponseBuilder logoutResponseBuilder = new LogoutResponseBuilder(); - LogoutResponse logoutResponse = logoutResponseBuilder.buildObject(); - logoutResponse.setID("id"); - StatusBuilder statusBuilder = new StatusBuilder(); - StatusCodeBuilder statusCodeBuilder = new StatusCodeBuilder(); - StatusCode code = statusCodeBuilder.buildObject(); - code.setValue(StatusCode.SUCCESS); - Status status = statusBuilder.buildObject(); - status.setStatusCode(code); - logoutResponse.setStatus(status); - IssuerBuilder issuerBuilder = new IssuerBuilder(); - Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); - logoutResponse.setIssuer(issuer); - logoutResponse.setDestination(registration.getSingleLogoutServiceResponseLocation()); - return logoutResponse; - } - static T build(QName qName) { return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); } diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java index 21e77cbb42..2613e452b3 100644 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java @@ -41,8 +41,7 @@ public class OpenSamlMetadataResolverTests { .contains("") .contains("MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh") .contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"") - .contains("Location=\"https://rp.example.org/acs\" index=\"1\"") - .contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\""); + .contains("Location=\"https://rp.example.org/acs\" index=\"1\""); } @Test @@ -57,8 +56,7 @@ public class OpenSamlMetadataResolverTests { .contains("WantAssertionsSigned=\"true\"").doesNotContain("") .doesNotContain("") .contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"") - .contains("Location=\"https://rp.example.org/acs\" index=\"1\"") - .contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\""); + .contains("Location=\"https://rp.example.org/acs\" index=\"1\""); } } diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java index c5626821fc..7d105aecf3 100644 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/registration/TestRelyingPartyRegistrations.java @@ -37,23 +37,17 @@ public final class TestRelyingPartyRegistrations { String apEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"; Saml2X509Credential verificationCertificate = TestSaml2X509Credentials.relyingPartyVerifyingCredential(); String singleSignOnServiceLocation = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"; - String singleLogoutServiceLocation = "{baseUrl}/logout/saml2/slo"; return RelyingPartyRegistration.withRegistrationId(registrationId).entityId(rpEntityId) .assertionConsumerServiceLocation(assertionConsumerServiceLocation) - .singleLogoutServiceLocation(singleLogoutServiceLocation).credentials((c) -> c.add(signingCredential)) + .credentials((c) -> c.add(signingCredential)) .providerDetails((c) -> c.entityId(apEntityId).webSsoUrl(singleSignOnServiceLocation)) .credentials((c) -> c.add(verificationCertificate)); } public static RelyingPartyRegistration.Builder noCredentials() { return RelyingPartyRegistration.withRegistrationId("registration-id").entityId("rp-entity-id") - .singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request") - .singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response") - .assertionConsumerServiceLocation("https://rp.example.org/acs") - .assertingPartyDetails((party) -> party.entityId("ap-entity-id") - .singleSignOnServiceLocation("https://ap.example.org/sso") - .singleLogoutServiceLocation("https://ap.example.org/logout/saml2/request") - .singleLogoutServiceResponseLocation("https://ap.example.org/logout/saml2/response")); + .assertionConsumerServiceLocation("https://rp.example.org/acs").assertingPartyDetails((party) -> party + .entityId("ap-entity-id").singleSignOnServiceLocation("https://ap.example.org/sso")); } public static RelyingPartyRegistration.Builder full() { diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java index d8ebda2816..41418978c4 100644 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java +++ b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/DefaultRelyingPartyRegistrationResolverTests.java @@ -52,9 +52,6 @@ public class DefaultRelyingPartyRegistrationResolverTests { .isEqualTo("http://localhost/saml2/service-provider-metadata/" + this.registration.getRegistrationId()); assertThat(registration.getAssertionConsumerServiceLocation()) .isEqualTo("http://localhost/login/saml2/sso/" + this.registration.getRegistrationId()); - assertThat(registration.getSingleLogoutServiceLocation()).isEqualTo("http://localhost/logout/saml2/slo"); - assertThat(registration.getSingleLogoutServiceResponseLocation()) - .isEqualTo("http://localhost/logout/saml2/slo"); } @Test diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepositoryTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepositoryTests.java deleted file mode 100644 index ca567530b2..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/HttpSessionLogoutRequestRepositoryTests.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link HttpSessionLogoutRequestRepository} - */ -public class HttpSessionLogoutRequestRepositoryTests { - - private HttpSessionLogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); - - @Test - public void loadLogoutRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.logoutRequestRepository.loadLogoutRequest(null)); - } - - @Test - public void loadLogoutRequestWhenNotSavedThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter("RelayState", "state-1234"); - Saml2LogoutRequest logoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(logoutRequest).isNull(); - } - - @Test - public void loadLogoutRequestWhenSavedThenReturnLogoutRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, response); - request.addParameter("RelayState", logoutRequest.getRelayState()); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest).isEqualTo(logoutRequest); - } - - // gh-5110 - @Test - public void loadLogoutRequestWhenMultipleSavedThenReturnMatchingLogoutRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - String state1 = "state-1122"; - Saml2LogoutRequest logoutRequest1 = createLogoutRequest().relayState(state1).build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest1, request, response); - String state2 = "state-3344"; - Saml2LogoutRequest logoutRequest2 = createLogoutRequest().relayState(state2).build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest2, request, response); - String state3 = "state-5566"; - Saml2LogoutRequest logoutRequest3 = createLogoutRequest().relayState(state3).build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest3, request, response); - request.addParameter("RelayState", state1); - Saml2LogoutRequest loadedLogoutRequest1 = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest1).isEqualTo(logoutRequest1); - request.removeParameter("RelayState"); - request.addParameter("RelayState", state2); - Saml2LogoutRequest loadedLogoutRequest2 = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest2).isEqualTo(logoutRequest2); - request.removeParameter("RelayState"); - request.addParameter("RelayState", state3); - Saml2LogoutRequest loadedLogoutRequest3 = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest3).isEqualTo(logoutRequest3); - } - - @Test - public void loadLogoutRequestWhenSavedAndStateParameterNullThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, new MockHttpServletResponse()); - assertThat(this.logoutRequestRepository.loadLogoutRequest(request)).isNull(); - } - - @Test - public void saveLogoutRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() { - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - assertThatIllegalArgumentException().isThrownBy(() -> this.logoutRequestRepository - .saveLogoutRequest(logoutRequest, null, new MockHttpServletResponse())); - } - - @Test - public void saveLogoutRequestWhenHttpServletResponseIsNullThenThrowIllegalArgumentException() { - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - assertThatIllegalArgumentException().isThrownBy(() -> this.logoutRequestRepository - .saveLogoutRequest(logoutRequest, new MockHttpServletRequest(), null)); - } - - @Test - public void saveLogoutRequestWhenStateNullThenThrowIllegalArgumentException() { - Saml2LogoutRequest logoutRequest = createLogoutRequest().relayState(null).build(); - assertThatIllegalArgumentException().isThrownBy(() -> this.logoutRequestRepository - .saveLogoutRequest(logoutRequest, new MockHttpServletRequest(), new MockHttpServletResponse())); - } - - @Test - public void saveLogoutRequestWhenNotNullThenSaved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, new MockHttpServletResponse()); - request.addParameter("RelayState", logoutRequest.getRelayState()); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest).isEqualTo(logoutRequest); - } - - @Test - public void saveLogoutRequestWhenNoExistingSessionAndDistributedSessionThenSaved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(new MockDistributedHttpSession()); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, new MockHttpServletResponse()); - request.addParameter("RelayState", logoutRequest.getRelayState()); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest).isEqualTo(logoutRequest); - } - - @Test - public void saveLogoutRequestWhenExistingSessionAndDistributedSessionThenSaved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(new MockDistributedHttpSession()); - Saml2LogoutRequest logoutRequest1 = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest1, request, new MockHttpServletResponse()); - Saml2LogoutRequest logoutRequest2 = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest2, request, new MockHttpServletResponse()); - request.addParameter("RelayState", logoutRequest2.getRelayState()); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest).isEqualTo(logoutRequest2); - } - - @Test - public void saveLogoutRequestWhenNullThenRemoved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, response); - request.addParameter("RelayState", logoutRequest.getRelayState()); - this.logoutRequestRepository.saveLogoutRequest(null, request, response); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(loadedLogoutRequest).isNull(); - } - - @Test - public void removeLogoutRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy( - () -> this.logoutRequestRepository.removeLogoutRequest(null, new MockHttpServletResponse())); - } - - @Test - public void removeLogoutRequestWhenHttpServletResponseIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.logoutRequestRepository.removeLogoutRequest(new MockHttpServletRequest(), null)); - } - - @Test - public void removeLogoutRequestWhenSavedThenRemoved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, response); - request.addParameter("RelayState", logoutRequest.getRelayState()); - Saml2LogoutRequest removedLogoutRequest = this.logoutRequestRepository.removeLogoutRequest(request, response); - Saml2LogoutRequest loadedLogoutRequest = this.logoutRequestRepository.loadLogoutRequest(request); - assertThat(removedLogoutRequest).isNotNull(); - assertThat(loadedLogoutRequest).isNull(); - } - - // gh-5263 - @Test - public void removeLogoutRequestWhenSavedThenRemovedFromSession() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequest logoutRequest = createLogoutRequest().build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, request, response); - request.addParameter("RelayState", logoutRequest.getRelayState()); - Saml2LogoutRequest removedLogoutRequest = this.logoutRequestRepository.removeLogoutRequest(request, response); - String sessionAttributeName = HttpSessionLogoutRequestRepository.class.getName() + ".AUTHORIZATION_REQUEST"; - assertThat(removedLogoutRequest).isNotNull(); - assertThat(request.getSession().getAttribute(sessionAttributeName)).isNull(); - } - - @Test - public void removeLogoutRequestWhenNotSavedThenNotRemoved() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter("RelayState", "state-1234"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequest removedLogoutRequest = this.logoutRequestRepository.removeLogoutRequest(request, response); - assertThat(removedLogoutRequest).isNull(); - } - - private Saml2LogoutRequest.Builder createLogoutRequest() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - return Saml2LogoutRequest.withRelyingPartyRegistration(registration).samlRequest("request").id("id") - .parameters((params) -> params.put("RelayState", "state-1234")); - } - - static class MockDistributedHttpSession extends MockHttpSession { - - @Override - public Object getAttribute(String name) { - return wrap(super.getAttribute(name)); - } - - @Override - public void setAttribute(String name, Object value) { - super.setAttribute(name, wrap(value)); - } - - private Object wrap(Object object) { - if (object instanceof Map) { - object = new HashMap<>((Map) object); - } - return object; - } - - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandlerTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandlerTests.java deleted file mode 100644 index 0e3e0b6665..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestHandlerTests.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.saml.saml2.core.LogoutRequest; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlSigningUtils.QueryParametersPartial; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; - -/** - * Tests for {@link OpenSamlLogoutRequestHandler} - * - * @author Josh Cummings - */ -public class OpenSamlLogoutRequestHandlerTests { - - private final RelyingPartyRegistrationResolver resolver = mock(RelyingPartyRegistrationResolver.class); - - private final OpenSamlLogoutRequestHandler handler = new OpenSamlLogoutRequestHandler(this.resolver); - - @Test - public void handleWhenAuthenticatedThenSavesRequestId() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - sign(logoutRequest, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - this.handler.logout(request, null, authentication); - String id = ((LogoutRequest) request.getAttribute(LogoutRequest.class.getName())).getID(); - assertThat(id).isEqualTo(logoutRequest.getID()); - } - - @Test - public void handleWhenRedirectBindingThenValidatesSignatureParameter() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = redirect(logoutRequest, OpenSamlSigningUtils.sign(registration)); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - this.handler.logout(request, null, authentication); - String id = ((LogoutRequest) request.getAttribute(LogoutRequest.class.getName())).getID(); - assertThat(id).isEqualTo(logoutRequest.getID()); - } - - @Test - public void handleWhenInvalidIssuerThenInvalidSignatureError() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.getIssuer().setValue("wrong"); - sign(logoutRequest, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_SIGNATURE); - } - - @Test - public void handleWhenMismatchedUserThenInvalidRequestError() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.getNameID().setValue("wrong"); - sign(logoutRequest, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void handleWhenMissingUserThenSubjectNotFoundError() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.setNameID(null); - sign(logoutRequest, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.SUBJECT_NOT_FOUND); - } - - @Test - public void handleWhenMismatchedDestinationThenInvalidDestinationError() { - RelyingPartyRegistration registration = registration().build(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.setDestination("wrong"); - sign(logoutRequest, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_DESTINATION); - } - - private RelyingPartyRegistration.Builder registration() { - return signing(verifying(TestRelyingPartyRegistrations.noCredentials())); - } - - private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { - return builder.assertingPartyDetails((party) -> party - .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); - } - - private RelyingPartyRegistration.Builder signing(RelyingPartyRegistration.Builder builder) { - return builder.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())); - } - - private Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - - private MockHttpServletRequest post(LogoutRequest logoutRequest) { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod("POST"); - request.setParameter("SAMLRequest", - Saml2Utils.samlEncode(serialize(logoutRequest).getBytes(StandardCharsets.UTF_8))); - return request; - } - - private MockHttpServletRequest redirect(LogoutRequest logoutRequest, QueryParametersPartial partial) { - String serialized = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(serialize(logoutRequest))); - Map parameters = partial.param("SAMLRequest", serialized).parameters(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameters(parameters); - request.setMethod("GET"); - return request; - } - - private void sign(LogoutRequest logoutRequest, RelyingPartyRegistration registration) { - TestOpenSamlObjects.signed(logoutRequest, registration.getSigningX509Credentials().iterator().next(), - registration.getAssertingPartyDetails().getEntityId()); - } - - private String serialize(XMLObject object) { - return OpenSamlSigningUtils.serialize(object); - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolverTests.java deleted file mode 100644 index cb0b68ae5c..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestResolverTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.Test; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; - -/** - * Tests for {@link OpenSamlLogoutRequestResolver} - * - * @author Josh Cummings - */ -public class OpenSamlLogoutRequestResolverTests { - - private final RelyingPartyRegistrationResolver resolver = mock(RelyingPartyRegistrationResolver.class); - - private final OpenSamlLogoutRequestResolver logoutResolver = new OpenSamlLogoutRequestResolver(this.resolver); - - @Test - public void resolveRedirectWhenAuthenticatedThenIncludesName() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - Saml2Authentication authentication = authentication(registration); - HttpServletRequest request = new MockHttpServletRequest(); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - Saml2LogoutRequest saml2LogoutRequest = this.logoutResolver.resolveLogoutRequest(request, authentication) - .logoutRequest(); - assertThat(saml2LogoutRequest.getParameter("SigAlg")).isNotNull(); - assertThat(saml2LogoutRequest.getParameter("Signature")).isNotNull(); - Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(); - LogoutRequest logoutRequest = getLogoutRequest(saml2LogoutRequest.getSamlRequest(), binding); - assertThat(logoutRequest.getNameID().getValue()).isEqualTo(authentication.getName()); - } - - @Test - public void resolvePostWhenAuthenticatedThenIncludesName() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full() - .assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST)).build(); - Saml2Authentication authentication = authentication(registration); - HttpServletRequest request = new MockHttpServletRequest(); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - Saml2LogoutRequest saml2LogoutRequest = this.logoutResolver.resolveLogoutRequest(request, authentication) - .logoutRequest(); - assertThat(saml2LogoutRequest.getParameter("SigAlg")).isNull(); - assertThat(saml2LogoutRequest.getParameter("Signature")).isNull(); - Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(); - LogoutRequest logoutRequest = getLogoutRequest(saml2LogoutRequest.getSamlRequest(), binding); - assertThat(logoutRequest.getNameID().getValue()).isEqualTo(authentication.getName()); - } - - private Saml2Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - - private LogoutRequest getLogoutRequest(String samlRequest, Saml2MessageBinding binding) { - if (binding == Saml2MessageBinding.REDIRECT) { - samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest)); - } - else { - samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8); - } - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool() - .parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (LogoutRequest) XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(element) - .unmarshall(element); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandlerTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandlerTests.java deleted file mode 100644 index 3030f68b88..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseHandlerTests.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.saml.saml2.core.LogoutResponse; -import org.opensaml.saml.saml2.core.StatusCode; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlSigningUtils.QueryParametersPartial; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; - -/** - * Tests for {@link OpenSamlLogoutResponseHandler} - * - * @author Josh Cummings - */ -public class OpenSamlLogoutResponseHandlerTests { - - private final RelyingPartyRegistrationResolver resolver = mock(RelyingPartyRegistrationResolver.class); - - private final Saml2LogoutRequestRepository repository = mock(Saml2LogoutRequestRepository.class); - - private final OpenSamlLogoutResponseHandler handler = new OpenSamlLogoutResponseHandler(this.resolver); - - @Before - public void setUp() { - this.handler.setLogoutRequestRepository(this.repository); - } - - @Test - public void handleWhenAuthenticatedThenHandles() { - RelyingPartyRegistration registration = signing(verifying(registration())).build(); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id") - .build(); - given(this.repository.removeLogoutRequest(any(), any())).willReturn(logoutRequest); - LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration); - sign(logoutResponse, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutResponse); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - this.handler.logout(request, null, authentication); - } - - @Test - public void handleWhenRedirectBindingThenValidatesSignatureParameter() { - RelyingPartyRegistration registration = signing(verifying(registration())).build(); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id") - .build(); - given(this.repository.removeLogoutRequest(any(), any())).willReturn(logoutRequest); - LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = redirect(logoutResponse, OpenSamlSigningUtils.sign(registration)); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - this.handler.logout(request, null, authentication); - } - - @Test - public void handleWhenInvalidIssuerThenInvalidSignatureError() { - RelyingPartyRegistration registration = registration().build(); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id") - .build(); - given(this.repository.removeLogoutRequest(any(), any())).willReturn(logoutRequest); - LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration); - logoutResponse.getIssuer().setValue("wrong"); - sign(logoutResponse, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutResponse); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_SIGNATURE); - } - - @Test - public void handleWhenMismatchedDestinationThenInvalidDestinationError() { - RelyingPartyRegistration registration = registration().build(); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id") - .build(); - given(this.repository.removeLogoutRequest(any(), any())).willReturn(logoutRequest); - LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration); - logoutResponse.setDestination("wrong"); - sign(logoutResponse, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutResponse); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_DESTINATION); - } - - @Test - public void handleWhenStatusNotSuccessThenInvalidResponseError() { - RelyingPartyRegistration registration = registration().build(); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id") - .build(); - given(this.repository.removeLogoutRequest(any(), any())).willReturn(logoutRequest); - LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration); - logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL); - sign(logoutResponse, registration); - Authentication authentication = authentication(registration); - MockHttpServletRequest request = post(logoutResponse); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> this.handler.logout(request, null, authentication)) - .withMessageContaining(Saml2ErrorCodes.INVALID_RESPONSE); - } - - private RelyingPartyRegistration.Builder registration() { - return signing(verifying(TestRelyingPartyRegistrations.noCredentials())); - } - - private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { - return builder.assertingPartyDetails((party) -> party - .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); - } - - private RelyingPartyRegistration.Builder signing(RelyingPartyRegistration.Builder builder) { - return builder.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())); - } - - private Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - - private MockHttpServletRequest post(LogoutResponse logoutResponse) { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod("POST"); - request.setParameter("SAMLResponse", - Saml2Utils.samlEncode(serialize(logoutResponse).getBytes(StandardCharsets.UTF_8))); - return request; - } - - private MockHttpServletRequest redirect(LogoutResponse logoutResponse, QueryParametersPartial partial) { - String serialized = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(serialize(logoutResponse))); - Map parameters = partial.param("SAMLResponse", serialized).parameters(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameters(parameters); - request.setMethod("GET"); - return request; - } - - private void sign(LogoutResponse logoutResponse, RelyingPartyRegistration registration) { - TestOpenSamlObjects.signed(logoutResponse, registration.getSigningX509Credentials().iterator().next(), - registration.getAssertingPartyDetails().getEntityId()); - } - - private String serialize(XMLObject object) { - return OpenSamlSigningUtils.serialize(object); - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolverTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolverTests.java deleted file mode 100644 index 4ee2b32f4e..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutResponseResolverTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; - -import javax.servlet.http.HttpServletRequest; - -import org.junit.Test; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.LogoutResponse; -import org.opensaml.saml.saml2.core.StatusCode; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; - -/** - * Tests for {@link OpenSamlLogoutResponseResolver} - * - * @author Josh Cummings - */ -public class OpenSamlLogoutResponseResolverTests { - - private final RelyingPartyRegistrationResolver resolver = mock(RelyingPartyRegistrationResolver.class); - - private final OpenSamlLogoutResponseResolver logoutResolver = new OpenSamlLogoutResponseResolver(this.resolver); - - @Test - public void resolveRedirectWhenAuthenticatedThenSuccess() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - Saml2Authentication authentication = authentication(registration); - HttpServletRequest request = new MockHttpServletRequest(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - request.setAttribute(LogoutRequest.class.getName(), logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - Saml2LogoutResponse saml2LogoutResponse = this.logoutResolver.resolveLogoutResponse(request, authentication) - .logoutResponse(); - assertThat(saml2LogoutResponse.getParameter("SigAlg")).isNotNull(); - assertThat(saml2LogoutResponse.getParameter("Signature")).isNotNull(); - Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(); - LogoutResponse logoutResponse = getLogoutResponse(saml2LogoutResponse.getSamlResponse(), binding); - assertThat(logoutResponse.getStatus().getStatusCode().getValue()).isEqualTo(StatusCode.SUCCESS); - } - - @Test - public void resolvePostWhenAuthenticatedThenSuccess() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full() - .assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST)).build(); - Saml2Authentication authentication = authentication(registration); - HttpServletRequest request = new MockHttpServletRequest(); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - request.setAttribute(LogoutRequest.class.getName(), logoutRequest); - given(this.resolver.resolve(request, registration.getRegistrationId())).willReturn(registration); - Saml2LogoutResponse saml2LogoutResponse = this.logoutResolver.resolveLogoutResponse(request, authentication) - .logoutResponse(); - assertThat(saml2LogoutResponse.getParameter("SigAlg")).isNull(); - assertThat(saml2LogoutResponse.getParameter("Signature")).isNull(); - Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding(); - LogoutResponse logoutResponse = getLogoutResponse(saml2LogoutResponse.getSamlResponse(), binding); - assertThat(logoutResponse.getStatus().getStatusCode().getValue()).isEqualTo(StatusCode.SUCCESS); - } - - private Saml2Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - - private LogoutResponse getLogoutResponse(String saml2Response, Saml2MessageBinding binding) { - if (binding == Saml2MessageBinding.REDIRECT) { - saml2Response = Saml2Utils.samlInflate(Saml2Utils.samlDecode(saml2Response)); - } - else { - saml2Response = new String(Saml2Utils.samlDecode(saml2Response), StandardCharsets.UTF_8); - } - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool() - .parse(new ByteArrayInputStream(saml2Response.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (LogoutResponse) XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(element) - .unmarshall(element); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java deleted file mode 100644 index c65fcae670..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilterTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import org.junit.After; -import org.junit.Test; - -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.verifyNoInteractions; - -public class Saml2LogoutRequestFilterTests { - - private final LogoutHandler handler = mock(LogoutHandler.class); - - private final LogoutSuccessHandler successHandler = mock(LogoutSuccessHandler.class); - - private final Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(this.successHandler, this.handler); - - @After - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void doFilterWhenSamlRequestMatchesThenLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLRequest", "request"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verify(this.handler).logout(request, response, authentication); - verify(this.successHandler).onLogoutSuccess(request, response, authentication); - } - - @Test - public void doFilterWhenSamlResponseMatchesThenLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLRequest", "request"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verify(this.handler).logout(request, response, authentication); - verify(this.successHandler).onLogoutSuccess(request, response, authentication); - } - - @Test - public void doFilterWhenRequestMismatchesThenNoLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout"); - request.setServletPath("/logout"); - request.setParameter("SAMLRequest", "request"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verifyNoInteractions(this.handler); - verifyNoInteractions(this.successHandler); - } - - @Test - public void doFilterWhenNoSamlRequestOrResponseThenNoLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verifyNoInteractions(this.handler); - verifyNoInteractions(this.successHandler); - } - - @Test - public void doFilterWhenLogoutHandlerFailsThenNoSuccessHandler() { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLRequest", "request"); - MockHttpServletResponse response = new MockHttpServletResponse(); - willThrow(RuntimeException.class).given(this.handler).logout(request, response, authentication); - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> this.filter.doFilterInternal(request, response, new MockFilterChain())); - verify(this.handler).logout(request, response, authentication); - verifyNoInteractions(this.successHandler); - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandlerTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandlerTests.java deleted file mode 100644 index eb3459e279..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestSuccessHandlerTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.util.ArrayList; -import java.util.HashMap; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver.Saml2LogoutRequestBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.RETURNS_SELF; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.willReturn; - -/** - * Tests for {@link Saml2LogoutRequestSuccessHandler} - * - * @author Josh Cummings - */ -public class Saml2LogoutRequestSuccessHandlerTests { - - private final Saml2LogoutRequestResolver resolver = mock(Saml2LogoutRequestResolver.class); - - private final Saml2LogoutRequestRepository repository = mock(Saml2LogoutRequestRepository.class); - - private final Saml2LogoutRequestSuccessHandler handler = new Saml2LogoutRequestSuccessHandler(this.resolver); - - @Before - public void setUp() { - this.handler.setLogoutRequestRepository(this.repository); - } - - @After - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void doFilterWhenRedirectThenRedirectsToAssertingParty() throws Exception { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - Authentication authentication = authentication(registration); - SecurityContextHolder.getContext().setAuthentication(authentication); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest("request").build(); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/saml2/logout"); - request.setServletPath("/saml2/logout"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequestBuilder partial = mock(Saml2LogoutRequestBuilder.class, RETURNS_SELF); - given(partial.logoutRequest()).willReturn(logoutRequest); - willReturn(partial).given(this.resolver).resolveLogoutRequest(request, authentication); - this.handler.onLogoutSuccess(request, response, authentication); - String content = response.getHeader("Location"); - assertThat(content).contains("SAMLRequest"); - assertThat(content).startsWith(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()); - } - - @Test - public void doFilterWhenPostThenPostsToAssertingParty() throws Exception { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full() - .assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST)).build(); - Authentication authentication = authentication(registration); - SecurityContextHolder.getContext().setAuthentication(authentication); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest("request").build(); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/saml2/logout"); - request.setServletPath("/saml2/logout"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutRequestBuilder partial = mock(Saml2LogoutRequestBuilder.class, RETURNS_SELF); - given(partial.logoutRequest()).willReturn(logoutRequest); - willReturn(partial).given(this.resolver).resolveLogoutRequest(request, authentication); - this.handler.onLogoutSuccess(request, response, authentication); - String content = response.getContentAsString(); - assertThat(content).contains("SAMLRequest"); - assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()); - } - - private Saml2Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilterTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilterTests.java deleted file mode 100644 index 2a98bc1905..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilterTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.verifyNoInteractions; - -public class Saml2LogoutResponseFilterTests { - - private final LogoutHandler handler = mock(LogoutHandler.class); - - private final LogoutSuccessHandler successHandler = mock(LogoutSuccessHandler.class); - - private final Saml2LogoutResponseFilter filter = new Saml2LogoutResponseFilter(this.handler); - - @Before - public void setUp() { - this.filter.setLogoutSuccessHandler(this.successHandler); - } - - @After - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void doFilterWhenSamlRequestMatchesThenLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLResponse", "response"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verify(this.handler).logout(request, response, authentication); - verify(this.successHandler).onLogoutSuccess(request, response, authentication); - } - - @Test - public void doFilterWhenSamlResponseMatchesThenLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLResponse", "response"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verify(this.handler).logout(request, response, authentication); - verify(this.successHandler).onLogoutSuccess(request, response, authentication); - } - - @Test - public void doFilterWhenRequestMismatchesThenNoLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout"); - request.setServletPath("/logout"); - request.setParameter("SAMLResponse", "response"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verifyNoInteractions(this.handler); - verifyNoInteractions(this.successHandler); - } - - @Test - public void doFilterWhenNoSamlRequestOrResponseThenNoLogout() throws Exception { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2"); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.filter.doFilterInternal(request, response, new MockFilterChain()); - verifyNoInteractions(this.handler); - verifyNoInteractions(this.successHandler); - } - - @Test - public void doFilterWhenLogoutHandlerFailsThenNoSuccessHandler() { - Authentication authentication = new TestingAuthenticationToken("user", "password"); - SecurityContextHolder.getContext().setAuthentication(authentication); - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo"); - request.setServletPath("/logout/saml2/slo"); - request.setParameter("SAMLResponse", "response"); - MockHttpServletResponse response = new MockHttpServletResponse(); - willThrow(RuntimeException.class).given(this.handler).logout(request, response, authentication); - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> this.filter.doFilterInternal(request, response, new MockFilterChain())); - verify(this.handler).logout(request, response, authentication); - verifyNoInteractions(this.successHandler); - } - -} diff --git a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandlerTests.java b/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandlerTests.java deleted file mode 100644 index 7cb61800ad..0000000000 --- a/saml2/saml2-service-provider/core/src/test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseSuccessHandlerTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.util.ArrayList; -import java.util.HashMap; - -import org.junit.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver.Saml2LogoutResponseBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.RETURNS_SELF; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.willReturn; - -/** - * Tests for {@link Saml2LogoutResponseSuccessHandler} - * - * @author Josh Cummings - */ -public class Saml2LogoutResponseSuccessHandlerTests { - - private final Saml2LogoutResponseResolver resolver = mock(Saml2LogoutResponseResolver.class); - - private final Saml2LogoutResponseSuccessHandler handler = new Saml2LogoutResponseSuccessHandler(this.resolver); - - @Test - public void doFilterWhenRedirectThenRedirectsToAssertingParty() throws Exception { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - Authentication authentication = authentication(registration); - Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration) - .samlResponse("response").build(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(Saml2RequestAttributeNames.LOGOUT_REQUEST_ID, "id"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutResponseBuilder partial = mock(Saml2LogoutResponseBuilder.class, RETURNS_SELF); - given(partial.logoutResponse()).willReturn(logoutResponse); - willReturn(partial).given(this.resolver).resolveLogoutResponse(request, authentication); - this.handler.onLogoutSuccess(request, response, authentication); - String content = response.getHeader("Location"); - assertThat(content).contains("SAMLResponse"); - assertThat(content) - .startsWith(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()); - } - - @Test - public void doFilterWhenPostThenPostsToAssertingParty() throws Exception { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full() - .assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST)).build(); - Authentication authentication = authentication(registration); - Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration) - .samlResponse("response").build(); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(Saml2RequestAttributeNames.LOGOUT_REQUEST_ID, "id"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Saml2LogoutResponseBuilder partial = mock(Saml2LogoutResponseBuilder.class, RETURNS_SELF); - given(partial.logoutResponse()).willReturn(logoutResponse); - willReturn(partial).given(this.resolver).resolveLogoutResponse(request, authentication); - this.handler.onLogoutSuccess(request, response, authentication); - String content = response.getContentAsString(); - assertThat(content).contains("SAMLResponse"); - assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation()); - } - - private Saml2Authentication authentication(RelyingPartyRegistration registration) { - return new Saml2Authentication(new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>()), "response", - new ArrayList<>(), registration.getRegistrationId()); - } - -} diff --git a/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutRequestResolver.java b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutRequestResolver.java deleted file mode 100644 index a5bd7c49b7..0000000000 --- a/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutRequestResolver.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.time.Clock; - -import javax.servlet.http.HttpServletRequest; - -import org.joda.time.DateTime; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestResolver.OpenSamlLogoutRequestBuilder; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Requests with - * OpenSAML 3 - * - * @author Josh Cummings - * @since 5.5 - * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to - * {@code OpenSaml4LogoutRequestResolver} - */ -public class OpenSaml3LogoutRequestResolver implements Saml2LogoutRequestResolver { - - private final OpenSamlLogoutRequestResolver logoutRequestResolver; - - private Clock clock = Clock.systemUTC(); - - /** - * Construct a {@link OpenSaml3LogoutRequestResolver} with the provided parameters - * @param relyingPartyRegistrationResolver a strategy for resolving a - * {@link RelyingPartyRegistration} - */ - public OpenSaml3LogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.logoutRequestResolver = new OpenSamlLogoutRequestResolver(relyingPartyRegistrationResolver); - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Request. - * - * By default, includes a {@code NameID} based on the {@link Authentication} instance - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. The - * request also contains its issued {@link DateTime}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public Saml2LogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, - Authentication authentication) { - OpenSamlLogoutRequestBuilder builder = this.logoutRequestResolver.resolveLogoutRequest(request, authentication); - if (builder == null) { - return null; - } - return builder - .logoutRequest((logoutRequest) -> logoutRequest.setIssueInstant(new DateTime(this.clock.millis()))); - } - - /** - * Use this {@link Clock} for generating the issued {@link DateTime} - * @param clock the {@link Clock} to use - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.clock = clock; - } - -} diff --git a/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutResponseResolver.java b/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutResponseResolver.java deleted file mode 100644 index 8d5c2138f1..0000000000 --- a/saml2/saml2-service-provider/opensaml3/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml3LogoutResponseResolver.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.time.Clock; - -import javax.servlet.http.HttpServletRequest; - -import org.joda.time.DateTime; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutResponseResolver.OpenSamlLogoutResponseBuilder; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutResponseResolver} for resolving SAML 2.0 Logout Responses with - * OpenSAML 3 - * - * @author Josh Cummings - * @since 5.5 - * @deprecated Because OpenSAML 3 has reached End-of-Life, please update to - * {@code OpenSaml4LogoutResponseResolver} - */ -public class OpenSaml3LogoutResponseResolver implements Saml2LogoutResponseResolver { - - private final OpenSamlLogoutResponseResolver logoutResponseResolver; - - private Clock clock = Clock.systemUTC(); - - /** - * Construct a {@link OpenSaml3LogoutResponseResolver} with the provided parameters - * @param relyingPartyRegistrationResolver a strategy for resolving a - * {@link RelyingPartyRegistration} - */ - public OpenSaml3LogoutResponseResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.logoutResponseResolver = new OpenSamlLogoutResponseResolver(relyingPartyRegistrationResolver); - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Response. - * - * By default, includes a {@code RelayState} based on the {@link HttpServletRequest} - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. The - * logout response also includes an issued {@link DateTime} and is marked as - * {@code SUCCESS}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public Saml2LogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, - Authentication authentication) { - OpenSamlLogoutResponseBuilder builder = this.logoutResponseResolver.resolveLogoutResponse(request, - authentication); - if (builder == null) { - return null; - } - return builder - .logoutResponse((logoutResponse) -> logoutResponse.setIssueInstant(new DateTime(this.clock.millis()))); - } - - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.clock = clock; - } - -} diff --git a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java deleted file mode 100644 index 07397f15d2..0000000000 --- a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.time.Clock; -import java.time.Instant; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestResolver.OpenSamlLogoutRequestBuilder; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Requests with - * OpenSAML 4 - * - * @author Josh Cummings - * @since 5.5 - */ -public class OpenSaml4LogoutRequestResolver implements Saml2LogoutRequestResolver { - - private final OpenSamlLogoutRequestResolver logoutRequestResolver; - - private Clock clock = Clock.systemUTC(); - - /** - * Construct a {@link OpenSaml4LogoutRequestResolver} with the provided parameters - * @param relyingPartyRegistrationResolver a strategy for resolving a - * {@link RelyingPartyRegistration} - */ - public OpenSaml4LogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.logoutRequestResolver = new OpenSamlLogoutRequestResolver(relyingPartyRegistrationResolver); - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Request. - * - * By default, includes a {@code NameID} based on the {@link Authentication} instance - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. The - * request also contains its issued {@link Instant}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public Saml2LogoutRequestBuilder resolveLogoutRequest(HttpServletRequest request, - Authentication authentication) { - OpenSamlLogoutRequestBuilder builder = this.logoutRequestResolver.resolveLogoutRequest(request, authentication); - if (builder == null) { - return null; - } - return builder.logoutRequest((logoutRequest) -> logoutRequest.setIssueInstant(Instant.now(this.clock))); - } - - /** - * Use this {@link Clock} for determining the issued {@link Instant} - * @param clock the {@link Clock} to use - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.clock = clock; - } - -} diff --git a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java b/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java deleted file mode 100644 index 5d2c9c0a99..0000000000 --- a/saml2/saml2-service-provider/opensaml4/src/main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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. - * 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.web.authentication.logout; - -import java.time.Clock; -import java.time.Instant; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutResponseResolver.OpenSamlLogoutResponseBuilder; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutResponseResolver} for resolving SAML 2.0 Logout Responses with - * OpenSAML 4 - * - * @author Josh Cummings - * @since 5.5 - */ -public class OpenSaml4LogoutResponseResolver implements Saml2LogoutResponseResolver { - - private final OpenSamlLogoutResponseResolver logoutResponseResolver; - - private Clock clock = Clock.systemUTC(); - - /** - * Construct a {@link OpenSaml4LogoutResponseResolver} with the provided parameters - * @param relyingPartyRegistrationResolver the strategy for resolving a - * {@link RelyingPartyRegistration} - */ - public OpenSaml4LogoutResponseResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.logoutResponseResolver = new OpenSamlLogoutResponseResolver(relyingPartyRegistrationResolver); - } - - /** - * Prepare to create, sign, and serialize a SAML 2.0 Logout Response. - * - * By default, includes a {@code RelayState} based on the {@link HttpServletRequest} - * as well as the {@code Destination} and {@code Issuer} based on the - * {@link RelyingPartyRegistration} derived from the {@link Authentication}. The - * logout response also includes an issued {@link Instant} and is marked as - * {@code SUCCESS}. - * - * The {@link Authentication} must be of type {@link Saml2Authentication} in order to - * look up the {@link RelyingPartyRegistration} that holds the signing key. - * @param request the HTTP request - * @param authentication the current principal details - * @return a builder, useful for overriding any aspects of the SAML 2.0 Logout Request - * that the resolver supplied - */ - @Override - public Saml2LogoutResponseBuilder resolveLogoutResponse(HttpServletRequest request, - Authentication authentication) { - OpenSamlLogoutResponseBuilder builder = this.logoutResponseResolver.resolveLogoutResponse(request, - authentication); - if (builder == null) { - return null; - } - return builder.logoutResponse((logoutResponse) -> logoutResponse.setIssueInstant(Instant.now(this.clock))); - } - - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.clock = clock; - } - -}