diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java index f1814ab90b..9c61bae376 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java @@ -43,7 +43,6 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.OpenSaml4AuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.OpenSaml5AuthenticationTokenConverter; -import org.springframework.security.saml2.provider.service.web.OpenSamlAuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter; @@ -416,9 +415,6 @@ public final class Saml2LoginConfigurer> } AuthenticationConverter authenticationConverterBean = getBeanOrNull(http, Saml2AuthenticationTokenConverter.class); - if (authenticationConverterBean == null) { - authenticationConverterBean = getBeanOrNull(http, OpenSamlAuthenticationTokenConverter.class); - } if (authenticationConverterBean != null) { return authenticationConverterBean; } diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java deleted file mode 100644 index ac4229be66..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidator.java +++ /dev/null @@ -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> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest, - RelyingPartyRegistration registration) { - AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); - Collection credentials = details.getVerificationX509Credentials(); - VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId()); - return (errors) -> { - if (logoutRequest.isSigned()) { - errors.addAll(verify.verify(logoutRequest)); - } - else { - RedirectParameters params = new RedirectParameters(request.getParameters(), - request.getParametersQuery(), logoutRequest); - errors.addAll(verify.verify(params)); - } - }; - } - - private Consumer> validateRequest(LogoutRequest request, - RelyingPartyRegistration registration, Authentication authentication) { - return (errors) -> { - validateIssuer(request, registration).accept(errors); - validateDestination(request, registration).accept(errors); - validateSubject(request, registration, authentication).accept(errors); - }; - } - - private Consumer> validateIssuer(LogoutRequest request, - RelyingPartyRegistration registration) { - return (errors) -> { - if (request.getIssuer() == null) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest")); - return; - } - String issuer = request.getIssuer().getValue(); - if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) { - errors - .add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer")); - } - }; - } - - private Consumer> validateDestination(LogoutRequest request, - RelyingPartyRegistration registration) { - return (errors) -> { - if (request.getDestination() == null) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to find destination in LogoutRequest")); - return; - } - String destination = request.getDestination(); - if (!destination.equals(registration.getSingleLogoutServiceLocation())) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to match destination to configured destination")); - } - }; - } - - private Consumer> validateSubject(LogoutRequest request, - RelyingPartyRegistration registration, Authentication authentication) { - return (errors) -> { - if (authentication == null) { - return; - } - NameID nameId = getNameId(request, registration); - if (nameId == null) { - errors - .add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest")); - return; - } - - validateNameId(nameId, authentication, errors); - }; - } - - private NameID getNameId(LogoutRequest request, RelyingPartyRegistration registration) { - this.saml.withDecryptionKeys(registration.getDecryptionX509Credentials()).decrypt(request); - return request.getNameID(); - } - - private void validateNameId(NameID nameId, Authentication authentication, Collection 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")); - } - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java deleted file mode 100644 index 96eaed94a2..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidator.java +++ /dev/null @@ -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> verifySignature(Saml2LogoutResponse response, - LogoutResponse logoutResponse, RelyingPartyRegistration registration) { - return (errors) -> { - AssertingPartyMetadata details = registration.getAssertingPartyMetadata(); - Collection credentials = details.getVerificationX509Credentials(); - VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId()); - if (logoutResponse.isSigned()) { - errors.addAll(verify.verify(logoutResponse)); - } - else { - RedirectParameters params = new RedirectParameters(response.getParameters(), - response.getParametersQuery(), logoutResponse); - errors.addAll(verify.verify(params)); - } - }; - } - - private Consumer> validateRequest(LogoutResponse response, - RelyingPartyRegistration registration) { - return (errors) -> { - validateIssuer(response, registration).accept(errors); - validateDestination(response, registration).accept(errors); - validateStatus(response).accept(errors); - }; - } - - private Consumer> validateIssuer(LogoutResponse response, - RelyingPartyRegistration registration) { - return (errors) -> { - if (response.getIssuer() == null) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse")); - return; - } - String issuer = response.getIssuer().getValue(); - if (!issuer.equals(registration.getAssertingPartyMetadata().getEntityId())) { - errors - .add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer")); - } - }; - } - - private Consumer> validateDestination(LogoutResponse response, - RelyingPartyRegistration registration) { - return (errors) -> { - if (response.getDestination() == null) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to find destination in LogoutResponse")); - return; - } - String destination = response.getDestination(); - if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, - "Failed to match destination to configured destination")); - } - }; - } - - private Consumer> validateStatus(LogoutResponse response) { - return (errors) -> { - if (response.getStatus() == null) { - return; - } - if (response.getStatus().getStatusCode() == null) { - return; - } - if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) { - return; - } - if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) { - return; - } - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed")); - }; - } - - private Consumer> validateLogoutRequest(LogoutResponse response, String id) { - return (errors) -> { - if (response.getInResponseTo() == null) { - return; - } - if (response.getInResponseTo().equals(id)) { - return; - } - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, - "LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest")); - }; - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java deleted file mode 100644 index aebe13b37f..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java +++ /dev/null @@ -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 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 relyingPartyRegistrations) { - Collection 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 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 buildKeys(Collection credentials, UsageType usageType) { - List 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; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java deleted file mode 100644 index 00475fa246..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverter.java +++ /dev/null @@ -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 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}. - * - *

- * First uses the configured {@link RequestMatcher} to deduce whether an - * authentication request is being made and optionally for which - * {@code registrationId}. - * - *

- * If there is an associated {@code }, then the - * {@code registrationId} is looked up and used. - * - *

- * 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. - * - *

- * 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 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); - } - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java deleted file mode 100644 index d9dd3de177..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolver.java +++ /dev/null @@ -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 } based on the given {@link HttpServletRequest} - * - *

- * Uses the configured {@link RequestMatcher} to identify the processing request, - * including looking for any indicated {@code registrationId}. - * - *

- * If a {@code registrationId} is found in the request, it will attempt to use that, - * erroring if no {@link RelyingPartyRegistration} is found. - * - *

- * If no {@code registrationId} is found in the request, it will look for a currently - * logged-in user and use the associated {@code registrationId}. - * - *

- * 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 }'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 }. By default, checks for {@code /logout/saml2/slo} and - * {@code /logout/saml2/slo/{registrationId}}. - * - *

- * 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 }. - * @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(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java deleted file mode 100644 index 070e8866c9..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutRequestValidatorTests.java +++ /dev/null @@ -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 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(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java deleted file mode 100644 index f0e4eacabd..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSamlLogoutResponseValidatorTests.java +++ /dev/null @@ -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 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(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java deleted file mode 100644 index 920bc4f493..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java +++ /dev/null @@ -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("") - .contains("") - .contains("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("") - .contains("") - .contains("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("") - .doesNotContain("") - .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("format"); - } - - @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("") - .contains("") - .contains("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("") - .contains("") - .contains("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"); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverterTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverterTests.java deleted file mode 100644 index ffb2196836..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSamlAuthenticationTokenConverterTests.java +++ /dev/null @@ -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 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("https://idp.ssocircle.com"); - } - - 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 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(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolverTests.java deleted file mode 100644 index e036b749cc..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSamlLogoutRequestValidatorParametersResolverTests.java +++ /dev/null @@ -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(); - } - -}