From 43098d41cc0871babed00f3a3f6a762d1fe7ad18 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 12 Feb 2020 13:18:11 -0800 Subject: [PATCH] Revert "Correct signature handling for SAML2 AuthNRequest" This reverts commit a3e09fadd77cb6bbacbd626c48deb2a4473e1e54. Build failure on Java 9+ XML generation does not add linefeeds by default Change since Java 8 --- .../AbstractSaml2AuthenticationRequest.java | 148 --------------- .../OpenSamlAuthenticationRequestFactory.java | 78 ++------ .../OpenSamlImplementation.java | 76 +------- .../Saml2AuthenticationRequest.java | 20 +- .../Saml2AuthenticationRequestContext.java | 172 ------------------ .../Saml2AuthenticationRequestFactory.java | 95 +--------- .../Saml2PostAuthenticationRequest.java | 85 --------- .../Saml2RedirectAuthenticationRequest.java | 133 -------------- .../service/authentication/Saml2Utils.java | 73 -------- .../registration/Saml2MessageBinding.java | 45 ----- .../servlet/filter/Saml2ServletUtils.java | 88 --------- .../service/servlet/filter/Saml2Utils.java | 76 +++++++- .../Saml2WebSsoAuthenticationFilter.java | 10 +- ...aml2WebSsoAuthenticationRequestFilter.java | 65 +++---- ...SamlAuthenticationRequestFactoryTests.java | 70 ++----- .../OpenSamlImplementationTests.java | 55 ------ ...aml2AuthenticationRequestFactoryTests.java | 72 -------- .../servlet/filter/Saml2UtilsTests.java | 4 +- ...ebSsoAuthenticationRequestFilterTests.java | 79 +++----- .../OpenSamlActionTestingSupport.java | 58 +++++- .../Saml2LoginIntegrationTests.java | 33 ++-- 21 files changed, 243 insertions(+), 1292 deletions(-) delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java delete mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java rename samples/boot/saml2login/src/integration-test/java/org/springframework/security/{saml2/provider/service/authentication => samples}/OpenSamlActionTestingSupport.java (92%) rename samples/boot/saml2login/src/integration-test/java/org/springframework/security/{saml2/provider/service/authentication => samples}/Saml2LoginIntegrationTests.java (94%) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java deleted file mode 100644 index 99ef2cdc22..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/AbstractSaml2AuthenticationRequest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2002-2020 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 org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.util.Assert; - -import java.nio.charset.Charset; - -/** - * Data holder for {@code AuthNRequest} parameters to be sent using either the - * {@link Saml2MessageBinding#POST} or {@link Saml2MessageBinding#REDIRECT} binding. - * Data will be encoded and possibly deflated, but will not be escaped for transport, - * ie URL encoded, {@link org.springframework.web.util.UriUtils#encode(String, Charset)} - * or HTML encoded, {@link org.springframework.web.util.HtmlUtils#htmlEscape(String)}. - * https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031) - * - * @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext) - * @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext) - * @since 5.3 - */ -abstract class AbstractSaml2AuthenticationRequest { - - private final String samlRequest; - private final String relayState; - private final String authenticationRequestUri; - - /** - * Mandatory constructor for the {@link AbstractSaml2AuthenticationRequest} - * @param samlRequest - the SAMLRequest XML data, SAML encoded, cannot be empty or null - * @param relayState - RelayState value that accompanies the request, may be null - * @param authenticationRequestUri - The authenticationRequestUri, a URL, where to send the XML message, cannot be empty or null - */ - AbstractSaml2AuthenticationRequest( - String samlRequest, - String relayState, - String authenticationRequestUri) { - Assert.hasText(samlRequest, "samlRequest cannot be null or empty"); - Assert.hasText(authenticationRequestUri, "authenticationRequestUri cannot be null or empty"); - this.authenticationRequestUri = authenticationRequestUri; - this.samlRequest = samlRequest; - this.relayState = relayState; - } - - /** - * Returns the AuthNRequest XML value to be sent. This value is already encoded for transport. - * If {@link #getBinding()} is {@link Saml2MessageBinding#REDIRECT} the value is deflated and SAML encoded. - * If {@link #getBinding()} is {@link Saml2MessageBinding#POST} the value is SAML encoded. - * @return the SAMLRequest parameter value - */ - public String getSamlRequest() { - return this.samlRequest; - } - - /** - * Returns the RelayState value, if present in the parameters - * @return the RelayState value, or null if not available - */ - public String getRelayState() { - return this.relayState; - } - - /** - * Returns the URI endpoint that this AuthNRequest should be sent to. - * @return the URI endpoint for this message - */ - public String getAuthenticationRequestUri() { - return this.authenticationRequestUri; - } - - /** - * Returns the binding this AuthNRequest will be sent and - * encoded with. If {@link Saml2MessageBinding#REDIRECT} is used, the DEFLATE encoding will be automatically applied. - * @return the binding this message will be sent with. - */ - public abstract Saml2MessageBinding getBinding(); - - /** - * A builder for {@link AbstractSaml2AuthenticationRequest} and its subclasses. - */ - static class Builder> { - String authenticationRequestUri; - String samlRequest; - String relayState; - - protected Builder() { - } - - /** - * Casting the return as the generic subtype, when returning itself - * @return this object - */ - @SuppressWarnings("unchecked") - protected final T _this() { - return (T) this; - } - - - /** - * Sets the {@code RelayState} parameter that will accompany this AuthNRequest - * - * @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the - * map. - * @return this object - */ - public T relayState(String relayState) { - this.relayState = relayState; - return _this(); - } - - /** - * Sets the {@code SAMLRequest} parameter that will accompany this AuthNRequest - * - * @param samlRequest the SAMLRequest parameter. - * @return this object - */ - public T samlRequest(String samlRequest) { - this.samlRequest = samlRequest; - return _this(); - } - - /** - * Sets the {@code authenticationRequestUri}, a URL that will receive the AuthNRequest message - * - * @param authenticationRequestUri the relay state value, unencoded. - * @return this object - */ - public T authenticationRequestUri(String authenticationRequestUri) { - this.authenticationRequestUri = authenticationRequestUri; - return _this(); - } - } - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java index 67983a12f9..035a2dec4c 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -16,25 +16,17 @@ package org.springframework.security.saml2.provider.service.authentication; -import org.joda.time.DateTime; import org.opensaml.saml.common.xml.SAMLConstants; +import org.springframework.util.Assert; + +import org.joda.time.DateTime; import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.Issuer; -import org.springframework.security.saml2.credentials.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.Builder; -import org.springframework.util.Assert; import java.time.Clock; import java.time.Instant; -import java.util.List; -import java.util.Map; import java.util.UUID; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyList; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode; - /** * @since 5.2 */ @@ -43,50 +35,11 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance(); private String protocolBinding = SAMLConstants.SAML2_POST_BINDING_URI; + /** + * {@inheritDoc} + */ @Override - @Deprecated public String createAuthenticationRequest(Saml2AuthenticationRequest request) { - return createAuthenticationRequest(request, request.getCredentials()); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2PostAuthenticationRequest createPostAuthenticationRequest(Saml2AuthenticationRequestContext context) { - String xml = createAuthenticationRequest(context, context.getRelyingPartyRegistration().getSigningCredentials()); - return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context) - .samlRequest(samlEncode(xml.getBytes(UTF_8))) - .build(); - } - - /** - * {@inheritDoc} - */ - @Override - public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext context) { - String xml = createAuthenticationRequest(context, emptyList()); - List signingCredentials = context.getRelyingPartyRegistration().getSigningCredentials(); - Builder result = Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context); - - String deflatedAndEncoded = samlEncode(samlDeflate(xml)); - Map signedParams = this.saml.signQueryParameters( - signingCredentials, - deflatedAndEncoded, - context.getRelayState() - ); - result.samlRequest(signedParams.get("SAMLRequest")) - .relayState(signedParams.get("RelayState")) - .sigAlg(signedParams.get("SigAlg")) - .signature(signedParams.get("Signature")); - return result.build(); - } - - private String createAuthenticationRequest(Saml2AuthenticationRequestContext request, List credentials) { - return createAuthenticationRequest(Saml2AuthenticationRequest.withAuthenticationRequestContext(request).build(), credentials); - } - - private String createAuthenticationRequest(Saml2AuthenticationRequest context, List credentials) { AuthnRequest auth = this.saml.buildSAMLObject(AuthnRequest.class); auth.setID("ARQ" + UUID.randomUUID().toString().substring(1)); auth.setIssueInstant(new DateTime(this.clock.millis())); @@ -94,14 +47,14 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication auth.setIsPassive(Boolean.FALSE); auth.setProtocolBinding(protocolBinding); Issuer issuer = this.saml.buildSAMLObject(Issuer.class); - issuer.setValue(context.getIssuer()); + issuer.setValue(request.getIssuer()); auth.setIssuer(issuer); - auth.setDestination(context.getDestination()); - auth.setAssertionConsumerServiceURL(context.getAssertionConsumerServiceUrl()); + auth.setDestination(request.getDestination()); + auth.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceUrl()); return this.saml.toXml( auth, - credentials, - context.getIssuer() + request.getCredentials(), + request.getIssuer() ); } @@ -118,14 +71,11 @@ public class OpenSamlAuthenticationRequestFactory implements Saml2Authentication } /** - * Sets the {@code protocolBinding} to use when generating authentication requests. + * Sets the {@code protocolBinding} to use when generating authentication requests * Acceptable values are {@link SAMLConstants#SAML2_POST_BINDING_URI} and * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} - * The IDP will be reading this value in the {@code AuthNRequest} to determine how to - * send the Response/Assertion to the ACS URL, assertion consumer service URL. * - * @param protocolBinding either {@link SAMLConstants#SAML2_POST_BINDING_URI} or - * {@link SAMLConstants#SAML2_REDIRECT_BINDING_URI} + * @param protocolBinding * @throws IllegalArgumentException if the protocolBinding is not valid */ public void setProtocolBinding(String protocolBinding) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java index a049fc7d9c..67d2fb2cc4 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlImplementation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.security.saml2.provider.service.authentication; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.credentials.Saml2X509Credential; + import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.BasicParserPool; import net.shibboleth.utilities.java.support.xml.SerializeSupport; @@ -39,7 +41,6 @@ import org.opensaml.security.credential.CredentialSupport; import org.opensaml.security.credential.UsageType; import org.opensaml.security.x509.BasicX509Credential; import org.opensaml.xmlsec.SignatureSigningParameters; -import org.opensaml.xmlsec.crypto.XMLSigningUtil; import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; @@ -47,29 +48,21 @@ import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyR import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.SignatureSupport; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.credentials.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.authentication.Saml2Utils; -import org.springframework.util.Assert; -import org.springframework.web.util.UriUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; -import javax.xml.XMLConstants; -import javax.xml.namespace.QName; import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static java.util.Arrays.asList; import static org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport.getBuilderFactory; -import static org.springframework.util.StringUtils.hasText; /** * @since 5.2 @@ -198,68 +191,11 @@ final class OpenSamlImplementation { } } - /** - * Returns query parameter after creating a Query String signature - * All return values are unencoded and will need to be encoded prior to sending - * The methods {@link UriUtils#encode(String, Charset)} and {@link UriUtils#decode(String, Charset)} - * with the {@link StandardCharsets#ISO_8859_1} character set are used for all URL encoding/decoding. - * @param signingCredentials - credentials to be used for signature - * @return a map of unencoded query parameters with the following keys: - * {@code {SAMLRequest, RelayState (may be null)}, SigAlg, Signature} - * - */ - Map signQueryParameters( - List signingCredentials, - String samlRequest, - String relayState) { - Assert.notNull(samlRequest, "samlRequest cannot be null"); - String algorithmUri = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - StringBuilder queryString = new StringBuilder(); - queryString - .append("SAMLRequest") - .append("=") - .append(UriUtils.encode(samlRequest, StandardCharsets.ISO_8859_1)) - .append("&"); - if (hasText(relayState)) { - queryString - .append("RelayState") - .append("=") - .append(UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)) - .append("&"); - } - queryString - .append("SigAlg") - .append("=") - .append(UriUtils.encode(algorithmUri, StandardCharsets.ISO_8859_1)); - - try { - byte[] rawSignature = XMLSigningUtil.signWithURI( - getSigningCredential(signingCredentials, ""), - algorithmUri, - queryString.toString().getBytes(StandardCharsets.UTF_8) - ); - String b64Signature = Saml2Utils.samlEncode(rawSignature); - - Map result = new LinkedHashMap<>(); - result.put("SAMLRequest", samlRequest); - if (hasText(relayState)) { - result.put("RelayState", relayState); - } - result.put("SigAlg", algorithmUri); - result.put("Signature", b64Signature); - return result; - } - catch (SecurityException e) { - throw new Saml2Exception(e); - } - } - /* * ============================================================== * PRIVATE METHODS * ============================================================== */ - private XMLObject resolve(byte[] xml) { XMLObject parsed = parse(xml); if (parsed != null) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java index 1fcc1a80a2..89adbedac7 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -29,10 +29,9 @@ import java.util.function.Consumer; * from the service provider to the identity provider * https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031) * + * @see {@link Saml2AuthenticationRequestFactory} * @since 5.2 - * @deprecated use {@link Saml2AuthenticationRequestContext} */ -@Deprecated public final class Saml2AuthenticationRequest { private final String issuer; private final List credentials; @@ -56,6 +55,7 @@ public final class Saml2AuthenticationRequest { this.credentials.add(c); } } + Assert.notEmpty(this.credentials, "at least one SIGNING credential must be present"); } @@ -104,20 +104,6 @@ public final class Saml2AuthenticationRequest { return new Builder(); } - /** - * A builder for {@link Saml2AuthenticationRequest}. - * @param context a context object to copy values from. - * returns a builder object - */ - public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) { - return new Builder() - .assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl()) - .issuer(context.getIssuer()) - .destination(context.getDestination()) - .credentials(c -> c.addAll(context.getRelyingPartyRegistration().getCredentials())) - ; - } - /** * A builder for {@link Saml2AuthenticationRequest}. */ diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java deleted file mode 100644 index e769b4354d..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestContext.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2002-2020 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 org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.util.Assert; - -/** - * Data holder for information required to create an {@code AuthNRequest} - * to be sent from the service provider to the identity provider - * - * Assertions and Protocols for SAML 2 (line 2031) - * - * @see Saml2AuthenticationRequestFactory#createPostAuthenticationRequest(Saml2AuthenticationRequestContext) - * @see Saml2AuthenticationRequestFactory#createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext) - * @since 5.3 - */ -public final class Saml2AuthenticationRequestContext { - private final RelyingPartyRegistration relyingPartyRegistration; - private final String issuer; - private final String assertionConsumerServiceUrl; - private final String relayState; - - private Saml2AuthenticationRequestContext( - RelyingPartyRegistration relyingPartyRegistration, - String issuer, - String assertionConsumerServiceUrl, - String relayState) { - Assert.hasText(issuer, "issuer cannot be null or empty"); - Assert.notNull(relyingPartyRegistration, "relyingPartyRegistration cannot be null"); - Assert.hasText(assertionConsumerServiceUrl, "spAssertionConsumerServiceUrl cannot be null or empty"); - this.issuer = issuer; - this.relyingPartyRegistration = relyingPartyRegistration; - this.assertionConsumerServiceUrl = assertionConsumerServiceUrl; - this.relayState = relayState; - } - - /** - * Returns the {@link RelyingPartyRegistration} configuration for which the AuthNRequest is intended for. - * @return the {@link RelyingPartyRegistration} configuration - */ - public RelyingPartyRegistration getRelyingPartyRegistration() { - return this.relyingPartyRegistration; - } - - /** - * Returns the {@code Issuer} value to be used in the {@code AuthNRequest} object. - * This property should be used to populate the {@code AuthNRequest.Issuer} XML element. - * This value typically is a URI, but can be an arbitrary string. - * @return the Issuer value - */ - public String getIssuer() { - return this.issuer; - } - - /** - * Returns the desired {@code AssertionConsumerServiceUrl} that this SP wishes to receive the - * assertion on. The IDP may or may not honor this request. - * This property populates the {@code AuthNRequest.AssertionConsumerServiceURL} XML attribute. - * @return the AssertionConsumerServiceURL value - */ - public String getAssertionConsumerServiceUrl() { - return assertionConsumerServiceUrl; - } - - /** - * Returns the RelayState value, if present in the parameters - * @return the RelayState value, or null if not available - */ - public String getRelayState() { - return this.relayState; - } - - /** - * Returns the {@code Destination}, the WEB Single Sign On URI, for this authentication request. - * This property can also populate the {@code AuthNRequest.Destination} XML attribute. - * @return the Destination value - */ - public String getDestination() { - return this.getRelyingPartyRegistration().getIdpWebSsoUrl(); - } - - /** - * A builder for {@link Saml2AuthenticationRequestContext}. - * @return a builder object - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for {@link Saml2AuthenticationRequestContext}. - */ - public static class Builder { - private String issuer; - private String assertionConsumerServiceUrl; - private String relayState; - private RelyingPartyRegistration relyingPartyRegistration; - - private Builder() { - } - - /** - * Sets the issuer for the authentication request. - * @param issuer - a required value - * @return this {@code Builder} - */ - public Builder issuer(String issuer) { - this.issuer = issuer; - return this; - } - - /** - * Sets the {@link RelyingPartyRegistration} used to build the authentication request. - * @param relyingPartyRegistration - a required value - * @return this {@code Builder} - */ - public Builder relyingPartyRegistration(RelyingPartyRegistration relyingPartyRegistration) { - this.relyingPartyRegistration = relyingPartyRegistration; - return this; - } - - /** - * Sets the {@code assertionConsumerServiceURL} for the authentication request. - * Typically the {@code Service Provider EntityID} - * @param assertionConsumerServiceUrl - a required value - * @return this {@code Builder} - */ - public Builder assertionConsumerServiceUrl(String assertionConsumerServiceUrl) { - this.assertionConsumerServiceUrl = assertionConsumerServiceUrl; - return this; - } - - /** - * Sets the {@code RelayState} parameter that will accompany this AuthNRequest - * @param relayState the relay state value, unencoded. if null or empty, the parameter will be removed from the map. - * @return this object - */ - public Builder relayState(String relayState) { - this.relayState = relayState; - return this; - } - - /** - * Creates a {@link Saml2AuthenticationRequestContext} object. - * @return the Saml2AuthenticationRequest object - * @throws {@link IllegalArgumentException} if a required property is not set - */ - public Saml2AuthenticationRequestContext build() { - return new Saml2AuthenticationRequestContext( - this.relyingPartyRegistration, - this.issuer, - this.assertionConsumerServiceUrl, - this.relayState - ); - } - } -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java index 9ae7c284e4..35edb477b1 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -17,102 +17,25 @@ package org.springframework.security.saml2.provider.service.authentication; import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; - -import java.nio.charset.StandardCharsets; - -import static org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest.withAuthenticationRequestContext; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDeflate; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlEncode; /** - * Component that generates AuthenticationRequest, samlp:AuthnRequestType XML, and accompanying - * signature data. - * as defined by https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf + * Component that generates an AuthenticationRequest, samlp:AuthnRequestType as defined by + * https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf * Page 50, Line 2147 * * @since 5.2 */ public interface Saml2AuthenticationRequestFactory { - /** - * Creates an authentication request from the Service Provider, sp, to the Identity Provider, idp. + * Creates an authentication request from the Service Provider, sp, + * to the Identity Provider, idp. * The authentication result is an XML string that may be signed, encrypted, both or neither. - * This method only returns the {@code SAMLRequest} string for the request, and for a complete - * set of data parameters please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)} - * or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)} * - * @param request information about the identity provider, - * the recipient of this authentication request and accompanying data - * @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted with the - * signature embedded in the XML or neither signed and encrypted + * @param request - information about the identity provider, the recipient of this authentication request and + * accompanying data + * @return XML data in the format of a String. This data may be signed, encrypted, both signed and encrypted or + * neither signed and encrypted * @throws Saml2Exception when a SAML library exception occurs - * @since 5.2 - * @deprecated please use {@link #createRedirectAuthenticationRequest(Saml2AuthenticationRequestContext)} - * or {@link #createPostAuthenticationRequest(Saml2AuthenticationRequestContext)} - * This method will be removed in future versions of Spring Security */ - @Deprecated String createAuthenticationRequest(Saml2AuthenticationRequest request); - - /** - * Creates all the necessary AuthNRequest parameters for a REDIRECT binding. - * If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials - * the result will not contain any signatures. - * The data set will be signed and encoded for REDIRECT binding including the DEFLATE encoding. - * It will contain the following parameters to be sent as part of the query string: - * {@code SAMLRequest, RelayState, SigAlg, Signature}. - * The default implementation, for sake of backwards compatibility, of this method returns the - * SAMLRequest message with an XML signature embedded, that should only be used for the{@link Saml2MessageBinding#POST} - * binding, but works over {@link Saml2MessageBinding#POST} with most providers. - * @param context - information about the identity provider, the recipient of this authentication request and - * accompanying data - * @return a {@link Saml2RedirectAuthenticationRequest} object with applicable http parameters - * necessary to make the AuthNRequest over a POST or REDIRECT binding. - * All parameters will be SAML encoded/deflated, but escaped, ie URI encoded or encoded for Form Data. - * @throws Saml2Exception when a SAML library exception occurs - * @since 5.3 - */ - default Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest( - Saml2AuthenticationRequestContext context - ) { - //backwards compatible with 5.2.x settings - Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context); - String samlRequest = createAuthenticationRequest(resultBuilder.build()); - samlRequest = samlEncode(samlDeflate(samlRequest)); - return Saml2RedirectAuthenticationRequest.withAuthenticationRequestContext(context) - .samlRequest(samlRequest) - .build(); - } - - - /** - * Creates all the necessary AuthNRequest parameters for a POST binding. - * If the {@link Saml2AuthenticationRequestContext} doesn't contain any {@link Saml2X509CredentialType#SIGNING} credentials - * the result will not contain any signatures. - * The data set will be signed and encoded for POST binding and if applicable signed with XML signatures. - * will contain the following parameters to be sent as part of the form data: {@code SAMLRequest, RelayState}. - * The default implementation of this method returns the SAMLRequest message with an XML signature embedded, - * that should only be used for the {@link Saml2MessageBinding#POST} binding. - * @param context - information about the identity provider, the recipient of this authentication request and - * accompanying data - * @return a {@link Saml2PostAuthenticationRequest} object with applicable http parameters - * necessary to make the AuthNRequest over a POST binding. - * All parameters will be SAML encoded but not escaped for Form Data. - * @throws Saml2Exception when a SAML library exception occurs - * @since 5.3 - */ - default Saml2PostAuthenticationRequest createPostAuthenticationRequest( - Saml2AuthenticationRequestContext context - ) { - //backwards compatible with 5.2.x settings - Saml2AuthenticationRequest.Builder resultBuilder = withAuthenticationRequestContext(context); - String samlRequest = createAuthenticationRequest(resultBuilder.build()); - samlRequest = samlEncode(samlRequest.getBytes(StandardCharsets.UTF_8)); - return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context) - .samlRequest(samlRequest) - .build(); - } - } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java deleted file mode 100644 index d621a7c704..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2PostAuthenticationRequest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2002-2020 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 org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; - -import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST; - -/** - * Data holder for information required to send an {@code AuthNRequest} over a POST binding - * from the service provider to the identity provider - * https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031) - * - * @see Saml2AuthenticationRequestFactory - * @since 5.3 - */ -public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationRequest { - - private Saml2PostAuthenticationRequest( - String samlRequest, - String relayState, - String authenticationRequestUri) { - super(samlRequest, relayState, authenticationRequestUri); - } - - /** - * @return {@link Saml2MessageBinding#POST} - */ - @Override - public Saml2MessageBinding getBinding() { - return POST; - } - - /** - * Constructs a {@link Builder} from a {@link Saml2AuthenticationRequestContext} object. - * By default the {@link Saml2PostAuthenticationRequest#getAuthenticationRequestUri()} will be set to the - * {@link Saml2AuthenticationRequestContext#getDestination()} value. - * @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects. - * @return a modifiable builder object - */ - public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) { - return new Builder() - .authenticationRequestUri(context.getDestination()) - .relayState(context.getRelayState()) - ; - } - - /** - * Builder class for a {@link Saml2PostAuthenticationRequest} object. - */ - public static class Builder extends AbstractSaml2AuthenticationRequest.Builder { - - private Builder() { - super(); - } - - /** - * Constructs an immutable {@link Saml2PostAuthenticationRequest} object. - * @return an immutable {@link Saml2PostAuthenticationRequest} object. - */ - public Saml2PostAuthenticationRequest build() { - return new Saml2PostAuthenticationRequest( - this.samlRequest, - this.relayState, - this.authenticationRequestUri - ); - } - } - - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java deleted file mode 100644 index fdfc8372aa..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2RedirectAuthenticationRequest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2002-2020 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 org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; - -import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT; - -/** - * Data holder for information required to send an {@code AuthNRequest} over a REDIRECT binding - * from the service provider to the identity provider - * https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf (line 2031) - * - * @see Saml2AuthenticationRequestFactory - * @since 5.3 - */ -public class Saml2RedirectAuthenticationRequest extends AbstractSaml2AuthenticationRequest { - - private final String sigAlg; - private final String signature; - - private Saml2RedirectAuthenticationRequest( - String samlRequest, - String sigAlg, - String signature, - String relayState, - String authenticationRequestUri) { - super(samlRequest, relayState, authenticationRequestUri); - this.sigAlg = sigAlg; - this.signature = signature; - } - - /** - * Returns the SigAlg value for {@link Saml2MessageBinding#REDIRECT} requests - * @return the SigAlg value - */ - public String getSigAlg() { - return this.sigAlg; - } - - /** - * Returns the Signature value for {@link Saml2MessageBinding#REDIRECT} requests - * @return the Signature value - */ - public String getSignature() { - return this.signature; - } - - /** - * @return {@link Saml2MessageBinding#REDIRECT} - */ - @Override - public Saml2MessageBinding getBinding() { - return REDIRECT; - } - - /** - * Constructs a {@link Saml2RedirectAuthenticationRequest.Builder} from a {@link Saml2AuthenticationRequestContext} object. - * By default the {@link Saml2RedirectAuthenticationRequest#getAuthenticationRequestUri()} will be set to the - * {@link Saml2AuthenticationRequestContext#getDestination()} value. - * @param context input providing {@code Destination}, {@code RelayState}, and {@code Issuer} objects. - * @return a modifiable builder object - */ - public static Builder withAuthenticationRequestContext(Saml2AuthenticationRequestContext context) { - return new Builder() - .authenticationRequestUri(context.getDestination()) - .relayState(context.getRelayState()) - ; - } - - /** - * Builder class for a {@link Saml2RedirectAuthenticationRequest} object. - */ - public static class Builder extends AbstractSaml2AuthenticationRequest.Builder { - private String sigAlg; - private String signature; - - private Builder() { - super(); - } - - /** - * Sets the {@code SigAlg} parameter that will accompany this AuthNRequest - * @param sigAlg the SigAlg parameter value. - * @return this object - */ - public Builder sigAlg(String sigAlg) { - this.sigAlg = sigAlg; - return _this(); - } - - /** - * Sets the {@code Signature} parameter that will accompany this AuthNRequest - * @param signature the Signature parameter value. - * @return this object - */ - public Builder signature(String signature) { - this.signature = signature; - return _this(); - } - - /** - * Constructs an immutable {@link Saml2RedirectAuthenticationRequest} object. - * @return an immutable {@link Saml2RedirectAuthenticationRequest} object. - */ - public Saml2RedirectAuthenticationRequest build() { - return new Saml2RedirectAuthenticationRequest( - this.samlRequest, - this.sigAlg, - this.signature, - this.relayState, - this.authenticationRequestUri - ); - } - - } - - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java deleted file mode 100644 index ae271df111..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Utils.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2020 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 org.apache.commons.codec.binary.Base64; -import org.springframework.security.saml2.Saml2Exception; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterOutputStream; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.zip.Deflater.DEFLATED; - -/** - * @since 5.3 - */ -final class Saml2Utils { - - - private static Base64 BASE64 = new Base64(0, new byte[]{'\n'}); - - static String samlEncode(byte[] b) { - return BASE64.encodeAsString(b); - } - - static byte[] samlDecode(String s) { - return BASE64.decode(s); - } - - static byte[] samlDeflate(String s) { - try { - ByteArrayOutputStream b = new ByteArrayOutputStream(); - DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true)); - deflater.write(s.getBytes(UTF_8)); - deflater.finish(); - return b.toByteArray(); - } - catch (IOException e) { - throw new Saml2Exception("Unable to deflate string", e); - } - } - - 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(), UTF_8); - } - catch (IOException e) { - throw new Saml2Exception("Unable to inflate string", e); - } - } -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java deleted file mode 100644 index 154dcc88f4..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/Saml2MessageBinding.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2002-2020 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; - -/** - * The type of bindings that messages are exchanged using - * Supported bindings are {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST} - * and {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect}. - * In addition there is support for {@code urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect} - * with an XML signature in the message rather than query parameters. - * @since 5.3 - */ -public enum Saml2MessageBinding { - - POST("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"), - REDIRECT("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"); - - private final String urn; - - Saml2MessageBinding(String s) { - this.urn = s; - } - - /** - * Returns the URN value from the SAML 2 specification for this binding. - * @return URN value representing this binding - */ - public String getUrn() { - return urn; - } -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java deleted file mode 100644 index e78017184e..0000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2ServletUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-2020 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.servlet.filter; - -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; - -import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl; -import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl; - -/** - * @since 5.3 - */ -final class Saml2ServletUtils { - - private static final char PATH_DELIMITER = '/'; - - static String getServiceProviderEntityId(RelyingPartyRegistration rp, HttpServletRequest request) { - return resolveUrlTemplate( - rp.getLocalEntityIdTemplate(), - getApplicationUri(request), - rp.getRemoteIdpEntityId(), - rp.getRegistrationId() - ); - } - - static String resolveUrlTemplate(String template, String baseUrl, String entityId, String registrationId) { - if (!StringUtils.hasText(template)) { - return baseUrl; - } - - Map uriVariables = new HashMap<>(); - UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl) - .replaceQuery(null) - .fragment(null) - .build(); - String scheme = uriComponents.getScheme(); - uriVariables.put("baseScheme", scheme == null ? "" : scheme); - String host = uriComponents.getHost(); - uriVariables.put("baseHost", host == null ? "" : host); - // following logic is based on HierarchicalUriComponents#toUriString() - int port = uriComponents.getPort(); - uriVariables.put("basePort", port == -1 ? "" : ":" + port); - String path = uriComponents.getPath(); - if (StringUtils.hasLength(path)) { - if (path.charAt(0) != PATH_DELIMITER) { - path = PATH_DELIMITER + path; - } - } - uriVariables.put("basePath", path == null ? "" : path); - uriVariables.put("baseUrl", uriComponents.toUriString()); - uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : ""); - uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : ""); - - return UriComponentsBuilder.fromUriString(template) - .buildAndExpand(uriVariables) - .toUriString(); - } - - static String getApplicationUri(HttpServletRequest request) { - UriComponents uriComponents = fromHttpUrl(buildFullRequestUrl(request)) - .replacePath(request.getContextPath()) - .replaceQuery(null) - .fragment(null) - .build(); - return uriComponents.toUriString(); - } -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java index f472a2376b..994db67849 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2Utils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -18,34 +18,43 @@ package org.springframework.security.saml2.provider.service.servlet.filter; import org.apache.commons.codec.binary.Base64; import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterOutputStream; +import javax.servlet.http.HttpServletRequest; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.zip.Deflater.DEFLATED; +import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl; +import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl; /** - * @since 5.3 + * @since 5.2 */ final class Saml2Utils { + private static final char PATH_DELIMITER = '/'; + private static org.apache.commons.codec.binary.Base64 BASE64 = new Base64(0, new byte[]{'\n'}); - private static Base64 BASE64 = new Base64(0, new byte[]{'\n'}); - - static String samlEncode(byte[] b) { + static String encode(byte[] b) { return BASE64.encodeAsString(b); } - static byte[] samlDecode(String s) { + static byte[] decode(String s) { return BASE64.decode(s); } - static byte[] samlDeflate(String s) { + static byte[] deflate(String s) { try { ByteArrayOutputStream b = new ByteArrayOutputStream(); DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true)); @@ -58,7 +67,7 @@ final class Saml2Utils { } } - static String samlInflate(byte[] b) { + static String inflate(byte[] b) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true)); @@ -70,4 +79,55 @@ final class Saml2Utils { throw new Saml2Exception("Unable to inflate string", e); } } + + static String getServiceProviderEntityId(RelyingPartyRegistration rp, HttpServletRequest request) { + return resolveUrlTemplate( + rp.getLocalEntityIdTemplate(), + getApplicationUri(request), + rp.getRemoteIdpEntityId(), + rp.getRegistrationId() + ); + } + + static String resolveUrlTemplate(String template, String baseUrl, String entityId, String registrationId) { + if (!StringUtils.hasText(template)) { + return baseUrl; + } + + Map uriVariables = new HashMap<>(); + UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(baseUrl) + .replaceQuery(null) + .fragment(null) + .build(); + String scheme = uriComponents.getScheme(); + uriVariables.put("baseScheme", scheme == null ? "" : scheme); + String host = uriComponents.getHost(); + uriVariables.put("baseHost", host == null ? "" : host); + // following logic is based on HierarchicalUriComponents#toUriString() + int port = uriComponents.getPort(); + uriVariables.put("basePort", port == -1 ? "" : ":" + port); + String path = uriComponents.getPath(); + if (StringUtils.hasLength(path)) { + if (path.charAt(0) != PATH_DELIMITER) { + path = PATH_DELIMITER + path; + } + } + uriVariables.put("basePath", path == null ? "" : path); + uriVariables.put("baseUrl", uriComponents.toUriString()); + uriVariables.put("entityId", StringUtils.hasText(entityId) ? entityId : ""); + uriVariables.put("registrationId", StringUtils.hasText(registrationId) ? registrationId : ""); + + return UriComponentsBuilder.fromUriString(template) + .buildAndExpand(uriVariables) + .toUriString(); + } + + static String getApplicationUri(HttpServletRequest request) { + UriComponents uriComponents = fromHttpUrl(buildFullRequestUrl(request)) + .replacePath(request.getContextPath()) + .replaceQuery(null) + .fragment(null) + .build(); + return uriComponents.toUriString(); + } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java index 666013f1cc..5c1ba70c53 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java @@ -61,8 +61,8 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce * @param filterProcessesUrl the processing URL, must contain a {registrationId} variable. Required. */ public Saml2WebSsoAuthenticationFilter( - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, - String filterProcessesUrl) { + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, + String filterProcessesUrl) { super(filterProcessesUrl); Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null"); Assert.hasText(filterProcessesUrl, "filterProcessesUrl must contain a URL pattern"); @@ -86,7 +86,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String saml2Response = request.getParameter("SAMLResponse"); - byte[] b = Saml2Utils.samlDecode(saml2Response); + byte[] b = Saml2Utils.decode(saml2Response); String responseXml = inflateIfRequired(request, b); String registrationId = this.matcher.matcher(request).getVariables().get("registrationId"); @@ -97,7 +97,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce "Relying Party Registration not found with ID: " + registrationId); throw new Saml2AuthenticationException(saml2Error); } - String localSpEntityId = Saml2ServletUtils.getServiceProviderEntityId(rp, request); + String localSpEntityId = Saml2Utils.getServiceProviderEntityId(rp, request); final Saml2AuthenticationToken authentication = new Saml2AuthenticationToken( responseXml, request.getRequestURL().toString(), @@ -110,7 +110,7 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce private String inflateIfRequired(HttpServletRequest request, byte[] b) { if (HttpMethod.GET.matches(request.getMethod())) { - return Saml2Utils.samlInflate(b); + return Saml2Utils.inflate(b); } else { return new String(b, UTF_8); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java index 881afda820..b27927647f 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -17,28 +17,29 @@ package org.springframework.security.saml2.provider.service.servlet.filter; import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestFactory; -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.RelyingPartyRegistrationRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static org.springframework.util.StringUtils.hasText; +import static org.springframework.security.saml2.provider.service.servlet.filter.Saml2Utils.deflate; +import static org.springframework.security.saml2.provider.service.servlet.filter.Saml2Utils.encode; /** * @since 5.2 @@ -46,7 +47,9 @@ import static org.springframework.util.StringUtils.hasText; public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter { private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; + private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}"); + private Saml2AuthenticationRequestFactory authenticationRequestFactory = new OpenSamlAuthenticationRequestFactory(); public Saml2WebSsoAuthenticationRequestFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { @@ -88,47 +91,39 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter } private String createSamlRequestRedirectUrl(HttpServletRequest request, RelyingPartyRegistration relyingParty) { - Saml2AuthenticationRequestContext authnRequest = createRedirectAuthenticationRequestContext(relyingParty, request); - Saml2RedirectAuthenticationRequest authNData = - this.authenticationRequestFactory.createRedirectAuthenticationRequest(authnRequest); - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authNData.getAuthenticationRequestUri()); - addParameter("SAMLRequest", authNData.getSamlRequest(), uriBuilder); - addParameter("RelayState", authNData.getRelayState(), uriBuilder); - addParameter("SigAlg", authNData.getSigAlg(), uriBuilder); - addParameter("Signature", authNData.getSignature(), uriBuilder); + Saml2AuthenticationRequest authNRequest = createAuthenticationRequest(relyingParty, request); + String xml = this.authenticationRequestFactory.createAuthenticationRequest(authNRequest); + String encoded = encode(deflate(xml)); + String relayState = request.getParameter("RelayState"); + UriComponentsBuilder uriBuilder = UriComponentsBuilder + .fromUriString(relyingParty.getIdpWebSsoUrl()) + .queryParam("SAMLRequest", UriUtils.encode(encoded, StandardCharsets.ISO_8859_1)); + + if (StringUtils.hasText(relayState)) { + uriBuilder.queryParam("RelayState", UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)); + } + return uriBuilder .build(true) .toUriString(); } - private void addParameter(String name, String value, UriComponentsBuilder builder) { - Assert.hasText(name, "name cannot be empty or null"); - if (hasText(value)) { - builder.queryParam( - UriUtils.encode(name, ISO_8859_1), - UriUtils.encode(value, ISO_8859_1) - ); - } - } - - private Saml2AuthenticationRequestContext createRedirectAuthenticationRequestContext( - RelyingPartyRegistration relyingParty, - HttpServletRequest request) { - String localSpEntityId = Saml2ServletUtils.getServiceProviderEntityId(relyingParty, request); - return Saml2AuthenticationRequestContext + private Saml2AuthenticationRequest createAuthenticationRequest(RelyingPartyRegistration relyingParty, HttpServletRequest request) { + String localSpEntityId = Saml2Utils.getServiceProviderEntityId(relyingParty, request); + return Saml2AuthenticationRequest .builder() .issuer(localSpEntityId) - .relyingPartyRegistration(relyingParty) + .destination(relyingParty.getIdpWebSsoUrl()) + .credentials(c -> c.addAll(relyingParty.getCredentials())) .assertionConsumerServiceUrl( - Saml2ServletUtils.resolveUrlTemplate( + Saml2Utils.resolveUrlTemplate( relyingParty.getAssertionConsumerServiceUrlTemplate(), - Saml2ServletUtils.getApplicationUri(request), + Saml2Utils.getApplicationUri(request), relyingParty.getRemoteIdpEntityId(), relyingParty.getRegistrationId() ) ) - .relayState(request.getParameter("RelayState")) - .build() - ; + .build(); } + } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java index 1142714203..b7823e07c5 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationRequestFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -23,77 +23,39 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AuthnRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode; import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials; -import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.POST; -import static org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding.REDIRECT; -/** - * Tests for {@link OpenSamlAuthenticationRequestFactory} - */ public class OpenSamlAuthenticationRequestFactoryTests { private OpenSamlAuthenticationRequestFactory factory; - private Saml2AuthenticationRequestContext.Builder contextBuilder; - private Saml2AuthenticationRequestContext context; + private Saml2AuthenticationRequest request; @Rule public ExpectedException exception = ExpectedException.none(); @Before public void setUp() { - RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id") - .assertionConsumerServiceUrlTemplate("template") - .idpWebSsoUrl("https://destination/sso") - .remoteIdpEntityId("remote-entity-id") - .localEntityIdTemplate("local-entity-id") + request = Saml2AuthenticationRequest.builder() + .issuer("https://issuer") + .destination("https://destination/sso") + .assertionConsumerServiceUrl("https://issuer/sso") .credentials(c -> c.addAll(relyingPartyCredentials())) .build(); - contextBuilder = Saml2AuthenticationRequestContext.builder() - .issuer("https://issuer") - .relyingPartyRegistration(registration) - .assertionConsumerServiceUrl("https://issuer/sso"); - context = contextBuilder.build(); factory = new OpenSamlAuthenticationRequestFactory(); } - @Test - public void createAuthenticationRequestWhenInvokingDeprecatedMethodThenReturnsXML() { - Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(context).build(); - String result = factory.createAuthenticationRequest(request); - assertThat(result).startsWith("\n signCredentials = relyingPartyCredentials(); - List verifyCredentials = assertingPartyCredentials(); - String samlRequest = "saml-request-example"; - String encoded = Saml2Utils.samlEncode(samlRequest.getBytes(UTF_8)); - String relayState = "test relay state"; - Map parameters = impl.signQueryParameters(signCredentials, encoded, relayState); - - String queryString = "SAMLRequest=" + - UriUtils.encode(encoded, ISO_8859_1) + - "&RelayState=" + - UriUtils.encode(relayState, ISO_8859_1) + - "&SigAlg=" + - UriUtils.encode(ALGO_ID_SIGNATURE_RSA_SHA256, ISO_8859_1); - - - byte[] signature = Saml2Utils.samlDecode(parameters.get("Signature")); - boolean result = XMLSigningUtil.verifyWithURI( - getOpenSamlCredential(verifyCredentials.get(1), "local-sp-entity-id", UsageType.SIGNING), - ALGO_ID_SIGNATURE_RSA_SHA256, - signature, - queryString.getBytes(UTF_8) - ); - assertThat(result).isTrue(); - } - - private Credential getOpenSamlCredential(Saml2X509Credential credential, String localSpEntityId, UsageType usageType) { - BasicCredential cred = CredentialSupport.getSimpleCredential( - credential.getCertificate(), - credential.getPrivateKey() - ); - cred.setEntityId(localSpEntityId); - cred.setUsageType(usageType); - return cred; - } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java deleted file mode 100644 index 6c79ba187d..0000000000 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticationRequestFactoryTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2020 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 org.junit.Test; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlDecode; -import static org.springframework.security.saml2.provider.service.authentication.Saml2Utils.samlInflate; -import static org.springframework.security.saml2.provider.service.authentication.TestSaml2X509Credentials.relyingPartyCredentials; - -/** - * Tests for {@link Saml2AuthenticationRequestFactory} default interface methods - */ -public class Saml2AuthenticationRequestFactoryTests { - - private RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("id") - .assertionConsumerServiceUrlTemplate("template") - .idpWebSsoUrl("https://example.com/destination") - .remoteIdpEntityId("remote-entity-id") - .localEntityIdTemplate("local-entity-id") - .credentials(c -> c.addAll(relyingPartyCredentials())) - .build(); - - @Test - public void createAuthenticationRequestParametersWhenRedirectDefaultIsUsedMessageIsDeflatedAndEncoded() { - final String value = "Test String: "+ UUID.randomUUID().toString(); - Saml2AuthenticationRequestFactory factory = request -> value; - Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder() - .relyingPartyRegistration(registration) - .issuer("https://example.com/issuer") - .assertionConsumerServiceUrl("https://example.com/acs-url") - .build(); - Saml2RedirectAuthenticationRequest response = factory.createRedirectAuthenticationRequest(request); - String resultValue = response.getSamlRequest(); - byte[] decoded = samlDecode(resultValue); - String inflated = samlInflate(decoded); - assertThat(inflated).isEqualTo(value); - } - - @Test - public void createAuthenticationRequestParametersWhenPostDefaultIsUsedMessageIsEncoded() { - final String value = "Test String: "+ UUID.randomUUID().toString(); - Saml2AuthenticationRequestFactory factory = request -> value; - Saml2AuthenticationRequestContext request = Saml2AuthenticationRequestContext.builder() - .relyingPartyRegistration(registration) - .issuer("https://example.com/issuer") - .assertionConsumerServiceUrl("https://example.com/acs-url") - .build(); - Saml2PostAuthenticationRequest response = factory.createPostAuthenticationRequest(request); - String resultValue = response.getSamlRequest(); - byte[] decoded = samlDecode(resultValue); - assertThat(new String(decoded)).isEqualTo(value); - } -} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java index be3405f924..972937715c 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2UtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -50,7 +50,7 @@ public class Saml2UtilsTests { @Test public void decodeWhenUsingSamlUtilsBase64ThenXmlIsValid() throws Exception { String responseUrlDecoded = getSsoCircleEncodedXml(); - String xml = new String(Saml2Utils.samlDecode(responseUrlDecoded), UTF_8); + String xml = new String(Saml2Utils.decode(responseUrlDecoded), UTF_8); validateSsoCircleXml(xml); } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java index 3e2af6e65d..c17e9c820c 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -17,9 +17,10 @@ package org.springframework.security.saml2.provider.service.servlet.filter; import java.io.IOException; -import java.nio.charset.StandardCharsets; import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockFilterChain; @@ -27,22 +28,18 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.web.util.UriUtils; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.security.saml2.provider.service.servlet.filter.TestSaml2SigningCredentials.signingCredential; public class Saml2WebSsoAuthenticationRequestFilterTests { - private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO"; private Saml2WebSsoAuthenticationRequestFilter filter; private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); private MockHttpServletRequest request; - private MockHttpServletResponse response; + private HttpServletResponse response; private MockFilterChain filterChain; - private RelyingPartyRegistration.Builder rpBuilder; @Before public void setup() { @@ -52,61 +49,43 @@ public class Saml2WebSsoAuthenticationRequestFilterTests { request.setPathInfo("/saml2/authenticate/registration-id"); filterChain = new MockFilterChain(); + } - rpBuilder = RelyingPartyRegistration + @Test + public void createSamlRequestRedirectUrlAndReturnUrlWithoutRelayState() throws ServletException, IOException { + RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration .withRegistrationId("registration-id") .remoteIdpEntityId("idp-entity-id") - .idpWebSsoUrl(IDP_SSO_URL) + .idpWebSsoUrl("sso-url") .assertionConsumerServiceUrlTemplate("template") - .credentials(c -> c.add(signingCredential())); - } + .credentials(c -> c.add(signingCredential())) + .build(); + + when(repository.findByRegistrationId("registration-id")) + .thenReturn(relyingPartyRegistration); - @Test - public void doFilterWhenNoRelayStateThenRedirectDoesNotContainParameter() throws ServletException, IOException { - when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build()); filter.doFilterInternal(request, response, filterChain); - assertThat(response.getHeader("Location")) - .doesNotContain("RelayState=") - .startsWith(IDP_SSO_URL); + + Assert.assertFalse(response.getHeader("Location").contains("RelayState=")); } @Test - public void doFilterWhenRelayStateThenRedirectDoesContainParameter() throws ServletException, IOException { - when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build()); + public void createSamlRequestRedirectUrlAndReturnUrlWithRelayState() throws ServletException, IOException { + RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration + .withRegistrationId("registration-id") + .remoteIdpEntityId("idp-entity-id") + .idpWebSsoUrl("sso-url") + .assertionConsumerServiceUrlTemplate("template") + .credentials(c -> c.add(signingCredential())) + .build(); + + when(repository.findByRegistrationId("registration-id")) + .thenReturn(relyingPartyRegistration); + request.setParameter("RelayState", "my-relay-state"); - filter.doFilterInternal(request, response, filterChain); - assertThat(response.getHeader("Location")) - .contains("RelayState=my-relay-state") - .startsWith(IDP_SSO_URL); - } - @Test - public void doFilterWhenRelayStateThatRequiresEncodingThenRedirectDoesContainsEncodedParameter() throws Exception { - when(repository.findByRegistrationId("registration-id")).thenReturn(rpBuilder.build()); - final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param"; - final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1); - request.setParameter("RelayState", relayStateValue); filter.doFilterInternal(request, response, filterChain); - assertThat(response.getHeader("Location")) - .contains("RelayState="+relayStateEncoded) - .startsWith(IDP_SSO_URL); - } - @Test - public void doFilterWhenSimpleSignatureSpecifiedThenSignatureParametersAreInTheRedirectURL() throws Exception { - when(repository.findByRegistrationId("registration-id")).thenReturn( - rpBuilder - .build() - ); - final String relayStateValue = "https://my-relay-state.example.com?with=param&other=param"; - final String relayStateEncoded = UriUtils.encode(relayStateValue, StandardCharsets.ISO_8859_1); - request.setParameter("RelayState", relayStateValue); - filter.doFilterInternal(request, response, filterChain); - assertThat(response.getHeader("Location")) - .contains("RelayState="+relayStateEncoded) - .contains("SigAlg=") - .contains("Signature=") - .startsWith(IDP_SSO_URL); + Assert.assertTrue(response.getHeader("Location").contains("RelayState=my-relay-state")); } - } diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlActionTestingSupport.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/OpenSamlActionTestingSupport.java similarity index 92% rename from samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlActionTestingSupport.java rename to samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/OpenSamlActionTestingSupport.java index 09806bef97..b56eeb1a2d 100644 --- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlActionTestingSupport.java +++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/OpenSamlActionTestingSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -14,9 +14,12 @@ * limitations under the License. */ -package org.springframework.security.saml2.provider.service.authentication; +package org.springframework.security.samples; + +import org.springframework.security.saml2.Saml2Exception; import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty; +import org.apache.commons.codec.binary.Base64; import org.apache.xml.security.algorithms.JCEMapper; import org.apache.xml.security.encryption.XMLCipherParameters; import org.joda.time.DateTime; @@ -54,16 +57,23 @@ import org.opensaml.security.credential.CredentialSupport; import org.opensaml.xmlsec.encryption.support.DataEncryptionParameters; import org.opensaml.xmlsec.encryption.support.EncryptionException; import org.opensaml.xmlsec.encryption.support.KeyEncryptionParameters; -import org.springframework.security.saml2.Saml2Exception; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.crypto.SecretKey; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.X509Certificate; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterOutputStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.crypto.SecretKey; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; +import static java.util.zip.Deflater.DEFLATED; import static org.opensaml.security.crypto.KeySupport.generateKey; /** @@ -73,6 +83,8 @@ import static org.opensaml.security.crypto.KeySupport.generateKey; */ public class OpenSamlActionTestingSupport { + static Base64 UNCHUNKED_ENCODER = new Base64(0, new byte[] { '\n' }); + /** ID used for all generated {@link Response} objects. */ final static String REQUEST_ID = "request"; @@ -82,6 +94,40 @@ public class OpenSamlActionTestingSupport { /** ID used for all generated {@link Assertion} objects. */ final static String ASSERTION_ID = "assertion"; + static String encode(byte[] b) { + return UNCHUNKED_ENCODER.encodeToString(b); + } + + static byte[] decode(String s) { + return UNCHUNKED_ENCODER.decode(s); + } + + static byte[] deflate(String s) { + try { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(DEFLATED, true)); + deflater.write(s.getBytes(UTF_8)); + deflater.finish(); + return b.toByteArray(); + } + catch (IOException e) { + throw new Saml2Exception("Unable to deflate string", e); + } + } + + static String inflate(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(), UTF_8); + } + catch (IOException e) { + throw new Saml2Exception("Unable to inflate string", e); + } + } + static EncryptedAssertion encryptAssertion(Assertion assertion, X509Certificate certificate) { Encrypter encrypter = getEncrypter(certificate); try { diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/Saml2LoginIntegrationTests.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java similarity index 94% rename from samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/Saml2LoginIntegrationTests.java rename to samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java index 1cb224713e..1f40142987 100644 --- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/saml2/provider/service/authentication/Saml2LoginIntegrationTests.java +++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 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. @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.springframework.security.saml2.provider.service.authentication; +package org.springframework.security.samples; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.BasicParserPool; @@ -53,6 +52,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.AssertionErrors; import org.springframework.test.web.servlet.MockMvc; @@ -63,7 +63,6 @@ import org.springframework.web.util.UriComponentsBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; -import javax.servlet.http.HttpSession; import java.io.ByteArrayInputStream; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -74,18 +73,19 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.UUID; +import javax.servlet.http.HttpSession; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.matchesRegex; import static org.hamcrest.Matchers.startsWith; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildConditions; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildIssuer; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubject; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubjectConfirmation; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.buildSubjectConfirmationData; -import static org.springframework.security.saml2.provider.service.authentication.OpenSamlActionTestingSupport.encryptNameId; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildConditions; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildIssuer; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubject; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubjectConfirmation; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildSubjectConfirmationData; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.encryptNameId; +import static org.springframework.security.samples.OpenSamlActionTestingSupport.inflate; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; import static org.springframework.security.web.WebAttributes.AUTHENTICATION_EXCEPTION; @@ -133,15 +133,10 @@ public class Saml2LoginIntegrationTests { mockMvc.perform( get("http://localhost:8080/saml2/authenticate/simplesamlphp") .param("RelayState", "relay state value with spaces") - .param("OtherParam", "OtherParamValue") - .param("OtherParam2", "OtherParamValue2") ) .andExpect(status().is3xxRedirection()) .andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest="))) - .andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces"))) - //check order of parameters - .andExpect(header().string("Location", matchesRegex(".*\\?SAMLRequest\\=.*\\&RelayState\\=.*\\&SigAlg\\=.*\\&Signature\\=.*"))); - + .andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces"))); } @Test @@ -156,7 +151,7 @@ public class Saml2LoginIntegrationTests { String request = parameters.getFirst("SAMLRequest"); AssertionErrors.assertNotNull("SAMLRequest parameter is missing", request); request = URLDecoder.decode(request); - request = Saml2Utils.samlInflate(Saml2Utils.samlDecode(request)); + request = inflate(OpenSamlActionTestingSupport.decode(request)); AuthnRequest authnRequest = (AuthnRequest) fromXml(request); String destination = authnRequest.getDestination(); assertEquals( @@ -303,7 +298,7 @@ public class Saml2LoginIntegrationTests { String xml = toXml(response); return mockMvc.perform(post("http://localhost:8080/login/saml2/sso/simplesamlphp") .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .param("SAMLResponse", Saml2Utils.samlEncode(xml.getBytes(UTF_8)))) + .param("SAMLResponse", OpenSamlActionTestingSupport.encode(xml.getBytes(UTF_8)))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl(redirectUrl)); }