diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java index 8fb3c4c62b..b8b021ad97 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutRequestValidator.java @@ -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 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> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest, + private Collection verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest, RelyingPartyRegistration registration) { AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); Collection 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> 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 validateRequest(LogoutRequest request, RelyingPartyRegistration registration, + Authentication authentication) { + Collection errors = new ArrayList<>(); + errors.addAll(validateIssuer(request, registration)); + errors.addAll(validateDestination(request, registration)); + errors.addAll(validateSubject(request, registration, authentication)); + return errors; } - private Consumer> 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 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> 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 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> 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 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 errors) { + private Collection 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(); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutResponseValidator.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutResponseValidator.java index 92305d8610..73480fbdf9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutResponseValidator.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/logout/BaseOpenSamlLogoutResponseValidator.java @@ -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 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> verifySignature(Saml2LogoutResponse response, - LogoutResponse logoutResponse, RelyingPartyRegistration registration) { - return (errors) -> { - AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); - Collection 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> validateRequest(LogoutResponse response, + private Collection 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 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> 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 validateRequest(LogoutResponse response, RelyingPartyRegistration registration, + String logoutRequestId) { + Collection 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> 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 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> 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 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> 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 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 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")); } } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java index 59c35c00f3..870d5bdf4d 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java @@ -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 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> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest, + private Collection verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest, RelyingPartyRegistration registration) { AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); Collection 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> 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 validateRequest(LogoutRequest request, RelyingPartyRegistration registration, + Authentication authentication) { + Collection errors = new ArrayList<>(); + errors.addAll(validateIssuer(request, registration)); + errors.addAll(validateDestination(request, registration)); + errors.addAll(validateSubject(request, registration, authentication)); + return errors; } - private Consumer> 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 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> 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 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> 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 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 errors) { + private Collection 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(); } } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java index 4d21f3acea..f42612eb73 100644 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java +++ b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java @@ -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 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> verifySignature(Saml2LogoutResponse response, - LogoutResponse logoutResponse, RelyingPartyRegistration registration) { - return (errors) -> { - AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); - Collection 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> validateRequest(LogoutResponse response, + private Collection 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 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> 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 validateRequest(LogoutResponse response, RelyingPartyRegistration registration, + String logoutRequestId) { + Collection 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> 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 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> 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 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> 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 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 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")); } } diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java index 3e77d0f70f..3c4c1ced60 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java @@ -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(); diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java index 0a84829704..9c21b1bbab 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java @@ -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() { diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java index ebb8dab07c..ffc3ee3951 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java @@ -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(); diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java index 5b9eeed6dd..72e0a63e04 100644 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java @@ -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() { diff --git a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidatorTests.java b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidatorTests.java index 0bb4ff9f36..7421b9dc6b 100644 --- a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidatorTests.java @@ -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(); diff --git a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidatorTests.java b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidatorTests.java index e081bafc9d..6bae7e565c 100644 --- a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidatorTests.java +++ b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidatorTests.java @@ -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() {