mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-10 12:23:30 +00:00
Remove Deprecated OpenSaml Components
Closes gh-17306
This commit is contained in:
parent
571b6fe4a8
commit
da182a2d7c
@ -43,7 +43,6 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
||||
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.OpenSaml4AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.OpenSaml5AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.OpenSamlAuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter;
|
||||
@ -416,9 +415,6 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
AuthenticationConverter authenticationConverterBean = getBeanOrNull(http,
|
||||
Saml2AuthenticationTokenConverter.class);
|
||||
if (authenticationConverterBean == null) {
|
||||
authenticationConverterBean = getBeanOrNull(http, OpenSamlAuthenticationTokenConverter.class);
|
||||
}
|
||||
if (authenticationConverterBean != null) {
|
||||
return authenticationConverterBean;
|
||||
}
|
||||
|
@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters;
|
||||
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
* A {@link Saml2LogoutRequestValidator} that authenticates a SAML 2.0 Logout Requests
|
||||
* received from a SAML 2.0 Asserting Party using OpenSAML.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
* @deprecated Please use the version-specific {@link Saml2LogoutRequestValidator} such as
|
||||
* {@code OpenSaml4LogoutRequestValidator}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
/**
|
||||
* Constructs a {@link OpenSamlLogoutRequestValidator}
|
||||
*/
|
||||
public OpenSamlLogoutRequestValidator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutValidatorResult validate(Saml2LogoutRequestValidatorParameters parameters) {
|
||||
Saml2LogoutRequest request = parameters.getLogoutRequest();
|
||||
RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration();
|
||||
Authentication authentication = parameters.getAuthentication();
|
||||
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();
|
||||
}
|
||||
|
||||
private Consumer<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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 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 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 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 NameID getNameId(LogoutRequest request, RelyingPartyRegistration registration) {
|
||||
this.saml.withDecryptionKeys(registration.getDecryptionX509Credentials()).decrypt(request);
|
||||
return request.getNameID();
|
||||
}
|
||||
|
||||
private void validateNameId(NameID nameId, Authentication authentication, Collection<Saml2Error> errors) {
|
||||
String name = nameId.getValue();
|
||||
if (!name.equals(authentication.getName())) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
|
||||
"Failed to match subject in LogoutRequest with currently logged in user"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.saml.saml2.core.LogoutResponse;
|
||||
import org.opensaml.saml.saml2.core.StatusCode;
|
||||
import org.opensaml.saml.saml2.core.impl.LogoutResponseUnmarshaller;
|
||||
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.VerificationConfigurer.RedirectParameters;
|
||||
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
* A {@link Saml2LogoutResponseValidator} that authenticates a SAML 2.0 Logout Responses
|
||||
* received from a SAML 2.0 Asserting Party using OpenSAML.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
* @deprecated Please use the version-specific {@link Saml2LogoutResponseValidator}
|
||||
* instead such as {@code OpenSaml4LogoutResponseValidator}
|
||||
*/
|
||||
@Deprecated
|
||||
public class OpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private final XMLObjectProviderRegistry registry;
|
||||
|
||||
private final LogoutResponseUnmarshaller unmarshaller;
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
/**
|
||||
* Constructs a {@link OpenSamlLogoutRequestValidator}
|
||||
*/
|
||||
public OpenSamlLogoutResponseValidator() {
|
||||
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
this.unmarshaller = (LogoutResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory()
|
||||
.getUnmarshaller(LogoutResponse.DEFAULT_ELEMENT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutValidatorResult validate(Saml2LogoutResponseValidatorParameters parameters) {
|
||||
Saml2LogoutResponse response = parameters.getLogoutResponse();
|
||||
Saml2LogoutRequest request = parameters.getLogoutRequest();
|
||||
RelyingPartyRegistration registration = parameters.getRelyingPartyRegistration();
|
||||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
RelyingPartyRegistration registration) {
|
||||
return (errors) -> {
|
||||
validateIssuer(response, registration).accept(errors);
|
||||
validateDestination(response, registration).accept(errors);
|
||||
validateStatus(response).accept(errors);
|
||||
};
|
||||
}
|
||||
|
||||
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 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 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 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"));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.metadata;
|
||||
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
|
||||
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.NameIDFormat;
|
||||
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.xmlsec.signature.KeyInfo;
|
||||
import org.opensaml.xmlsec.signature.X509Certificate;
|
||||
import org.opensaml.xmlsec.signature.X509Data;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Resolves the SAML 2.0 Relying Party Metadata for a given
|
||||
* {@link RelyingPartyRegistration} using the OpenSAML API.
|
||||
*
|
||||
* @author Jakub Kubrynski
|
||||
* @author Josh Cummings
|
||||
* @since 5.4
|
||||
* @deprecated Please use version-specific {@link Saml2MetadataResolver} instead, for
|
||||
* example {@code OpenSaml4MetadataResolver}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private Consumer<EntityDescriptorParameters> entityDescriptorCustomizer = (parameters) -> {
|
||||
};
|
||||
|
||||
private boolean usePrettyPrint = true;
|
||||
|
||||
private boolean signMetadata = false;
|
||||
|
||||
public OpenSamlMetadataResolver() {
|
||||
}
|
||||
|
||||
OpenSamlMetadataResolver(OpenSamlOperations saml) {
|
||||
this.saml = saml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(RelyingPartyRegistration relyingPartyRegistration) {
|
||||
EntityDescriptor entityDescriptor = entityDescriptor(relyingPartyRegistration);
|
||||
return serialize(entityDescriptor);
|
||||
}
|
||||
|
||||
public String resolve(Iterable<RelyingPartyRegistration> relyingPartyRegistrations) {
|
||||
Collection<EntityDescriptor> entityDescriptors = new ArrayList<>();
|
||||
for (RelyingPartyRegistration registration : relyingPartyRegistrations) {
|
||||
EntityDescriptor entityDescriptor = entityDescriptor(registration);
|
||||
entityDescriptors.add(entityDescriptor);
|
||||
}
|
||||
if (entityDescriptors.size() == 1) {
|
||||
return serialize(entityDescriptors.iterator().next());
|
||||
}
|
||||
EntitiesDescriptor entities = this.saml.build(EntitiesDescriptor.DEFAULT_ELEMENT_NAME);
|
||||
entities.getEntityDescriptors().addAll(entityDescriptors);
|
||||
return serialize(entities);
|
||||
}
|
||||
|
||||
private EntityDescriptor entityDescriptor(RelyingPartyRegistration registration) {
|
||||
EntityDescriptor entityDescriptor = this.saml.build(EntityDescriptor.DEFAULT_ELEMENT_NAME);
|
||||
entityDescriptor.setEntityID(registration.getEntityId());
|
||||
SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(registration);
|
||||
entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor);
|
||||
this.entityDescriptorCustomizer.accept(new EntityDescriptorParameters(entityDescriptor, registration));
|
||||
if (this.signMetadata) {
|
||||
return this.saml.withSigningKeys(registration.getSigningX509Credentials())
|
||||
.algorithms(registration.getAssertingPartyMetadata().getSigningAlgorithms())
|
||||
.sign(entityDescriptor);
|
||||
}
|
||||
else {
|
||||
this.logger.trace("Did not sign metadata since `signMetadata` is `false`");
|
||||
}
|
||||
return entityDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link EntityDescriptor}
|
||||
* @param entityDescriptorCustomizer a consumer that accepts an
|
||||
* {@link EntityDescriptorParameters}
|
||||
* @since 5.7
|
||||
*/
|
||||
public void setEntityDescriptorCustomizer(Consumer<EntityDescriptorParameters> entityDescriptorCustomizer) {
|
||||
Assert.notNull(entityDescriptorCustomizer, "entityDescriptorCustomizer cannot be null");
|
||||
this.entityDescriptorCustomizer = entityDescriptorCustomizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure whether to pretty-print the metadata XML. This can be helpful when
|
||||
* signing the metadata payload.
|
||||
*
|
||||
* @since 6.2
|
||||
**/
|
||||
public void setUsePrettyPrint(boolean usePrettyPrint) {
|
||||
this.usePrettyPrint = usePrettyPrint;
|
||||
}
|
||||
|
||||
private SPSSODescriptor buildSpSsoDescriptor(RelyingPartyRegistration registration) {
|
||||
SPSSODescriptor spSsoDescriptor = this.saml.build(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
|
||||
spSsoDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);
|
||||
spSsoDescriptor.getKeyDescriptors()
|
||||
.addAll(buildKeys(registration.getSigningX509Credentials(), UsageType.SIGNING));
|
||||
spSsoDescriptor.getKeyDescriptors()
|
||||
.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
|
||||
spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
|
||||
if (registration.getSingleLogoutServiceLocation() != null) {
|
||||
for (Saml2MessageBinding binding : registration.getSingleLogoutServiceBindings()) {
|
||||
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration, binding));
|
||||
}
|
||||
}
|
||||
if (registration.getNameIdFormat() != null) {
|
||||
spSsoDescriptor.getNameIDFormats().add(buildNameIDFormat(registration));
|
||||
}
|
||||
return spSsoDescriptor;
|
||||
}
|
||||
|
||||
private List<KeyDescriptor> buildKeys(Collection<Saml2X509Credential> credentials, UsageType usageType) {
|
||||
List<KeyDescriptor> list = new ArrayList<>();
|
||||
for (Saml2X509Credential credential : credentials) {
|
||||
KeyDescriptor keyDescriptor = buildKeyDescriptor(usageType, credential.getCertificate());
|
||||
list.add(keyDescriptor);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private KeyDescriptor buildKeyDescriptor(UsageType usageType, java.security.cert.X509Certificate certificate) {
|
||||
KeyDescriptor keyDescriptor = this.saml.build(KeyDescriptor.DEFAULT_ELEMENT_NAME);
|
||||
KeyInfo keyInfo = this.saml.build(KeyInfo.DEFAULT_ELEMENT_NAME);
|
||||
X509Certificate x509Certificate = this.saml.build(X509Certificate.DEFAULT_ELEMENT_NAME);
|
||||
X509Data x509Data = this.saml.build(X509Data.DEFAULT_ELEMENT_NAME);
|
||||
try {
|
||||
x509Certificate.setValue(new String(Base64.getEncoder().encode(certificate.getEncoded())));
|
||||
}
|
||||
catch (CertificateEncodingException ex) {
|
||||
throw new Saml2Exception("Cannot encode certificate " + certificate.toString());
|
||||
}
|
||||
x509Data.getX509Certificates().add(x509Certificate);
|
||||
keyInfo.getX509Datas().add(x509Data);
|
||||
keyDescriptor.setUse(usageType);
|
||||
keyDescriptor.setKeyInfo(keyInfo);
|
||||
return keyDescriptor;
|
||||
}
|
||||
|
||||
private AssertionConsumerService buildAssertionConsumerService(RelyingPartyRegistration registration) {
|
||||
AssertionConsumerService assertionConsumerService = this.saml
|
||||
.build(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
|
||||
assertionConsumerService.setLocation(registration.getAssertionConsumerServiceLocation());
|
||||
assertionConsumerService.setBinding(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertionConsumerService.setIndex(1);
|
||||
return assertionConsumerService;
|
||||
}
|
||||
|
||||
private SingleLogoutService buildSingleLogoutService(RelyingPartyRegistration registration,
|
||||
Saml2MessageBinding binding) {
|
||||
SingleLogoutService singleLogoutService = this.saml.build(SingleLogoutService.DEFAULT_ELEMENT_NAME);
|
||||
singleLogoutService.setLocation(registration.getSingleLogoutServiceLocation());
|
||||
singleLogoutService.setResponseLocation(registration.getSingleLogoutServiceResponseLocation());
|
||||
singleLogoutService.setBinding(binding.getUrn());
|
||||
return singleLogoutService;
|
||||
}
|
||||
|
||||
private NameIDFormat buildNameIDFormat(RelyingPartyRegistration registration) {
|
||||
NameIDFormat nameIdFormat = this.saml.build(NameIDFormat.DEFAULT_ELEMENT_NAME);
|
||||
nameIdFormat.setURI(registration.getNameIdFormat());
|
||||
return nameIdFormat;
|
||||
}
|
||||
|
||||
private String serialize(EntityDescriptor entityDescriptor) {
|
||||
return this.saml.serialize(entityDescriptor).prettyPrint(this.usePrettyPrint).serialize();
|
||||
}
|
||||
|
||||
private String serialize(EntitiesDescriptor entities) {
|
||||
return this.saml.serialize(entities).prettyPrint(this.usePrettyPrint).serialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure whether to sign the metadata, defaults to {@code false}.
|
||||
*
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setSignMetadata(boolean signMetadata) {
|
||||
this.signMetadata = signMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tuple containing an OpenSAML {@link EntityDescriptor} and its associated
|
||||
* {@link RelyingPartyRegistration}
|
||||
*
|
||||
* @since 5.7
|
||||
*/
|
||||
public static final class EntityDescriptorParameters {
|
||||
|
||||
private final EntityDescriptor entityDescriptor;
|
||||
|
||||
private final RelyingPartyRegistration registration;
|
||||
|
||||
public EntityDescriptorParameters(EntityDescriptor entityDescriptor, RelyingPartyRegistration registration) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
public EntityDescriptor getEntityDescriptor() {
|
||||
return this.entityDescriptor;
|
||||
}
|
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.opensaml.saml.saml2.core.Response;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationConverter} that generates a {@link Saml2AuthenticationToken}
|
||||
* appropriate for authenticated a SAML 2.0 Assertion against an
|
||||
* {@link org.springframework.security.authentication.AuthenticationManager}.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.1
|
||||
* @deprecated Please use a version-specific SAML 2.0 {@link AuthenticationConverter}
|
||||
* instead such as {@code OpenSaml4AuthenticationTokenConverter}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OpenSamlAuthenticationTokenConverter implements AuthenticationConverter {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private final RelyingPartyRegistrationRepository registrations;
|
||||
|
||||
private RequestMatcher requestMatcher = new OrRequestMatcher(pathPattern("/login/saml2/sso/{registrationId}"),
|
||||
pathPattern("/login/saml2/sso"));
|
||||
|
||||
private Function<HttpServletRequest, AbstractSaml2AuthenticationRequest> loader;
|
||||
|
||||
/**
|
||||
* Constructs a {@link OpenSamlAuthenticationTokenConverter} given a repository for
|
||||
* {@link RelyingPartyRegistration}s
|
||||
* @param registrations the repository for {@link RelyingPartyRegistration}s
|
||||
* {@link RelyingPartyRegistration}s
|
||||
*/
|
||||
public OpenSamlAuthenticationTokenConverter(RelyingPartyRegistrationRepository registrations) {
|
||||
Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null");
|
||||
this.registrations = registrations;
|
||||
this.loader = new HttpSessionSaml2AuthenticationRequestRepository()::loadAuthenticationRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an authentication request from the given {@link HttpServletRequest}.
|
||||
*
|
||||
* <p>
|
||||
* First uses the configured {@link RequestMatcher} to deduce whether an
|
||||
* authentication request is being made and optionally for which
|
||||
* {@code registrationId}.
|
||||
*
|
||||
* <p>
|
||||
* If there is an associated {@code <saml2:AuthnRequest>}, then the
|
||||
* {@code registrationId} is looked up and used.
|
||||
*
|
||||
* <p>
|
||||
* If a {@code registrationId} is found in the request, then it is looked up and used.
|
||||
* In that case, if none is found a {@link Saml2AuthenticationException} is thrown.
|
||||
*
|
||||
* <p>
|
||||
* Finally, if no {@code registrationId} is found in the request, then the code
|
||||
* attempts to resolve the {@link RelyingPartyRegistration} from the SAML Response's
|
||||
* Issuer.
|
||||
* @param request the HTTP request
|
||||
* @return the {@link Saml2AuthenticationToken} authentication request
|
||||
* @throws Saml2AuthenticationException if the {@link RequestMatcher} specifies a
|
||||
* non-existent {@code registrationId}
|
||||
*/
|
||||
@Override
|
||||
public Saml2AuthenticationToken convert(HttpServletRequest request) {
|
||||
String serialized = request.getParameter(Saml2ParameterNames.SAML_RESPONSE);
|
||||
if (serialized == null) {
|
||||
return null;
|
||||
}
|
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
|
||||
if (!result.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
Saml2AuthenticationToken token = tokenByAuthenticationRequest(request);
|
||||
if (token == null) {
|
||||
token = tokenByRegistrationId(request, result);
|
||||
}
|
||||
if (token == null) {
|
||||
token = tokenByEntityId(request);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private Saml2AuthenticationToken tokenByAuthenticationRequest(HttpServletRequest request) {
|
||||
AbstractSaml2AuthenticationRequest authenticationRequest = loadAuthenticationRequest(request);
|
||||
if (authenticationRequest == null) {
|
||||
return null;
|
||||
}
|
||||
String registrationId = authenticationRequest.getRelyingPartyRegistrationId();
|
||||
RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId);
|
||||
return tokenByRegistration(request, registration, authenticationRequest);
|
||||
}
|
||||
|
||||
private Saml2AuthenticationToken tokenByRegistrationId(HttpServletRequest request,
|
||||
RequestMatcher.MatchResult result) {
|
||||
String registrationId = result.getVariables().get("registrationId");
|
||||
if (registrationId == null) {
|
||||
return null;
|
||||
}
|
||||
RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId);
|
||||
return tokenByRegistration(request, registration, null);
|
||||
}
|
||||
|
||||
private Saml2AuthenticationToken tokenByEntityId(HttpServletRequest request) {
|
||||
Response response = this.saml.deserialize(decode(request));
|
||||
String issuer = response.getIssuer().getValue();
|
||||
RelyingPartyRegistration registration = this.registrations.findUniqueByAssertingPartyEntityId(issuer);
|
||||
return tokenByRegistration(request, registration, null);
|
||||
}
|
||||
|
||||
private Saml2AuthenticationToken tokenByRegistration(HttpServletRequest request,
|
||||
RelyingPartyRegistration registration, AbstractSaml2AuthenticationRequest authenticationRequest) {
|
||||
if (registration == null) {
|
||||
return null;
|
||||
}
|
||||
String decoded = decode(request);
|
||||
UriResolver resolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
registration = registration.mutate()
|
||||
.entityId(resolver.resolve(registration.getEntityId()))
|
||||
.assertionConsumerServiceLocation(resolver.resolve(registration.getAssertionConsumerServiceLocation()))
|
||||
.build();
|
||||
return new Saml2AuthenticationToken(registration, decoded, authenticationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link Saml2AuthenticationRequestRepository} to load authentication
|
||||
* request.
|
||||
* @param authenticationRequestRepository the
|
||||
* {@link Saml2AuthenticationRequestRepository} to use
|
||||
*/
|
||||
public void setAuthenticationRequestRepository(
|
||||
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository) {
|
||||
Assert.notNull(authenticationRequestRepository, "authenticationRequestRepository cannot be null");
|
||||
this.loader = authenticationRequestRepository::loadAuthenticationRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link RequestMatcher} to match the request.
|
||||
* @param requestMatcher the {@link RequestMatcher} to use
|
||||
*/
|
||||
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
private AbstractSaml2AuthenticationRequest loadAuthenticationRequest(HttpServletRequest request) {
|
||||
return this.loader.apply(request);
|
||||
}
|
||||
|
||||
private String decode(HttpServletRequest request) {
|
||||
String encoded = request.getParameter(Saml2ParameterNames.SAML_RESPONSE);
|
||||
try {
|
||||
return Saml2Utils.withEncoded(encoded)
|
||||
.requireBase64(true)
|
||||
.inflate(HttpMethod.GET.matches(request.getMethod()))
|
||||
.decode();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new Saml2AuthenticationException(Saml2Error.invalidResponse(ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
import org.opensaml.saml.saml2.core.impl.LogoutRequestUnmarshaller;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern;
|
||||
|
||||
/**
|
||||
* An OpenSAML-based implementation of
|
||||
* {@link Saml2LogoutRequestValidatorParametersResolver}
|
||||
*
|
||||
* @deprecated Please use a version-specific
|
||||
* {@link Saml2LogoutRequestValidatorParametersResolver} such as
|
||||
* {@code OpenSaml4LogoutRequestValidatorParametersResolver}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OpenSamlLogoutRequestValidatorParametersResolver
|
||||
implements Saml2LogoutRequestValidatorParametersResolver {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private RequestMatcher requestMatcher = new OrRequestMatcher(pathPattern("/logout/saml2/slo/{registrationId}"),
|
||||
pathPattern("/logout/saml2/slo"));
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private final RelyingPartyRegistrationRepository registrations;
|
||||
|
||||
private final XMLObjectProviderRegistry registry;
|
||||
|
||||
private final LogoutRequestUnmarshaller unmarshaller;
|
||||
|
||||
/**
|
||||
* Constructs a {@link OpenSamlLogoutRequestValidatorParametersResolver}
|
||||
*/
|
||||
public OpenSamlLogoutRequestValidatorParametersResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null");
|
||||
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
this.unmarshaller = (LogoutRequestUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory()
|
||||
.getUnmarshaller(LogoutRequest.DEFAULT_ELEMENT_NAME);
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the parameters necessary for validating an asserting party's
|
||||
* {@code <saml2:LogoutRequest>} based on the given {@link HttpServletRequest}
|
||||
*
|
||||
* <p>
|
||||
* Uses the configured {@link RequestMatcher} to identify the processing request,
|
||||
* including looking for any indicated {@code registrationId}.
|
||||
*
|
||||
* <p>
|
||||
* If a {@code registrationId} is found in the request, it will attempt to use that,
|
||||
* erroring if no {@link RelyingPartyRegistration} is found.
|
||||
*
|
||||
* <p>
|
||||
* If no {@code registrationId} is found in the request, it will look for a currently
|
||||
* logged-in user and use the associated {@code registrationId}.
|
||||
*
|
||||
* <p>
|
||||
* In the event that neither the URL nor any logged in user could determine a
|
||||
* {@code registrationId}, this code then will try and derive a
|
||||
* {@link RelyingPartyRegistration} given the {@code <saml2:LogoutRequest>}'s
|
||||
* {@code Issuer} value.
|
||||
* @param request the HTTP request
|
||||
* @return a {@link Saml2LogoutRequestValidatorParameters} instance, or {@code null}
|
||||
* if one could not be resolved
|
||||
* @throws Saml2AuthenticationException if the {@link RequestMatcher} specifies a
|
||||
* non-existent {@code registrationId}
|
||||
*/
|
||||
@Override
|
||||
public Saml2LogoutRequestValidatorParameters resolve(HttpServletRequest request, Authentication authentication) {
|
||||
if (request.getParameter(Saml2ParameterNames.SAML_REQUEST) == null) {
|
||||
return null;
|
||||
}
|
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
|
||||
if (!result.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
String registrationId = getRegistrationId(result, authentication);
|
||||
if (registrationId == null) {
|
||||
return logoutRequestByEntityId(request, authentication);
|
||||
}
|
||||
return logoutRequestById(request, authentication, registrationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request matcher to use to identify a request to process a
|
||||
* {@code <saml2:LogoutRequest>}. By default, checks for {@code /logout/saml2/slo} and
|
||||
* {@code /logout/saml2/slo/{registrationId}}.
|
||||
*
|
||||
* <p>
|
||||
* Generally speaking, the URL does not need to have a {@code registrationId} in it
|
||||
* since either it can be looked up from the active logged in user or it can be
|
||||
* derived through the {@code Issuer} in the {@code <saml2:LogoutRequest>}.
|
||||
* @param requestMatcher the {@link RequestMatcher} to use
|
||||
*/
|
||||
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
private String getRegistrationId(RequestMatcher.MatchResult result, Authentication authentication) {
|
||||
String registrationId = result.getVariables().get("registrationId");
|
||||
if (registrationId != null) {
|
||||
return registrationId;
|
||||
}
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
}
|
||||
if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal principal) {
|
||||
return principal.getRelyingPartyRegistrationId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Saml2LogoutRequestValidatorParameters logoutRequestById(HttpServletRequest request,
|
||||
Authentication authentication, String registrationId) {
|
||||
RelyingPartyRegistration registration = this.registrations.findByRegistrationId(registrationId);
|
||||
if (registration == null) {
|
||||
throw new Saml2AuthenticationException(
|
||||
Saml2Error.relyingPartyRegistrationNotFound("registration not found"));
|
||||
}
|
||||
return logoutRequestByRegistration(request, registration, authentication);
|
||||
}
|
||||
|
||||
private Saml2LogoutRequestValidatorParameters logoutRequestByEntityId(HttpServletRequest request,
|
||||
Authentication authentication) {
|
||||
String serialized = request.getParameter(Saml2ParameterNames.SAML_REQUEST);
|
||||
LogoutRequest logoutRequest = this.saml
|
||||
.deserialize(org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2Utils
|
||||
.withEncoded(serialized)
|
||||
.inflate(HttpMethod.GET.matches(request.getMethod()))
|
||||
.decode());
|
||||
String issuer = logoutRequest.getIssuer().getValue();
|
||||
RelyingPartyRegistration registration = this.registrations.findUniqueByAssertingPartyEntityId(issuer);
|
||||
return logoutRequestByRegistration(request, registration, authentication);
|
||||
}
|
||||
|
||||
private Saml2LogoutRequestValidatorParameters logoutRequestByRegistration(HttpServletRequest request,
|
||||
RelyingPartyRegistration registration, Authentication authentication) {
|
||||
if (registration == null) {
|
||||
return null;
|
||||
}
|
||||
Saml2MessageBinding saml2MessageBinding = Saml2MessageBindingUtils.resolveBinding(request);
|
||||
registration = fromRequest(request, registration);
|
||||
String serialized = request.getParameter(Saml2ParameterNames.SAML_REQUEST);
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.samlRequest(serialized)
|
||||
.relayState(request.getParameter(Saml2ParameterNames.RELAY_STATE))
|
||||
.binding(saml2MessageBinding)
|
||||
.location(registration.getSingleLogoutServiceLocation())
|
||||
.parameters((params) -> params.put(Saml2ParameterNames.SIG_ALG,
|
||||
request.getParameter(Saml2ParameterNames.SIG_ALG)))
|
||||
.parameters((params) -> params.put(Saml2ParameterNames.SIGNATURE,
|
||||
request.getParameter(Saml2ParameterNames.SIGNATURE)))
|
||||
.parametersQuery((params) -> request.getQueryString())
|
||||
.build();
|
||||
return new Saml2LogoutRequestValidatorParameters(logoutRequest, registration, authentication);
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration fromRequest(HttpServletRequest request, RelyingPartyRegistration registration) {
|
||||
RelyingPartyRegistrationPlaceholderResolvers.UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers
|
||||
.uriResolver(request, registration);
|
||||
String entityId = uriResolver.resolve(registration.getEntityId());
|
||||
String logoutLocation = uriResolver.resolve(registration.getSingleLogoutServiceLocation());
|
||||
String logoutResponseLocation = uriResolver.resolve(registration.getSingleLogoutServiceResponseLocation());
|
||||
return registration.mutate()
|
||||
.entityId(entityId)
|
||||
.singleLogoutServiceLocation(logoutLocation)
|
||||
.singleLogoutServiceResponseLocation(logoutResponseLocation)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.SignatureConfigurer;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlLogoutRequestValidator}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class OpenSamlLogoutRequestValidatorTests {
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private final OpenSamlLogoutRequestValidator manager = new OpenSamlLogoutRequestValidator();
|
||||
|
||||
@Test
|
||||
public void handleWhenPostBindingThenValidates() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
sign(logoutRequest, registration);
|
||||
Saml2LogoutRequest request = post(logoutRequest, registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
|
||||
registration, authentication(registration));
|
||||
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
|
||||
assertThat(result.hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenNameIdIsEncryptedIdPostThenValidates() {
|
||||
|
||||
RelyingPartyRegistration registration = decrypting(encrypting(registration())).build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequestNameIdInEncryptedId(registration);
|
||||
sign(logoutRequest, registration);
|
||||
Saml2LogoutRequest request = post(logoutRequest, registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
|
||||
registration, authentication(registration));
|
||||
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
|
||||
assertThat(result.hasErrors()).withFailMessage(() -> result.getErrors().toString()).isFalse();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenRedirectBindingThenValidatesSignatureParameter() {
|
||||
RelyingPartyRegistration registration = registration()
|
||||
.assertingPartyMetadata((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT))
|
||||
.build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
Saml2LogoutRequest request = redirect(logoutRequest, registration,
|
||||
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
|
||||
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
|
||||
registration, authentication(registration));
|
||||
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
|
||||
assertThat(result.hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
logoutRequest.getIssuer().setValue("wrong");
|
||||
sign(logoutRequest, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenMismatchedUserThenInvalidRequestError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
logoutRequest.getNameID().setValue("wrong");
|
||||
sign(logoutRequest, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenMissingUserThenSubjectNotFoundError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
logoutRequest.setNameID(null);
|
||||
sign(logoutRequest, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.SUBJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenMismatchedDestinationThenInvalidDestinationError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
logoutRequest.setDestination("wrong");
|
||||
sign(logoutRequest, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_DESTINATION);
|
||||
}
|
||||
|
||||
// gh-10923
|
||||
@Test
|
||||
public void handleWhenLogoutResponseHasLineBreaksThenHandles() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
|
||||
sign(logoutRequest, registration);
|
||||
String encoded = new StringBuffer(
|
||||
Saml2Utils.samlEncode(serialize(logoutRequest).getBytes(StandardCharsets.UTF_8)))
|
||||
.insert(10, "\r\n")
|
||||
.toString();
|
||||
Saml2LogoutRequest request = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.samlRequest(encoded)
|
||||
.build();
|
||||
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
|
||||
registration, authentication(registration));
|
||||
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
|
||||
assertThat(result.hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder registration() {
|
||||
return signing(verifying(TestRelyingPartyRegistrations.noCredentials()))
|
||||
.assertingPartyMetadata((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) {
|
||||
return builder
|
||||
.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential()));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder encrypting(RelyingPartyRegistration.Builder builder) {
|
||||
return builder.assertingPartyMetadata((party) -> party
|
||||
.encryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyEncryptingCredential())));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) {
|
||||
return builder.assertingPartyMetadata((party) -> party
|
||||
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder signing(RelyingPartyRegistration.Builder builder) {
|
||||
return builder.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential()));
|
||||
}
|
||||
|
||||
private Authentication authentication(RelyingPartyRegistration registration) {
|
||||
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>());
|
||||
principal.setRelyingPartyRegistrationId(registration.getRegistrationId());
|
||||
return new Saml2Authentication(principal, "response", new ArrayList<>());
|
||||
}
|
||||
|
||||
private Saml2LogoutRequest post(LogoutRequest logoutRequest, RelyingPartyRegistration registration) {
|
||||
return Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.samlRequest(Saml2Utils.samlEncode(serialize(logoutRequest).getBytes(StandardCharsets.UTF_8)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Saml2LogoutRequest redirect(LogoutRequest logoutRequest, RelyingPartyRegistration registration,
|
||||
SignatureConfigurer configurer) {
|
||||
String serialized = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(serialize(logoutRequest)));
|
||||
Map<String, String> parameters = configurer.sign(Map.of(Saml2ParameterNames.SAML_REQUEST, serialized));
|
||||
return Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.samlRequest(serialized)
|
||||
.parameters((params) -> params.putAll(parameters))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void sign(LogoutRequest logoutRequest, RelyingPartyRegistration registration) {
|
||||
TestOpenSamlObjects.signed(logoutRequest, registration.getSigningX509Credentials().iterator().next(),
|
||||
registration.getAssertingPartyMetadata().getEntityId());
|
||||
}
|
||||
|
||||
private String serialize(XMLObject object) {
|
||||
return this.saml.serialize(object).serialize();
|
||||
}
|
||||
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.authentication.logout;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlOperations.SignatureConfigurer;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlLogoutResponseValidator}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
public class OpenSamlLogoutResponseValidatorTests {
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private final OpenSamlLogoutResponseValidator manager = new OpenSamlLogoutResponseValidator();
|
||||
|
||||
@Test
|
||||
public void handleWhenAuthenticatedThenHandles() {
|
||||
RelyingPartyRegistration registration = signing(verifying(registration())).build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
sign(logoutResponse, registration);
|
||||
Saml2LogoutResponse response = post(logoutResponse, registration);
|
||||
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
|
||||
logoutRequest, registration);
|
||||
this.manager.validate(parameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenRedirectBindingThenValidatesSignatureParameter() {
|
||||
RelyingPartyRegistration registration = signing(verifying(registration()))
|
||||
.assertingPartyMetadata((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT))
|
||||
.build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
Saml2LogoutResponse response = redirect(logoutResponse, registration,
|
||||
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
|
||||
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
|
||||
logoutRequest, registration);
|
||||
this.manager.validate(parameters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
logoutResponse.getIssuer().setValue("wrong");
|
||||
sign(logoutResponse, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenMismatchedDestinationThenInvalidDestinationError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
logoutResponse.setDestination("wrong");
|
||||
sign(logoutResponse, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_DESTINATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleWhenStatusNotSuccessThenInvalidResponseError() {
|
||||
RelyingPartyRegistration registration = registration().build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
|
||||
sign(logoutResponse, registration);
|
||||
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().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE);
|
||||
}
|
||||
|
||||
// gh-10923
|
||||
@Test
|
||||
public void handleWhenLogoutResponseHasLineBreaksThenHandles() {
|
||||
RelyingPartyRegistration registration = signing(verifying(registration())).build();
|
||||
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
|
||||
.id("id")
|
||||
.build();
|
||||
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
|
||||
sign(logoutResponse, registration);
|
||||
String encoded = new StringBuilder(
|
||||
Saml2Utils.samlEncode(serialize(logoutResponse).getBytes(StandardCharsets.UTF_8)))
|
||||
.insert(10, "\r\n")
|
||||
.toString();
|
||||
Saml2LogoutResponse response = Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse(encoded)
|
||||
.build();
|
||||
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
|
||||
logoutRequest, registration);
|
||||
this.manager.validate(parameters);
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder registration() {
|
||||
return signing(verifying(TestRelyingPartyRegistrations.noCredentials()))
|
||||
.assertingPartyMetadata((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) {
|
||||
return builder.assertingPartyMetadata((party) -> party
|
||||
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())));
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration.Builder signing(RelyingPartyRegistration.Builder builder) {
|
||||
return builder.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential()));
|
||||
}
|
||||
|
||||
private Saml2LogoutResponse post(LogoutResponse logoutResponse, RelyingPartyRegistration registration) {
|
||||
return Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse(Saml2Utils.samlEncode(serialize(logoutResponse).getBytes(StandardCharsets.UTF_8)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Saml2LogoutResponse redirect(LogoutResponse logoutResponse, RelyingPartyRegistration registration,
|
||||
SignatureConfigurer configurer) {
|
||||
String serialized = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(serialize(logoutResponse)));
|
||||
Map<String, String> parameters = configurer.sign(Map.of(Saml2ParameterNames.SAML_RESPONSE, serialized));
|
||||
return Saml2LogoutResponse.withRelyingPartyRegistration(registration)
|
||||
.samlResponse(serialized)
|
||||
.parameters((params) -> params.putAll(parameters))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void sign(LogoutResponse logoutResponse, RelyingPartyRegistration registration) {
|
||||
TestOpenSamlObjects.signed(logoutResponse, registration.getSigningX509Credentials().iterator().next(),
|
||||
registration.getAssertingPartyMetadata().getEntityId());
|
||||
}
|
||||
|
||||
private String serialize(XMLObject object) {
|
||||
return this.saml.serialize(object).serialize();
|
||||
}
|
||||
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.metadata;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlMetadataResolver}
|
||||
*/
|
||||
public class OpenSamlMetadataResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenRelyingPartyThenMetadataMatches() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).contains("<md:EntityDescriptor")
|
||||
.contains("entityID=\"rp-entity-id\"")
|
||||
.contains("<md:KeyDescriptor use=\"signing\">")
|
||||
.contains("<md:KeyDescriptor use=\"encryption\">")
|
||||
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
|
||||
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
|
||||
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"")
|
||||
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenRelyingPartyAndSignMetadataSetThenMetadataMatches() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
openSamlMetadataResolver.setSignMetadata(true);
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).contains("<md:EntityDescriptor")
|
||||
.contains("entityID=\"rp-entity-id\"")
|
||||
.contains("<md:KeyDescriptor use=\"signing\">")
|
||||
.contains("<md:KeyDescriptor use=\"encryption\">")
|
||||
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
|
||||
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
|
||||
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"")
|
||||
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"")
|
||||
.contains("Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"")
|
||||
.contains("CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#")
|
||||
.contains("SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
|
||||
.contains("Reference URI=\"\"")
|
||||
.contains("Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature")
|
||||
.contains("Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"")
|
||||
.contains("DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"")
|
||||
.contains("DigestValue")
|
||||
.contains("SignatureValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenRelyingPartyNoCredentialsThenMetadataMatches() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
|
||||
.assertingPartyMetadata((party) -> party
|
||||
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).contains("<md:EntityDescriptor")
|
||||
.contains("entityID=\"rp-entity-id\"")
|
||||
.doesNotContain("<md:KeyDescriptor use=\"signing\">")
|
||||
.doesNotContain("<md:KeyDescriptor use=\"encryption\">")
|
||||
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"")
|
||||
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"")
|
||||
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenRelyingPartyNameIDFormatThenMetadataMatches() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
|
||||
.nameIdFormat("format")
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).contains("<md:NameIDFormat>format</md:NameIDFormat>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenRelyingPartyNoLogoutThenMetadataMatches() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
|
||||
.singleLogoutServiceLocation(null)
|
||||
.nameIdFormat("format")
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).doesNotContain("ResponseLocation");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenEntityDescriptorCustomizerThenUses() {
|
||||
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
|
||||
.entityId("originalEntityId")
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
openSamlMetadataResolver.setEntityDescriptorCustomizer(
|
||||
(parameters) -> parameters.getEntityDescriptor().setEntityID("overriddenEntityId"));
|
||||
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
|
||||
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"overriddenEntityId\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveIterableWhenRelyingPartiesThenMetadataMatches() {
|
||||
RelyingPartyRegistration one = TestRelyingPartyRegistrations.full()
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
RelyingPartyRegistration two = TestRelyingPartyRegistrations.full()
|
||||
.entityId("two")
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
String metadata = openSamlMetadataResolver.resolve(List.of(one, two));
|
||||
assertThat(metadata).contains("<md:EntitiesDescriptor")
|
||||
.contains("<md:EntityDescriptor")
|
||||
.contains("entityID=\"rp-entity-id\"")
|
||||
.contains("entityID=\"two\"")
|
||||
.contains("<md:KeyDescriptor use=\"signing\">")
|
||||
.contains("<md:KeyDescriptor use=\"encryption\">")
|
||||
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
|
||||
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
|
||||
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"")
|
||||
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveIterableWhenRelyingPartiesAndSignMetadataSetThenMetadataMatches() {
|
||||
RelyingPartyRegistration one = TestRelyingPartyRegistrations.full()
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
RelyingPartyRegistration two = TestRelyingPartyRegistrations.full()
|
||||
.entityId("two")
|
||||
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT)
|
||||
.build();
|
||||
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
|
||||
openSamlMetadataResolver.setSignMetadata(true);
|
||||
String metadata = openSamlMetadataResolver.resolve(List.of(one, two));
|
||||
assertThat(metadata).contains("<md:EntitiesDescriptor")
|
||||
.contains("<md:EntityDescriptor")
|
||||
.contains("entityID=\"rp-entity-id\"")
|
||||
.contains("entityID=\"two\"")
|
||||
.contains("<md:KeyDescriptor use=\"signing\">")
|
||||
.contains("<md:KeyDescriptor use=\"encryption\">")
|
||||
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
|
||||
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"")
|
||||
.contains("Location=\"https://rp.example.org/acs\" index=\"1\"")
|
||||
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"")
|
||||
.contains("Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"")
|
||||
.contains("CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#")
|
||||
.contains("SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
|
||||
.contains("Reference URI=\"\"")
|
||||
.contains("Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature")
|
||||
.contains("Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"")
|
||||
.contains("DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"")
|
||||
.contains("DigestValue")
|
||||
.contains("SignatureValue");
|
||||
}
|
||||
|
||||
}
|
@ -1,243 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.saml.common.SignableSAMLObject;
|
||||
import org.opensaml.saml.saml2.core.Response;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.core.Saml2Utils;
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlAuthenticationTokenConverter}
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public final class OpenSamlAuthenticationTokenConverterTests {
|
||||
|
||||
@Mock
|
||||
RelyingPartyRegistrationRepository registrations;
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
|
||||
|
||||
@Test
|
||||
public void convertWhenSamlResponseThenToken() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE,
|
||||
Saml2Utils.samlEncode("response".getBytes(StandardCharsets.UTF_8)));
|
||||
Saml2AuthenticationToken token = converter.convert(request);
|
||||
assertThat(token.getSaml2Response()).isEqualTo("response");
|
||||
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
|
||||
.isEqualTo(this.registration.getRegistrationId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSamlResponseInvalidBase64ThenSaml2AuthenticationException() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, "invalid");
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request))
|
||||
.withCauseInstanceOf(IllegalArgumentException.class)
|
||||
.satisfies(
|
||||
(ex) -> assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE))
|
||||
.satisfies(
|
||||
(ex) -> assertThat(ex.getSaml2Error().getDescription()).isEqualTo("Failed to decode SAMLResponse"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenNoSamlResponseThenNull() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
assertThat(converter.convert(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenNoMatchingRequestThenNull() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, "ignored");
|
||||
assertThat(converter.convert(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenNoRelyingPartyRegistrationThenNull() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
String response = Saml2Utils.samlEncode(serialize(signed(response())).getBytes(StandardCharsets.UTF_8));
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, response);
|
||||
assertThat(converter.convert(request)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenGetRequestThenInflates() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
MockHttpServletRequest request = get("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
byte[] deflated = Saml2Utils.samlDeflate("response");
|
||||
String encoded = Saml2Utils.samlEncode(deflated);
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, encoded);
|
||||
Saml2AuthenticationToken token = converter.convert(request);
|
||||
assertThat(token.getSaml2Response()).isEqualTo("response");
|
||||
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
|
||||
.isEqualTo(this.registration.getRegistrationId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenGetRequestInvalidDeflatedThenSaml2AuthenticationException() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
MockHttpServletRequest request = get("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
byte[] invalidDeflated = "invalid".getBytes();
|
||||
String encoded = Saml2Utils.samlEncode(invalidDeflated);
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, encoded);
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> converter.convert(request))
|
||||
.withRootCauseInstanceOf(IOException.class)
|
||||
.satisfies(
|
||||
(ex) -> assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE))
|
||||
.satisfies((ex) -> assertThat(ex.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenUsingSamlUtilsBase64ThenXmlIsValid() throws Exception {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, getSsoCircleEncodedXml());
|
||||
Saml2AuthenticationToken token = converter.convert(request);
|
||||
validateSsoCircleXml(token.getSaml2Response());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSavedAuthenticationRequestThenToken() {
|
||||
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = mock(
|
||||
Saml2AuthenticationRequestRepository.class);
|
||||
AbstractSaml2AuthenticationRequest authenticationRequest = mock(AbstractSaml2AuthenticationRequest.class);
|
||||
given(authenticationRequest.getRelyingPartyRegistrationId()).willReturn(this.registration.getRegistrationId());
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
converter.setAuthenticationRequestRepository(authenticationRequestRepository);
|
||||
given(this.registrations.findByRegistrationId(any())).willReturn(this.registration);
|
||||
given(authenticationRequestRepository.loadAuthenticationRequest(any(HttpServletRequest.class)))
|
||||
.willReturn(authenticationRequest);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId());
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE,
|
||||
Saml2Utils.samlEncode("response".getBytes(StandardCharsets.UTF_8)));
|
||||
Saml2AuthenticationToken token = converter.convert(request);
|
||||
assertThat(token.getSaml2Response()).isEqualTo("response");
|
||||
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
|
||||
.isEqualTo(this.registration.getRegistrationId());
|
||||
assertThat(token.getAuthenticationRequest()).isEqualTo(authenticationRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenMatchingNoRegistrationIdThenLooksUpByAssertingEntityId() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
String response = serialize(signed(response()));
|
||||
String encoded = Saml2Utils.samlEncode(response.getBytes(StandardCharsets.UTF_8));
|
||||
given(this.registrations.findUniqueByAssertingPartyEntityId(TestOpenSamlObjects.ASSERTING_PARTY_ENTITY_ID))
|
||||
.willReturn(this.registration);
|
||||
MockHttpServletRequest request = post("/login/saml2/sso");
|
||||
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, encoded);
|
||||
Saml2AuthenticationToken token = converter.convert(request);
|
||||
assertThat(token.getSaml2Response()).isEqualTo(response);
|
||||
assertThat(token.getRelyingPartyRegistration().getRegistrationId())
|
||||
.isEqualTo(this.registration.getRegistrationId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenResolverIsNullThenIllegalArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new Saml2AuthenticationTokenConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthenticationRequestRepositoryWhenNullThenIllegalArgument() {
|
||||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(this.registrations);
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> converter.setAuthenticationRequestRepository(null));
|
||||
}
|
||||
|
||||
private void validateSsoCircleXml(String xml) {
|
||||
assertThat(xml).contains("InResponseTo=\"ARQ9a73ead-7dcf-45a8-89eb-26f3c9900c36\"")
|
||||
.contains(" ID=\"s246d157446618e90e43fb79bdd4d9e9e19cf2c7c4\"")
|
||||
.contains("<saml:Issuer>https://idp.ssocircle.com</saml:Issuer>");
|
||||
}
|
||||
|
||||
private String getSsoCircleEncodedXml() throws IOException {
|
||||
ClassPathResource resource = new ClassPathResource("saml2-response-sso-circle.encoded");
|
||||
String response = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
|
||||
return UriUtils.decode(response, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private MockHttpServletRequest post(String uri) {
|
||||
return TestMockHttpServletRequests.post(uri).build();
|
||||
}
|
||||
|
||||
private MockHttpServletRequest get(String uri) {
|
||||
return TestMockHttpServletRequests.get(uri).build();
|
||||
}
|
||||
|
||||
private <T extends SignableSAMLObject> T signed(T toSign) {
|
||||
TestOpenSamlObjects.signed(toSign, TestSaml2X509Credentials.assertingPartySigningCredential(),
|
||||
TestOpenSamlObjects.RELYING_PARTY_ENTITY_ID);
|
||||
return toSign;
|
||||
}
|
||||
|
||||
private Response response() {
|
||||
Response response = TestOpenSamlObjects.response();
|
||||
response.setIssueInstant(Instant.now());
|
||||
return response;
|
||||
}
|
||||
|
||||
private String serialize(XMLObject object) {
|
||||
return this.saml.serialize(object).serialize();
|
||||
}
|
||||
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication.logout;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications;
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidatorParameters;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public final class OpenSamlLogoutRequestValidatorParametersResolverTests {
|
||||
|
||||
@Mock
|
||||
RelyingPartyRegistrationRepository registrations;
|
||||
|
||||
private final OpenSamlOperations saml = new OpenSaml4Template();
|
||||
|
||||
private RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
|
||||
|
||||
private OpenSamlLogoutRequestValidatorParametersResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.resolver = new OpenSamlLogoutRequestValidatorParametersResolver(this.registrations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutRegistrationIdResolveWhenMatchesThenParameters() {
|
||||
String registrationId = this.registration.getRegistrationId();
|
||||
MockHttpServletRequest request = post("/logout/saml2/slo/" + registrationId);
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "pass");
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
given(this.registrations.findByRegistrationId(registrationId)).willReturn(this.registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.resolver.resolve(request, authentication);
|
||||
assertThat(parameters.getAuthentication()).isEqualTo(authentication);
|
||||
assertThat(parameters.getRelyingPartyRegistration().getRegistrationId()).isEqualTo(registrationId);
|
||||
assertThat(parameters.getLogoutRequest().getSamlRequest()).isEqualTo("request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutRegistrationIdWhenUnauthenticatedThenParameters() {
|
||||
String registrationId = this.registration.getRegistrationId();
|
||||
MockHttpServletRequest request = post("/logout/saml2/slo/" + registrationId);
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
given(this.registrations.findByRegistrationId(registrationId)).willReturn(this.registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.resolver.resolve(request, null);
|
||||
assertThat(parameters.getAuthentication()).isNull();
|
||||
assertThat(parameters.getRelyingPartyRegistration().getRegistrationId()).isEqualTo(registrationId);
|
||||
assertThat(parameters.getLogoutRequest().getSamlRequest()).isEqualTo("request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutResolveWhenAuthenticatedThenParameters() {
|
||||
String registrationId = this.registration.getRegistrationId();
|
||||
MockHttpServletRequest request = post("/logout/saml2/slo");
|
||||
Authentication authentication = TestSaml2Authentications.authentication();
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
given(this.registrations.findByRegistrationId(registrationId)).willReturn(this.registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.resolver.resolve(request, authentication);
|
||||
assertThat(parameters.getAuthentication()).isEqualTo(authentication);
|
||||
assertThat(parameters.getRelyingPartyRegistration().getRegistrationId()).isEqualTo(registrationId);
|
||||
assertThat(parameters.getLogoutRequest().getSamlRequest()).isEqualTo("request");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutResolveWhenUnauthenticatedThenParameters() {
|
||||
String registrationId = this.registration.getRegistrationId();
|
||||
MockHttpServletRequest request = post("/logout/saml2/slo");
|
||||
String logoutRequest = serialize(TestOpenSamlObjects.logoutRequest());
|
||||
String encoded = Saml2Utils.samlEncode(logoutRequest.getBytes(StandardCharsets.UTF_8));
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, encoded);
|
||||
given(this.registrations.findUniqueByAssertingPartyEntityId(TestOpenSamlObjects.ASSERTING_PARTY_ENTITY_ID))
|
||||
.willReturn(this.registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.resolver.resolve(request, null);
|
||||
assertThat(parameters.getAuthentication()).isNull();
|
||||
assertThat(parameters.getRelyingPartyRegistration().getRegistrationId()).isEqualTo(registrationId);
|
||||
assertThat(parameters.getLogoutRequest().getSamlRequest()).isEqualTo(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutResolveWhenUnauthenticatedGetRequestThenInflates() {
|
||||
String registrationId = this.registration.getRegistrationId();
|
||||
MockHttpServletRequest request = get("/logout/saml2/slo");
|
||||
String logoutRequest = serialize(TestOpenSamlObjects.logoutRequest());
|
||||
String encoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(logoutRequest));
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, encoded);
|
||||
given(this.registrations.findUniqueByAssertingPartyEntityId(TestOpenSamlObjects.ASSERTING_PARTY_ENTITY_ID))
|
||||
.willReturn(this.registration);
|
||||
Saml2LogoutRequestValidatorParameters parameters = this.resolver.resolve(request, null);
|
||||
assertThat(parameters.getAuthentication()).isNull();
|
||||
assertThat(parameters.getRelyingPartyRegistration().getRegistrationId()).isEqualTo(registrationId);
|
||||
assertThat(parameters.getLogoutRequest().getSamlRequest()).isEqualTo(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saml2LogoutRegistrationIdResolveWhenNoMatchingRegistrationIdThenSaml2Exception() {
|
||||
MockHttpServletRequest request = post("/logout/saml2/slo/id");
|
||||
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
|
||||
assertThatExceptionOfType(Saml2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.resolver.resolve(request, null));
|
||||
}
|
||||
|
||||
private MockHttpServletRequest post(String uri) {
|
||||
return TestMockHttpServletRequests.post(uri).build();
|
||||
}
|
||||
|
||||
private MockHttpServletRequest get(String uri) {
|
||||
return TestMockHttpServletRequests.get(uri).build();
|
||||
}
|
||||
|
||||
private String serialize(XMLObject object) {
|
||||
return this.saml.serialize(object).serialize();
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user