1
0
mirror of synced 2026-07-07 03:40:04 +00:00

Rework Logout Validation Logic

This commit simplifies the code, favoring the construction
of a list of error codes over composing a set of consumers.
This will simplify future analysis by making the code more
readable.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
Josh Cummings
2026-04-30 21:18:01 -06:00
parent e50c2a6a74
commit cf06871200
10 changed files with 509 additions and 312 deletions
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
@@ -56,84 +57,74 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator
LogoutRequest logoutRequest = this.saml.deserialize(Saml2Utils.withEncoded(request.getSamlRequest())
.inflate(request.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(request, logoutRequest, registration))
.errors(validateRequest(logoutRequest, registration, authentication))
.build();
Collection<Saml2Error> errors = verifySignature(request, logoutRequest, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutRequest, registration, authentication);
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
private Collection<Saml2Error> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
RelyingPartyRegistration registration) {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
return (errors) -> {
if (logoutRequest.isSigned()) {
errors.addAll(verify.verify(logoutRequest));
}
else {
RedirectParameters params = new RedirectParameters(request.getParameters(),
request.getParametersQuery(), logoutRequest);
errors.addAll(verify.verify(params));
}
};
if (logoutRequest.isSigned()) {
return verify.verify(logoutRequest);
}
RedirectParameters params = new RedirectParameters(request.getParameters(), request.getParametersQuery(),
logoutRequest);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutRequest request,
RelyingPartyRegistration registration, Authentication authentication) {
return (errors) -> {
validateIssuer(request, registration).accept(errors);
validateDestination(request, registration).accept(errors);
validateSubject(request, registration, authentication).accept(errors);
};
private Collection<Saml2Error> validateRequest(LogoutRequest request, RelyingPartyRegistration registration,
Authentication authentication) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(request, registration));
errors.addAll(validateDestination(request, registration));
errors.addAll(validateSubject(request, registration, authentication));
return errors;
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
return;
}
String issuer = request.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
}
String issuer = request.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutRequest"));
return;
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateDestination(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getDestination() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, "Failed to find destination in LogoutRequest"));
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateSubject(LogoutRequest request,
RelyingPartyRegistration registration, Authentication authentication) {
return (errors) -> {
if (authentication == null) {
return;
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
errors
.add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
return;
}
validateNameId(nameId, authentication, errors);
};
private Collection<Saml2Error> validateSubject(LogoutRequest request, RelyingPartyRegistration registration,
Authentication authentication) {
if (authentication == null) {
return Collections.emptyList();
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
}
return validateNameId(nameId, authentication);
}
private NameID getNameId(LogoutRequest request, RelyingPartyRegistration registration) {
@@ -141,12 +132,13 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator
return request.getNameID();
}
private void validateNameId(NameID nameId, Authentication authentication, Collection<Saml2Error> errors) {
private Collection<Saml2Error> validateNameId(NameID nameId, Authentication authentication) {
String name = nameId.getValue();
if (!name.equals(authentication.getName())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
"Failed to match subject in LogoutRequest with currently logged in user"));
}
return Collections.emptyList();
}
}
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.StatusCode;
@@ -52,101 +53,90 @@ class BaseOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidato
LogoutResponse logoutResponse = this.saml.deserialize(Saml2Utils.withEncoded(response.getSamlResponse())
.inflate(response.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(response, logoutResponse, registration))
.errors(validateRequest(logoutResponse, registration))
.errors(validateLogoutRequest(logoutResponse, request.getId()))
.build();
Collection<Saml2Error> errors = verifySignature(response, logoutResponse, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutResponse, registration, request.getId());
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutResponse response,
LogoutResponse logoutResponse, RelyingPartyRegistration registration) {
return (errors) -> {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials)
.entityId(details.getEntityId())
.entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
errors.addAll(verify.verify(logoutResponse));
}
else {
RedirectParameters params = new RedirectParameters(response.getParameters(),
response.getParametersQuery(), logoutResponse);
errors.addAll(verify.verify(params));
}
};
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutResponse response,
private Collection<Saml2Error> verifySignature(Saml2LogoutResponse response, LogoutResponse logoutResponse,
RelyingPartyRegistration registration) {
return (errors) -> {
validateIssuer(response, registration).accept(errors);
validateDestination(response, registration).accept(errors);
validateStatus(response).accept(errors);
};
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
return verify.verify(logoutResponse);
}
RedirectParameters params = new RedirectParameters(response.getParameters(), response.getParametersQuery(),
logoutResponse);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
return;
}
String issuer = response.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateRequest(LogoutResponse response, RelyingPartyRegistration registration,
String logoutRequestId) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(response, registration));
errors.addAll(validateDestination(response, registration));
errors.addAll(validateStatus(response));
errors.addAll(validateLogoutRequest(response, logoutRequestId));
return errors;
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
return;
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
}
String issuer = response.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateStatus(LogoutResponse response) {
return (errors) -> {
if (response.getStatus() == null) {
return;
}
if (response.getStatus().getStatusCode() == null) {
return;
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
};
private Collection<Saml2Error> validateDestination(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getDestination() == null) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateLogoutRequest(LogoutResponse response, String id) {
return (errors) -> {
if (response.getInResponseTo() == null) {
return;
}
if (response.getInResponseTo().equals(id)) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
};
private Collection<Saml2Error> validateStatus(LogoutResponse response) {
if (response.getStatus() == null) {
return Collections.emptyList();
}
if (response.getStatus().getStatusCode() == null) {
return Collections.emptyList();
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
return Collections
.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
}
private Collection<Saml2Error> validateLogoutRequest(LogoutResponse response, String id) {
if (response.getInResponseTo() == null) {
return Collections.emptyList();
}
if (response.getInResponseTo().equals(id)) {
return Collections.emptyList();
}
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
}
}
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
@@ -68,84 +69,74 @@ public final class OpenSamlLogoutRequestValidator implements Saml2LogoutRequestV
LogoutRequest logoutRequest = this.saml.deserialize(Saml2Utils.withEncoded(request.getSamlRequest())
.inflate(request.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(request, logoutRequest, registration))
.errors(validateRequest(logoutRequest, registration, authentication))
.build();
Collection<Saml2Error> errors = verifySignature(request, logoutRequest, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutRequest, registration, authentication);
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
private Collection<Saml2Error> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
RelyingPartyRegistration registration) {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
return (errors) -> {
if (logoutRequest.isSigned()) {
errors.addAll(verify.verify(logoutRequest));
}
else {
RedirectParameters params = new RedirectParameters(request.getParameters(),
request.getParametersQuery(), logoutRequest);
errors.addAll(verify.verify(params));
}
};
if (logoutRequest.isSigned()) {
return verify.verify(logoutRequest);
}
RedirectParameters params = new RedirectParameters(request.getParameters(), request.getParametersQuery(),
logoutRequest);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutRequest request,
RelyingPartyRegistration registration, Authentication authentication) {
return (errors) -> {
validateIssuer(request, registration).accept(errors);
validateDestination(request, registration).accept(errors);
validateSubject(request, registration, authentication).accept(errors);
};
private Collection<Saml2Error> validateRequest(LogoutRequest request, RelyingPartyRegistration registration,
Authentication authentication) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(request, registration));
errors.addAll(validateDestination(request, registration));
errors.addAll(validateSubject(request, registration, authentication));
return errors;
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
return;
}
String issuer = request.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
}
String issuer = request.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutRequest"));
return;
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateDestination(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getDestination() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, "Failed to find destination in LogoutRequest"));
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateSubject(LogoutRequest request,
RelyingPartyRegistration registration, Authentication authentication) {
return (errors) -> {
if (authentication == null) {
return;
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
errors
.add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
return;
}
validateNameId(nameId, authentication, errors);
};
private Collection<Saml2Error> validateSubject(LogoutRequest request, RelyingPartyRegistration registration,
Authentication authentication) {
if (authentication == null) {
return Collections.emptyList();
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
}
return validateNameId(nameId, authentication);
}
private NameID getNameId(LogoutRequest request, RelyingPartyRegistration registration) {
@@ -153,12 +144,13 @@ public final class OpenSamlLogoutRequestValidator implements Saml2LogoutRequestV
return request.getNameID();
}
private void validateNameId(NameID nameId, Authentication authentication, Collection<Saml2Error> errors) {
private Collection<Saml2Error> validateNameId(NameID nameId, Authentication authentication) {
String name = nameId.getValue();
if (!name.equals(authentication.getName())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
"Failed to match subject in LogoutRequest with currently logged in user"));
}
return Collections.emptyList();
}
}
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
@@ -59,7 +60,7 @@ public class OpenSamlLogoutResponseValidator implements Saml2LogoutResponseValid
private final OpenSamlOperations saml = new OpenSaml4Template();
/**
* Constructs a {@link OpenSamlLogoutRequestValidator}
* Constructs an {@link OpenSamlLogoutResponseValidator}
*/
public OpenSamlLogoutResponseValidator() {
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
@@ -78,99 +79,90 @@ public class OpenSamlLogoutResponseValidator implements Saml2LogoutResponseValid
LogoutResponse logoutResponse = this.saml.deserialize(Saml2Utils.withEncoded(response.getSamlResponse())
.inflate(response.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(response, logoutResponse, registration))
.errors(validateRequest(logoutResponse, registration))
.errors(validateLogoutRequest(logoutResponse, request.getId()))
.build();
Collection<Saml2Error> errors = verifySignature(response, logoutResponse, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutResponse, registration, request.getId());
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutResponse response,
LogoutResponse logoutResponse, RelyingPartyRegistration registration) {
return (errors) -> {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
errors.addAll(verify.verify(logoutResponse));
}
else {
RedirectParameters params = new RedirectParameters(response.getParameters(),
response.getParametersQuery(), logoutResponse);
errors.addAll(verify.verify(params));
}
};
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutResponse response,
private Collection<Saml2Error> verifySignature(Saml2LogoutResponse response, LogoutResponse logoutResponse,
RelyingPartyRegistration registration) {
return (errors) -> {
validateIssuer(response, registration).accept(errors);
validateDestination(response, registration).accept(errors);
validateStatus(response).accept(errors);
};
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
return verify.verify(logoutResponse);
}
RedirectParameters params = new RedirectParameters(response.getParameters(), response.getParametersQuery(),
logoutResponse);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
return;
}
String issuer = response.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateRequest(LogoutResponse response, RelyingPartyRegistration registration,
String logoutRequestId) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(response, registration));
errors.addAll(validateDestination(response, registration));
errors.addAll(validateStatus(response));
errors.addAll(validateLogoutRequest(response, logoutRequestId));
return errors;
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
return;
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
}
String issuer = response.getIssuer().getValue();
if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateStatus(LogoutResponse response) {
return (errors) -> {
if (response.getStatus() == null) {
return;
}
if (response.getStatus().getStatusCode() == null) {
return;
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
};
private Collection<Saml2Error> validateDestination(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getDestination() == null) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateLogoutRequest(LogoutResponse response, String id) {
return (errors) -> {
if (response.getInResponseTo() == null) {
return;
}
if (response.getInResponseTo().equals(id)) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
};
private Collection<Saml2Error> validateStatus(LogoutResponse response) {
if (response.getStatus() == null) {
return Collections.emptyList();
}
if (response.getStatus().getStatusCode() == null) {
return Collections.emptyList();
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
return Collections
.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
}
private Collection<Saml2Error> validateLogoutRequest(LogoutResponse response, String id) {
if (response.getInResponseTo() == null) {
return Collections.emptyList();
}
if (response.getInResponseTo().equals(id)) {
return Collections.emptyList();
}
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
}
}
@@ -26,6 +26,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutRequest;
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.core.TestSaml2X509Credentials;
@@ -90,6 +91,38 @@ public class OpenSaml4LogoutRequestValidatorTests {
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateRequestFurther() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
sign(logoutRequest, registration);
logoutRequest.getIssuer().setValue("https://other-asserting-party.example");
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("someone-else");
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationAndNameIdInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("wrong-user");
sign(logoutRequest, registration);
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_REQUEST);
}
@Test
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
RelyingPartyRegistration registration = registration().build();
@@ -24,6 +24,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.StatusCode;
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.core.TestSaml2X509Credentials;
@@ -57,7 +58,8 @@ public class OpenSaml4LogoutResponseValidatorTests {
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
@@ -73,7 +75,48 @@ public class OpenSaml4LogoutResponseValidatorTests {
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateResponseFurther() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
sign(logoutResponse, registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationStatusAndInResponseToInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
sign(logoutResponse, registration);
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_RESPONSE,
Saml2ErrorCodes.INVALID_RESPONSE);
}
@Test
@@ -145,7 +188,8 @@ public class OpenSaml4LogoutResponseValidatorTests {
.build();
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
private RelyingPartyRegistration.Builder registration() {
@@ -26,6 +26,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutRequest;
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.core.TestSaml2X509Credentials;
@@ -90,6 +91,38 @@ public class OpenSamlLogoutRequestValidatorTests {
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateRequestFurther() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
sign(logoutRequest, registration);
logoutRequest.getIssuer().setValue("https://other-asserting-party.example");
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("someone-else");
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationAndNameIdInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("wrong-user");
sign(logoutRequest, registration);
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_REQUEST);
}
@Test
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
RelyingPartyRegistration registration = registration().build();
@@ -24,6 +24,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.StatusCode;
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.core.TestSaml2X509Credentials;
@@ -57,7 +58,8 @@ public class OpenSamlLogoutResponseValidatorTests {
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
@@ -73,7 +75,48 @@ public class OpenSamlLogoutResponseValidatorTests {
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateResponseFurther() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
sign(logoutResponse, registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationStatusAndInResponseToInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
sign(logoutResponse, registration);
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_RESPONSE,
Saml2ErrorCodes.INVALID_RESPONSE);
}
@Test
@@ -145,7 +188,8 @@ public class OpenSamlLogoutResponseValidatorTests {
.build();
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
private RelyingPartyRegistration.Builder registration() {
@@ -26,6 +26,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutRequest;
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.core.TestSaml2X509Credentials;
@@ -90,6 +91,38 @@ public class OpenSaml5LogoutRequestValidatorTests {
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateRequestFurther() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
sign(logoutRequest, registration);
logoutRequest.getIssuer().setValue("https://other-asserting-party.example");
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("someone-else");
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationAndNameIdInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("wrong-user");
sign(logoutRequest, registration);
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_REQUEST);
}
@Test
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
RelyingPartyRegistration registration = registration().build();
@@ -24,6 +24,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.StatusCode;
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.core.TestSaml2X509Credentials;
@@ -57,7 +58,8 @@ public class OpenSaml5LogoutResponseValidatorTests {
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
@@ -73,7 +75,48 @@ public class OpenSaml5LogoutResponseValidatorTests {
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateResponseFurther() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
sign(logoutResponse, registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationStatusAndInResponseToInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
sign(logoutResponse, registration);
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_RESPONSE,
Saml2ErrorCodes.INVALID_RESPONSE);
}
@Test
@@ -145,7 +188,8 @@ public class OpenSaml5LogoutResponseValidatorTests {
.build();
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
private RelyingPartyRegistration.Builder registration() {