mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-24 03:03:44 +00:00
Send saml logout response even when validation errors happen
Signed-off-by: Liviu Gheorghe <liviu.gheorghe.ro@gmail.com>
This commit is contained in:
parent
097640b72a
commit
eaf8184142
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -130,6 +130,30 @@ public final class Saml2ErrorCodes {
|
||||
*/
|
||||
public static final String INVALID_IN_RESPONSE_TO = "invalid_in_response_to";
|
||||
|
||||
/**
|
||||
* The RP registration does not have configured a logout request endpoint
|
||||
* @since 6.3
|
||||
*/
|
||||
public static final String MISSING_LOGOUT_REQUEST_ENDPOINT = "missing_logout_request_endpoint";
|
||||
|
||||
/**
|
||||
* The saml response or logout request was delivered via an invalid binding
|
||||
* @since 6.3
|
||||
*/
|
||||
public static final String INVALID_BINDING = "invalid_binding";
|
||||
|
||||
/**
|
||||
* The saml logout request failed validation
|
||||
* @since 6.3
|
||||
*/
|
||||
public static final String INVALID_LOGOUT_REQUEST = "invalid_logout_request";
|
||||
|
||||
/**
|
||||
* The saml logout response could not be generated
|
||||
* @since 6.3
|
||||
*/
|
||||
public static final String FAILED_TO_GENERATE_LOGOUT_RESPONSE = "failed_to_generate_logout_response";
|
||||
|
||||
private Saml2ErrorCodes() {
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,11 @@ import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
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.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
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.RelyingPartyRegistrationRepository;
|
||||
@ -130,6 +133,16 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication) {
|
||||
return resolve(request, authentication, StatusCode.SUCCESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication,
|
||||
Saml2AuthenticationException authenticationException) {
|
||||
return resolve(request, authentication, getSamlStatus(authenticationException));
|
||||
}
|
||||
|
||||
private Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, String statusCode) {
|
||||
LogoutRequest logoutRequest = this.saml.deserialize(extractSamlRequest(request));
|
||||
String registrationId = getRegistrationId(authentication);
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, registrationId);
|
||||
@ -152,7 +165,7 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes
|
||||
issuer.setValue(entityId);
|
||||
logoutResponse.setIssuer(issuer);
|
||||
StatusCode code = this.statusCodeBuilder.buildObject();
|
||||
code.setValue(StatusCode.SUCCESS);
|
||||
code.setValue(statusCode);
|
||||
Status status = this.statusBuilder.buildObject();
|
||||
status.setStatusCode(code);
|
||||
logoutResponse.setStatus(status);
|
||||
@ -224,6 +237,16 @@ final class BaseOpenSamlLogoutResponseResolver implements Saml2LogoutResponseRes
|
||||
return this.saml.serialize(logoutResponse).serialize();
|
||||
}
|
||||
|
||||
private String getSamlStatus(Saml2AuthenticationException exception) {
|
||||
Saml2Error saml2Error = exception.getSaml2Error();
|
||||
return switch (saml2Error.getErrorCode()) {
|
||||
case Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT, Saml2ErrorCodes.INVALID_BINDING ->
|
||||
StatusCode.REQUEST_DENIED;
|
||||
case Saml2ErrorCodes.INVALID_LOGOUT_REQUEST -> StatusCode.REQUESTER;
|
||||
default -> StatusCode.RESPONDER;
|
||||
};
|
||||
}
|
||||
|
||||
static final class LogoutResponseParameters {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
@ -31,6 +31,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
@ -112,52 +113,26 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
Saml2LogoutRequestValidatorParameters parameters;
|
||||
try {
|
||||
parameters = this.logoutRequestResolver.resolve(request, authentication);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.logoutRequestResolver.resolve(request,
|
||||
authentication);
|
||||
if (parameters == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Saml2LogoutResponse logoutResponse = processLogoutRequest(request, response, authentication, parameters);
|
||||
sendLogoutResponse(request, response, logoutResponse);
|
||||
}
|
||||
catch (Saml2AuthenticationException ex) {
|
||||
this.logger.trace("Did not process logout request since failed to find requested RelyingPartyRegistration");
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
if (parameters == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration();
|
||||
if (registration.getSingleLogoutServiceLocation() == null) {
|
||||
this.logger.trace(
|
||||
"Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint");
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
Saml2LogoutResponse errorLogoutResponse = this.logoutResponseResolver.resolve(request, authentication, ex);
|
||||
if (errorLogoutResponse == null) {
|
||||
this.logger.trace("Returning error since no error logout response could be generated", ex);
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
Saml2MessageBinding saml2MessageBinding = Saml2MessageBindingUtils.resolveBinding(request);
|
||||
if (!registration.getSingleLogoutServiceBindings().contains(saml2MessageBinding)) {
|
||||
this.logger.trace("Did not process logout request since used incorrect binding");
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters);
|
||||
if (result.hasErrors()) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, result.getErrors().iterator().next().toString());
|
||||
this.logger.debug(LogMessage.format("Failed to validate LogoutRequest: %s", result.getErrors()));
|
||||
return;
|
||||
}
|
||||
this.handler.logout(request, response, authentication);
|
||||
Saml2LogoutResponse logoutResponse = this.logoutResponseResolver.resolve(request, authentication);
|
||||
if (logoutResponse == null) {
|
||||
this.logger.trace("Returning 401 since no logout response generated");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
if (logoutResponse.getBinding() == Saml2MessageBinding.REDIRECT) {
|
||||
doRedirect(request, response, logoutResponse);
|
||||
}
|
||||
else {
|
||||
doPost(response, logoutResponse);
|
||||
sendLogoutResponse(request, response, errorLogoutResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,6 +155,50 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
private Saml2LogoutResponse processLogoutRequest(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication, Saml2LogoutRequestValidatorParameters parameters) {
|
||||
RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration();
|
||||
if (registration.getSingleLogoutServiceLocation() == null) {
|
||||
this.logger.trace(
|
||||
"Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint");
|
||||
throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT,
|
||||
"RelyingPartyRegistration has not been configured with a logout request endpoint"));
|
||||
}
|
||||
|
||||
Saml2MessageBinding saml2MessageBinding = Saml2MessageBindingUtils.resolveBinding(request);
|
||||
if (!registration.getSingleLogoutServiceBindings().contains(saml2MessageBinding)) {
|
||||
this.logger.trace("Did not process logout request since used incorrect binding");
|
||||
throw new Saml2AuthenticationException(
|
||||
new Saml2Error(Saml2ErrorCodes.INVALID_BINDING, "Logout request used invalid binding"));
|
||||
}
|
||||
|
||||
Saml2LogoutValidatorResult result = this.logoutRequestValidator.validate(parameters);
|
||||
if (result.hasErrors()) {
|
||||
this.logger.debug(LogMessage.format("Failed to validate LogoutRequest: %s", result.getErrors()));
|
||||
throw new Saml2AuthenticationException(
|
||||
new Saml2Error(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST, "Failed to validate the logout request"));
|
||||
}
|
||||
|
||||
this.handler.logout(request, response, authentication);
|
||||
Saml2LogoutResponse logoutResponse = this.logoutResponseResolver.resolve(request, authentication);
|
||||
if (logoutResponse == null) {
|
||||
this.logger.trace("Returning error since no logout response generated");
|
||||
throw new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.FAILED_TO_GENERATE_LOGOUT_RESPONSE,
|
||||
"Could not generated logout response"));
|
||||
}
|
||||
return logoutResponse;
|
||||
}
|
||||
|
||||
private void sendLogoutResponse(HttpServletRequest request, HttpServletResponse response,
|
||||
Saml2LogoutResponse logoutResponse) throws IOException {
|
||||
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();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,6 +19,7 @@ package org.springframework.security.saml2.provider.service.web.authentication.l
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
|
||||
@ -44,4 +45,15 @@ public interface Saml2LogoutResponseResolver {
|
||||
*/
|
||||
Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication);
|
||||
|
||||
/**
|
||||
* Prepare to create, sign, and serialize a SAML 2.0 Error Logout Response.
|
||||
* @param request the HTTP request
|
||||
* @param authentication the current user
|
||||
* @param authenticationException the thrown exception when the logout request was
|
||||
* processed
|
||||
* @return a signed and serialized SAML 2.0 Logout Response
|
||||
*/
|
||||
Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication,
|
||||
Saml2AuthenticationException authenticationException);
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
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.RelyingPartyRegistrationRepository;
|
||||
@ -66,6 +67,15 @@ public final class OpenSaml4LogoutResponseResolver implements Saml2LogoutRespons
|
||||
return this.delegate.resolve(request, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication,
|
||||
Saml2AuthenticationException exception) {
|
||||
return this.delegate.resolve(request, authentication, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest}
|
||||
* @param parametersConsumer a consumer that accepts an
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,17 +17,26 @@
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
import org.opensaml.saml.saml2.core.StatusCode;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
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 org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver.LogoutResponseParameters;
|
||||
@ -69,6 +78,27 @@ public class OpenSaml4LogoutResponseResolverTests {
|
||||
verify(parametersConsumer).accept(any());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideAuthExceptionAndExpectedSamlStatusCode")
|
||||
public void resolveWithAuthException(Saml2AuthenticationException exception, String expectedStatusCode) {
|
||||
OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver(
|
||||
this.relyingPartyRegistrationResolver);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
|
||||
.assertingPartyMetadata(
|
||||
(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout")
|
||||
.singleLogoutServiceBinding(Saml2MessageBinding.POST))
|
||||
.build();
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password");
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST,
|
||||
Saml2Utils.samlEncode(this.saml.serialize(logoutRequest).serialize().getBytes()));
|
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
|
||||
Saml2LogoutResponse logoutResponse = logoutResponseResolver.resolve(request, authentication, exception);
|
||||
assertThat(logoutResponse).isNotNull();
|
||||
assertThat(new String(Saml2Utils.samlDecode(logoutResponse.getSamlResponse()))).contains(expectedStatusCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setParametersConsumerWhenNullThenIllegalArgument() {
|
||||
OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver(
|
||||
@ -77,4 +107,23 @@ public class OpenSaml4LogoutResponseResolverTests {
|
||||
.isThrownBy(() -> logoutRequestResolver.setParametersConsumer(null));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideAuthExceptionAndExpectedSamlStatusCode() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
new Saml2AuthenticationException(
|
||||
new Saml2Error(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT, "")),
|
||||
StatusCode.REQUEST_DENIED),
|
||||
Arguments.of(new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_BINDING, "")),
|
||||
StatusCode.REQUEST_DENIED),
|
||||
Arguments.of(
|
||||
new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST, "")),
|
||||
StatusCode.REQUESTER),
|
||||
Arguments.of(
|
||||
new Saml2AuthenticationException(
|
||||
new Saml2Error(Saml2ErrorCodes.FAILED_TO_GENERATE_LOGOUT_RESPONSE, "")),
|
||||
StatusCode.RESPONDER)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
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.RelyingPartyRegistrationRepository;
|
||||
@ -66,6 +67,15 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons
|
||||
return this.delegate.resolve(request, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication,
|
||||
Saml2AuthenticationException exception) {
|
||||
return this.delegate.resolve(request, authentication, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest}
|
||||
* @param parametersConsumer a consumer that accepts an
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -28,6 +28,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse;
|
||||
@ -40,6 +41,7 @@ import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@ -117,12 +119,7 @@ public class Saml2LogoutRequestFilterTests {
|
||||
verify(this.logoutRequestValidator).validate(any());
|
||||
verify(this.logoutHandler).logout(any(), any(), any());
|
||||
verify(this.logoutResponseResolver).resolve(any(), any());
|
||||
String content = response.getContentAsString();
|
||||
assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE);
|
||||
assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation());
|
||||
assertThat(content).contains(
|
||||
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
|
||||
assertThat(content).contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
|
||||
checkResponse(response.getContentAsString(), registration);
|
||||
verify(this.securityContextHolderStrategy).getContext();
|
||||
}
|
||||
|
||||
@ -150,24 +147,38 @@ public class Saml2LogoutRequestFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenValidationFailsThen401() throws Exception {
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build();
|
||||
public void doFilterWhenValidationFailsErrorLogoutResponseIsPosted() throws Exception {
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full()
|
||||
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST))
|
||||
.build();
|
||||
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(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse("response")
|
||||
.build();
|
||||
|
||||
given(this.relyingPartyRegistrationResolver.resolve(request, null)).willReturn(registration);
|
||||
given(this.logoutRequestValidator.validate(any()))
|
||||
.willReturn(Saml2LogoutValidatorResult.withErrors(new Saml2Error("error", "description")).build());
|
||||
given(this.logoutResponseResolver.resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST))))
|
||||
.willReturn(logoutResponse);
|
||||
|
||||
this.logoutRequestProcessingFilter.doFilter(request, response, new MockFilterChain());
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
|
||||
checkResponse(response.getContentAsString(), registration);
|
||||
verify(this.logoutRequestValidator).validate(any());
|
||||
verify(this.logoutResponseResolver).resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_LOGOUT_REQUEST)));
|
||||
verifyNoInteractions(this.logoutHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNoRelyingPartyLogoutThen401() throws Exception {
|
||||
public void doFilterWhenNoRelyingErrorLogoutResponseIsPosted() throws Exception {
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password");
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo");
|
||||
@ -175,12 +186,96 @@ public class Saml2LogoutRequestFilterTests {
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full()
|
||||
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST))
|
||||
.singleLogoutServiceLocation(null)
|
||||
.build();
|
||||
Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse("response")
|
||||
.build();
|
||||
|
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
|
||||
given(this.logoutResponseResolver.resolve(any(), any(), argThat(
|
||||
(ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT))))
|
||||
.willReturn(logoutResponse);
|
||||
|
||||
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
|
||||
checkResponse(response.getContentAsString(), registration);
|
||||
verify(this.logoutResponseResolver).resolve(any(), any(), argThat(
|
||||
(ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.MISSING_LOGOUT_REQUEST_ENDPOINT)));
|
||||
verifyNoInteractions(this.logoutHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenInvalidBindingErrorLogoutResponseIsPosted() 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(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full()
|
||||
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST))
|
||||
.singleLogoutServiceBindings((bindings) -> {
|
||||
bindings.clear();
|
||||
bindings.add(Saml2MessageBinding.REDIRECT);
|
||||
})
|
||||
.build();
|
||||
Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse("response")
|
||||
.build();
|
||||
|
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
|
||||
given(this.logoutResponseResolver.resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_BINDING))))
|
||||
.willReturn(logoutResponse);
|
||||
|
||||
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
|
||||
|
||||
checkResponse(response.getContentAsString(), registration);
|
||||
verify(this.logoutResponseResolver).resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_BINDING)));
|
||||
verifyNoInteractions(this.logoutHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNoErrorResponseCanBeGeneratedThen401() 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(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full()
|
||||
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST))
|
||||
.singleLogoutServiceBindings((bindings) -> {
|
||||
bindings.clear();
|
||||
bindings.add(Saml2MessageBinding.REDIRECT);
|
||||
})
|
||||
.build();
|
||||
|
||||
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
|
||||
given(this.logoutResponseResolver.resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_BINDING))))
|
||||
.willReturn(null);
|
||||
|
||||
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
verify(this.logoutResponseResolver).resolve(any(), any(),
|
||||
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_BINDING)));
|
||||
verifyNoInteractions(this.logoutHandler);
|
||||
}
|
||||
|
||||
private void checkResponse(String responseContent, RelyingPartyRegistration registration) {
|
||||
assertThat(responseContent).contains(Saml2ParameterNames.SAML_RESPONSE);
|
||||
assertThat(responseContent)
|
||||
.contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation());
|
||||
assertThat(responseContent).contains(
|
||||
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
|
||||
assertThat(responseContent)
|
||||
.contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user