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 664ae446b0..b62b83de8c 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 @@ -35,18 +35,15 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; 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.RelyingPartyRegistrations; 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.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; @@ -381,10 +378,8 @@ public final class Saml2LoginConfigurer> return openSamlAuthenticationRequestResolver; } else { - OpenSaml4AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver( - relyingPartyRegistrationRepository(http)); - openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher()); - return openSamlAuthenticationRequestResolver; + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } } @@ -429,15 +424,8 @@ public final class Saml2LoginConfigurer> converter.setRequestMatcher(getLoginProcessingEndpoint()); return converter; } - authenticationConverterBean = getBeanOrNull(http, OpenSaml4AuthenticationTokenConverter.class); - if (authenticationConverterBean != null) { - return authenticationConverterBean; - } - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter( - this.relyingPartyRegistrationRepository); - converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http)); - converter.setRequestMatcher(getLoginProcessingEndpoint()); - return converter; + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } private void registerDefaultAuthenticationProvider(B http) { @@ -448,10 +436,8 @@ public final class Saml2LoginConfigurer> } } else { - OpenSaml4AuthenticationProvider provider = getBeanOrNull(http, OpenSaml4AuthenticationProvider.class); - if (provider == null) { - http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider())); - } + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index 691da1dbdf..5704deb4b0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -35,8 +35,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor; -import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; @@ -44,9 +42,6 @@ import org.springframework.security.saml2.provider.service.authentication.logout 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.authentication.logout.HttpSessionLogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestValidatorParametersResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestValidatorParametersResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutResponseResolver; @@ -250,10 +245,8 @@ public final class Saml2LogoutConfigurer> parameters.setRequestMatcher(requestMatcher); return parameters; } - OpenSaml4LogoutRequestValidatorParametersResolver parameters = new OpenSaml4LogoutRequestValidatorParametersResolver( - registrations); - parameters.setRequestMatcher(requestMatcher); - return parameters; + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } private Saml2LogoutResponseFilter createLogoutResponseProcessingFilter( @@ -384,7 +377,8 @@ public final class Saml2LogoutConfigurer> if (USE_OPENSAML_5) { return new OpenSaml5LogoutRequestValidator(); } - return new OpenSaml4LogoutRequestValidator(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } private Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) { @@ -394,7 +388,8 @@ public final class Saml2LogoutConfigurer> if (USE_OPENSAML_5) { return new OpenSaml5LogoutRequestResolver(registrations); } - return new OpenSaml4LogoutRequestResolver(registrations); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } } @@ -454,7 +449,8 @@ public final class Saml2LogoutConfigurer> if (USE_OPENSAML_5) { return new OpenSaml5LogoutResponseValidator(); } - return new OpenSaml4LogoutResponseValidator(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } private Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) { @@ -464,7 +460,8 @@ public final class Saml2LogoutConfigurer> if (USE_OPENSAML_5) { return new OpenSaml5LogoutResponseResolver(registrations); } - return new OpenSaml4LogoutResponseResolver(registrations); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java index ac61d71550..ab32891c68 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurer.java @@ -24,7 +24,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -113,10 +112,8 @@ public class Saml2MetadataConfigurer> metadata.setRequestMatcher(getRequestMatcherBuilder().matcher(metadataUrl)); return metadata; } - RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(registrations, - new OpenSaml4MetadataResolver()); - metadata.setRequestMatcher(getRequestMatcherBuilder().matcher(metadataUrl)); - return metadata; + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); }; return this; } @@ -156,7 +153,8 @@ public class Saml2MetadataConfigurer> if (USE_OPENSAML_5) { return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver()); } - return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml4MetadataResolver()); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } private RelyingPartyRegistrationRepository getRelyingPartyRegistrationRepository(H http) { diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserUtils.java b/config/src/main/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserUtils.java index e15e91c4b2..3089d2af3c 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserUtils.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserUtils.java @@ -24,13 +24,11 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import org.springframework.util.StringUtils; @@ -90,16 +88,16 @@ final class Saml2LoginBeanDefinitionParserUtils { .addConstructorArgValue(defaultRelyingPartyRegistrationResolver) .getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4AuthenticationRequestResolver.class) - .addConstructorArgValue(defaultRelyingPartyRegistrationResolver) - .getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } static BeanDefinition createAuthenticationProvider() { if (USE_OPENSAML_5) { return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5AuthenticationProvider.class).getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4AuthenticationProvider.class).getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } static BeanMetadataElement getAuthenticationConverter(Element element) { diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserUtils.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserUtils.java index 37374037fd..0ef506fde9 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserUtils.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserUtils.java @@ -22,14 +22,10 @@ import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutResponseValidator; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutResponseResolver; import org.springframework.util.StringUtils; @@ -76,9 +72,8 @@ final class Saml2LogoutBeanDefinitionParserUtils { .addConstructorArgValue(registrations) .getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutResponseResolver.class) - .addConstructorArgValue(registrations) - .getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } static BeanMetadataElement getLogoutRequestValidator(Element element) { @@ -89,7 +84,8 @@ final class Saml2LogoutBeanDefinitionParserUtils { if (USE_OPENSAML_5) { return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5LogoutRequestValidator.class).getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutRequestValidator.class).getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } static BeanMetadataElement getLogoutResponseValidator(Element element) { @@ -100,7 +96,8 @@ final class Saml2LogoutBeanDefinitionParserUtils { if (USE_OPENSAML_5) { return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5LogoutResponseValidator.class).getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutResponseValidator.class).getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } static BeanMetadataElement getLogoutRequestRepository(Element element) { @@ -121,9 +118,8 @@ final class Saml2LogoutBeanDefinitionParserUtils { .addConstructorArgValue(registrations) .getBeanDefinition(); } - return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutRequestResolver.class) - .addConstructorArgValue(registrations) - .getBeanDefinition(); + throw new IllegalArgumentException( + "Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5"); } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index 05f60003e0..fbec2d93c7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -32,7 +32,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.opensaml.core.Version; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.Marshaller; import org.opensaml.saml.saml2.core.Assertion; @@ -69,7 +68,6 @@ import org.springframework.security.saml2.core.Saml2ErrorCodes; 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.OpenSaml4AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; @@ -84,7 +82,6 @@ import org.springframework.security.saml2.provider.service.web.DefaultRelyingPar import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; 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.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.web.FilterChainProxy; @@ -139,8 +136,6 @@ public class Saml2LoginConfigurerTests { .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) .build(); - private static final boolean USE_OPENSAML_5 = Version.getVersion().startsWith("5"); - private static String SIGNED_RESPONSE; private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class); @@ -550,13 +545,7 @@ public class Saml2LoginConfigurerTests { RelyingPartyRegistrationRepository registrations) { RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( registrations); - if (USE_OPENSAML_5) { - OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( - registrationResolver); - delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); - return delegate; - } - OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver( + OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( registrationResolver); delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); return delegate; @@ -589,13 +578,7 @@ public class Saml2LoginConfigurerTests { RelyingPartyRegistrationRepository registrations) { RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( registrations); - if (USE_OPENSAML_5) { - OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( - registrationResolver); - delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); - return delegate; - } - OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver( + OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( registrationResolver); delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); return delegate; @@ -773,8 +756,7 @@ public class Saml2LoginConfigurerTests { @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationProviderConfig { - private final AuthenticationProvider provider = spy( - USE_OPENSAML_5 ? new OpenSaml5AuthenticationProvider() : new OpenSaml4AuthenticationProvider()); + private final AuthenticationProvider provider = spy(new OpenSaml5AuthenticationProvider()); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java index 0262ba3f74..470e99aaea 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java @@ -30,7 +30,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver; +import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver; @@ -159,7 +159,7 @@ public class Saml2MetadataConfigurerTests { // should ignore @Bean Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) { - return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml4MetadataResolver()); + return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver()); } } diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java index 87c9bb296c..bf09abc5ff 100644 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java @@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; +import net.shibboleth.shared.xml.SerializeSupport; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java index 966ce4363f..42690a16e8 100644 --- a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java @@ -37,7 +37,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP 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.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; @@ -279,8 +279,8 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests { public void parseWhenRelayStateResolverThenUses() { this.spring.configLocations(xml("RelayStateResolver")).autowire(); Converter relayStateResolver = this.spring.getContext().getBean(Converter.class); - OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext() - .getBean(OpenSaml4AuthenticationRequestResolver.class); + OpenSaml5AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext() + .getBean(OpenSaml5AuthenticationRequestResolver.class); MockHttpServletRequest request = get("/saml2/authenticate/one").build(); authenticationRequestResolver.resolve(request); verify(relayStateResolver).convert(request); diff --git a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml index 930659d73d..761eb5467f 100644 --- a/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml +++ b/config/src/test/resources/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml @@ -47,7 +47,7 @@ - + diff --git a/dependencies/spring-security-dependencies.gradle b/dependencies/spring-security-dependencies.gradle index 1a8be3ff73..8df1adae80 100644 --- a/dependencies/spring-security-dependencies.gradle +++ b/dependencies/spring-security-dependencies.gradle @@ -61,8 +61,8 @@ dependencies { api libs.org.hibernate.orm.hibernate.core api libs.org.hsqldb api libs.org.apereo.cas.client.cas.client.core - api libs.org.opensaml.opensaml.saml.api - api libs.org.opensaml.opensaml.saml.impl + api libs.org.opensaml.opensaml5.saml.api + api libs.org.opensaml.opensaml5.saml.impl api libs.org.python.jython api libs.org.seleniumhq.selenium.htmlunit.driver api libs.org.seleniumhq.selenium.selenium.java diff --git a/docs/modules/ROOT/pages/servlet/saml2/opensaml.adoc b/docs/modules/ROOT/pages/servlet/saml2/opensaml.adoc deleted file mode 100644 index 9ffdac62d0..0000000000 --- a/docs/modules/ROOT/pages/servlet/saml2/opensaml.adoc +++ /dev/null @@ -1,76 +0,0 @@ -= OpenSAML Support - -Spring Security provides an API for implementing SAML 2.0 features, and it also provides a default implementation using OpenSAML. - -Because Spring Security supports more than one version of OpenSAML at the same time, the components use the following naming convention: - -* Any component that is usable across all supported versions is named `OpenSamlXXX`. -* Any component that targets OpenSAML 4.x is named `OpenSaml4XXX` -* Any component that targets OpenSAML 5.x is named `OpenSaml5XXX` - -`spring-security-config` selects between these implementations by default by discovering which version your application is currently using. -For example, if you are using OpenSAML 4, Spring Security will use the `OpenSaml4XXX` components. - -== Selecting OpenSAML 4 - -Spring Security depends on OpenSAML 4 by default, so you need do nothing to begin using it other than importing the `spring-security-saml` dependency. - -== Selecting OpenSAML 5 - -To use OpenSAML, you should override the `opensaml` dependencies as follows: - -[tabs] -====== -Maven:: -+ -[source,maven,role="primary"] ----- - - - org.springframework.security - spring-security-saml2-service-provider - - - org.opensaml - * - - - - - org.opensaml - opensaml-saml-api - 5.1.2 - - - org.opensaml - opensaml-saml-impl - 5.1.2 - - ----- - -Gradle:: -+ -[source,gradle,role="secondary"] ----- -dependencies { - constraints { - implementation "org.opensaml:opensaml-core-api:5.1.2" - implementation "org.opensaml:opensaml-core-impl:5.1.2" - implementation "org.opensaml:opensaml-saml-api:5.1.2" - implementation "org.opensaml:opensaml-saml-impl:5.1.2" - } - - // ... - - implementation ('org.springframework.security:spring-security-saml2-service-provider') { - exclude group: "org.opensaml", module: "opensaml-core" - } - - // ... -} ----- -====== - -[NOTE] -The exclusion is necessary because OpenSAML 5 splits `opensaml-core` into `opensaml-core-api` and `opensaml-core-impl` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8de0204f6..0013310f54 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,6 @@ org-eclipse-jetty = "11.0.25" org-jetbrains-kotlin = "2.2.0" org-jetbrains-kotlinx = "1.10.2" org-mockito = "5.17.0" -org-opensaml = "4.3.2" org-opensaml5 = "5.1.2" org-springframework = "7.0.0-M7" @@ -70,8 +69,6 @@ org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle- org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } org-junit-junit-bom = "org.junit:junit-bom:5.12.2" org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } -org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" } -org-opensaml-opensaml-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml" } org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" } org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" } org-python-jython = { module = "org.python:jython", version = "2.5.3" } diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle index 64511c5079..f1b68b259b 100644 --- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle +++ b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle @@ -6,25 +6,12 @@ configurations { } sourceSets { - opensaml4Main { - java { - compileClasspath += main.output - srcDir 'src/opensaml4Main/java' - } - } opensaml5Main { java { compileClasspath = main.output + configurations.opensamlFiveMain srcDir 'src/opensaml5Main/java' } } - opensaml4Test { - java { - compileClasspath += main.output + test.output + opensaml4Main.output + test.compileClasspath - runtimeClasspath += main.output + test.output + opensaml4Main.output + test.runtimeClasspath - srcDir 'src/opensaml4Test/java' - } - } opensaml5Test { java { compileClasspath = main.output + test.output + opensaml5Main.output + configurations.opensamlFiveTest @@ -99,7 +86,7 @@ dependencies { opensamlFiveMain (libs.org.opensaml.opensaml5.saml.api) { exclude group: 'commons-logging', module: 'commons-logging' } - opensamlFiveMain (libs.org.opensaml.opensaml5.saml.impl) { + opensamlFiveMain (libs.org.opensaml.opensaml5.saml.api) { exclude group: 'commons-logging', module: 'commons-logging' } @@ -125,31 +112,22 @@ dependencies { jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from sourceSets.opensaml4Main.output from sourceSets.opensaml5Main.output } sourcesJar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from sourceSets.opensaml4Main.allJava from sourceSets.opensaml5Main.allJava } testJar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from sourceSets.opensaml4Test.output from sourceSets.opensaml5Test.output } javadoc { classpath += configurations.opensamlFiveMain - source = sourceSets.main.allJava + sourceSets.opensaml4Main.allJava + sourceSets.opensaml5Main.allJava -} - -tasks.register("opensaml4Test", Test) { - useJUnitPlatform() - testClassesDirs = sourceSets.opensaml4Test.output.classesDirs - classpath = sourceSets.opensaml4Test.runtimeClasspath + source = sourceSets.main.allJava + sourceSets.opensaml5Main.allJava } tasks.register("opensaml5Test", Test) { @@ -159,6 +137,5 @@ tasks.register("opensaml5Test", Test) { } tasks.named("test") { - dependsOn opensaml4Test dependsOn opensaml5Test } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java index 95dc1c2671..3ac6f0845b 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/BaseOpenSamlAuthenticationProvider.java @@ -253,13 +253,6 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { (params) -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5))); } - static Converter createDefaultAssertionValidator( - Converter contextConverter) { - - return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, - (assertionToken) -> SAML20AssertionValidators.attributeValidator, contextConverter); - } - static Converter createDefaultAssertionValidatorWithParameters( Consumer> validationContextParameters) { return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, @@ -523,7 +516,7 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider { return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); } String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), context.getValidationFailureMessage()); + ((Response) assertion.getParent()).getID(), context.getValidationFailureMessages()); return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); }; } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java index 7134ae5739..cfef7d0097 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java @@ -20,7 +20,6 @@ import java.io.InputStream; import java.util.Collection; import java.util.Collections; -import org.opensaml.core.Version; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.Unmarshaller; @@ -43,19 +42,7 @@ final class OpenSamlMetadataUtils { } static OpenSamlDeserializer resolveDeserializer() { - if (Version.getVersion().startsWith("4")) { - return new OpenSaml4Deserializer(); - } - String opensaml5 = "org.springframework.security.saml2.provider.service.registration.OpenSaml5Template"; - try { - Class template = Class.forName(opensaml5); - OpenSamlOperations operations = (OpenSamlOperations) template.getDeclaredConstructor().newInstance(); - return operations::deserialize; - } - catch (Exception ex) { - throw new IllegalStateException( - "Application appears to be using OpenSAML 5, but Spring's OpenSAML 5 support is not on the classpath"); - } + return new OpenSaml5Deserializer(); } private OpenSamlMetadataUtils() { @@ -79,7 +66,7 @@ final class OpenSamlMetadataUtils { } - private static class OpenSaml4Deserializer implements OpenSamlDeserializer { + private static class OpenSaml5Deserializer implements OpenSamlDeserializer { @Override public XMLObject deserialize(InputStream serialized) { diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/internal/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/internal/OpenSaml4Template.java deleted file mode 100644 index 239c1c6ad6..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/internal/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.internal; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java deleted file mode 100644 index 426538b951..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProvider.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.time.Duration; -import java.util.Map; -import java.util.function.Consumer; - -import org.opensaml.saml.common.assertion.ValidationContext; -import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.encryption.Decrypter; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.util.Assert; - -/** - * Implementation of {@link AuthenticationProvider} for SAML authentications when - * receiving a {@code Response} object containing an {@code Assertion}. This - * implementation uses the {@code OpenSAML 4} library. - * - *

- * The {@link OpenSaml4AuthenticationProvider} supports {@link Saml2AuthenticationToken} - * objects that contain a SAML response in its decoded XML format - * {@link Saml2AuthenticationToken#getSaml2Response()} along with the information about - * the asserting party, the identity provider (IDP), as well as the relying party, the - * service provider (SP, this application). - *

- * The {@link Saml2AuthenticationToken} will be processed into a SAML Response object. The - * SAML response object can be signed. If the Response is signed, a signature will not be - * required on the assertion. - *

- * While a response object can contain a list of assertion, this provider will only - * leverage the first valid assertion for the purpose of authentication. Assertions that - * do not pass validation will be ignored. If no valid assertions are found a - * {@link Saml2AuthenticationException} is thrown. - *

- * This provider supports two types of encrypted SAML elements - *

- * If the assertion is encrypted, then signature validation on the assertion is no longer - * required. - *

- * This provider does not perform an X509 certificate validation on the configured - * asserting party, IDP, verification certificates. - * - * @author Josh Cummings - * @since 5.5 - * @see SAML 2 - * StatusResponse - * @see OpenSAML - */ -public final class OpenSaml4AuthenticationProvider implements AuthenticationProvider { - - private final BaseOpenSamlAuthenticationProvider delegate; - - /** - * Creates an {@link OpenSaml4AuthenticationProvider} - */ - public OpenSaml4AuthenticationProvider() { - this.delegate = new BaseOpenSamlAuthenticationProvider(new OpenSaml4Template()); - this.delegate.setValidateResponseAfterAssertions(false); - } - - /** - * Set the {@link Consumer} strategy to use for decrypting elements of a validated - * {@link Response}. The default strategy decrypts all {@link EncryptedAssertion}s - * using OpenSAML's {@link Decrypter}, adding the results to - * {@link Response#getAssertions()}. - * - * You can use this method to configure the {@link Decrypter} instance like so: - * - *

-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	provider.setResponseElementsDecrypter((responseToken) -> {
-	 *	    DecrypterParameters parameters = new DecrypterParameters();
-	 *	    // ... set parameters as needed
-	 *	    Decrypter decrypter = new Decrypter(parameters);
-	 *		Response response = responseToken.getResponse();
-	 *  	EncryptedAssertion encrypted = response.getEncryptedAssertions().get(0);
-	 *  	try {
-	 *  		Assertion assertion = decrypter.decrypt(encrypted);
-	 *  		response.getAssertions().add(assertion);
-	 *  	} catch (Exception e) {
-	 *  	 	throw new Saml2AuthenticationException(...);
-	 *  	}
-	 *	});
-	 * 
- * - * Or, in the event that you have your own custom decryption interface, the same - * pattern applies: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	Converter<EncryptedAssertion, Assertion> myService = ...
-	 *	provider.setResponseDecrypter((responseToken) -> {
-	 *	   Response response = responseToken.getResponse();
-	 *	   response.getEncryptedAssertions().stream()
-	 *	   		.map(service::decrypt).forEach(response.getAssertions()::add);
-	 *	});
-	 * 
- * - * This is valuable when using an external service to perform the decryption. - * @param responseElementsDecrypter the {@link Consumer} for decrypting response - * elements - * @since 5.5 - */ - public void setResponseElementsDecrypter(Consumer responseElementsDecrypter) { - Assert.notNull(responseElementsDecrypter, "responseElementsDecrypter cannot be null"); - this.delegate - .setResponseElementsDecrypter((token) -> responseElementsDecrypter.accept(new ResponseToken(token))); - } - - /** - * Set the {@link Converter} to use for validating the SAML 2.0 Response. - * - * You can still invoke the default validator by delegating to - * {@link #createDefaultResponseValidator()}, like so: - * - *
-	 * OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
-	 * provider.setResponseValidator(responseToken -> {
-	 * 		Saml2ResponseValidatorResult result = createDefaultResponseValidator()
-	 * 			.convert(responseToken)
-	 * 		return result.concat(myCustomValidator.convert(responseToken));
-	 * });
-	 * 
- * @param responseValidator the {@link Converter} to use - * @since 5.6 - */ - public void setResponseValidator(Converter responseValidator) { - Assert.notNull(responseValidator, "responseValidator cannot be null"); - this.delegate.setResponseValidator((token) -> responseValidator.convert(new ResponseToken(token))); - } - - /** - * Set the {@link Converter} to use for validating each {@link Assertion} in the SAML - * 2.0 Response. - * - * You can still invoke the default validator by delgating to - * {@link #createAssertionValidator}, like so: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *  provider.setAssertionValidator(assertionToken -> {
-	 *		Saml2ResponseValidatorResult result = createDefaultAssertionValidator()
-	 *			.convert(assertionToken)
-	 *		return result.concat(myCustomValidator.convert(assertionToken));
-	 *  });
-	 * 
- * - * You can also use this method to configure the provider to use a different - * {@link ValidationContext} from the default, like so: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	provider.setAssertionValidator(
-	 *		createDefaultAssertionValidator(assertionToken -> {
-	 *			Map<String, Object> params = new HashMap<>();
-	 *			params.put(CLOCK_SKEW, 2 * 60 * 1000);
-	 *			// other parameters
-	 *			return new ValidationContext(params);
-	 *		}));
-	 * 
- * - * Consider taking a look at {@link #createValidationContext} to see how it constructs - * a {@link ValidationContext}. - * - * It is not necessary to delegate to the default validator. You can safely replace it - * entirely with your own. Note that signature verification is performed as a separate - * step from this validator. - * @param assertionValidator the validator to use - * @since 5.4 - */ - public void setAssertionValidator(Converter assertionValidator) { - Assert.notNull(assertionValidator, "assertionValidator cannot be null"); - this.delegate.setAssertionValidator((token) -> assertionValidator.convert(new AssertionToken(token))); - } - - /** - * Set the {@link Consumer} strategy to use for decrypting elements of a validated - * {@link Assertion}. - * - * You can use this method to configure the {@link Decrypter} used like so: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	provider.setResponseDecrypter((assertionToken) -> {
-	 *	    DecrypterParameters parameters = new DecrypterParameters();
-	 *	    // ... set parameters as needed
-	 *	    Decrypter decrypter = new Decrypter(parameters);
-	 *		Assertion assertion = assertionToken.getAssertion();
-	 *  	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
-	 *  	try {
-	 *  		NameID name = decrypter.decrypt(encrypted);
-	 *  		assertion.getSubject().setNameID(name);
-	 *  	} catch (Exception e) {
-	 *  	 	throw new Saml2AuthenticationException(...);
-	 *  	}
-	 *	});
-	 * 
- * - * Or, in the event that you have your own custom interface, the same pattern applies: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 *	MyDecryptionService myService = ...
-	 *	provider.setResponseDecrypter((responseToken) -> {
-	 *	   	Assertion assertion = assertionToken.getAssertion();
-	 *	   	EncryptedID encrypted = assertion.getSubject().getEncryptedID();
-	 *		NameID name = myService.decrypt(encrypted);
-	 *		assertion.getSubject().setNameID(name);
-	 *	});
-	 * 
- * @param assertionDecrypter the {@link Consumer} for decrypting assertion elements - * @since 5.5 - */ - public void setAssertionElementsDecrypter(Consumer assertionDecrypter) { - Assert.notNull(assertionDecrypter, "assertionDecrypter cannot be null"); - this.delegate.setAssertionElementsDecrypter((token) -> assertionDecrypter.accept(new AssertionToken(token))); - } - - /** - * Set the {@link Converter} to use for converting a validated {@link Response} into - * an {@link AbstractAuthenticationToken}. - * - * You can delegate to the default behavior by calling - * {@link #createDefaultResponseAuthenticationConverter()} like so: - * - *
-	 *	OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-	 * 	Converter<ResponseToken, Saml2Authentication> authenticationConverter =
-	 * 			createDefaultResponseAuthenticationConverter();
-	 *	provider.setResponseAuthenticationConverter(responseToken -> {
-	 *		Saml2Authentication authentication = authenticationConverter.convert(responseToken);
-	 *		User user = myUserRepository.findByUsername(authentication.getName());
-	 *		return new MyAuthentication(authentication, user);
-	 *	});
-	 * 
- * @param responseAuthenticationConverter the {@link Converter} to use - * @since 5.4 - */ - public void setResponseAuthenticationConverter( - Converter responseAuthenticationConverter) { - Assert.notNull(responseAuthenticationConverter, "responseAuthenticationConverter cannot be null"); - this.delegate.setResponseAuthenticationConverter( - (token) -> responseAuthenticationConverter.convert(new ResponseToken(token))); - } - - /** - * Construct a default strategy for validating the SAML 2.0 Response - * @return the default response validator strategy - * @since 5.6 - */ - public static Converter createDefaultResponseValidator() { - Converter delegate = BaseOpenSamlAuthenticationProvider - .createDefaultResponseValidator(); - return (token) -> delegate - .convert(new BaseOpenSamlAuthenticationProvider.ResponseToken(token.getResponse(), token.getToken())); - } - - /** - * Construct a default strategy for validating each SAML 2.0 Assertion and associated - * {@link Authentication} token - * @return the default assertion validator strategy - */ - public static Converter createDefaultAssertionValidator() { - return createDefaultAssertionValidatorWithParameters( - (params) -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5))); - } - - /** - * Construct a default strategy for validating each SAML 2.0 Assertion and associated - * {@link Authentication} token - * @param contextConverter the conversion strategy to use to generate a - * {@link ValidationContext} for each assertion being validated - * @return the default assertion validator strategy - * @deprecated Use {@link #createDefaultAssertionValidatorWithParameters} instead - */ - @Deprecated - public static Converter createDefaultAssertionValidator( - Converter contextConverter) { - Converter contextDelegate = ( - token) -> contextConverter.convert(new AssertionToken(token.getAssertion(), token.getToken())); - Converter delegate = BaseOpenSamlAuthenticationProvider - .createDefaultAssertionValidator(contextDelegate); - return (token) -> delegate - .convert(new BaseOpenSamlAuthenticationProvider.AssertionToken(token.getAssertion(), token.getToken())); - } - - /** - * Construct a default strategy for validating each SAML 2.0 Assertion and associated - * {@link Authentication} token - * @param validationContextParameters a consumer for editing the values passed to the - * {@link ValidationContext} for each assertion being validated - * @return the default assertion validator strategy - * @since 5.8 - */ - public static Converter createDefaultAssertionValidatorWithParameters( - Consumer> validationContextParameters) { - Converter delegate = BaseOpenSamlAuthenticationProvider - .createDefaultAssertionValidatorWithParameters(validationContextParameters); - return (token) -> delegate - .convert(new BaseOpenSamlAuthenticationProvider.AssertionToken(token.getAssertion(), token.getToken())); - } - - /** - * Construct a default strategy for converting a SAML 2.0 Response and - * {@link Authentication} token into a {@link Saml2Authentication} - * @return the default response authentication converter strategy - */ - public static Converter createDefaultResponseAuthenticationConverter() { - Converter delegate = BaseOpenSamlAuthenticationProvider - .createDefaultResponseAuthenticationConverter(); - return (token) -> delegate - .convert(new BaseOpenSamlAuthenticationProvider.ResponseToken(token.getResponse(), token.getToken())); - } - - /** - * @param authentication the authentication request object, must be of type - * {@link Saml2AuthenticationToken} - * @return {@link Saml2Authentication} if the assertion is valid - * @throws AuthenticationException if a validation exception occurs - */ - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return this.delegate.authenticate(authentication); - } - - @Override - public boolean supports(Class authentication) { - return authentication != null && Saml2AuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * A tuple containing an OpenSAML {@link Response} and its associated authentication - * token. - * - * @since 5.4 - */ - public static class ResponseToken { - - private final Saml2AuthenticationToken token; - - private final Response response; - - ResponseToken(Response response, Saml2AuthenticationToken token) { - this.token = token; - this.response = response; - } - - ResponseToken(BaseOpenSamlAuthenticationProvider.ResponseToken token) { - this.token = token.getToken(); - this.response = token.getResponse(); - } - - public Response getResponse() { - return this.response; - } - - public Saml2AuthenticationToken getToken() { - return this.token; - } - - } - - /** - * A tuple containing an OpenSAML {@link Assertion} and its associated authentication - * token. - * - * @since 5.4 - */ - public static class AssertionToken { - - private final Saml2AuthenticationToken token; - - private final Assertion assertion; - - AssertionToken(Assertion assertion, Saml2AuthenticationToken token) { - this.token = token; - this.assertion = assertion; - } - - AssertionToken(BaseOpenSamlAuthenticationProvider.AssertionToken token) { - this.token = token.getToken(); - this.assertion = token.getAssertion(); - } - - public Assertion getAssertion() { - return this.assertion; - } - - public Saml2AuthenticationToken getToken() { - return this.token; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4Template.java deleted file mode 100644 index 185ec0c2f3..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidator.java deleted file mode 100644 index a75f4ffed3..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2004-present 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; - -public final class OpenSaml4LogoutRequestValidator implements Saml2LogoutRequestValidator { - - @SuppressWarnings("deprecation") - private final Saml2LogoutRequestValidator delegate = new BaseOpenSamlLogoutRequestValidator( - new OpenSaml4Template()); - - @Override - public Saml2LogoutValidatorResult validate(Saml2LogoutRequestValidatorParameters parameters) { - return this.delegate.validate(parameters); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidator.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidator.java deleted file mode 100644 index b48a320eaa..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2004-present 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; - -public final class OpenSaml4LogoutResponseValidator implements Saml2LogoutResponseValidator { - - @SuppressWarnings("deprecation") - private final Saml2LogoutResponseValidator delegate = new BaseOpenSamlLogoutResponseValidator( - new OpenSaml4Template()); - - @Override - public Saml2LogoutValidatorResult validate(Saml2LogoutResponseValidatorParameters parameters) { - return this.delegate.validate(parameters); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4Template.java deleted file mode 100644 index 212fc0d907..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java deleted file mode 100644 index 75129fbeaf..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2004-present 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.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Base64; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterOutputStream; - -import org.springframework.security.saml2.Saml2Exception; - -/** - * Utility methods for working with serialized SAML messages. - * - * For internal use only. - * - * @author Josh Cummings - */ -final class Saml2Utils { - - private Saml2Utils() { - } - - static String samlEncode(byte[] b) { - return Base64.getEncoder().encodeToString(b); - } - - static byte[] samlDecode(String s) { - return Base64.getMimeDecoder().decode(s); - } - - static byte[] samlDeflate(String s) { - try { - ByteArrayOutputStream b = new ByteArrayOutputStream(); - DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(Deflater.DEFLATED, true)); - deflater.write(s.getBytes(StandardCharsets.UTF_8)); - deflater.finish(); - return b.toByteArray(); - } - catch (IOException ex) { - throw new Saml2Exception("Unable to deflate string", ex); - } - } - - static String samlInflate(byte[] b) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true)); - iout.write(b); - iout.finish(); - return new String(out.toByteArray(), StandardCharsets.UTF_8); - } - catch (IOException ex) { - throw new Saml2Exception("Unable to inflate string", ex); - } - } - - static EncodingConfigurer withDecoded(String decoded) { - return new EncodingConfigurer(decoded); - } - - static DecodingConfigurer withEncoded(String encoded) { - return new DecodingConfigurer(encoded); - } - - static final class EncodingConfigurer { - - private final String decoded; - - private boolean deflate; - - private EncodingConfigurer(String decoded) { - this.decoded = decoded; - } - - EncodingConfigurer deflate(boolean deflate) { - this.deflate = deflate; - return this; - } - - String encode() { - byte[] bytes = (this.deflate) ? Saml2Utils.samlDeflate(this.decoded) - : this.decoded.getBytes(StandardCharsets.UTF_8); - return Saml2Utils.samlEncode(bytes); - } - - } - - static final class DecodingConfigurer { - - private static final Base64Checker BASE_64_CHECKER = new Base64Checker(); - - private final String encoded; - - private boolean inflate; - - private boolean requireBase64; - - private DecodingConfigurer(String encoded) { - this.encoded = encoded; - } - - DecodingConfigurer inflate(boolean inflate) { - this.inflate = inflate; - return this; - } - - DecodingConfigurer requireBase64(boolean requireBase64) { - this.requireBase64 = requireBase64; - return this; - } - - String decode() { - if (this.requireBase64) { - BASE_64_CHECKER.checkAcceptable(this.encoded); - } - byte[] bytes = Saml2Utils.samlDecode(this.encoded); - return (this.inflate) ? Saml2Utils.samlInflate(bytes) : new String(bytes, StandardCharsets.UTF_8); - } - - static class Base64Checker { - - private static final int[] values = genValueMapping(); - - Base64Checker() { - - } - - private static int[] genValueMapping() { - byte[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - .getBytes(StandardCharsets.ISO_8859_1); - - int[] values = new int[256]; - Arrays.fill(values, -1); - for (int i = 0; i < alphabet.length; i++) { - values[alphabet[i] & 0xff] = i; - } - return values; - } - - boolean isAcceptable(String s) { - int goodChars = 0; - int lastGoodCharVal = -1; - - // count number of characters from Base64 alphabet - for (int i = 0; i < s.length(); i++) { - int val = values[0xff & s.charAt(i)]; - if (val != -1) { - lastGoodCharVal = val; - goodChars++; - } - } - - // in cases of an incomplete final chunk, ensure the unused bits are zero - switch (goodChars % 4) { - case 0: - return true; - case 2: - return (lastGoodCharVal & 0b1111) == 0; - case 3: - return (lastGoodCharVal & 0b11) == 0; - default: - return false; - } - } - - void checkAcceptable(String ins) { - if (!isAcceptable(ins)) { - throw new IllegalArgumentException("Failed to decode SAMLResponse"); - } - } - - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4MetadataResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4MetadataResolver.java deleted file mode 100644 index d8ccaac4d3..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4MetadataResolver.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2004-present 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.function.Consumer; - -import org.opensaml.saml.saml2.metadata.EntityDescriptor; - -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -/** - * 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 - */ -public final class OpenSaml4MetadataResolver implements Saml2MetadataResolver { - - static { - OpenSamlInitializationService.initialize(); - } - - private final BaseOpenSamlMetadataResolver delegate; - - public OpenSaml4MetadataResolver() { - this.delegate = new BaseOpenSamlMetadataResolver(new OpenSaml4Template()); - } - - @Override - public String resolve(RelyingPartyRegistration relyingPartyRegistration) { - return this.delegate.resolve(relyingPartyRegistration); - } - - public String resolve(Iterable relyingPartyRegistrations) { - return this.delegate.resolve(relyingPartyRegistrations); - } - - /** - * 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) { - this.delegate.setEntityDescriptorCustomizer( - (parameters) -> entityDescriptorCustomizer.accept(new EntityDescriptorParameters(parameters))); - } - - /** - * 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.delegate.setUsePrettyPrint(usePrettyPrint); - } - - /** - * Configure whether to sign the metadata, defaults to {@code false}. - * - * @since 6.4 - */ - public void setSignMetadata(boolean signMetadata) { - this.delegate.setSignMetadata(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; - } - - EntityDescriptorParameters(BaseOpenSamlMetadataResolver.EntityDescriptorParameters parameters) { - this.entityDescriptor = parameters.getEntityDescriptor(); - this.registration = parameters.getRelyingPartyRegistration(); - } - - 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/metadata/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4Template.java deleted file mode 100644 index 7c535e9de1..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4AssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4AssertingPartyMetadataRepository.java deleted file mode 100644 index cd83e156ef..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4AssertingPartyMetadataRepository.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2004-present 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.registration; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.function.Consumer; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.criterion.EntityRoleCriterion; -import org.opensaml.saml.metadata.IterableMetadataSource; -import org.opensaml.saml.metadata.resolver.MetadataResolver; -import org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter; -import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; -import org.opensaml.saml.metadata.resolver.index.impl.RoleMetadataIndex; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; - -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.provider.service.registration.BaseOpenSamlAssertingPartyMetadataRepository.MetadataResolverAdapter; -import org.springframework.util.Assert; - -/** - * An implementation of {@link AssertingPartyMetadataRepository} that uses a - * {@link MetadataResolver} to retrieve {@link AssertingPartyMetadata} instances. - * - *

- * The {@link MetadataResolver} constructed in {@link #withTrustedMetadataLocation} - * provides expiry-aware refreshing. - * - * @author Josh Cummings - * @since 6.4 - * @see AssertingPartyMetadataRepository - * @see RelyingPartyRegistrations - */ -public final class OpenSaml4AssertingPartyMetadataRepository implements AssertingPartyMetadataRepository { - - static { - OpenSamlInitializationService.initialize(); - } - - private final BaseOpenSamlAssertingPartyMetadataRepository delegate; - - /** - * Construct an {@link OpenSaml4AssertingPartyMetadataRepository} using the provided - * {@link MetadataResolver}. - * - *

- * The {@link MetadataResolver} should either be of type - * {@link IterableMetadataSource} or it should have a {@link RoleMetadataIndex} - * configured. - * @param metadataResolver the {@link MetadataResolver} to use - */ - public OpenSaml4AssertingPartyMetadataRepository(MetadataResolver metadataResolver) { - Assert.notNull(metadataResolver, "metadataResolver cannot be null"); - this.delegate = new BaseOpenSamlAssertingPartyMetadataRepository( - new CriteriaSetResolverWrapper(metadataResolver)); - } - - /** - * {@inheritDoc} - */ - @Override - @NonNull - public Iterator iterator() { - return this.delegate.iterator(); - } - - /** - * {@inheritDoc} - */ - @Nullable - @Override - public AssertingPartyMetadata findByEntityId(String entityId) { - return this.delegate.findByEntityId(entityId); - } - - /** - * Use this trusted {@code metadataLocation} to retrieve refreshable, expiry-aware - * SAML 2.0 Asserting Party (IDP) metadata. - * - *

- * Valid locations can be classpath- or file-based or they can be HTTPS endpoints. - * Some valid endpoints might include: - * - *

-	 *   metadataLocation = "classpath:asserting-party-metadata.xml";
-	 *   metadataLocation = "file:asserting-party-metadata.xml";
-	 *   metadataLocation = "https://ap.example.org/metadata";
-	 * 
- * - *

- * Resolution of location is attempted immediately. To defer, wrap in - * {@link CachingRelyingPartyRegistrationRepository}. - * @param metadataLocation the classpath- or file-based locations or HTTPS endpoints - * of the asserting party metadata file - * @return the {@link MetadataLocationRepositoryBuilder} for further configuration - */ - public static MetadataLocationRepositoryBuilder withTrustedMetadataLocation(String metadataLocation) { - return new MetadataLocationRepositoryBuilder(metadataLocation, true); - } - - /** - * Use this {@code metadataLocation} to retrieve refreshable, expiry-aware SAML 2.0 - * Asserting Party (IDP) metadata. Verification credentials are required. - * - *

- * Valid locations can be classpath- or file-based or they can be remote endpoints. - * Some valid endpoints might include: - * - *

-	 *   metadataLocation = "classpath:asserting-party-metadata.xml";
-	 *   metadataLocation = "file:asserting-party-metadata.xml";
-	 *   metadataLocation = "https://ap.example.org/metadata";
-	 * 
- * - *

- * Resolution of location is attempted immediately. To defer, wrap in - * {@link CachingRelyingPartyRegistrationRepository}. - * @param metadataLocation the classpath- or file-based locations or remote endpoints - * of the asserting party metadata file - * @return the {@link MetadataLocationRepositoryBuilder} for further configuration - */ - public static MetadataLocationRepositoryBuilder withMetadataLocation(String metadataLocation) { - return new MetadataLocationRepositoryBuilder(metadataLocation, false); - } - - /** - * A builder class for configuring {@link OpenSaml4AssertingPartyMetadataRepository} - * for a specific metadata location. - * - * @author Josh Cummings - */ - public static final class MetadataLocationRepositoryBuilder { - - private final String metadataLocation; - - private final boolean requireVerificationCredentials; - - private final Collection verificationCredentials = new ArrayList<>(); - - private ResourceLoader resourceLoader = new DefaultResourceLoader(); - - MetadataLocationRepositoryBuilder(String metadataLocation, boolean trusted) { - this.metadataLocation = metadataLocation; - this.requireVerificationCredentials = !trusted; - } - - public MetadataLocationRepositoryBuilder verificationCredentials(Consumer> credentials) { - credentials.accept(this.verificationCredentials); - return this; - } - - public MetadataLocationRepositoryBuilder resourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - return this; - } - - public OpenSaml4AssertingPartyMetadataRepository build() { - return new OpenSaml4AssertingPartyMetadataRepository(metadataResolver()); - } - - private MetadataResolver metadataResolver() { - ResourceBackedMetadataResolver metadataResolver = resourceBackedMetadataResolver(); - boolean missingCredentials = this.requireVerificationCredentials && this.verificationCredentials.isEmpty(); - Assert.isTrue(!missingCredentials, "Verification credentials are required"); - return initialize(metadataResolver); - } - - private ResourceBackedMetadataResolver resourceBackedMetadataResolver() { - Resource resource = this.resourceLoader.getResource(this.metadataLocation); - try { - ResourceBackedMetadataResolver metadataResolver = new ResourceBackedMetadataResolver( - new SpringResource(resource)); - if (this.verificationCredentials.isEmpty()) { - return metadataResolver; - } - SignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine( - new CollectionCredentialResolver(this.verificationCredentials), - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - SignatureValidationFilter filter = new SignatureValidationFilter(engine); - filter.setRequireSignedRoot(true); - metadataResolver.setMetadataFilter(filter); - return metadataResolver; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private MetadataResolver initialize(ResourceBackedMetadataResolver metadataResolver) { - metadataResolver.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); - return BaseOpenSamlAssertingPartyMetadataRepository.initialize(metadataResolver); - } - - private static final class SpringResource implements net.shibboleth.utilities.java.support.resource.Resource { - - private final Resource resource; - - SpringResource(Resource resource) { - this.resource = resource; - } - - @Override - public boolean exists() { - return this.resource.exists(); - } - - @Override - public boolean isReadable() { - return this.resource.isReadable(); - } - - @Override - public boolean isOpen() { - return this.resource.isOpen(); - } - - @Override - public URL getURL() throws IOException { - return this.resource.getURL(); - } - - @Override - public URI getURI() throws IOException { - return this.resource.getURI(); - } - - @Override - public File getFile() throws IOException { - return this.resource.getFile(); - } - - @NonNull - @Override - public InputStream getInputStream() throws IOException { - return this.resource.getInputStream(); - } - - @Override - public long contentLength() throws IOException { - return this.resource.contentLength(); - } - - @Override - public long lastModified() throws IOException { - return this.resource.lastModified(); - } - - @Override - public net.shibboleth.utilities.java.support.resource.Resource createRelativeResource(String relativePath) - throws IOException { - return new SpringResource(this.resource.createRelative(relativePath)); - } - - @Override - public String getFilename() { - return this.resource.getFilename(); - } - - @Override - public String getDescription() { - return this.resource.getDescription(); - } - - } - - } - - private static final class CriteriaSetResolverWrapper extends MetadataResolverAdapter { - - CriteriaSetResolverWrapper(MetadataResolver metadataResolver) { - super(metadataResolver); - } - - @Override - EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception { - return super.metadataResolver.resolveSingle(new CriteriaSet(entityId)); - } - - @Override - Iterable resolve(EntityRoleCriterion role) throws Exception { - return super.metadataResolver.resolve(new CriteriaSet(role)); - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4Template.java deleted file mode 100644 index 6a60e92bb4..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.registration; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java deleted file mode 100644 index ae012b0667..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverter.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2004-present 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 jakarta.servlet.http.HttpServletRequest; - -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.web.authentication.AuthenticationConverter; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * 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 - */ -public final class OpenSaml4AuthenticationTokenConverter implements AuthenticationConverter { - - private final BaseOpenSamlAuthenticationTokenConverter delegate; - - /** - * Constructs a {@link OpenSaml4AuthenticationTokenConverter} given a repository for - * {@link RelyingPartyRegistration}s - * @param registrations the repository for {@link RelyingPartyRegistration}s - * {@link RelyingPartyRegistration}s - */ - public OpenSaml4AuthenticationTokenConverter(RelyingPartyRegistrationRepository registrations) { - Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null"); - this.delegate = new BaseOpenSamlAuthenticationTokenConverter(registrations, new OpenSaml4Template()); - } - - /** - * 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) { - return this.delegate.convert(request); - } - - /** - * 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.delegate.setAuthenticationRequestRepository(authenticationRequestRepository); - } - - /** - * 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.delegate.setRequestMatcher(requestMatcher); - } - - /** - * Use the given {@code shouldConvertGetRequests} to convert {@code GET} requests. - * Default is {@code true}. - * @param shouldConvertGetRequests the {@code shouldConvertGetRequests} to use - * @since 7.0 - */ - public void setShouldConvertGetRequests(boolean shouldConvertGetRequests) { - this.delegate.setShouldConvertGetRequests(shouldConvertGetRequests); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4Template.java deleted file mode 100644 index 2214b4b603..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java deleted file mode 100644 index 26d1574f4b..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolver.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.time.Clock; -import java.time.Instant; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; -import org.opensaml.saml.saml2.core.AuthnRequest; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -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.RelyingPartyRegistrationResolver; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * A strategy for resolving a SAML 2.0 Authentication Request from the - * {@link HttpServletRequest} using OpenSAML. - * - * @author Josh Cummings - * @since 5.7 - */ -public final class OpenSaml4AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver { - - private final BaseOpenSamlAuthenticationRequestResolver delegate; - - /** - * Construct an {@link OpenSaml4AuthenticationRequestResolver} - * @param registrations a repository for relying and asserting party configuration - * @since 6.1 - */ - public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationRepository registrations) { - this.delegate = new BaseOpenSamlAuthenticationRequestResolver((request, id) -> { - if (id == null) { - return null; - } - return registrations.findByRegistrationId(id); - }, new OpenSaml4Template()); - } - - /** - * Construct a {@link OpenSaml4AuthenticationRequestResolver} - */ - public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.delegate = new BaseOpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver, - new OpenSaml4Template()); - } - - @Override - public T resolve(HttpServletRequest request) { - return this.delegate.resolve(request); - } - - /** - * Set a {@link Consumer} for modifying the OpenSAML {@link AuthnRequest} - * @param contextConsumer a consumer that accepts an {@link AuthnRequestContext} - */ - public void setAuthnRequestCustomizer(Consumer contextConsumer) { - Assert.notNull(contextConsumer, "contextConsumer cannot be null"); - this.delegate.setParametersConsumer( - (parameters) -> contextConsumer.accept(new AuthnRequestContext(parameters.getRequest(), - parameters.getRelyingPartyRegistration(), parameters.getAuthnRequest()))); - } - - /** - * Set the {@link RequestMatcher} to use for setting the - * {@link BaseOpenSamlAuthenticationRequestResolver#setRequestMatcher(RequestMatcher)} - * (RequestMatcher)} - * @param requestMatcher the {@link RequestMatcher} to identify authentication - * requests. - * @since 5.8 - */ - public void setRequestMatcher(RequestMatcher requestMatcher) { - Assert.notNull(requestMatcher, "requestMatcher cannot be null"); - this.delegate.setRequestMatcher(requestMatcher); - } - - /** - * Use this {@link Clock} for generating the issued {@link Instant} - * @param clock the {@link Clock} to use - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.delegate.setClock(clock); - } - - /** - * Use this {@link Converter} to compute the RelayState - * @param relayStateResolver the {@link Converter} to use - * @since 5.8 - */ - public void setRelayStateResolver(Converter relayStateResolver) { - Assert.notNull(relayStateResolver, "relayStateResolver cannot be null"); - this.delegate.setRelayStateResolver(relayStateResolver); - } - - public static final class AuthnRequestContext { - - private final HttpServletRequest request; - - private final RelyingPartyRegistration registration; - - private final AuthnRequest authnRequest; - - public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration, - AuthnRequest authnRequest) { - this.request = request; - this.registration = registration; - this.authnRequest = authnRequest; - } - - public HttpServletRequest getRequest() { - return this.request; - } - - public RelyingPartyRegistration getRelyingPartyRegistration() { - return this.registration; - } - - public AuthnRequest getAuthnRequest() { - return this.authnRequest; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4Template.java deleted file mode 100644 index 4801b81188..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java deleted file mode 100644 index 6e106327ea..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolver.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2004-present 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.time.Clock; -import java.time.Instant; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; -import org.opensaml.saml.saml2.core.LogoutRequest; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -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.RelyingPartyRegistrationResolver; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutRequestResolver} for resolving SAML 2.0 Logout Requests with - * OpenSAML 4 - * - * @author Josh Cummings - * @author Gerhard Haege - * @since 5.6 - */ -public final class OpenSaml4LogoutRequestResolver implements Saml2LogoutRequestResolver { - - private final BaseOpenSamlLogoutRequestResolver delegate; - - public OpenSaml4LogoutRequestResolver(RelyingPartyRegistrationRepository registrations) { - this((request, id) -> { - if (id == null) { - return null; - } - return registrations.findByRegistrationId(id); - }); - } - - /** - * Construct a {@link OpenSaml4LogoutRequestResolver} - */ - public OpenSaml4LogoutRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.delegate = new BaseOpenSamlLogoutRequestResolver(relyingPartyRegistrationResolver, - new OpenSaml4Template()); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) { - return this.delegate.resolve(request, authentication); - } - - /** - * Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} - * @param parametersConsumer a consumer that accepts an - * {@link LogoutRequestParameters} - */ - public void setParametersConsumer(Consumer parametersConsumer) { - Assert.notNull(parametersConsumer, "parametersConsumer cannot be null"); - this.delegate - .setParametersConsumer((parameters) -> parametersConsumer.accept(new LogoutRequestParameters(parameters))); - } - - /** - * Use this {@link Clock} for determining the issued {@link Instant} - * @param clock the {@link Clock} to use - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.delegate.setClock(clock); - } - - /** - * Use this {@link Converter} to compute the RelayState - * @param relayStateResolver the {@link Converter} to use - * @since 6.1 - */ - public void setRelayStateResolver(Converter relayStateResolver) { - Assert.notNull(relayStateResolver, "relayStateResolver cannot be null"); - this.delegate.setRelayStateResolver(relayStateResolver); - } - - public static final class LogoutRequestParameters { - - private final HttpServletRequest request; - - private final RelyingPartyRegistration registration; - - private final Authentication authentication; - - private final LogoutRequest logoutRequest; - - public LogoutRequestParameters(HttpServletRequest request, RelyingPartyRegistration registration, - Authentication authentication, LogoutRequest logoutRequest) { - this.request = request; - this.registration = registration; - this.authentication = authentication; - this.logoutRequest = logoutRequest; - } - - LogoutRequestParameters(BaseOpenSamlLogoutRequestResolver.LogoutRequestParameters parameters) { - this(parameters.getRequest(), parameters.getRelyingPartyRegistration(), parameters.getAuthentication(), - parameters.getLogoutRequest()); - } - - public HttpServletRequest getRequest() { - return this.request; - } - - public RelyingPartyRegistration getRelyingPartyRegistration() { - return this.registration; - } - - public Authentication getAuthentication() { - return this.authentication; - } - - public LogoutRequest getLogoutRequest() { - return this.logoutRequest; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolver.java deleted file mode 100644 index c93ad4ed55..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolver.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2004-present 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.springframework.security.core.Authentication; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -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.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * An OpenSAML-based implementation of - * {@link Saml2LogoutRequestValidatorParametersResolver} - */ -public final class OpenSaml4LogoutRequestValidatorParametersResolver - implements Saml2LogoutRequestValidatorParametersResolver { - - static { - OpenSamlInitializationService.initialize(); - } - - private final BaseOpenSamlLogoutRequestValidatorParametersResolver delegate; - - /** - * Constructs a {@link OpenSaml4LogoutRequestValidatorParametersResolver} - */ - public OpenSaml4LogoutRequestValidatorParametersResolver(RelyingPartyRegistrationRepository registrations) { - Assert.notNull(registrations, "relyingPartyRegistrationRepository cannot be null"); - this.delegate = new BaseOpenSamlLogoutRequestValidatorParametersResolver(new OpenSaml4Template(), - 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) { - return this.delegate.resolve(request, authentication); - } - - /** - * 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.delegate.setRequestMatcher(requestMatcher); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java deleted file mode 100644 index c1fffe557c..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolver.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2004-present 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.time.Clock; -import java.time.Instant; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; -import org.opensaml.saml.saml2.core.LogoutRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -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.RelyingPartyRegistrationResolver; -import org.springframework.util.Assert; - -/** - * A {@link Saml2LogoutResponseResolver} for resolving SAML 2.0 Logout Responses with - * OpenSAML 4 - * - * @author Josh Cummings - * @since 5.6 - */ -public final class OpenSaml4LogoutResponseResolver implements Saml2LogoutResponseResolver { - - private final BaseOpenSamlLogoutResponseResolver delegate; - - public OpenSaml4LogoutResponseResolver(RelyingPartyRegistrationRepository registrations) { - this.delegate = new BaseOpenSamlLogoutResponseResolver(registrations, (request, id) -> { - if (id == null) { - return null; - } - return registrations.findByRegistrationId(id); - }, new OpenSaml4Template()); - } - - /** - * Construct a {@link OpenSaml4LogoutResponseResolver} - */ - public OpenSaml4LogoutResponseResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) { - this.delegate = new BaseOpenSamlLogoutResponseResolver(null, relyingPartyRegistrationResolver, - new OpenSaml4Template()); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication) { - return this.delegate.resolve(request, authentication); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, - Saml2AuthenticationException exception) { - return this.delegate.resolve(request, authentication, exception); - } - - /** - * Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest} - * @param parametersConsumer a consumer that accepts an - * {@link OpenSaml4LogoutRequestResolver.LogoutRequestParameters} - */ - public void setParametersConsumer(Consumer parametersConsumer) { - Assert.notNull(parametersConsumer, "parametersConsumer cannot be null"); - this.delegate - .setParametersConsumer((parameters) -> parametersConsumer.accept(new LogoutResponseParameters(parameters))); - } - - /** - * Use this {@link Clock} for determining the issued {@link Instant} - * @param clock the {@link Clock} to use - */ - public void setClock(Clock clock) { - Assert.notNull(clock, "clock must not be null"); - this.delegate.setClock(clock); - } - - public static final class LogoutResponseParameters { - - private final HttpServletRequest request; - - private final RelyingPartyRegistration registration; - - private final Authentication authentication; - - private final LogoutRequest logoutRequest; - - public LogoutResponseParameters(HttpServletRequest request, RelyingPartyRegistration registration, - Authentication authentication, LogoutRequest logoutRequest) { - this.request = request; - this.registration = registration; - this.authentication = authentication; - this.logoutRequest = logoutRequest; - } - - LogoutResponseParameters(BaseOpenSamlLogoutResponseResolver.LogoutResponseParameters parameters) { - this(parameters.getRequest(), parameters.getRelyingPartyRegistration(), parameters.getAuthentication(), - parameters.getLogoutRequest()); - } - - public HttpServletRequest getRequest() { - return this.request; - } - - public RelyingPartyRegistration getRelyingPartyRegistration() { - return this.registration; - } - - public Authentication getAuthentication() { - return this.authentication; - } - - public LogoutRequest getLogoutRequest() { - return this.logoutRequest; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4Template.java b/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4Template.java deleted file mode 100644 index 03225a2f42..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4Template.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright 2004-present 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.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.resolver.CriteriaSet; -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.opensaml.core.criterion.EntityIdCriterion; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.XMLObjectBuilder; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.core.xml.io.UnmarshallerFactory; -import org.opensaml.core.xml.util.XMLObjectSupport; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.criterion.ProtocolCriterion; -import org.opensaml.saml.ext.saml2delrestrict.Delegate; -import org.opensaml.saml.ext.saml2delrestrict.DelegationRestrictionType; -import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.RequestAbstractType; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.StatusResponseType; -import org.opensaml.saml.saml2.core.Subject; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.encryption.Decrypter; -import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; -import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.SecurityException; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.credential.Credential; -import org.opensaml.security.credential.CredentialResolver; -import org.opensaml.security.credential.CredentialSupport; -import org.opensaml.security.credential.UsageType; -import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; -import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; -import org.opensaml.security.credential.impl.CollectionCredentialResolver; -import org.opensaml.security.criteria.UsageCriterion; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.SignatureSigningParametersResolver; -import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; -import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; -import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.DecryptionException; -import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; -import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; -import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration; -import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager; -import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; -import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; -import org.opensaml.xmlsec.signature.SignableXMLObject; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureConstants; -import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.util.Assert; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * For internal use only. Subject to breaking changes at any time. - */ -final class OpenSaml4Template implements OpenSamlOperations { - - private static final Log logger = LogFactory.getLog(OpenSaml4Template.class); - - @Override - public T build(QName elementName) { - XMLObjectBuilder builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName); - if (builder == null) { - throw new Saml2Exception("Unable to resolve Builder for " + elementName); - } - return (T) builder.buildObject(elementName); - } - - @Override - public T deserialize(String serialized) { - return deserialize(new ByteArrayInputStream(serialized.getBytes(StandardCharsets.UTF_8))); - } - - @Override - public T deserialize(InputStream serialized) { - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); - Element element = document.getDocumentElement(); - UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); - Unmarshaller unmarshaller = factory.getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - return (T) unmarshaller.unmarshall(element); - } - catch (Saml2Exception ex) { - throw ex; - } - catch (Exception ex) { - throw new Saml2Exception("Failed to deserialize payload", ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(XMLObject object) { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - try { - return serialize(marshaller.marshall(object)); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - @Override - public OpenSaml4SerializationConfigurer serialize(Element element) { - return new OpenSaml4SerializationConfigurer(element); - } - - @Override - public OpenSaml4SignatureConfigurer withSigningKeys(Collection credentials) { - return new OpenSaml4SignatureConfigurer(credentials); - } - - @Override - public OpenSaml4VerificationConfigurer withVerificationKeys(Collection credentials) { - return new OpenSaml4VerificationConfigurer(credentials); - } - - @Override - public OpenSaml4DecryptionConfigurer withDecryptionKeys(Collection credentials) { - return new OpenSaml4DecryptionConfigurer(credentials); - } - - OpenSaml4Template() { - - } - - static final class OpenSaml4SerializationConfigurer - implements SerializationConfigurer { - - private final Element element; - - boolean pretty; - - OpenSaml4SerializationConfigurer(Element element) { - this.element = element; - } - - @Override - public OpenSaml4SerializationConfigurer prettyPrint(boolean pretty) { - this.pretty = pretty; - return this; - } - - @Override - public String serialize() { - if (this.pretty) { - return SerializeSupport.prettyPrintXML(this.element); - } - return SerializeSupport.nodeToString(this.element); - } - - } - - static final class OpenSaml4SignatureConfigurer implements SignatureConfigurer { - - private final Collection credentials; - - private final Map components = new LinkedHashMap<>(); - - private List algs = List.of(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); - - OpenSaml4SignatureConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public OpenSaml4SignatureConfigurer algorithms(List algs) { - this.algs = algs; - return this; - } - - @Override - public O sign(O object) { - SignatureSigningParameters parameters = resolveSigningParameters(); - try { - SignatureSupport.signObject(object, parameters); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - return object; - } - - @Override - public Map sign(Map params) { - SignatureSigningParameters parameters = resolveSigningParameters(); - this.components.putAll(params); - Credential credential = parameters.getSigningCredential(); - String algorithmUri = parameters.getSignatureAlgorithm(); - this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry component : this.components.entrySet()) { - builder.queryParam(component.getKey(), - UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1)); - } - String queryString = builder.build(true).toString().substring(1); - try { - byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri, - queryString.getBytes(StandardCharsets.UTF_8)); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - this.components.put(Saml2ParameterNames.SIGNATURE, b64Signature); - } - catch (SecurityException ex) { - throw new Saml2Exception(ex); - } - return this.components; - } - - private SignatureSigningParameters resolveSigningParameters() { - List credentials = resolveSigningCredentials(); - List digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256); - String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; - SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver(); - BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration(); - signingConfiguration.setSigningCredentials(credentials); - signingConfiguration.setSignatureAlgorithms(this.algs); - signingConfiguration.setSignatureReferenceDigestMethods(digests); - signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization); - signingConfiguration.setKeyInfoGeneratorManager(buildSignatureKeyInfoGeneratorManager()); - CriteriaSet criteria = new CriteriaSet(new SignatureSigningConfigurationCriterion(signingConfiguration)); - try { - SignatureSigningParameters parameters = resolver.resolveSingle(criteria); - Assert.notNull(parameters, "Failed to resolve any signing credential"); - return parameters; - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private NamedKeyInfoGeneratorManager buildSignatureKeyInfoGeneratorManager() { - final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager(); - - namedManager.setUseDefaultManager(true); - final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager(); - - // Generator for X509Credentials - final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory(); - x509Factory.setEmitEntityCertificate(true); - x509Factory.setEmitEntityCertificateChain(true); - - defaultManager.registerFactory(x509Factory); - - return namedManager; - } - - private List resolveSigningCredentials() { - List credentials = new ArrayList<>(); - for (Saml2X509Credential x509Credential : this.credentials) { - X509Certificate certificate = x509Credential.getCertificate(); - PrivateKey privateKey = x509Credential.getPrivateKey(); - BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey); - credential.setUsageType(UsageType.SIGNING); - credentials.add(credential); - } - return credentials; - } - - } - - static final class OpenSaml4VerificationConfigurer implements VerificationConfigurer { - - private final Collection credentials; - - private String entityId; - - OpenSaml4VerificationConfigurer(Collection credentials) { - this.credentials = credentials; - } - - @Override - public VerificationConfigurer entityId(String entityId) { - this.entityId = entityId; - return this; - } - - private SignatureTrustEngine trustEngine(Collection keys) { - Set credentials = new HashSet<>(); - for (Saml2X509Credential key : keys) { - BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); - cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(this.entityId); - credentials.add(cred); - } - CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); - return new ExplicitKeySignatureTrustEngine(credentialsResolver, - DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); - } - - private CriteriaSet verificationCriteria(Issuer issuer) { - return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), - new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), - new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); - } - - @Override - public Collection verify(SignableXMLObject signable) { - if (signable instanceof StatusResponseType response) { - return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); - } - if (signable instanceof RequestAbstractType request) { - return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); - } - if (signable instanceof Assertion assertion) { - return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); - } - throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); - } - - private Collection verifySignature(String id, Issuer issuer, Signature signature) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(issuer); - Collection errors = new ArrayList<>(); - SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); - try { - profileValidator.validate(signature); - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - try { - if (!trustEngine.validate(signature, criteria)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + id + "]: ")); - } - - return errors; - } - - @Override - public Collection verify(RedirectParameters parameters) { - SignatureTrustEngine trustEngine = trustEngine(this.credentials); - CriteriaSet criteria = verificationCriteria(parameters.getIssuer()); - if (parameters.getAlgorithm() == null) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature algorithm for object [" + parameters.getId() + "]")); - } - if (!parameters.hasSignature()) { - return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Missing signature for object [" + parameters.getId() + "]")); - } - Collection errors = new ArrayList<>(); - String algorithmUri = parameters.getAlgorithm(); - try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]")); - } - } - catch (Exception ex) { - errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, - "Invalid signature for object [" + parameters.getId() + "]: ")); - } - return errors; - } - - } - - static final class OpenSaml4DecryptionConfigurer implements DecryptionConfigurer { - - private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( - Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), - new SimpleRetrievalMethodEncryptedKeyResolver())); - - private final Decrypter decrypter; - - OpenSaml4DecryptionConfigurer(Collection decryptionCredentials) { - this.decrypter = decrypter(decryptionCredentials); - } - - private static Decrypter decrypter(Collection decryptionCredentials) { - Collection credentials = new ArrayList<>(); - for (Saml2X509Credential key : decryptionCredentials) { - Credential cred = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey()); - credentials.add(cred); - } - KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); - Decrypter decrypter = new Decrypter(null, resolver, encryptedKeyResolver); - decrypter.setRootInNewDocument(true); - return decrypter; - } - - @Override - public void decrypt(XMLObject object) { - if (object instanceof Response response) { - decryptResponse(response); - return; - } - if (object instanceof Assertion assertion) { - decryptAssertion(assertion); - } - if (object instanceof LogoutRequest request) { - decryptLogoutRequest(request); - } - } - - /* - * The methods that follow are adapted from OpenSAML's {@link DecryptAssertions}, - * {@link DecryptNameIDs}, and {@link DecryptAttributes}. - * - *

The reason that these OpenSAML classes are not used directly is because they - * reference {@link javax.servlet.http.HttpServletRequest} which is a lower - * Servlet API version than what Spring Security SAML uses. - * - * If OpenSAML 5 updates to {@link jakarta.servlet.http.HttpServletRequest}, then - * this arrangement can be revisited. - */ - - private void decryptResponse(Response response) { - Collection decrypteds = new ArrayList<>(); - - int count = 0; - int size = response.getEncryptedAssertions().size(); - for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) { - logger.trace(String.format("Decrypting EncryptedAssertion (%d/%d) in Response [%s]", count, size, - response.getID())); - try { - Assertion decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - count++; - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - response.getAssertions().addAll(decrypteds); - - // Re-marshall the response so that any ID attributes within the decrypted - // Assertions - // will have their ID-ness re-established at the DOM level. - if (!decrypteds.isEmpty()) { - try { - XMLObjectSupport.marshall(response); - } - catch (final MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - } - - private void decryptAssertion(Assertion assertion) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - decryptAttributes(statement); - } - decryptSubject(assertion.getSubject()); - if (assertion.getConditions() != null) { - for (Condition c : assertion.getConditions().getConditions()) { - if (!(c instanceof DelegationRestrictionType delegation)) { - continue; - } - for (Delegate d : delegation.getDelegates()) { - if (d.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(d.getEncryptedID()); - if (decrypted != null) { - d.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - } - - private void decryptAttributes(AttributeStatement statement) { - Collection decrypteds = new ArrayList<>(); - for (EncryptedAttribute encrypted : statement.getEncryptedAttributes()) { - try { - Attribute decrypted = this.decrypter.decrypt(encrypted); - if (decrypted != null) { - decrypteds.add(decrypted); - } - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - statement.getAttributes().addAll(decrypteds); - } - - private void decryptSubject(Subject subject) { - if (subject != null) { - if (subject.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(subject.getEncryptedID()); - if (decrypted != null) { - subject.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - - for (final SubjectConfirmation sc : subject.getSubjectConfirmations()) { - if (sc.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(sc.getEncryptedID()); - if (decrypted != null) { - sc.setNameID(decrypted); - } - } - catch (final DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - } - } - - private void decryptLogoutRequest(LogoutRequest request) { - if (request.getEncryptedID() != null) { - try { - NameID decrypted = (NameID) this.decrypter.decrypt(request.getEncryptedID()); - if (decrypted != null) { - request.setNameID(decrypted); - } - } - catch (DecryptionException ex) { - throw new Saml2Exception(ex); - } - } - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java deleted file mode 100644 index d485697ddc..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationProviderTests.java +++ /dev/null @@ -1,1012 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import javax.xml.namespace.QName; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.schema.XSDateTime; -import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; -import org.opensaml.saml.common.SignableSAMLObject; -import org.opensaml.saml.common.assertion.ValidationContext; -import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.AttributeValue; -import org.opensaml.saml.saml2.core.Conditions; -import org.opensaml.saml.saml2.core.EncryptedAssertion; -import org.opensaml.saml.saml2.core.EncryptedAttribute; -import org.opensaml.saml.saml2.core.EncryptedID; -import org.opensaml.saml.saml2.core.NameID; -import org.opensaml.saml.saml2.core.OneTimeUse; -import org.opensaml.saml.saml2.core.ProxyRestriction; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.Status; -import org.opensaml.saml.saml2.core.StatusCode; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.core.SubjectConfirmationData; -import org.opensaml.saml.saml2.core.impl.AttributeBuilder; -import org.opensaml.saml.saml2.core.impl.EncryptedAssertionBuilder; -import org.opensaml.saml.saml2.core.impl.EncryptedIDBuilder; -import org.opensaml.saml.saml2.core.impl.NameIDBuilder; -import org.opensaml.saml.saml2.core.impl.ProxyRestrictionBuilder; -import org.opensaml.saml.saml2.core.impl.StatusBuilder; -import org.opensaml.saml.saml2.core.impl.StatusCodeBuilder; -import org.opensaml.xmlsec.encryption.impl.EncryptedDataBuilder; -import org.opensaml.xmlsec.signature.support.SignatureConstants; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.Authentication; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; -import org.springframework.security.saml2.provider.service.authentication.TestCustomOpenSaml4Objects.CustomOpenSamlObject; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.util.StringUtils; - -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.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OpenSaml4AuthenticationProvider} - * - * @author Filip Hanik - * @author Josh Cummings - */ -public class OpenSaml4AuthenticationProviderTests { - - private static String DESTINATION = "https://localhost/login/saml2/sso/idp-alias"; - - private static String RELYING_PARTY_ENTITY_ID = "https://localhost/saml2/service-provider-metadata/idp-alias"; - - private static String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - private OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - - private Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("name", - Collections.emptyMap()); - - private Saml2Authentication authentication = new Saml2Authentication(this.principal, "response", - Collections.emptyList()); - - @Test - public void supportsWhenSaml2AuthenticationTokenThenReturnTrue() { - assertThat(this.provider.supports(Saml2AuthenticationToken.class)) - .withFailMessage(OpenSaml4AuthenticationProvider.class + "should support " + Saml2AuthenticationToken.class) - .isTrue(); - } - - @Test - public void supportsWhenNotSaml2AuthenticationTokenThenReturnFalse() { - assertThat(!this.provider.supports(Authentication.class)) - .withFailMessage(OpenSaml4AuthenticationProvider.class + "should not support " + Authentication.class) - .isTrue(); - } - - @Test - public void authenticateWhenUnknownDataClassThenThrowAuthenticationException() { - Assertion assertion = (Assertion) XMLObjectProviderRegistrySupport.getBuilderFactory() - .getBuilder(Assertion.DEFAULT_ELEMENT_NAME) - .buildObject(Assertion.DEFAULT_ELEMENT_NAME); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider - .authenticate(new Saml2AuthenticationToken(verifying(registration()).build(), serialize(assertion)))) - .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); - } - - @Test - public void authenticateWhenXmlErrorThenThrowAuthenticationException() { - Saml2AuthenticationToken token = new Saml2AuthenticationToken(verifying(registration()).build(), "invalid xml"); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA)); - } - - @Test - public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() { - Response response = response(DESTINATION + "invalid", ASSERTING_PARTY_ENTITY_ID); - response.getAssertions().add(assertion()); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.INVALID_DESTINATION)); - } - - @Test - public void authenticateWhenNoAssertionsPresentThenThrowAuthenticationException() { - Saml2AuthenticationToken token = token(); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, "No assertions found in response.")); - } - - @Test - public void authenticateWhenInvalidSignatureOnAssertionThenThrowAuthenticationException() { - Response response = response(); - response.getAssertions().add(assertion()); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE)); - } - - @Test - public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() { - Response response = response(); - Assertion assertion = assertion(); - assertion.getSubject() - .getSubjectConfirmations() - .get(0) - .getSubjectConfirmationData() - .setNotOnOrAfter(Instant.now().minus(Duration.ofDays(3))); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.INVALID_ASSERTION)); - } - - @Test - public void authenticateWhenMissingSubjectThenThrowAuthenticationException() { - Response response = response(); - Assertion assertion = assertion(); - assertion.setSubject(null); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); - } - - @Test - public void authenticateWhenUsernameMissingThenThrowAuthenticationException() { - Response response = response(); - Assertion assertion = assertion(); - assertion.getSubject().getNameID().setValue(null); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.SUBJECT_NOT_FOUND)); - } - - @Test - public void authenticateWhenAssertionContainsValidationAddressThenItSucceeds() { - Response response = response(); - Assertion assertion = assertion(); - assertion.getSubject() - .getSubjectConfirmations() - .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - this.provider.authenticate(token); - } - - @Test - public void evaluateInResponseToSucceedsWhenInResponseToInResponseAndAssertionsMatchRequestID() { - Response response = response(); - response.setInResponseTo("SAML2"); - response.getAssertions().add(signed(assertion("SAML2"))); - response.getAssertions().add(signed(assertion("SAML2"))); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - this.provider.authenticate(token); - } - - @Test - public void evaluateInResponseToSucceedsWhenInResponseToInAssertionOnlyMatchRequestID() { - Response response = response(); - response.getAssertions().add(signed(assertion())); - response.getAssertions().add(signed(assertion("SAML2"))); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - this.provider.authenticate(token); - } - - @Test - public void evaluateInResponseToFailsWhenInResponseToInAssertionMismatchWithRequestID() { - Response response = response(); - response.setInResponseTo("SAML2"); - response.getAssertions().add(signed(assertion("SAML2"))); - response.getAssertions().add(signed(assertion("BAD"))); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .withStackTraceContaining("invalid_assertion"); - } - - @Test - public void evaluateInResponseToFailsWhenInResponseToInAssertionOnlyAndMismatchWithRequestID() { - Response response = response(); - response.getAssertions().add(signed(assertion())); - response.getAssertions().add(signed(assertion("BAD"))); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .withStackTraceContaining("invalid_assertion"); - } - - @Test - public void evaluateInResponseToFailsWhenInResponseInToResponseMismatchWithRequestID() { - Response response = response(); - response.setInResponseTo("BAD"); - response.getAssertions().add(signed(assertion("SAML2"))); - response.getAssertions().add(signed(assertion("SAML2"))); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .withStackTraceContaining("invalid_in_response_to"); - } - - @Test - public void evaluateInResponseToFailsWhenInResponseToInResponseButNoSavedRequest() { - Response response = response(); - response.setInResponseTo("BAD"); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .withStackTraceContaining("invalid_in_response_to"); - } - - @Test - public void evaluateInResponseToSucceedsWhenNoInResponseToInResponseOrAssertions() { - Response response = response(); - response.getAssertions().add(signed(assertion())); - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mockedStoredAuthenticationRequest("SAML2"); - Saml2AuthenticationToken token = token(response, verifying(registration()), mockAuthenticationRequest); - this.provider.authenticate(token); - } - - @Test - public void authenticateWhenAssertionContainsAttributesThenItSucceeds() { - Response response = response(); - Assertion assertion = assertion(); - List attributes = attributeStatements(); - assertion.getAttributeStatements().addAll(attributes); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - Authentication authentication = this.provider.authenticate(token); - Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); - Map expected = new LinkedHashMap<>(); - expected.put("email", Arrays.asList("john.doe@example.com", "doe.john@example.com")); - expected.put("name", Collections.singletonList("John Doe")); - expected.put("age", Collections.singletonList(21)); - expected.put("website", Collections.singletonList("https://johndoe.com/")); - expected.put("registered", Collections.singletonList(true)); - Instant registeredDate = Instant.parse("1970-01-01T00:00:00Z"); - expected.put("registeredDate", Collections.singletonList(registeredDate)); - expected.put("role", Arrays.asList("RoleOne", "RoleTwo")); // gh-11042 - assertThat((String) principal.getFirstAttribute("name")).isEqualTo("John Doe"); - assertThat(principal.getAttributes()).isEqualTo(expected); - assertThat(principal.getSessionIndexes()).contains("session-index"); - } - - // gh-11785 - @Test - public void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - ClassLoader loader = getClass().getClassLoader(); - mapper.registerModules(SecurityJackson2Modules.getModules(loader)); - Response response = response(); - Assertion assertion = assertion(); - List attributes = TestOpenSamlObjects.attributeStatements(); - assertion.getAttributeStatements().addAll(attributes); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - Authentication authentication = this.provider.authenticate(token); - String result = mapper.writeValueAsString(authentication); - mapper.readValue(result, Authentication.class); - } - - @Test - public void authenticateWhenAssertionContainsCustomAttributesThenItSucceeds() { - Response response = response(); - Assertion assertion = assertion(); - AttributeStatement attribute = TestOpenSamlObjects.customAttributeStatement("Address", - TestCustomOpenSaml4Objects.instance()); - assertion.getAttributeStatements().add(attribute); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - Authentication authentication = this.provider.authenticate(token); - Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); - CustomOpenSamlObject address = (CustomOpenSamlObject) principal.getAttribute("Address").get(0); - assertThat(address.getStreet()).isEqualTo("Test Street"); - assertThat(address.getStreetNumber()).isEqualTo("1"); - assertThat(address.getZIP()).isEqualTo("11111"); - assertThat(address.getCity()).isEqualTo("Test City"); - } - - @Test - public void authenticateWhenEncryptedAssertionWithoutSignatureThenItFails() { - Response response = response(); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE, "Did not decrypt response")); - } - - @Test - public void authenticateWhenEncryptedAssertionWithSignatureThenItSucceeds() { - Response response = response(); - Assertion assertion = TestOpenSamlObjects.signed(assertion(), - TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - this.provider.authenticate(token); - } - - // gh-16367 - @Test - public void authenticateWhenEncryptedAssertionWithSignatureThenEncryptedAssertionStillAvailable() { - Response response = response(); - Assertion assertion = TestOpenSamlObjects.signed(assertion(), - TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setResponseValidator((t) -> { - assertThat(t.getResponse().getEncryptedAssertions()).isNotEmpty(); - return Saml2ResponseValidatorResult.success(); - }); - provider.authenticate(token); - } - - @Test - public void authenticateWhenEncryptedAssertionWithResponseSignatureThenItSucceeds() { - Response response = response(); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - this.provider.authenticate(token); - } - - @Test - public void authenticateWhenEncryptedNameIdWithSignatureThenItSucceeds() { - Response response = response(); - Assertion assertion = assertion(); - NameID nameId = assertion.getSubject().getNameID(); - EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId, - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - assertion.getSubject().setNameID(null); - assertion.getSubject().setEncryptedID(encryptedID); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); - this.provider.authenticate(token); - } - - // gh-16367 - @Test - public void authenticateWhenEncryptedNameIdWithSignatureThenEncryptedNameIdStillAvailable() { - Response response = response(); - Assertion assertion = assertion(); - NameID nameId = assertion.getSubject().getNameID(); - EncryptedID encryptedID = TestOpenSamlObjects.encrypted(nameId, - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - assertion.getSubject().setNameID(null); - assertion.getSubject().setEncryptedID(encryptedID); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, decrypting(verifying(registration()))); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setAssertionValidator((t) -> { - assertThat(t.getAssertion().getSubject().getEncryptedID()).isNotNull(); - return Saml2ResponseValidatorResult.success(); - }); - provider.authenticate(token); - } - - @Test - public void authenticateWhenEncryptedAttributeThenDecrypts() { - Response response = response(); - Assertion assertion = assertion(); - EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); - statement.getEncryptedAttributes().add(attribute); - assertion.getAttributeStatements().add(statement); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); - Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); - assertThat(principal.getAttribute("name")).containsExactly("value"); - } - - // gh-16367 - @Test - public void authenticateWhenEncryptedAttributeThenEncryptedAttributesStillAvailable() { - Response response = response(); - Assertion assertion = assertion(); - EncryptedAttribute attribute = TestOpenSamlObjects.encrypted("name", "value", - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - AttributeStatement statement = build(AttributeStatement.DEFAULT_ELEMENT_NAME); - statement.getEncryptedAttributes().add(attribute); - assertion.getAttributeStatements().add(statement); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setAssertionValidator((t) -> { - assertThat(t.getAssertion().getAttributeStatements().get(0).getEncryptedAttributes()).isNotEmpty(); - return Saml2ResponseValidatorResult.success(); - }); - provider.authenticate(token); - } - - @Test - public void authenticateWhenDecryptionKeysAreMissingThenThrowAuthenticationException() { - Response response = response(); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); - } - - @Test - public void authenticateWhenDecryptionKeysAreWrongThenThrowAuthenticationException() { - Response response = response(); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(), - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), registration() - .decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential()))); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.DECRYPTION_ERROR, "Failed to decrypt EncryptedData")); - } - - @Test - public void authenticateWhenAuthenticationHasDetailsThenSucceeds() { - Response response = response(); - Assertion assertion = assertion(); - assertion.getSubject() - .getSubjectConfirmations() - .forEach((sc) -> sc.getSubjectConfirmationData().setAddress("10.10.10.10")); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - token.setDetails("some-details"); - Authentication authentication = this.provider.authenticate(token); - assertThat(authentication.getDetails()).isEqualTo("some-details"); - } - - @Test - public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { - Response response = response(); - Assertion assertion = TestOpenSamlObjects.signed(assertion(), - TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID); - EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion, - TestSaml2X509Credentials.assertingPartyEncryptingCredential()); - response.getEncryptedAssertions().add(encryptedAssertion); - Saml2AuthenticationToken token = token(signed(response), decrypting(verifying(registration()))); - Saml2Authentication authentication = (Saml2Authentication) this.provider.authenticate(token); - // the following code will throw an exception if authentication isn't serializable - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); - objectOutputStream.writeObject(authentication); - objectOutputStream.flush(); - } - - @Test - public void createDefaultAssertionValidatorWhenAssertionThenValidates() { - Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); - Assertion assertion = response.getAssertions().get(0); - OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken( - assertion, token()); - assertThat( - OpenSaml4AuthenticationProvider.createDefaultAssertionValidator().convert(assertionToken).hasErrors()) - .isFalse(); - } - - @Test - public void authenticateWhenDelegatingToDefaultAssertionValidatorThenUses() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - // @formatter:off - provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider - .createDefaultAssertionValidator((token) -> new ValidationContext()) - .convert(assertionToken) - .concat(new Saml2Error("wrong error", "wrong error")) - ); - // @formatter:on - Response response = response(); - Assertion assertion = assertion(); - OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME); - assertion.getConditions().getConditions().add(oneTimeUse); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - // @formatter:off - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) - .satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_ASSERTION)); - // @formatter:on - } - - // gh-11675 - @Test - public void authenticateWhenUsingCustomAssertionValidatorThenUses() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - Consumer> validationParameters = mock(Consumer.class); - // @formatter:off - provider.setAssertionValidator(OpenSaml4AuthenticationProvider - .createDefaultAssertionValidatorWithParameters(validationParameters)); - // @formatter:on - Response response = response(); - Assertion assertion = assertion(); - OneTimeUse oneTimeUse = build(OneTimeUse.DEFAULT_ELEMENT_NAME); - assertion.getConditions().getConditions().add(oneTimeUse); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - provider.authenticate(token); - verify(validationParameters).accept(any()); - } - - @Test - public void authenticateWhenCustomAssertionValidatorThenUses() { - Converter validator = mock( - Converter.class); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - // @formatter:off - provider.setAssertionValidator((assertionToken) -> OpenSaml4AuthenticationProvider.createDefaultAssertionValidator() - .convert(assertionToken) - .concat(validator.convert(assertionToken)) - ); - // @formatter:on - Response response = response(); - Assertion assertion = assertion(); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - given(validator.convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class))) - .willReturn(Saml2ResponseValidatorResult.success()); - provider.authenticate(token); - verify(validator).convert(any(OpenSaml4AuthenticationProvider.AssertionToken.class)); - } - - @Test - public void authenticateWhenDefaultConditionValidatorNotUsedThenSignatureStillChecked() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setAssertionValidator((assertionToken) -> Saml2ResponseValidatorResult.success()); - Response response = response(); - Assertion assertion = assertion(); - TestOpenSamlObjects.signed(assertion, TestSaml2X509Credentials.relyingPartyDecryptingCredential(), - RELYING_PARTY_ENTITY_ID); // broken - // signature - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - // @formatter:off - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> provider.authenticate(token)) - .satisfies((error) -> assertThat(error.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_SIGNATURE)); - // @formatter:on - } - - @Test - public void authenticateWhenValidationContextCustomizedThenUsers() { - Map parameters = new HashMap<>(); - parameters.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton("blah")); - ValidationContext context = mock(ValidationContext.class); - given(context.getStaticParameters()).willReturn(parameters); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setAssertionValidator( - OpenSaml4AuthenticationProvider.createDefaultAssertionValidator((assertionToken) -> context)); - Response response = response(); - Assertion assertion = assertion(); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - // @formatter:off - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> provider.authenticate(token)).isInstanceOf(Saml2AuthenticationException.class) - .satisfies((error) -> assertThat(error).hasMessageContaining("Invalid assertion")); - // @formatter:on - verify(context, atLeastOnce()).getStaticParameters(); - } - - @Test - public void authenticateWithSHA1SignatureThenItSucceeds() throws Exception { - Response response = response(); - Assertion assertion = TestOpenSamlObjects.signed(assertion(), - TestSaml2X509Credentials.assertingPartySigningCredential(), RELYING_PARTY_ENTITY_ID, - SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(response, verifying(registration())); - this.provider.authenticate(token); - } - - @Test - public void setAssertionValidatorWhenNullThenIllegalArgument() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.provider.setAssertionValidator(null)); - // @formatter:on - } - - @Test - public void createDefaultResponseAuthenticationConverterWhenResponseThenConverts() { - Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); - Saml2AuthenticationToken token = token(response, verifying(registration())); - ResponseToken responseToken = new ResponseToken(response, token); - Saml2Authentication authentication = OpenSaml4AuthenticationProvider - .createDefaultResponseAuthenticationConverter() - .convert(responseToken); - assertThat(authentication.getName()).isEqualTo("test@saml.user"); - } - - @Test - public void authenticateWhenResponseAuthenticationConverterConfiguredThenUses() { - Converter authenticationConverter = mock(Converter.class); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setResponseAuthenticationConverter(authenticationConverter); - Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); - Saml2AuthenticationToken token = token(response, verifying(registration())); - provider.authenticate(token); - verify(authenticationConverter).convert(any()); - } - - @Test - public void setResponseAuthenticationConverterWhenNullThenIllegalArgument() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.provider.setResponseAuthenticationConverter(null)); - // @formatter:on - } - - @Test - public void setResponseElementsDecrypterWhenNullThenIllegalArgument() { - assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseElementsDecrypter(null)); - } - - @Test - public void setAssertionElementsDecrypterWhenNullThenIllegalArgument() { - assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setAssertionElementsDecrypter(null)); - } - - @Test - public void authenticateWhenCustomResponseElementsDecrypterThenDecryptsResponse() { - Response response = response(); - Assertion assertion = assertion(); - response.getEncryptedAssertions().add(new EncryptedAssertionBuilder().buildObject()); - TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.assertingPartySigningCredential(), - RELYING_PARTY_ENTITY_ID); - Saml2AuthenticationToken token = token(response, verifying(registration())); - this.provider - .setResponseElementsDecrypter((tuple) -> tuple.getResponse().getAssertions().add(signed(assertion))); - Authentication authentication = this.provider.authenticate(token); - assertThat(authentication.getName()).isEqualTo("test@saml.user"); - } - - @Test - public void authenticateWhenCustomAssertionElementsDecrypterThenDecryptsAssertion() { - Response response = response(); - Assertion assertion = assertion(); - EncryptedID id = new EncryptedIDBuilder().buildObject(); - id.setEncryptedData(new EncryptedDataBuilder().buildObject()); - assertion.getSubject().setEncryptedID(id); - response.getAssertions().add(signed(assertion)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - this.provider.setAssertionElementsDecrypter((tuple) -> { - NameID name = new NameIDBuilder().buildObject(); - name.setValue("decrypted name"); - tuple.getAssertion().getSubject().setNameID(name); - }); - Authentication authentication = this.provider.authenticate(token); - assertThat(authentication.getName()).isEqualTo("decrypted name"); - } - - @Test - public void authenticateWhenResponseStatusIsNotSuccessThenFails() { - Response response = TestOpenSamlObjects - .signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.status(StatusCode.AUTHN_FAILED))); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class) - .isThrownBy(() -> this.provider.authenticate(token)) - .satisfies(errorOf(Saml2ErrorCodes.INVALID_RESPONSE, "Invalid status")); - } - - @Test - public void authenticateWhenResponseStatusIsSuccessThenSucceeds() { - Response response = TestOpenSamlObjects - .signedResponseWithOneAssertion((r) -> r.setStatus(TestOpenSamlObjects.successStatus())); - Saml2AuthenticationToken token = token(response, verifying(registration())); - Authentication authentication = this.provider.authenticate(token); - assertThat(authentication.getName()).isEqualTo("test@saml.user"); - } - - @Test - public void setResponseValidatorWhenNullThenIllegalArgument() { - assertThatIllegalArgumentException().isThrownBy(() -> this.provider.setResponseValidator(null)); - } - - @Test - public void authenticateWhenCustomResponseValidatorThenUses() { - Converter validator = mock( - Converter.class); - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - // @formatter:off - provider.setResponseValidator((responseToken) -> OpenSaml4AuthenticationProvider.createDefaultResponseValidator() - .convert(responseToken) - .concat(validator.convert(responseToken)) - ); - // @formatter:on - Response response = response(); - Assertion assertion = assertion(); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - given(validator.convert(any(OpenSaml4AuthenticationProvider.ResponseToken.class))) - .willReturn(Saml2ResponseValidatorResult.success()); - provider.authenticate(token); - verify(validator).convert(any(OpenSaml4AuthenticationProvider.ResponseToken.class)); - } - - @Test - public void authenticateWhenResponseStatusIsNotSuccessThenOnlyReturnParentStatusCodes() { - Saml2AuthenticationToken token = TestSaml2AuthenticationTokens.token(); - - Status parentStatus = new StatusBuilder().buildObject(); - StatusCode parentStatusCode = new StatusCodeBuilder().buildObject(); - parentStatusCode.setValue(StatusCode.AUTHN_FAILED); - StatusCode childStatusCode = new StatusCodeBuilder().buildObject(); - childStatusCode.setValue(StatusCode.NO_PASSIVE); - parentStatusCode.setStatusCode(childStatusCode); - parentStatus.setStatusCode(parentStatusCode); - - Response response = TestOpenSamlObjects.response(); - response.setStatus(parentStatus); - response.setIssuer(TestOpenSamlObjects.issuer("mockedIssuer")); - - Converter validator = OpenSaml4AuthenticationProvider - .createDefaultResponseValidator(); - Saml2ResponseValidatorResult result = validator.convert(new ResponseToken(response, token)); - - String expectedErrorMessage = String.format("Invalid status [%s] for SAML response", - parentStatusCode.getValue()); - assertThat( - result.getErrors().stream().anyMatch((error) -> error.getDescription().contains(expectedErrorMessage))) - .isTrue(); - assertThat(result.getErrors() - .stream() - .noneMatch((error) -> error.getDescription().contains(childStatusCode.getValue()))).isTrue(); - } - - @Test - public void authenticateWhenResponseStatusIsNotSuccessThenReturnParentAndChildStatusCode() { - Saml2AuthenticationToken token = TestSaml2AuthenticationTokens.token(); - Status parentStatus = new StatusBuilder().buildObject(); - StatusCode parentStatusCode = new StatusCodeBuilder().buildObject(); - parentStatusCode.setValue(StatusCode.REQUESTER); - StatusCode childStatusCode = new StatusCodeBuilder().buildObject(); - childStatusCode.setValue(StatusCode.NO_PASSIVE); - parentStatusCode.setStatusCode(childStatusCode); - parentStatus.setStatusCode(parentStatusCode); - - Response response = TestOpenSamlObjects.response(); - response.setStatus(parentStatus); - response.setIssuer(TestOpenSamlObjects.issuer("mockedIssuer")); - - Converter validator = OpenSaml4AuthenticationProvider - .createDefaultResponseValidator(); - Saml2ResponseValidatorResult result = validator.convert(new ResponseToken(response, token)); - - String expectedParentErrorMessage = String.format("Invalid status [%s] for SAML response", - parentStatusCode.getValue()); - String expectedChildErrorMessage = String.format("Invalid status [%s] for SAML response", - childStatusCode.getValue()); - assertThat(result.getErrors() - .stream() - .anyMatch((error) -> error.getDescription().contains(expectedParentErrorMessage))).isTrue(); - assertThat(result.getErrors() - .stream() - .anyMatch((error) -> error.getDescription().contains(expectedChildErrorMessage))).isTrue(); - } - - @Test - public void authenticateWhenAssertionIssuerNotValidThenFailsWithInvalidIssuer() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - Response response = response(); - Assertion assertion = assertion(); - assertion.setIssuer(TestOpenSamlObjects.issuer("https://invalid.idp.test/saml2/idp")); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token)) - .withMessageContaining("did not match any valid issuers"); - } - - // gh-14931 - @Test - public void authenticateWhenAssertionHasProxyRestrictionThenParses() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - Response response = response(); - Assertion assertion = assertion(); - ProxyRestriction condition = new ProxyRestrictionBuilder().buildObject(); - assertion.getConditions().getConditions().add(condition); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - provider.authenticate(token); - } - - // gh-15022 - @Test - public void authenticateWhenClockSkewThenVerifiesSignature() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - provider.setAssertionValidator(OpenSaml4AuthenticationProvider.createDefaultAssertionValidatorWithParameters( - (params) -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(10)))); - Response response = response(); - Assertion assertion = assertion(); - assertion.setIssueInstant(Instant.now().plus(Duration.ofMinutes(9))); - response.getAssertions().add(assertion); - Saml2AuthenticationToken token = token(signed(response), verifying(registration())); - provider.authenticate(token); - } - - // gh-16989 - @Test - public void authenticateWhenNullIssuerThenNoNullPointer() { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - Response response = TestOpenSamlObjects.signedResponseWithOneAssertion((r) -> r.setIssuer(null)); - Saml2AuthenticationToken token = token(response, verifying(registration())); - assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token)); - } - - private T build(QName qName) { - return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); - } - - private String serialize(XMLObject object) { - return this.saml.serialize(object).serialize(); - } - - private Consumer errorOf(String errorCode) { - return errorOf(errorCode, null); - } - - private Consumer errorOf(String errorCode, String description) { - return (ex) -> { - assertThat(ex.getSaml2Error().getErrorCode()).isEqualTo(errorCode); - if (StringUtils.hasText(description)) { - assertThat(ex.getSaml2Error().getDescription()).contains(description); - } - }; - } - - private Response response() { - Response response = TestOpenSamlObjects.response(); - response.setIssueInstant(Instant.now()); - return response; - } - - private Response response(String destination, String issuerEntityId) { - Response response = TestOpenSamlObjects.response(destination, issuerEntityId); - response.setIssueInstant(Instant.now()); - return response; - } - - private Assertion assertion(String inResponseTo) { - Assertion assertion = TestOpenSamlObjects.assertion(); - assertion.setIssueInstant(Instant.now()); - for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { - SubjectConfirmationData data = confirmation.getSubjectConfirmationData(); - data.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); - data.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); - if (StringUtils.hasText(inResponseTo)) { - data.setInResponseTo(inResponseTo); - } - } - Conditions conditions = assertion.getConditions(); - conditions.setNotBefore(Instant.now().minus(Duration.ofMillis(5 * 60 * 1000))); - conditions.setNotOnOrAfter(Instant.now().plus(Duration.ofMillis(5 * 60 * 1000))); - return assertion; - } - - private Assertion assertion() { - return assertion(null); - } - - private T signed(T toSign) { - TestOpenSamlObjects.signed(toSign, TestSaml2X509Credentials.assertingPartySigningCredential(), - RELYING_PARTY_ENTITY_ID); - return toSign; - } - - private List attributeStatements() { - List attributeStatements = TestOpenSamlObjects.attributeStatements(); - AttributeBuilder attributeBuilder = new AttributeBuilder(); - Attribute registeredDateAttr = attributeBuilder.buildObject(); - registeredDateAttr.setName("registeredDate"); - XSDateTime registeredDate = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, - XSDateTime.TYPE_NAME); - registeredDate.setValue(Instant.parse("1970-01-01T00:00:00Z")); - registeredDateAttr.getAttributeValues().add(registeredDate); - attributeStatements.iterator().next().getAttributes().add(registeredDateAttr); - return attributeStatements; - } - - private Saml2AuthenticationToken token() { - Response response = response(); - RelyingPartyRegistration registration = verifying(registration()).build(); - return new Saml2AuthenticationToken(registration, serialize(response)); - } - - private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration) { - return new Saml2AuthenticationToken(registration.build(), serialize(response)); - } - - private Saml2AuthenticationToken token(Response response, RelyingPartyRegistration.Builder registration, - AbstractSaml2AuthenticationRequest authenticationRequest) { - return new Saml2AuthenticationToken(registration.build(), serialize(response), authenticationRequest); - } - - private AbstractSaml2AuthenticationRequest mockedStoredAuthenticationRequest(String requestId) { - AbstractSaml2AuthenticationRequest mockAuthenticationRequest = mock(AbstractSaml2AuthenticationRequest.class); - given(mockAuthenticationRequest.getId()).willReturn(requestId); - return mockAuthenticationRequest; - } - - private RelyingPartyRegistration.Builder registration() { - return TestRelyingPartyRegistrations.noCredentials() - .entityId(RELYING_PARTY_ENTITY_ID) - .assertionConsumerServiceLocation(DESTINATION) - .assertingPartyMetadata((party) -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); - } - - private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { - return builder.assertingPartyMetadata((party) -> party - .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); - } - - private RelyingPartyRegistration.Builder decrypting(RelyingPartyRegistration.Builder builder) { - return builder - .decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml4Objects.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml4Objects.java deleted file mode 100644 index eae5896ba8..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml4Objects.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.util.Collections; -import java.util.List; - -import javax.xml.namespace.QName; - -import net.shibboleth.utilities.java.support.xml.ElementSupport; -import org.opensaml.core.xml.AbstractXMLObject; -import org.opensaml.core.xml.AbstractXMLObjectBuilder; -import org.opensaml.core.xml.ElementExtensibleXMLObject; -import org.opensaml.core.xml.Namespace; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.AbstractXMLObjectMarshaller; -import org.opensaml.core.xml.io.AbstractXMLObjectUnmarshaller; -import org.opensaml.core.xml.io.UnmarshallingException; -import org.opensaml.core.xml.schema.XSAny; -import org.opensaml.core.xml.schema.impl.XSAnyBuilder; -import org.opensaml.core.xml.util.IndexedXMLObjectChildrenList; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.saml2.core.AttributeValue; -import org.w3c.dom.Element; - -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.security.saml2.core.OpenSamlInitializationService; - -public final class TestCustomOpenSaml4Objects { - - static { - OpenSamlInitializationService.initialize(); - XMLObjectProviderRegistrySupport.getMarshallerFactory() - .registerMarshaller(CustomOpenSamlObject.TYPE_NAME, - new TestCustomOpenSaml4Objects.CustomSamlObjectMarshaller()); - XMLObjectProviderRegistrySupport.getUnmarshallerFactory() - .registerUnmarshaller(CustomOpenSamlObject.TYPE_NAME, - new TestCustomOpenSaml4Objects.CustomSamlObjectUnmarshaller()); - } - - public static CustomOpenSamlObject instance() { - CustomOpenSamlObject samlObject = new TestCustomOpenSaml4Objects.CustomSamlObjectBuilder() - .buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, CustomOpenSamlObject.TYPE_NAME); - XSAny street = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "Street", - CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); - street.setTextContent("Test Street"); - samlObject.getUnknownXMLObjects().add(street); - XSAny streetNumber = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "Number", - CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); - streetNumber.setTextContent("1"); - samlObject.getUnknownXMLObjects().add(streetNumber); - XSAny zip = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "ZIP", - CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); - zip.setTextContent("11111"); - samlObject.getUnknownXMLObjects().add(zip); - XSAny city = new XSAnyBuilder().buildObject(CustomOpenSamlObject.CUSTOM_NS, "City", - CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); - city.setTextContent("Test City"); - samlObject.getUnknownXMLObjects().add(city); - return samlObject; - } - - private TestCustomOpenSaml4Objects() { - - } - - public interface CustomOpenSamlObject extends ElementExtensibleXMLObject { - - String TYPE_LOCAL_NAME = "CustomType"; - - String TYPE_CUSTOM_PREFIX = "custom"; - - String CUSTOM_NS = "https://custom.com/schema/custom"; - - /** QName of the CustomType type. */ - QName TYPE_NAME = new QName(CUSTOM_NS, TYPE_LOCAL_NAME, TYPE_CUSTOM_PREFIX); - - String getStreet(); - - String getStreetNumber(); - - String getZIP(); - - String getCity(); - - } - - public static class CustomOpenSamlObjectImpl extends AbstractXMLObject implements CustomOpenSamlObject { - - @NonNull - private IndexedXMLObjectChildrenList unknownXMLObjects; - - /** - * Constructor. - * @param namespaceURI the namespace the element is in - * @param elementLocalName the local name of the XML element this Object - * represents - * @param namespacePrefix the prefix for the given namespace - */ - protected CustomOpenSamlObjectImpl(@Nullable String namespaceURI, @NonNull String elementLocalName, - @Nullable String namespacePrefix) { - super(namespaceURI, elementLocalName, namespacePrefix); - super.getNamespaceManager().registerNamespaceDeclaration(new Namespace(CUSTOM_NS, TYPE_CUSTOM_PREFIX)); - this.unknownXMLObjects = new IndexedXMLObjectChildrenList<>(this); - } - - @NonNull - @Override - public List getUnknownXMLObjects() { - return this.unknownXMLObjects; - } - - @NonNull - @Override - public List getUnknownXMLObjects(@NonNull QName typeOrName) { - return (List) this.unknownXMLObjects.subList(typeOrName); - } - - @Nullable - @Override - public List getOrderedChildren() { - return Collections.unmodifiableList(this.unknownXMLObjects); - } - - @Override - public String getStreet() { - return ((XSAny) getOrderedChildren().get(0)).getTextContent(); - } - - @Override - public String getStreetNumber() { - return ((XSAny) getOrderedChildren().get(1)).getTextContent(); - } - - @Override - public String getZIP() { - return ((XSAny) getOrderedChildren().get(2)).getTextContent(); - } - - @Override - public String getCity() { - return ((XSAny) getOrderedChildren().get(3)).getTextContent(); - } - - } - - public static class CustomSamlObjectBuilder extends AbstractXMLObjectBuilder { - - @NonNull - @Override - public CustomOpenSamlObject buildObject(@Nullable String namespaceURI, @NonNull String localName, - @Nullable String namespacePrefix) { - return new CustomOpenSamlObjectImpl(namespaceURI, localName, namespacePrefix); - } - - } - - public static class CustomSamlObjectMarshaller extends AbstractXMLObjectMarshaller { - - public CustomSamlObjectMarshaller() { - super(); - } - - @Override - protected void marshallElementContent(@NonNull XMLObject xmlObject, @NonNull Element domElement) { - final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) xmlObject; - - for (XMLObject object : customSamlObject.getOrderedChildren()) { - ElementSupport.appendChildElement(domElement, object.getDOM()); - } - } - - } - - public static class CustomSamlObjectUnmarshaller extends AbstractXMLObjectUnmarshaller { - - public CustomSamlObjectUnmarshaller() { - super(); - } - - @Override - protected void processChildElement(@NonNull XMLObject parentXMLObject, @NonNull XMLObject childXMLObject) - throws UnmarshallingException { - final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) parentXMLObject; - customSamlObject.getUnknownXMLObjects().add(childXMLObject); - } - - @NonNull - @Override - protected XMLObject buildXMLObject(@NonNull Element domElement) { - return new CustomOpenSamlObjectImpl(SAMLConstants.SAML20_NS, AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME, - CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java deleted file mode 100644 index 77fd25cbef..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutRequestValidatorTests.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2004-present 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 OpenSaml4LogoutRequestValidator} - * - * @author Josh Cummings - */ -public class OpenSaml4LogoutRequestValidatorTests { - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - private final OpenSaml4LogoutRequestValidator validator = new OpenSaml4LogoutRequestValidator(); - - @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.validator.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.validator.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.validator.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.validator.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.validator.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.validator.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.validator.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.validator.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/OpenSaml4LogoutResponseValidatorTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java deleted file mode 100644 index ed1dca16a7..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml4LogoutResponseValidatorTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2004-present 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 OpenSaml4LogoutResponseValidator} - * - * @author Josh Cummings - */ -public class OpenSaml4LogoutResponseValidatorTests { - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - private final OpenSaml4LogoutResponseValidator manager = new OpenSaml4LogoutResponseValidator(); - - @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/OpenSaml4MetadataResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4MetadataResolverTests.java deleted file mode 100644 index 86e4b950d8..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml4MetadataResolverTests.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2004-present 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 OpenSaml4MetadataResolver} - */ -public class OpenSaml4MetadataResolverTests { - - @Test - public void resolveWhenRelyingPartyThenMetadataMatches() { - RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() - .assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT) - .build(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.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(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - OpenSaml4MetadataResolver.setSignMetadata(true); - String metadata = OpenSaml4MetadataResolver.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(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.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(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); - assertThat(metadata).contains("format"); - } - - @Test - public void resolveWhenRelyingPartyNoLogoutThenMetadataMatches() { - RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() - .singleLogoutServiceLocation(null) - .nameIdFormat("format") - .build(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); - assertThat(metadata).doesNotContain("ResponseLocation"); - } - - @Test - public void resolveWhenEntityDescriptorCustomizerThenUses() { - RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() - .entityId("originalEntityId") - .build(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - OpenSaml4MetadataResolver.setEntityDescriptorCustomizer( - (parameters) -> parameters.getEntityDescriptor().setEntityID("overriddenEntityId")); - String metadata = OpenSaml4MetadataResolver.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(); - OpenSaml4MetadataResolver OpenSaml4MetadataResolver = new OpenSaml4MetadataResolver(); - OpenSaml4MetadataResolver.setSignMetadata(true); - String metadata = OpenSaml4MetadataResolver.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/registration/OpenSaml4AssertingPartyMetadataRepositoryTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4AssertingPartyMetadataRepositoryTests.java deleted file mode 100644 index d4f9e2fba8..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/registration/OpenSaml4AssertingPartyMetadataRepositoryTests.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2004-present 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.registration; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import net.shibboleth.utilities.java.support.xml.SerializeSupport; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.metadata.IterableMetadataSource; -import org.opensaml.saml.metadata.resolver.MetadataResolver; -import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver; -import org.opensaml.saml.metadata.resolver.index.impl.RoleMetadataIndex; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.security.credential.Credential; -import org.w3c.dom.Element; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.withSettings; - -/** - * Tests for {@link BaseOpenSamlAssertingPartyMetadataRepository} - */ -public class OpenSaml4AssertingPartyMetadataRepositoryTests { - - private static MetadataDispatcher dispatcher = new MetadataDispatcher() - .addResponse("/entity.xml", readFile("test-metadata.xml")) - .addResponse("/entities.xml", readFile("test-entitiesdescriptor.xml")); - - private static MockWebServer web = new MockWebServer(); - - private static String readFile(String fileName) { - try { - ClassPathResource resource = new ClassPathResource(fileName); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { - return reader.lines().collect(Collectors.joining()); - } - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - - @BeforeAll - public static void start() throws Exception { - web.setDispatcher(dispatcher); - web.start(); - } - - @AfterAll - public static void shutdown() throws Exception { - web.shutdown(); - } - - @Test - public void withMetadataUrlLocationWhenResolvableThenFindByEntityIdReturns() throws Exception { - AssertingPartyMetadataRepository parties = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation(web.url("/entity.xml").toString()) - .build(); - AssertingPartyMetadata party = parties.findByEntityId("https://idp.example.com/idp/shibboleth"); - assertThat(party.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); - assertThat(party.getSingleSignOnServiceLocation()) - .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); - assertThat(party.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(party.getVerificationX509Credentials()).hasSize(1); - assertThat(party.getEncryptionX509Credentials()).hasSize(1); - } - - @Test - public void withMetadataUrlLocationnWhenResolvableThenIteratorReturns() throws Exception { - List parties = new ArrayList<>(); - OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation(web.url("/entities.xml").toString()) - .build() - .iterator() - .forEachRemaining(parties::add); - assertThat(parties).hasSize(2); - assertThat(parties).extracting(AssertingPartyMetadata::getEntityId) - .contains("https://ap.example.org/idp/shibboleth", "https://idp.example.com/idp/shibboleth"); - } - - @Test - public void withMetadataUrlLocationWhenUnresolvableThenThrowsSaml2Exception() throws Exception { - try (MockWebServer server = new MockWebServer()) { - String url = server.url("/").toString(); - server.shutdown(); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation(url).build()); - } - } - - @Test - public void withMetadataUrlLocationWhenMalformedResponseThenSaml2Exception() throws Exception { - dispatcher.addResponse("/malformed", "malformed"); - String url = web.url("/malformed").toString(); - assertThatExceptionOfType(Saml2Exception.class) - .isThrownBy(() -> OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation(url).build()); - } - - @Test - public void fromMetadataFileLocationWhenResolvableThenFindByEntityIdReturns() { - File file = new File("src/test/resources/test-metadata.xml"); - AssertingPartyMetadata party = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation("file:" + file.getAbsolutePath()) - .build() - .findByEntityId("https://idp.example.com/idp/shibboleth"); - assertThat(party.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); - assertThat(party.getSingleSignOnServiceLocation()) - .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); - assertThat(party.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(party.getVerificationX509Credentials()).hasSize(1); - assertThat(party.getEncryptionX509Credentials()).hasSize(1); - } - - @Test - public void fromMetadataFileLocationWhenResolvableThenIteratorReturns() { - File file = new File("src/test/resources/test-entitiesdescriptor.xml"); - Collection parties = new ArrayList<>(); - OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation("file:" + file.getAbsolutePath()) - .build() - .iterator() - .forEachRemaining(parties::add); - assertThat(parties).hasSize(2); - assertThat(parties).extracting(AssertingPartyMetadata::getEntityId) - .contains("https://idp.example.com/idp/shibboleth", "https://ap.example.org/idp/shibboleth"); - } - - @Test - public void withMetadataFileLocationWhenNotFoundThenSaml2Exception() { - assertThatExceptionOfType(Saml2Exception.class).isThrownBy( - () -> OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation("file:path").build()); - } - - @Test - public void fromMetadataClasspathLocationWhenResolvableThenFindByEntityIdReturns() { - AssertingPartyMetadata party = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation("classpath:test-entitiesdescriptor.xml") - .build() - .findByEntityId("https://ap.example.org/idp/shibboleth"); - assertThat(party.getEntityId()).isEqualTo("https://ap.example.org/idp/shibboleth"); - assertThat(party.getSingleSignOnServiceLocation()) - .isEqualTo("https://ap.example.org/idp/profile/SAML2/POST/SSO"); - assertThat(party.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(party.getVerificationX509Credentials()).hasSize(1); - assertThat(party.getEncryptionX509Credentials()).hasSize(1); - } - - @Test - public void fromMetadataClasspathLocationWhenResolvableThenIteratorReturns() { - Collection parties = new ArrayList<>(); - OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation("classpath:test-entitiesdescriptor.xml") - .build() - .iterator() - .forEachRemaining(parties::add); - assertThat(parties).hasSize(2); - assertThat(parties).extracting(AssertingPartyMetadata::getEntityId) - .contains("https://idp.example.com/idp/shibboleth", "https://ap.example.org/idp/shibboleth"); - } - - @Test - public void withMetadataClasspathLocationWhenNotFoundThenSaml2Exception() { - assertThatExceptionOfType(Saml2Exception.class).isThrownBy( - () -> OpenSaml4AssertingPartyMetadataRepository.withTrustedMetadataLocation("classpath:path").build()); - } - - @Test - public void withTrustedMetadataLocationWhenMatchingCredentialsThenVerifiesSignature() throws IOException { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - EntityDescriptor descriptor = TestOpenSamlObjects.entityDescriptor(registration); - TestOpenSamlObjects.signed(descriptor, TestSaml2X509Credentials.assertingPartySigningCredential(), - descriptor.getEntityID()); - String serialized = serialize(descriptor); - Credential credential = TestOpenSamlObjects - .getSigningCredential(TestSaml2X509Credentials.relyingPartyVerifyingCredential(), descriptor.getEntityID()); - String endpoint = "/" + UUID.randomUUID().toString(); - dispatcher.addResponse(endpoint, serialized); - AssertingPartyMetadataRepository parties = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation(web.url(endpoint).toString()) - .verificationCredentials((c) -> c.add(credential)) - .build(); - assertThat(parties.findByEntityId(registration.getAssertingPartyMetadata().getEntityId())).isNotNull(); - } - - @Test - public void withTrustedMetadataLocationWhenMismatchingCredentialsThenSaml2Exception() throws IOException { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - EntityDescriptor descriptor = TestOpenSamlObjects.entityDescriptor(registration); - TestOpenSamlObjects.signed(descriptor, TestSaml2X509Credentials.relyingPartySigningCredential(), - descriptor.getEntityID()); - String serialized = serialize(descriptor); - Credential credential = TestOpenSamlObjects - .getSigningCredential(TestSaml2X509Credentials.relyingPartyVerifyingCredential(), descriptor.getEntityID()); - String endpoint = "/" + UUID.randomUUID().toString(); - dispatcher.addResponse(endpoint, serialized); - assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation(web.url(endpoint).toString()) - .verificationCredentials((c) -> c.add(credential)) - .build()); - } - - @Test - public void withTrustedMetadataLocationWhenNoCredentialsThenSkipsVerifySignature() throws IOException { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - EntityDescriptor descriptor = TestOpenSamlObjects.entityDescriptor(registration); - TestOpenSamlObjects.signed(descriptor, TestSaml2X509Credentials.assertingPartySigningCredential(), - descriptor.getEntityID()); - String serialized = serialize(descriptor); - String endpoint = "/" + UUID.randomUUID().toString(); - dispatcher.addResponse(endpoint, serialized); - AssertingPartyMetadataRepository parties = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation(web.url(endpoint).toString()) - .build(); - assertThat(parties.findByEntityId(registration.getAssertingPartyMetadata().getEntityId())).isNotNull(); - } - - @Test - public void withTrustedMetadataLocationWhenCustomResourceLoaderThenUses() { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - given(resourceLoader.getResource(any())).willReturn(new ClassPathResource("test-metadata.xml")); - AssertingPartyMetadata party = OpenSaml4AssertingPartyMetadataRepository - .withTrustedMetadataLocation("classpath:wrong") - .resourceLoader(resourceLoader) - .build() - .iterator() - .next(); - assertThat(party.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); - assertThat(party.getSingleSignOnServiceLocation()) - .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); - assertThat(party.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(party.getVerificationX509Credentials()).hasSize(1); - assertThat(party.getEncryptionX509Credentials()).hasSize(1); - verify(resourceLoader).getResource(any()); - } - - @Test - public void constructorWhenNoIndexAndNoIteratorThenException() { - MetadataResolver resolver = mock(MetadataResolver.class); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new OpenSaml4AssertingPartyMetadataRepository(resolver)); - } - - @Test - public void constructorWhenIterableResolverThenUses() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - EntityDescriptor descriptor = TestOpenSamlObjects.entityDescriptor(registration); - MetadataResolver resolver = mock(MetadataResolver.class, - withSettings().extraInterfaces(IterableMetadataSource.class)); - given(((IterableMetadataSource) resolver).iterator()).willReturn(List.of(descriptor).iterator()); - AssertingPartyMetadataRepository parties = new OpenSaml4AssertingPartyMetadataRepository(resolver); - parties.iterator() - .forEachRemaining((p) -> assertThat(p.getEntityId()) - .isEqualTo(registration.getAssertingPartyMetadata().getEntityId())); - verify(((IterableMetadataSource) resolver)).iterator(); - } - - @Test - public void constructorWhenIndexedResolverThenUses() throws Exception { - FilesystemMetadataResolver resolver = new FilesystemMetadataResolver( - new ClassPathResource("test-metadata.xml").getFile()); - resolver.setIndexes(Set.of(new RoleMetadataIndex())); - resolver.setId("id"); - resolver.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); - resolver.initialize(); - MetadataResolver spied = spy(resolver); - AssertingPartyMetadataRepository parties = new OpenSaml4AssertingPartyMetadataRepository(spied); - parties.iterator() - .forEachRemaining((p) -> assertThat(p.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth")); - verify(spied).resolve(any()); - } - - @Test - public void withMetadataLocationWhenNoCredentialsThenException() { - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy( - () -> OpenSaml4AssertingPartyMetadataRepository.withMetadataLocation("classpath:test-metadata.xml") - .build()); - } - - @Test - public void withMetadataLocationWhenMatchingCredentialsThenVerifiesSignature() throws IOException { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().build(); - EntityDescriptor descriptor = TestOpenSamlObjects.entityDescriptor(registration); - TestOpenSamlObjects.signed(descriptor, TestSaml2X509Credentials.assertingPartySigningCredential(), - descriptor.getEntityID()); - String serialized = serialize(descriptor); - Credential credential = TestOpenSamlObjects - .getSigningCredential(TestSaml2X509Credentials.relyingPartyVerifyingCredential(), descriptor.getEntityID()); - String endpoint = "/" + UUID.randomUUID().toString(); - dispatcher.addResponse(endpoint, serialized); - AssertingPartyMetadataRepository parties = OpenSaml4AssertingPartyMetadataRepository - .withMetadataLocation(web.url(endpoint).toString()) - .verificationCredentials((c) -> c.add(credential)) - .build(); - assertThat(parties.findByEntityId(registration.getAssertingPartyMetadata().getEntityId())).isNotNull(); - } - - private static String serialize(XMLObject object) { - try { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); - Element element = marshaller.marshall(object); - return SerializeSupport.nodeToString(element); - } - catch (MarshallingException ex) { - throw new Saml2Exception(ex); - } - } - - private static final class MetadataDispatcher extends Dispatcher { - - private final MockResponse head = new MockResponse(); - - private final Map responses = new ConcurrentHashMap<>(); - - private MetadataDispatcher() { - } - - @Override - public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - if ("HEAD".equals(request.getMethod())) { - return this.head; - } - return this.responses.get(request.getPath()); - } - - private MetadataDispatcher addResponse(String path, String body) { - this.responses.put(path, new MockResponse().setBody(body).setResponseCode(200)); - return this; - } - - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverterTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverterTests.java deleted file mode 100644 index 16dacef5ec..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/OpenSaml4AuthenticationTokenConverterTests.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2004-present 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 OpenSaml4AuthenticationTokenConverter} - */ -@ExtendWith(MockitoExtension.class) -public final class OpenSaml4AuthenticationTokenConverterTests { - - @Mock - RelyingPartyRegistrationRepository registrations; - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); - - @Test - public void convertWhenSamlResponseThenToken() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(this.registrations); - MockHttpServletRequest request = post("/login/saml2/sso/" + this.registration.getRegistrationId()); - assertThat(converter.convert(request)).isNull(); - } - - @Test - public void convertWhenNoMatchingRequestThenNull() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(this.registrations); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setParameter(Saml2ParameterNames.SAML_RESPONSE, "ignored"); - assertThat(converter.convert(request)).isNull(); - } - - @Test - public void convertWhenNoRelyingPartyRegistrationThenNull() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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 { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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()); - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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() { - OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(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/OpenSaml4AuthenticationRequestResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java deleted file mode 100644 index 2552044e16..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4AuthenticationRequestResolverTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2004-present 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; - -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; -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 org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; - -public class OpenSaml4AuthenticationRequestResolverTests { - - MockHttpServletRequest request; - - RelyingPartyRegistration registration; - - @BeforeEach - void setup() { - this.request = givenRequest("/saml2/authenticate/registration-id"); - this.registration = TestRelyingPartyRegistrations.full().build(); - } - - @Test - void resolveWhenRedirectThenSaml2RedirectAuthenticationRequest() { - RelyingPartyRegistrationResolver relyingParties = mock(RelyingPartyRegistrationResolver.class); - given(relyingParties.resolve(any(), any())).willReturn(this.registration); - OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingParties); - Saml2RedirectAuthenticationRequest authnRequest = resolver.resolve(this.request); - assertThat(authnRequest.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(authnRequest.getAuthenticationRequestUri()) - .isEqualTo(this.registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()); - } - - @Test - void resolveWhenPostThenSaml2PostAuthenticationRequest() { - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full() - .assertingPartyMetadata((party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST)) - .build(); - RelyingPartyRegistrationResolver relyingParties = mock(RelyingPartyRegistrationResolver.class); - given(relyingParties.resolve(any(), any())).willReturn(registration); - OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingParties); - Saml2PostAuthenticationRequest authnRequest = resolver.resolve(this.request); - assertThat(authnRequest.getBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(authnRequest.getAuthenticationRequestUri()) - .isEqualTo(this.registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()); - } - - @Test - void resolveWhenCustomRelayStateThenUses() { - RelyingPartyRegistrationResolver relyingParties = mock(RelyingPartyRegistrationResolver.class); - given(relyingParties.resolve(any(), any())).willReturn(this.registration); - Converter relayState = mock(Converter.class); - given(relayState.convert(any())).willReturn("state"); - OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingParties); - resolver.setRelayStateResolver(relayState); - Saml2RedirectAuthenticationRequest authnRequest = resolver.resolve(this.request); - assertThat(authnRequest.getRelayState()).isEqualTo("state"); - verify(relayState).convert(any()); - } - - @Test - void resolveWhenCustomAuthenticationUrlTHenUses() { - RelyingPartyRegistrationResolver relyingParties = mock(RelyingPartyRegistrationResolver.class); - given(relyingParties.resolve(any(), any())).willReturn(this.registration); - OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingParties); - resolver.setRequestMatcher(pathPattern("/custom/authentication/{registrationId}")); - Saml2RedirectAuthenticationRequest authnRequest = resolver - .resolve(givenRequest("/custom/authentication/registration-id")); - - assertThat(authnRequest.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(authnRequest.getAuthenticationRequestUri()) - .isEqualTo(this.registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()); - - } - - private MockHttpServletRequest givenRequest(String path) { - return TestMockHttpServletRequests.get(path).build(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4SigningUtilsTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4SigningUtilsTests.java deleted file mode 100644 index 1f6c71817f..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml4SigningUtilsTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2004-present 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; - -import java.util.UUID; - -import javax.xml.namespace.QName; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.common.SAMLVersion; -import org.opensaml.saml.saml2.core.Issuer; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.xmlsec.signature.Signature; - -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test open SAML signatures - */ -public class OpenSaml4SigningUtilsTests { - - static { - OpenSamlInitializationService.initialize(); - } - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - private RelyingPartyRegistration registration; - - @BeforeEach - public void setup() { - this.registration = RelyingPartyRegistration.withRegistrationId("saml-idp") - .entityId("https://some.idp.example.com/entity-id") - .signingX509Credentials((c) -> { - c.add(TestSaml2X509Credentials.relyingPartySigningCredential()); - c.add(TestSaml2X509Credentials.assertingPartySigningCredential()); - }) - .assertingPartyMetadata((c) -> c.entityId("https://some.idp.example.com/entity-id") - .singleSignOnServiceLocation("https://some.idp.example.com/service-location")) - .build(); - } - - @Test - public void whenSigningAnObjectThenKeyInfoIsPartOfTheSignature() { - Response response = response("destination", "issuer"); - this.saml.withSigningKeys(this.registration.getSigningX509Credentials()).sign(response); - Signature signature = response.getSignature(); - assertThat(signature).isNotNull(); - assertThat(signature.getKeyInfo()).isNotNull(); - } - - Response response(String destination, String issuerEntityId) { - Response response = build(Response.DEFAULT_ELEMENT_NAME); - response.setID("R" + UUID.randomUUID()); - response.setVersion(SAMLVersion.VERSION_20); - response.setID("_" + UUID.randomUUID()); - response.setDestination(destination); - response.setIssuer(issuer(issuerEntityId)); - return response; - } - - Issuer issuer(String entityId) { - Issuer issuer = build(Issuer.DEFAULT_ELEMENT_NAME); - issuer.setValue(entityId); - return issuer; - } - - T build(QName qName) { - return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java deleted file mode 100644 index ff3aa76e68..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestResolverTests.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2004-present 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.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OpenSaml4LogoutRequestResolver} - */ -public class OpenSaml4LogoutRequestResolverTests { - - RelyingPartyRegistration registration; - - RelyingPartyRegistrationResolver registrationResolver; - - OpenSaml4LogoutRequestResolver logoutRequestResolver; - - @BeforeEach - public void setup() { - this.registration = TestRelyingPartyRegistrations.full().build(); - this.registrationResolver = mock(RelyingPartyRegistrationResolver.class); - this.logoutRequestResolver = new OpenSaml4LogoutRequestResolver(this.registrationResolver); - } - - @Test - public void resolveWhenCustomParametersConsumerThenUses() { - this.logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid")); - given(this.registrationResolver.resolve(any(), any())).willReturn(this.registration); - - Saml2LogoutRequest logoutRequest = this.logoutRequestResolver.resolve(givenRequest(), givenAuthentication()); - - assertThat(logoutRequest.getId()).isEqualTo("myid"); - } - - @Test - public void setParametersConsumerWhenNullThenIllegalArgument() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> this.logoutRequestResolver.setParametersConsumer(null)); - } - - @Test - public void resolveWhenCustomRelayStateThenUses() { - given(this.registrationResolver.resolve(any(), any())).willReturn(this.registration); - Converter relayState = mock(Converter.class); - given(relayState.convert(any())).willReturn("any-state"); - this.logoutRequestResolver.setRelayStateResolver(relayState); - - Saml2LogoutRequest logoutRequest = this.logoutRequestResolver.resolve(givenRequest(), givenAuthentication()); - - assertThat(logoutRequest.getRelayState()).isEqualTo("any-state"); - verify(relayState).convert(any()); - } - - private static Authentication givenAuthentication() { - return new TestingAuthenticationToken("user", "password"); - } - - private MockHttpServletRequest givenRequest() { - return new MockHttpServletRequest(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolverTests.java deleted file mode 100644 index b8657ec041..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutRequestValidatorParametersResolverTests.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2004-present 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 OpenSaml4LogoutRequestValidatorParametersResolverTests { - - @Mock - RelyingPartyRegistrationRepository registrations; - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - private RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); - - private OpenSaml4LogoutRequestValidatorParametersResolver resolver; - - @BeforeEach - void setup() { - this.resolver = new OpenSaml4LogoutRequestValidatorParametersResolver(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(); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java b/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java deleted file mode 100644 index 397e609822..0000000000 --- a/saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml4LogoutResponseResolverTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2004-present 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.util.function.Consumer; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.saml.saml2.core.StatusCode; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -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 org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver.LogoutResponseParameters; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OpenSaml4LogoutResponseResolver} - */ -public class OpenSaml4LogoutResponseResolverTests { - - private final OpenSamlOperations saml = new OpenSaml4Template(); - - RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = mock(RelyingPartyRegistrationResolver.class); - - @Test - public void resolveWhenCustomParametersConsumerThenUses() { - OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver( - this.relyingPartyRegistrationResolver); - Consumer parametersConsumer = mock(Consumer.class); - logoutResponseResolver.setParametersConsumer(parametersConsumer); - MockHttpServletRequest request = new MockHttpServletRequest(); - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration() - .assertingPartyMetadata( - (party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout")) - .build(); - Authentication authentication = new TestingAuthenticationToken("user", "password"); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - request.setParameter(Saml2ParameterNames.SAML_REQUEST, - Saml2Utils.samlEncode(this.saml.serialize(logoutRequest).serialize().getBytes())); - given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration); - Saml2LogoutResponse logoutResponse = logoutResponseResolver.resolve(request, authentication); - assertThat(logoutResponse).isNotNull(); - verify(parametersConsumer).accept(any()); - } - - @ParameterizedTest - @MethodSource("provideAuthExceptionAndExpectedSamlStatusCode") - public void resolveWithAuthException(Saml2AuthenticationException exception, String expectedStatusCode) { - OpenSaml4LogoutResponseResolver logoutResponseResolver = new OpenSaml4LogoutResponseResolver( - this.relyingPartyRegistrationResolver); - MockHttpServletRequest request = new MockHttpServletRequest(); - RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration() - .assertingPartyMetadata( - (party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout") - .singleLogoutServiceBinding(Saml2MessageBinding.POST)) - .build(); - Authentication authentication = new TestingAuthenticationToken("user", "password"); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - request.setParameter(Saml2ParameterNames.SAML_REQUEST, - Saml2Utils.samlEncode(this.saml.serialize(logoutRequest).serialize().getBytes())); - given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration); - Saml2LogoutResponse logoutResponse = logoutResponseResolver.resolve(request, authentication, exception); - assertThat(logoutResponse).isNotNull(); - assertThat(new String(Saml2Utils.samlDecode(logoutResponse.getSamlResponse()))).contains(expectedStatusCode); - } - - @Test - public void setParametersConsumerWhenNullThenIllegalArgument() { - OpenSaml4LogoutRequestResolver logoutRequestResolver = new OpenSaml4LogoutRequestResolver( - this.relyingPartyRegistrationResolver); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> logoutRequestResolver.setParametersConsumer(null)); - } - - private static Stream provideAuthExceptionAndExpectedSamlStatusCode() { - return Stream.of( - Arguments.of(new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, "")), - StatusCode.REQUEST_DENIED), - Arguments.of(new Saml2AuthenticationException(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST, "")), - StatusCode.REQUESTER) - - ); - } - -} diff --git a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolverTests.java b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolverTests.java index abca17e893..39719c243b 100644 --- a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolverTests.java +++ b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolverTests.java @@ -37,8 +37,8 @@ public class OpenSaml5MetadataResolverTests { RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() .assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT) .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).contains("") @@ -54,9 +54,9 @@ public class OpenSaml5MetadataResolverTests { RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() .assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT) .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - OpenSaml4MetadataResolver.setSignMetadata(true); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + metadataResolver.setSignMetadata(true); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).contains("") @@ -82,8 +82,8 @@ public class OpenSaml5MetadataResolverTests { .assertingPartyMetadata((party) -> party .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).contains("") @@ -98,8 +98,8 @@ public class OpenSaml5MetadataResolverTests { RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() .nameIdFormat("format") .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).contains("format"); } @@ -109,8 +109,8 @@ public class OpenSaml5MetadataResolverTests { .singleLogoutServiceLocation(null) .nameIdFormat("format") .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).doesNotContain("ResponseLocation"); } @@ -119,10 +119,10 @@ public class OpenSaml5MetadataResolverTests { RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full() .entityId("originalEntityId") .build(); - OpenSaml5MetadataResolver OpenSaml4MetadataResolver = new OpenSaml5MetadataResolver(); - OpenSaml4MetadataResolver.setEntityDescriptorCustomizer( + OpenSaml5MetadataResolver metadataResolver = new OpenSaml5MetadataResolver(); + metadataResolver.setEntityDescriptorCustomizer( (parameters) -> parameters.getEntityDescriptor().setEntityID("overriddenEntityId")); - String metadata = OpenSaml4MetadataResolver.resolve(relyingPartyRegistration); + String metadata = metadataResolver.resolve(relyingPartyRegistration); assertThat(metadata).contains("