diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java index 6e0df5f..9b48296 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -102,6 +102,7 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter { protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) { return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName, + this.protocol.getServiceParameterName(), this.protocol.getArtifactParameterName(), this.encodeServiceUrl); } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java index 95eff51..92bec84 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java @@ -20,6 +20,7 @@ package org.jasig.cas.client.util; import java.io.*; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.*; @@ -259,70 +260,59 @@ public final class CommonUtils { } /** - * Constructs a service url from the HttpServletRequest or from the given - * serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a - * serviceName. - * - * @param request the HttpServletRequest - * @param response the HttpServletResponse - * @param service the configured service url (this will be used if not null) - * @param serverNames the server name to use to constuct the service url if the service param is empty. Note, prior to CAS Client 3.3, this was a single value. - * As of 3.3, it can be a space-separated value. We keep it as a single value, but will convert it to an array internally to get the matching value. This keeps backward compatability with anything using this public - * method. - * @param artifactParameterName the artifact parameter name to remove (i.e. ticket) - * @param encode whether to encode the url or not (i.e. Jsession). - * @return the service url to use. - */ + * Constructs a service url from the HttpServletRequest or from the given + * serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a + * serviceName. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @param service the configured service url (this will be used if not null) + * @param serverNames the server name to use to constuct the service url if the service param is empty. Note, prior to CAS Client 3.3, this was a single value. + * As of 3.3, it can be a space-separated value. We keep it as a single value, but will convert it to an array internally to get the matching value. This keeps backward compatability with anything using this public + * method. + * @param serviceParameterName the service parameter name to remove (i.e. service) + * @param artifactParameterName the artifact parameter name to remove (i.e. ticket) + * @param encode whether to encode the url or not (i.e. Jsession). + * @return the service url to use. + */ public static String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response, - final String service, final String serverNames, final String artifactParameterName, final boolean encode) { + final String service, final String serverNames, final String serviceParameterName, + final String artifactParameterName, final boolean encode) { if (CommonUtils.isNotBlank(service)) { return encode ? response.encodeURL(service) : service; } - final StringBuilder buffer = new StringBuilder(); - final String serverName = findMatchingServerName(request, serverNames); + final URIBuilder originalRequestUrl = new URIBuilder(request.getRequestURL().toString(), encode); + originalRequestUrl.setParameters(request.getQueryString()); + + URIBuilder builder = null; boolean containsScheme = true; if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) { - buffer.append(request.isSecure() ? "https://" : "http://"); + builder = new URIBuilder(encode); + builder.setScheme(request.isSecure() ? "https" : "http"); + builder.setHost(serverName); containsScheme = false; + } else { + builder = new URIBuilder(serverName, encode); } - buffer.append(serverName); if (!serverNameContainsPort(containsScheme, serverName) && !requestIsOnStandardPort(request)) { - buffer.append(":"); - buffer.append(request.getServerPort()); + builder.setPort(request.getServerPort()); } - buffer.append(request.getRequestURI()); + builder.setEncodedPath(request.getRequestURI()); - if (CommonUtils.isNotBlank(request.getQueryString())) { - final int location = request.getQueryString().indexOf(artifactParameterName + "="); - - if (location == 0) { - final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString(); - LOGGER.debug("serviceUrl generated: {}", returnValue); - return returnValue; - } - - buffer.append("?"); - - if (location == -1) { - buffer.append(request.getQueryString()); - } else if (location > 0) { - final int actualLocation = request.getQueryString().indexOf("&" + artifactParameterName + "="); - - if (actualLocation == -1) { - buffer.append(request.getQueryString()); - } else if (actualLocation > 0) { - buffer.append(request.getQueryString().substring(0, actualLocation)); - } + for (final URIBuilder.BasicNameValuePair pair : originalRequestUrl.getQueryParams()) { + if (!pair.getName().equals(artifactParameterName) && !pair.getName().equals(serviceParameterName)) { + builder.addParameter(pair.getName(), pair.getValue()); } } - final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString(); + final String result = builder.toString(); + final String returnValue = encode ? response.encodeURL(result) : result; LOGGER.debug("serviceUrl generated: {}", returnValue); return returnValue; } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/URIBuilder.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/URIBuilder.java new file mode 100644 index 0000000..5cad257 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/URIBuilder.java @@ -0,0 +1,604 @@ +/* + * Licensed to Jasig under one or more contributor license + * agreements. See the NOTICE file distributed with this work + * for additional information regarding copyright ownership. + * Jasig licenses this file to you 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 the following location: + * + * http://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.jasig.cas.client.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +/** + * A utility class borrowed from apache http-client to build uris. + * @author Misagh Moayyed + * @since 3.4 + */ +public final class URIBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(URIBuilder.class); + private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$"); + + private String scheme; + private String encodedSchemeSpecificPart; + private String encodedAuthority; + private String userInfo; + private String encodedUserInfo; + private String host; + private int port; + private String path; + private String encodedPath; + private String encodedQuery; + private List queryParams; + private String query; + private boolean encode; + private String fragment; + private String encodedFragment; + + /** + * Constructs an empty instance. + */ + public URIBuilder() { + super(); + this.port = -1; + } + + public URIBuilder(final boolean encode) { + this(); + setEncode(encode); + } + + /** + * Construct an instance from the string which must be a valid URI. + * + * @param string a valid URI in string form + * @throws URISyntaxException if the input is not a valid URI + */ + public URIBuilder(final String string) { + super(); + try { + digestURI(new URI(string)); + } catch (final URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public URIBuilder(final String string, boolean encode) { + super(); + try { + setEncode(encode); + digestURI(new URI(string)); + } catch (final URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + + /** + * Construct an instance from the provided URI. + * @param uri + */ + public URIBuilder(final URI uri) { + super(); + digestURI(uri); + } + + private List parseQuery(final String query) { + + try { + final Charset utf8 = Charset.forName("UTF-8"); + if (query != null && !query.isEmpty()) { + final List list = new ArrayList(); + final String queryValue = URLDecoder.decode(query, utf8.name()); + final String[] parametersArray = queryValue.split("&"); + + for (final String parameter : parametersArray) { + final String[] parameterCombo = parameter.split("="); + if (parameterCombo.length == 2) { + list.add(new BasicNameValuePair(parameterCombo[0], parameterCombo[1])); + } + } + return list; + } + } catch (final UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } + return Collections.emptyList(); + } + + /** + * Builds a {@link URI} instance. + */ + public URI build() { + try { + return new URI(buildString()); + } catch (final URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static boolean isIPv6Address(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + private String buildString() { + final StringBuilder sb = new StringBuilder(); + if (this.scheme != null) { + sb.append(this.scheme).append(':'); + } + if (this.encodedSchemeSpecificPart != null) { + sb.append(this.encodedSchemeSpecificPart); + } else { + if (this.encodedAuthority != null) { + sb.append("//").append(this.encodedAuthority); + } else if (this.host != null) { + sb.append("//"); + if (this.encodedUserInfo != null) { + sb.append(this.encodedUserInfo).append("@"); + } else if (this.userInfo != null) { + sb.append(encodeUserInfo(this.userInfo)).append("@"); + } + if (isIPv6Address(this.host)) { + sb.append("[").append(this.host).append("]"); + } else { + sb.append(this.host); + } + if (this.port >= 0) { + sb.append(":").append(this.port); + } + } + if (this.encodedPath != null) { + sb.append(normalizePath(this.encodedPath)); + } else if (this.path != null) { + sb.append(encodePath(normalizePath(this.path))); + } + if (this.encodedQuery != null) { + sb.append("?").append(this.encodedQuery); + } else if (this.queryParams != null && !this.queryParams.isEmpty()) { + sb.append("?").append(encodeUrlForm(this.queryParams)); + } else if (this.query != null) { + sb.append("?").append(encodeUric(this.query)); + } + } + if (this.encodedFragment != null) { + sb.append("#").append(this.encodedFragment); + } else if (this.fragment != null) { + sb.append("#").append(encodeUric(this.fragment)); + } + return sb.toString(); + } + + public URIBuilder digestURI(final URI uri) { + this.scheme = uri.getScheme(); + this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart(); + this.encodedAuthority = uri.getRawAuthority(); + this.host = uri.getHost(); + this.port = uri.getPort(); + this.encodedUserInfo = uri.getRawUserInfo(); + this.userInfo = uri.getUserInfo(); + this.encodedPath = uri.getRawPath(); + this.path = uri.getPath(); + this.encodedQuery = uri.getRawQuery(); + this.queryParams = parseQuery(uri.getRawQuery()); + this.encodedFragment = uri.getRawFragment(); + this.fragment = uri.getFragment(); + return this; + } + + private String encodeUserInfo(final String userInfo) { + return this.encode ? CommonUtils.urlEncode(userInfo) : userInfo; + } + + private String encodePath(final String path) { + return this.encode ? CommonUtils.urlEncode(path) : path; + } + + private String encodeUrlForm(final List params) { + final StringBuilder result = new StringBuilder(); + for (final BasicNameValuePair parameter : params) { + final String encodedName = this.encode ? CommonUtils.urlEncode(parameter.getName()) : parameter.getName(); + final String encodedValue = this.encode ? CommonUtils.urlEncode(parameter.getValue()) : parameter.getValue(); + + if (result.length() > 0) { + result.append("&"); + } + result.append(encodedName); + if (encodedValue != null) { + result.append("="); + result.append(encodedValue); + } + } + return result.toString(); + } + + private String encodeUric(final String fragment) { + return this.encode ? CommonUtils.urlEncode(fragment) : fragment; + } + + public void setEncode(boolean encode) { + this.encode = encode; + } + + /** + * Sets URI scheme. + */ + public URIBuilder setScheme(final String scheme) { + this.scheme = scheme; + return this; + } + + /** + * Sets URI user info. The value is expected to be unescaped and may contain non ASCII + * characters. + */ + public URIBuilder setUserInfo(final String userInfo) { + this.userInfo = userInfo; + this.encodedSchemeSpecificPart = null; + this.encodedAuthority = null; + this.encodedUserInfo = null; + return this; + } + + /** + * Sets URI user info as a combination of username and password. These values are expected to + * be unescaped and may contain non ASCII characters. + */ + public URIBuilder setUserInfo(final String username, final String password) { + return setUserInfo(username + ':' + password); + } + + /** + * Sets URI host. + */ + public URIBuilder setHost(final String host) { + this.host = host; + this.encodedSchemeSpecificPart = null; + this.encodedAuthority = null; + return this; + } + + /** + * Sets URI port. + */ + public URIBuilder setPort(final int port) { + this.port = port < 0 ? -1 : port; + this.encodedSchemeSpecificPart = null; + this.encodedAuthority = null; + return this; + } + + /** + * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters. + */ + public URIBuilder setPath(final String path) { + this.path = path; + this.encodedSchemeSpecificPart = null; + this.encodedPath = null; + return this; + } + + public URIBuilder setEncodedPath(final String path) { + this.encodedPath = path; + this.encodedSchemeSpecificPart = null; + return this; + } + + /** + * Removes URI query. + */ + public URIBuilder removeQuery() { + this.queryParams = null; + this.query = null; + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + + /** + * Sets URI query parameters. The parameter name / values are expected to be unescaped + * and may contain non ASCII characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove custom query if present. + *

+ */ + public URIBuilder setParameters(final List nvps) { + this.queryParams = new ArrayList(); + this.queryParams.addAll(nvps); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + public URIBuilder setParameters(final String queryParameters) { + this.queryParams = new ArrayList(); + this.queryParams.addAll(parseQuery(queryParameters)); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + + + /** + * Adds URI query parameters. The parameter name / values are expected to be unescaped + * and may contain non ASCII characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove custom query if present. + *

+ */ + public URIBuilder addParameters(final List nvps) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } + this.queryParams.addAll(nvps); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + /** + * Sets URI query parameters. The parameter name / values are expected to be unescaped + * and may contain non ASCII characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove custom query if present. + *

+ */ + public URIBuilder setParameters(final BasicNameValuePair... nvps) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } else { + this.queryParams.clear(); + } + for (final BasicNameValuePair nvp: nvps) { + this.queryParams.add(nvp); + } + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + /** + * Adds parameter to URI query. The parameter name and value are expected to be unescaped + * and may contain non ASCII characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove custom query if present. + *

+ */ + public URIBuilder addParameter(final String param, final String value) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } + this.queryParams.add(new BasicNameValuePair(param, value)); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + /** + * Sets parameter of URI query overriding existing value if set. The parameter name and value + * are expected to be unescaped and may contain non ASCII characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove custom query if present. + *

+ */ + public URIBuilder setParameter(final String param, final String value) { + if (this.queryParams == null) { + this.queryParams = new ArrayList(); + } + if (!this.queryParams.isEmpty()) { + for (final Iterator it = this.queryParams.iterator(); it.hasNext(); ) { + final BasicNameValuePair nvp = it.next(); + if (nvp.getName().equals(param)) { + it.remove(); + } + } + } + this.queryParams.add(new BasicNameValuePair(param, value)); + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.query = null; + return this; + } + + /** + * Clears URI query parameters. + */ + public URIBuilder clearParameters() { + this.queryParams = null; + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + return this; + } + + /** + * Sets custom URI query. The value is expected to be unescaped and may contain non ASCII + * characters. + *

+ * Please note query parameters and custom query component are mutually exclusive. This method + * will remove query parameters if present. + *

+ */ + public URIBuilder setCustomQuery(final String query) { + this.query = query; + this.encodedQuery = null; + this.encodedSchemeSpecificPart = null; + this.queryParams = null; + return this; + } + + /** + * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII + * characters. + */ + public URIBuilder setFragment(final String fragment) { + this.fragment = fragment; + this.encodedFragment = null; + return this; + } + + public boolean isAbsolute() { + return this.scheme != null; + } + + public boolean isOpaque() { + return this.path == null; + } + + public String getScheme() { + return this.scheme; + } + + public String getUserInfo() { + return this.userInfo; + } + + public String getHost() { + return this.host; + } + + public int getPort() { + return this.port; + } + + public String getPath() { + return this.path; + } + + public List getQueryParams() { + if (this.queryParams != null) { + return new ArrayList(this.queryParams); + } + return new ArrayList(); + + } + + public String getFragment() { + return this.fragment; + } + + @Override + public String toString() { + return buildString(); + } + + private static String normalizePath(final String path) { + String s = path; + if (s == null) { + return null; + } + int n = 0; + for (; n < s.length(); n++) { + if (s.charAt(n) != '/') { + break; + } + } + if (n > 1) { + s = s.substring(n - 1); + } + return s; + } + + public static class BasicNameValuePair implements Cloneable, Serializable { + private static final long serialVersionUID = -6437800749411518984L; + + private final String name; + private final String value; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicNameValuePair(final String name, final String value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + @Override + public String toString() { + // don't call complex default formatting for a simple toString + + if (this.value == null) { + return name; + } + final int len = this.name.length() + 1 + this.value.length(); + final StringBuilder buffer = new StringBuilder(len); + buffer.append(this.name); + buffer.append("="); + buffer.append(this.value); + return buffer.toString(); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + + if (object == null) { + return false; + } + + if (object instanceof BasicNameValuePair) { + final BasicNameValuePair that = (BasicNameValuePair) object; + return this.name.equals(that.name) + && this.value.equals(that.value); + } + return false; + } + + @Override + public int hashCode() { + return 133 * this.name.hashCode() * this.value.hashCode(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java index 1b28ad5..aaac4e6 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; import junit.framework.TestCase; +import org.jasig.cas.client.Protocol; import org.jasig.cas.client.PublicTestHttpServer; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; import org.springframework.mock.web.MockHttpServletRequest; @@ -132,11 +133,96 @@ public final class CommonUtilsTests extends TestCase { request.setSecure(true); final MockHttpServletResponse response = new MockHttpServletResponse(); final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", - "ticket", false); + "service", "ticket", false); assertEquals(CONST_MY_URL, constructedUrl); } + public void testConstructServiceUrlWithParamsCas() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("service=this&ticket=that&custom=custom"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.CAS3.getServiceParameterName(), Protocol.CAS3.getArtifactParameterName() , false); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom", constructedUrl); + } + + + public void testConstructServiceUrlWithParamsSaml() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("TARGET=this&SAMLart=that&custom=custom"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.SAML11.getServiceParameterName(), Protocol.SAML11.getArtifactParameterName() , false); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom", constructedUrl); + } + + public void testConstructServiceUrlWithEncodedParamsSaml() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("TARGET%3Dthis%26SAMLart%3Dthat%26custom%3Dcustom"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.SAML11.getServiceParameterName(), Protocol.SAML11.getArtifactParameterName() , false); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom", constructedUrl); + } + + public void testConstructServiceUrlWithEncodedParams2Saml() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("TARGET%3Dthis%26SAMLart%3Dthat%26custom%3Dcustom%20value%20here%26another%3Dgood"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.SAML11.getServiceParameterName(), Protocol.SAML11.getArtifactParameterName() , true); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom+value+here&another=good", constructedUrl); + } + + public void testConstructServiceUrlWithoutEncodedParamsSamlAndNoEncoding() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("TARGET=this&SAMLart=that&custom=custom value here&another=good"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.SAML11.getServiceParameterName(), Protocol.SAML11.getArtifactParameterName() , false); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom value here&another=good", constructedUrl); + } + + public void testConstructServiceUrlWithEncodedParamsSamlAndNoEncoding() { + final String CONST_MY_URL = "https://www.myserver.com/hello/hithere/"; + final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); + request.setScheme("https"); + request.setSecure(true); + request.setQueryString("TARGET=this&SAMLart=that&custom=custom+value+here&another=good"); + + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, "www.myserver.com", + Protocol.SAML11.getServiceParameterName(), Protocol.SAML11.getArtifactParameterName() , true); + + assertEquals("https://www.myserver.com/hello/hithere/?custom=custom+value+here&another=good", constructedUrl); + } + private void constructUrlNonStandardPortAndNoPortInConfigTest(final String serverNameList) { final String CONST_MY_URL = "https://www.myserver.com:555/hello/hithere/"; final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hello/hithere/"); @@ -146,7 +232,7 @@ public final class CommonUtilsTests extends TestCase { request.setServerPort(555); final MockHttpServletResponse response = new MockHttpServletResponse(); final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, - serverNameList, "ticket", false); + serverNameList, "service", "ticket", false); assertEquals(CONST_MY_URL, constructedUrl); } @@ -166,7 +252,7 @@ public final class CommonUtilsTests extends TestCase { request.setSecure(true); final MockHttpServletResponse response = new MockHttpServletResponse(); final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, - "www.amazon.com www.bestbuy.com www.myserver.com", "ticket", false); + "www.amazon.com www.bestbuy.com www.myserver.com", "service", "ticket", false); assertEquals(CONST_MY_URL, constructedUrl); } @@ -178,7 +264,7 @@ public final class CommonUtilsTests extends TestCase { request.setSecure(true); final MockHttpServletResponse response = new MockHttpServletResponse(); final String constructedUrl = CommonUtils.constructServiceUrl(request, response, null, - "http://www.amazon.com https://www.bestbuy.com https://www.myserver.com", "ticket", false); + "http://www.amazon.com https://www.bestbuy.com https://www.myserver.com", "service", "ticket", false); assertEquals(CONST_MY_URL, constructedUrl); } diff --git a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java index 3f92d32..9fae1a6 100644 --- a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java +++ b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java @@ -86,7 +86,7 @@ public final class AuthenticatorDelegate { logger.debug("CAS assertion not found in session -- authentication required."); final String token = request.getParameter(this.artifactParameterName); final String service = CommonUtils.constructServiceUrl(request, response, this.serviceUrl, this.serverName, - this.artifactParameterName, true); + this.serviceParameterName, this.artifactParameterName, true); if (CommonUtils.isBlank(token)) { final String redirectUrl = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.serviceParameterName, service, false, false);