11 changed files with 0 additions and 2007 deletions
@ -1,164 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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…
Reference in new issue