>();
+
+ static {
+ PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);
+ PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);
+ PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);
+ }
+
+ public AuthenticationFilter() {
+ this(Protocol.CAS2);
+ }
+
+ protected AuthenticationFilter(final Protocol protocol) {
+ super(protocol);
+ }
+
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
if (!isIgnoreInitConfiguration()) {
super.initInternal(filterConfig);
- setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "casServerLoginUrl", null));
- log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
- setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
- log.trace("Loaded renew parameter: " + this.renew);
- setGateway(parseBoolean(getPropertyFromInitParams(filterConfig, "gateway", "false")));
- log.trace("Loaded gateway parameter: " + this.gateway);
-
- final String gatewayStorageClass = getPropertyFromInitParams(filterConfig, "gatewayStorageClass", null);
+ setCasServerLoginUrl(getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL));
+ setRenew(getBoolean(ConfigurationKeys.RENEW));
+ setGateway(getBoolean(ConfigurationKeys.GATEWAY));
+
+ final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
+ final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
+
+ if (ignorePattern != null) {
+ final Class extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
+ if (ignoreUrlMatcherClass != null) {
+ this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
+ } else {
+ try {
+ logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
+ this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
+ } catch (final IllegalArgumentException e) {
+ logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
+ }
+ }
+ if (this.ignoreUrlPatternMatcherStrategyClass != null) {
+ this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
+ }
+ }
+
+ final Class extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
if (gatewayStorageClass != null) {
- try {
- this.gatewayStorage = (GatewayResolver) Class.forName(gatewayStorageClass).newInstance();
- } catch (final Exception e) {
- log.error(e,e);
- throw new ServletException(e);
- }
+ setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
+ }
+
+ final Class extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
+
+ if (authenticationRedirectStrategyClass != null) {
+ this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
}
}
}
@@ -97,9 +136,18 @@ public void init() {
CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
}
- public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+ public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
+
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+ if (isRequestUrlExcluded(request)) {
+ logger.debug("Request is ignored.");
+ filterChain.doFilter(request, response);
+ return;
+ }
+
final HttpSession session = request.getSession(false);
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
@@ -119,25 +167,21 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes
final String modifiedServiceUrl;
- log.debug("no ticket and no assertion found");
+ logger.debug("no ticket and no assertion found");
if (this.gateway) {
- log.debug("setting gateway attribute in session");
+ logger.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
- if (log.isDebugEnabled()) {
- log.debug("Constructed service url: " + modifiedServiceUrl);
- }
+ logger.debug("Constructed service url: {}", modifiedServiceUrl);
- final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
-
- if (log.isDebugEnabled()) {
- log.debug("redirecting to \"" + urlToRedirectTo + "\"");
- }
+ final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
+ getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
- response.sendRedirect(urlToRedirectTo);
+ logger.debug("redirecting to \"{}\"", urlToRedirectTo);
+ this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
}
public final void setRenew(final boolean renew) {
@@ -151,8 +195,21 @@ public final void setGateway(final boolean gateway) {
public final void setCasServerLoginUrl(final String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
-
+
public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
- this.gatewayStorage = gatewayStorage;
+ this.gatewayStorage = gatewayStorage;
+ }
+
+ private boolean isRequestUrlExcluded(final HttpServletRequest request) {
+ if (this.ignoreUrlPatternMatcherStrategyClass == null) {
+ return false;
+ }
+
+ final StringBuffer urlBuffer = request.getRequestURL();
+ if (request.getQueryString() != null) {
+ urlBuffer.append("?").append(request.getQueryString());
+ }
+ final String requestUri = urlBuffer.toString();
+ return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java
new file mode 100644
index 000000000..006458be5
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationRedirectStrategy.java
@@ -0,0 +1,46 @@
+/*
+ * 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.authentication;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Interface to abstract the authentication strategy for redirecting. The traditional method was to always just redirect,
+ * but due to AJAX, etc. we may need to support other strategies. This interface is designed to hold that logic such that
+ * authentication filter class does not get crazily complex.
+ *
+ * @author Scott Battaglia
+ * @since 3.3.0
+ */
+public interface AuthenticationRedirectStrategy {
+
+ /**
+ * Method name is a bit of a misnomer. This method handles "redirection" for a localized version of redirection (i.e. AJAX might mean an XML fragment that contains the url to go to).
+ *
+ * @param request the original HttpServletRequest. MAY NOT BE NULL.
+ * @param response the original HttpServletResponse. MAY NOT BE NULL.
+ * @param potentialRedirectUrl the url that might be used (there are no guarantees of course!)
+ * @throws IOException the exception to throw if there is some type of error. This will bubble up through the filter.
+ */
+ void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl)
+ throws IOException;
+
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java
new file mode 100644
index 000000000..48c9f5ff9
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ContainsPatternUrlPatternMatcherStrategy.java
@@ -0,0 +1,38 @@
+/*
+ * 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.authentication;
+
+/**
+ * A pattern matcher that looks inside the url to find the exact pattern specified.
+ *
+ * @author Misagh Moayyed
+ * @since 3.3.1
+ */
+public final class ContainsPatternUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy {
+
+ private String pattern;
+
+ public boolean matches(final String url) {
+ return url.contains(this.pattern);
+ }
+
+ public void setPattern(final String pattern) {
+ this.pattern = pattern;
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java
new file mode 100644
index 000000000..d38daed81
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultAuthenticationRedirectStrategy.java
@@ -0,0 +1,37 @@
+/*
+ * 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.authentication;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Implementation of the {@link AuthenticationRedirectStrategy} class that preserves the original behavior that existed prior to 3.3.0.
+ *
+ * @author Scott Battaglia
+ * @since 3.3.0
+ */
+public final class DefaultAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
+
+ public void redirect(final HttpServletRequest request, final HttpServletResponse response,
+ final String potentialRedirectUrl) throws IOException {
+ response.sendRedirect(potentialRedirectUrl);
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultGatewayResolverImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultGatewayResolverImpl.java
index 7b363690f..d0d24756d 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultGatewayResolverImpl.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/DefaultGatewayResolverImpl.java
@@ -1,47 +1,44 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public final class DefaultGatewayResolverImpl implements GatewayResolver {
-
+
public static final String CONST_CAS_GATEWAY = "_const_cas_gateway_";
- public boolean hasGatewayedAlready(final HttpServletRequest request,
- final String serviceUrl) {
- final HttpSession session = request.getSession(false);
-
- if (session == null) {
- return false;
- }
-
- final boolean result = session.getAttribute(CONST_CAS_GATEWAY) != null;
- session.removeAttribute(CONST_CAS_GATEWAY);
- return result;
- }
+ public boolean hasGatewayedAlready(final HttpServletRequest request, final String serviceUrl) {
+ final HttpSession session = request.getSession(false);
+
+ if (session == null) {
+ return false;
+ }
+
+ final boolean result = session.getAttribute(CONST_CAS_GATEWAY) != null;
+ session.removeAttribute(CONST_CAS_GATEWAY);
+ return result;
+ }
- public String storeGatewayInformation(final HttpServletRequest request,
- final String serviceUrl) {
- request.getSession(true).setAttribute(CONST_CAS_GATEWAY, "yes");
- return serviceUrl;
- }
+ public String storeGatewayInformation(final HttpServletRequest request, final String serviceUrl) {
+ request.getSession(true).setAttribute(CONST_CAS_GATEWAY, "yes");
+ return serviceUrl;
+ }
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java
new file mode 100644
index 000000000..9fd0ddf4e
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/ExactUrlPatternMatcherStrategy.java
@@ -0,0 +1,46 @@
+/*
+ * 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.authentication;
+
+/**
+ * A pattern matcher that produces a successful match if the pattern
+ * specified matches the given url exactly and equally.
+ *
+ * @author Misagh Moayyed
+ * @since 3.3.1
+ */
+public final class ExactUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy {
+
+ private String pattern;
+
+ public ExactUrlPatternMatcherStrategy() {}
+
+ public ExactUrlPatternMatcherStrategy(final String pattern) {
+ this.setPattern(pattern);
+ }
+
+ public boolean matches(final String url) {
+ return url.equals(this.pattern);
+ }
+
+ public void setPattern(final String pattern) {
+ this.pattern = pattern;
+ }
+
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java
new file mode 100644
index 000000000..29dce2f70
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/FacesCompatibleAuthenticationRedirectStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.jasig.cas.client.util.CommonUtils;
+
+/**
+ * Implementation of the redirect strategy that can handle a Faces Ajax request in addition to the standard redirect style.
+ *
+ * @author Scott Battaglia
+ * @since 3.3.0
+ */
+public final class FacesCompatibleAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
+
+ private static final String FACES_PARTIAL_AJAX_PARAMETER = "javax.faces.partial.ajax";
+
+ public void redirect(final HttpServletRequest request, final HttpServletResponse response,
+ final String potentialRedirectUrl) throws IOException {
+
+ if (CommonUtils.isNotBlank(request.getParameter(FACES_PARTIAL_AJAX_PARAMETER))) {
+ // this is an ajax request - redirect ajaxly
+ response.setContentType("text/xml");
+ response.setStatus(200);
+
+ final PrintWriter writer = response.getWriter();
+ writer.write("");
+ writer.write(String.format("",
+ potentialRedirectUrl));
+ } else {
+ response.sendRedirect(potentialRedirectUrl);
+ }
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/GatewayResolver.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/GatewayResolver.java
index 9c587fb93..9499a1947 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/GatewayResolver.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/GatewayResolver.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.authentication;
import javax.servlet.http.HttpServletRequest;
@@ -32,21 +31,21 @@
*/
public interface GatewayResolver {
- /**
- * Determines if the request has been gatewayed already. Should also do gateway clean up.
- *
- * @param request the Http Servlet Request
- * @param serviceUrl the service url
- * @return true if yes, false otherwise.
- */
- boolean hasGatewayedAlready(HttpServletRequest request, String serviceUrl);
-
- /**
- * Storage the request for gatewaying and return the service url, which can be modified.
- *
- * @param request the HttpServletRequest.
- * @param serviceUrl the service url
- * @return the potentially modified service url to redirect to
- */
- String storeGatewayInformation(HttpServletRequest request, String serviceUrl);
+ /**
+ * Determines if the request has been gatewayed already. Should also do gateway clean up.
+ *
+ * @param request the Http Servlet Request
+ * @param serviceUrl the service url
+ * @return true if yes, false otherwise.
+ */
+ boolean hasGatewayedAlready(HttpServletRequest request, String serviceUrl);
+
+ /**
+ * Storage the request for gatewaying and return the service url, which can be modified.
+ *
+ * @param request the HttpServletRequest.
+ * @param serviceUrl the service url
+ * @return the potentially modified service url to redirect to
+ */
+ String storeGatewayInformation(HttpServletRequest request, String serviceUrl);
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java
new file mode 100644
index 000000000..e5665cdf7
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/RegexUrlPatternMatcherStrategy.java
@@ -0,0 +1,47 @@
+/*
+ * 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.authentication;
+
+import java.util.regex.Pattern;
+
+/**
+ * A pattern matcher that looks inside the url to find the pattern,. that
+ * is assumed to have been specified via regular expressions syntax.
+ *
+ * @author Misagh Moayyed
+ * @since 3.3.1
+ */
+public final class RegexUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy {
+
+ private Pattern pattern;
+
+ public RegexUrlPatternMatcherStrategy() {}
+
+ public RegexUrlPatternMatcherStrategy(final String pattern) {
+ this.setPattern(pattern);
+ }
+
+ public boolean matches(final String url) {
+ return this.pattern.matcher(url).find();
+ }
+
+ public void setPattern(final String pattern) {
+ this.pattern = Pattern.compile(pattern);
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java
deleted file mode 100644
index b0db0c223..000000000
--- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * 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:
- *
- * 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.authentication;
-
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-
-/**
- * Extension to the default Authentication filter that sets the required SAML1.1 artifact parameter name and service parameter name.
- *
- * Note, as of 3.3, the final keyword was removed to allow you to override the method to retrieve tickets, per CASC-154s
- *
- * @author Scott Battaglia
- * @since 3.1.12
- * @version $Revision$ $Date$
- */
-public class Saml11AuthenticationFilter extends AuthenticationFilter {
-
- protected final void initInternal(final FilterConfig filterConfig) throws ServletException {
- super.initInternal(filterConfig);
-
- log.warn("SAML1.1 compliance requires the [artifactParameterName] and [serviceParameterName] to be set to specified values.");
- log.warn("This filter will overwrite any user-provided values (if any are provided)");
-
- setArtifactParameterName("SAMLart");
- setServiceParameterName("TARGET");
- }
-}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimpleGroup.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimpleGroup.java
index 0abe6029a..37e7f5930 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimpleGroup.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimpleGroup.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.authentication;
import java.security.Principal;
@@ -65,7 +64,7 @@ public Enumeration extends Principal> members() {
public boolean removeMember(final Principal user) {
return this.members.remove(user);
}
-
+
public String toString() {
return super.toString() + ": " + members.toString();
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimplePrincipal.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimplePrincipal.java
index fba5e09aa..4f73a7340 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimplePrincipal.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/SimplePrincipal.java
@@ -1,27 +1,25 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.authentication;
import java.io.Serializable;
import java.security.Principal;
-
import org.jasig.cas.client.util.CommonUtils;
/**
@@ -63,7 +61,7 @@ public boolean equals(final Object o) {
} else if (!(o instanceof SimplePrincipal)) {
return false;
} else {
- return getName().equals(((SimplePrincipal)o).getName());
+ return getName().equals(((SimplePrincipal) o).getName());
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java
new file mode 100644
index 000000000..a2e70e900
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/UrlPatternMatcherStrategy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.authentication;
+/**
+ * Defines an abstraction by which request urls can be matches against a given pattern.
+ * New instances for all extensions for this strategy interface will be created per
+ * each request. The client will ultimately invoke the {@link #matches(String)} method
+ * having already applied and set the pattern via the {@link #setPattern(String)} method.
+ * The pattern itself will be retrieved via the client configuration.
+ * @author Misagh Moayyed
+ * @since 3.3.1
+ */
+public interface UrlPatternMatcherStrategy {
+ /**
+ * Execute the match between the given pattern and the url
+ * @param url the request url typically with query strings included
+ * @return true if match is successful
+ */
+ boolean matches(String url);
+
+ /**
+ * The pattern against which the url is compared
+ * @param pattern
+ */
+ void setPattern(String pattern);
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/BaseConfigurationStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/BaseConfigurationStrategy.java
new file mode 100644
index 000000000..0ae9d976e
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/BaseConfigurationStrategy.java
@@ -0,0 +1,111 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+import org.jasig.cas.client.util.ReflectUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class to provide most of the boiler-plate code (i.e. checking for proper values, returning defaults, etc.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public abstract class BaseConfigurationStrategy implements ConfigurationStrategy {
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ public final boolean getBoolean(final ConfigurationKey configurationKey) {
+ return getValue(configurationKey, new Parser() {
+ public Boolean parse(final String value) {
+ return CommonUtils.toBoolean(value);
+ }
+ });
+ }
+
+ public final long getLong(final ConfigurationKey configurationKey) {
+ return getValue(configurationKey, new Parser() {
+ public Long parse(final String value) {
+ return CommonUtils.toLong(value, configurationKey.getDefaultValue());
+ }
+ });
+ }
+
+ public final int getInt(final ConfigurationKey configurationKey) {
+ return getValue(configurationKey, new Parser() {
+ public Integer parse(final String value) {
+ return CommonUtils.toInt(value, configurationKey.getDefaultValue());
+ }
+ });
+ }
+
+ public final String getString(final ConfigurationKey configurationKey) {
+ return getValue(configurationKey, new Parser() {
+ public String parse(final String value) {
+ return value;
+ }
+ });
+ }
+
+ public Class extends T> getClass(final ConfigurationKey> configurationKey) {
+ return getValue(configurationKey, new Parser>() {
+ public Class extends T> parse(final String value) {
+ try {
+ return ReflectUtils.loadClass(value);
+ } catch (final IllegalArgumentException e) {
+ return configurationKey.getDefaultValue();
+ }
+ }
+ });
+ }
+
+ private T getValue(final ConfigurationKey configurationKey, final Parser parser) {
+ final String value = getWithCheck(configurationKey);
+
+ if (CommonUtils.isBlank(value)) {
+ logger.trace("No value found for property {}, returning default {}", configurationKey.getName(), configurationKey.getDefaultValue());
+ return configurationKey.getDefaultValue();
+ } else {
+ logger.trace("Loaded property {} with value {}", configurationKey.getName(), configurationKey.getDefaultValue());
+ }
+
+ return parser.parse(value);
+ }
+
+ private String getWithCheck(final ConfigurationKey configurationKey) {
+ CommonUtils.assertNotNull(configurationKey, "configurationKey cannot be null");
+
+ return get(configurationKey);
+ }
+
+ /**
+ * Retrieve the String value for this key. Returns null if there is no value.
+ *
+ * @param configurationKey the key to retrieve. MUST NOT BE NULL.
+ * @return the String if its found, null otherwise.
+ */
+ protected abstract String get(ConfigurationKey configurationKey);
+
+ private interface Parser {
+
+ T parse(String value);
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKey.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKey.java
new file mode 100644
index 000000000..da6a3a19f
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKey.java
@@ -0,0 +1,63 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+
+/**
+ * Holder class to represent a particular configuration key and its optional default value.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public final class ConfigurationKey {
+
+ private final String name;
+
+ private final E defaultValue;
+
+ public ConfigurationKey(final String name) {
+ this(name, null);
+ }
+
+ public ConfigurationKey(final String name, final E defaultValue) {
+ CommonUtils.assertNotNull(name, "name must not be null.");
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * The referencing name of the configuration key (i.e. what you would use to look it up in your configuration strategy)
+ *
+ * @return the name. MUST NOT BE NULL.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+
+ /**
+ * The (optional) default value to use when this configuration key is not set. If a value is provided it should be used. A null value indicates that there is no default.
+ *
+ * @return the default value or null.
+ */
+ public E getDefaultValue() {
+ return this.defaultValue;
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java
new file mode 100644
index 000000000..14ec12062
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java
@@ -0,0 +1,82 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.authentication.AuthenticationRedirectStrategy;
+import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl;
+import org.jasig.cas.client.authentication.GatewayResolver;
+import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
+import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
+import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
+
+import javax.net.ssl.HostnameVerifier;
+
+/**
+ * Holder interface for all known configuration keys.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public interface ConfigurationKeys {
+
+ ConfigurationKey ARTIFACT_PARAMETER_NAME = new ConfigurationKey("artifactParameterName", Protocol.CAS2.getArtifactParameterName());
+ ConfigurationKey SERVER_NAME = new ConfigurationKey("serverName", null);
+ ConfigurationKey SERVICE = new ConfigurationKey("service");
+ ConfigurationKey RENEW = new ConfigurationKey("renew", Boolean.FALSE);
+ ConfigurationKey LOGOUT_PARAMETER_NAME = new ConfigurationKey("logoutParameterName", "logoutRequest");
+ ConfigurationKey ARTIFACT_PARAMETER_OVER_POST = new ConfigurationKey("artifactParameterOverPost", Boolean.FALSE);
+ ConfigurationKey EAGERLY_CREATE_SESSIONS = new ConfigurationKey("eagerlyCreateSessions", Boolean.TRUE);
+ ConfigurationKey ENCODE_SERVICE_URL = new ConfigurationKey("encodeServiceUrl", Boolean.TRUE);
+ ConfigurationKey SSL_CONFIG_FILE = new ConfigurationKey("sslConfigFile", null);
+ ConfigurationKey ROLE_ATTRIBUTE = new ConfigurationKey("roleAttribute", null);
+ ConfigurationKey IGNORE_CASE = new ConfigurationKey("ignoreCase", Boolean.FALSE);
+ ConfigurationKey CAS_SERVER_LOGIN_URL = new ConfigurationKey("casServerLoginUrl", null);
+ ConfigurationKey GATEWAY = new ConfigurationKey("gateway", Boolean.FALSE);
+ ConfigurationKey> AUTHENTICATION_REDIRECT_STRATEGY_CLASS = new ConfigurationKey>("authenticationRedirectStrategyClass", null);
+ ConfigurationKey> GATEWAY_STORAGE_CLASS = new ConfigurationKey>("gatewayStorageClass", DefaultGatewayResolverImpl.class);
+ ConfigurationKey CAS_SERVER_URL_PREFIX = new ConfigurationKey("casServerUrlPrefix", null);
+ ConfigurationKey ENCODING = new ConfigurationKey("encoding", null);
+ ConfigurationKey TOLERANCE = new ConfigurationKey("tolerance", 1000L);
+
+ /**
+ * @deprecated As of 3.4. This constant is not used by the client and will
+ * be removed in future versions.
+ */
+ @Deprecated
+ ConfigurationKey DISABLE_XML_SCHEMA_VALIDATION = new ConfigurationKey("disableXmlSchemaValidation", Boolean.FALSE);
+ ConfigurationKey IGNORE_PATTERN = new ConfigurationKey("ignorePattern", null);
+ ConfigurationKey IGNORE_URL_PATTERN_TYPE = new ConfigurationKey("ignoreUrlPatternType", "REGEX");
+ ConfigurationKey> HOSTNAME_VERIFIER = new ConfigurationKey>("hostnameVerifier", null);
+ ConfigurationKey HOSTNAME_VERIFIER_CONFIG = new ConfigurationKey("hostnameVerifierConfig", null);
+ ConfigurationKey EXCEPTION_ON_VALIDATION_FAILURE = new ConfigurationKey("exceptionOnValidationFailure", Boolean.TRUE);
+ ConfigurationKey REDIRECT_AFTER_VALIDATION = new ConfigurationKey("redirectAfterValidation", Boolean.TRUE);
+ ConfigurationKey USE_SESSION = new ConfigurationKey("useSession", Boolean.TRUE);
+ ConfigurationKey SECRET_KEY = new ConfigurationKey("secretKey", null);
+ ConfigurationKey CIPHER_ALGORITHM = new ConfigurationKey("cipherAlgorithm", "DESede");
+ ConfigurationKey PROXY_RECEPTOR_URL = new ConfigurationKey("proxyReceptorUrl", null);
+ ConfigurationKey> PROXY_GRANTING_TICKET_STORAGE_CLASS = new ConfigurationKey>("proxyGrantingTicketStorageClass", ProxyGrantingTicketStorageImpl.class);
+ ConfigurationKey MILLIS_BETWEEN_CLEAN_UPS = new ConfigurationKey("millisBetweenCleanUps", 60000);
+ ConfigurationKey ACCEPT_ANY_PROXY = new ConfigurationKey("acceptAnyProxy", Boolean.FALSE);
+ ConfigurationKey ALLOWED_PROXY_CHAINS = new ConfigurationKey("allowedProxyChains", null);
+ ConfigurationKey> TICKET_VALIDATOR_CLASS = new ConfigurationKey>("ticketValidatorClass", null);
+ ConfigurationKey PROXY_CALLBACK_URL = new ConfigurationKey("proxyCallbackUrl", null);
+ ConfigurationKey FRONT_LOGOUT_PARAMETER_NAME = new ConfigurationKey("frontLogoutParameterName", "SAMLRequest");
+ ConfigurationKey RELAY_STATE_PARAMETER_NAME = new ConfigurationKey("relayStateParameterName", "RelayState");
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategy.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategy.java
new file mode 100644
index 000000000..1493d7230
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategy.java
@@ -0,0 +1,79 @@
+/*
+ * 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.configuration;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+
+/**
+ * Abstraction to allow for pluggable methods for retrieving filter configuration.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public interface ConfigurationStrategy {
+
+ /**
+ * Retrieves the value for the provided {@param configurationKey}, falling back to the {@param configurationKey}'s {@link ConfigurationKey#getDefaultValue()} if nothing can be found.
+ *
+ * @param configurationKey the configuration key. MUST NOT BE NULL.
+ * @return the configured value, or the default value.
+ */
+ boolean getBoolean(ConfigurationKey configurationKey);
+
+ /**
+ * Retrieves the value for the provided {@param configurationKey}, falling back to the {@param configurationKey}'s {@link ConfigurationKey#getDefaultValue()} if nothing can be found.
+ *
+ * @param configurationKey the configuration key. MUST NOT BE NULL.
+ * @return the configured value, or the default value.
+ */
+ String getString(ConfigurationKey configurationKey);
+
+ /**
+ * Retrieves the value for the provided {@param configurationKey}, falling back to the {@param configurationKey}'s {@link ConfigurationKey#getDefaultValue()} if nothing can be found.
+ *
+ * @param configurationKey the configuration key. MUST NOT BE NULL.
+ * @return the configured value, or the default value.
+ */
+ long getLong(ConfigurationKey configurationKey);
+
+ /**
+ * Retrieves the value for the provided {@param configurationKey}, falling back to the {@param configurationKey}'s {@link ConfigurationKey#getDefaultValue()} if nothing can be found.
+ *
+ * @param configurationKey the configuration key. MUST NOT BE NULL.
+ * @return the configured value, or the default value.
+ */
+ int getInt(ConfigurationKey configurationKey);
+
+ /**
+ * Retrieves the value for the provided {@param configurationKey}, falling back to the {@param configurationKey}'s {@link ConfigurationKey#getDefaultValue()} if nothing can be found.
+ *
+ * @param configurationKey the configuration key. MUST NOT BE NULL.
+ * @return the configured value, or the default value.
+ */
+ Class extends T> getClass(ConfigurationKey> configurationKey);
+
+ /**
+ * Initializes the strategy. This must be called before calling any of the "get" methods.
+ *
+ * @param filterConfig the filter configuration object.
+ * @param filterClazz the filter
+ */
+ void init(FilterConfig filterConfig, Class extends Filter> filterClazz);
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategyName.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategyName.java
new file mode 100644
index 000000000..2d3146f0b
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationStrategyName.java
@@ -0,0 +1,74 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Enumeration to map simple names to the underlying classes so that deployers can reference the simple name in the
+ * web.xml instead of the fully qualified class name.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public enum ConfigurationStrategyName {
+
+ DEFAULT(LegacyConfigurationStrategyImpl.class), JNDI(JndiConfigurationStrategyImpl.class), WEB_XML(WebXmlConfigurationStrategyImpl.class),
+ PROPERTY_FILE(PropertiesConfigurationStrategyImpl.class), SYSTEM_PROPERTIES(SystemPropertiesConfigurationStrategyImpl.class);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationStrategyName.class);
+
+ private final Class extends ConfigurationStrategy> configurationStrategyClass;
+
+ private ConfigurationStrategyName(final Class extends ConfigurationStrategy> configurationStrategyClass) {
+ this.configurationStrategyClass = configurationStrategyClass;
+ }
+
+ /**
+ * Static helper method that will resolve a simple string to either an enum value or a {@link org.jasig.cas.client.configuration.ConfigurationStrategy} class.
+ *
+ * @param value the value to attempt to resolve.
+ * @return the underlying class that this maps to (either via simple name or fully qualified class name).
+ */
+ public static Class extends ConfigurationStrategy> resolveToConfigurationStrategy(final String value) {
+ if (CommonUtils.isBlank(value)) {
+ return DEFAULT.configurationStrategyClass;
+ }
+
+ for (final ConfigurationStrategyName csn : values()) {
+ if (csn.name().equalsIgnoreCase(value)) {
+ return csn.configurationStrategyClass;
+ }
+ }
+
+ try {
+ final Class> clazz = Class.forName(value);
+
+ if (clazz.isAssignableFrom(ConfigurationStrategy.class)) {
+ return (Class extends ConfigurationStrategy>) clazz;
+ }
+ } catch (final ClassNotFoundException e) {
+ LOGGER.error("Unable to locate strategy {} by name or class name. Using default strategy instead.", value, e);
+ }
+
+ return DEFAULT.configurationStrategyClass;
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/JndiConfigurationStrategyImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/JndiConfigurationStrategyImpl.java
new file mode 100644
index 000000000..6fcaa110b
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/JndiConfigurationStrategyImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+
+/**
+ * Loads configuration information from JNDI, using the defaultValue if it can't.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public class JndiConfigurationStrategyImpl extends BaseConfigurationStrategy {
+
+ private static final String ENVIRONMENT_PREFIX = "java:comp/env/cas/";
+
+ private final String environmentPrefix;
+
+ private InitialContext context;
+
+ private String simpleFilterName;
+
+ public JndiConfigurationStrategyImpl() {
+ this(ENVIRONMENT_PREFIX);
+ }
+
+ public JndiConfigurationStrategyImpl(final String environmentPrefix) {
+ this.environmentPrefix = environmentPrefix;
+ }
+
+ @Override
+ protected final String get(final ConfigurationKey configurationKey) {
+ if (context == null) {
+ return null;
+ }
+
+ final String propertyName = configurationKey.getName();
+ final String filterValue = loadFromContext(context, this.environmentPrefix + this.simpleFilterName + "/" + propertyName);
+
+ if (CommonUtils.isNotBlank(filterValue)) {
+ logger.info("Property [{}] loaded from JNDI Filter Specific Property with value [{}]", propertyName, filterValue);
+ return filterValue;
+ }
+
+ final String rootValue = loadFromContext(context, this.environmentPrefix + propertyName);
+
+ if (CommonUtils.isNotBlank(rootValue)) {
+ logger.info("Property [{}] loaded from JNDI with value [{}]", propertyName, rootValue);
+ return rootValue;
+ }
+
+ return null;
+ }
+
+ private String loadFromContext(final InitialContext context, final String path) {
+ try {
+ return (String) context.lookup(path);
+ } catch (final NamingException e) {
+ return null;
+ }
+ }
+
+
+ public final void init(final FilterConfig filterConfig, final Class extends Filter> clazz) {
+ this.simpleFilterName = clazz.getSimpleName();
+ try {
+ this.context = new InitialContext();
+ } catch (final NamingException e) {
+ logger.error("Unable to create InitialContext. No properties can be loaded via JNDI.", e);
+ }
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/LegacyConfigurationStrategyImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/LegacyConfigurationStrategyImpl.java
new file mode 100644
index 000000000..a9af77ca6
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/LegacyConfigurationStrategyImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+
+/**
+ * Replicates the original behavior by checking the {@link org.jasig.cas.client.configuration.WebXmlConfigurationStrategyImpl} first, and then
+ * the {@link org.jasig.cas.client.configuration.JndiConfigurationStrategyImpl} before using the defaultValue.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public final class LegacyConfigurationStrategyImpl extends BaseConfigurationStrategy {
+
+ private final WebXmlConfigurationStrategyImpl webXmlConfigurationStrategy = new WebXmlConfigurationStrategyImpl();
+
+ private final JndiConfigurationStrategyImpl jndiConfigurationStrategy = new JndiConfigurationStrategyImpl();
+
+ public void init(FilterConfig filterConfig, Class extends Filter> filterClazz) {
+ this.webXmlConfigurationStrategy.init(filterConfig, filterClazz);
+ this.jndiConfigurationStrategy.init(filterConfig, filterClazz);
+ }
+
+ protected String get(final ConfigurationKey key) {
+ final String value1 = this.webXmlConfigurationStrategy.get(key);
+
+ if (CommonUtils.isNotBlank(value1)) {
+ return value1;
+ }
+
+ return this.jndiConfigurationStrategy.get(key);
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/PropertiesConfigurationStrategyImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/PropertiesConfigurationStrategyImpl.java
new file mode 100644
index 000000000..5dd7dd9ae
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/PropertiesConfigurationStrategyImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public final class PropertiesConfigurationStrategyImpl extends BaseConfigurationStrategy {
+
+ /**
+ * Property name we'll use in the {@link javax.servlet.FilterConfig} and {@link javax.servlet.ServletConfig} to try and find where
+ * you stored the configuration file.
+ */
+ private static final String CONFIGURATION_FILE_LOCATION = "configFileLocation";
+
+ /**
+ * Default location of the configuration file. Mostly for testing/demo. You will most likely want to configure an alternative location.
+ */
+ private static final String DEFAULT_CONFIGURATION_FILE_LOCATION = "/etc/java-cas-client.properties";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesConfigurationStrategyImpl.class);
+
+ private String simpleFilterName;
+
+ private Properties properties = new Properties();
+
+ @Override
+ protected String get(final ConfigurationKey configurationKey) {
+ final String property = configurationKey.getName();
+ final String filterSpecificProperty = this.simpleFilterName + "." + property;
+
+ final String filterSpecificValue = this.properties.getProperty(filterSpecificProperty);
+
+ if (CommonUtils.isNotEmpty(filterSpecificValue)) {
+ return filterSpecificValue;
+ }
+
+ return this.properties.getProperty(property);
+ }
+
+ public void init(final FilterConfig filterConfig, final Class extends Filter> filterClazz) {
+ this.simpleFilterName = filterClazz.getSimpleName();
+ final String fileLocationFromFilterConfig = filterConfig.getInitParameter(CONFIGURATION_FILE_LOCATION);
+ final boolean filterConfigFileLoad = loadPropertiesFromFile(fileLocationFromFilterConfig);
+
+ if (!filterConfigFileLoad) {
+ final String fileLocationFromServletConfig = filterConfig.getServletContext().getInitParameter(CONFIGURATION_FILE_LOCATION);
+ final boolean servletContextFileLoad = loadPropertiesFromFile(fileLocationFromServletConfig);
+
+ if (!servletContextFileLoad) {
+ final boolean defaultConfigFileLoaded = loadPropertiesFromFile(DEFAULT_CONFIGURATION_FILE_LOCATION);
+ CommonUtils.assertTrue(defaultConfigFileLoaded, "unable to load properties to configure CAS client");
+ }
+ }
+ }
+
+ private boolean loadPropertiesFromFile(final String file) {
+ if (CommonUtils.isEmpty(file)) {
+ return false;
+ }
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ this.properties.load(fis);
+ return true;
+ } catch (final IOException e) {
+ LOGGER.warn("Unable to load properties for file {}", file, e);
+ return false;
+ } finally {
+ CommonUtils.closeQuietly(fis);
+ }
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/SystemPropertiesConfigurationStrategyImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/SystemPropertiesConfigurationStrategyImpl.java
new file mode 100644
index 000000000..49be2c5f0
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/SystemPropertiesConfigurationStrategyImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.configuration;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+
+/**
+ * Load all configuration from system properties.
+ *
+ * @author Jerome Leleu
+ * @since 3.4.0
+ */
+public class SystemPropertiesConfigurationStrategyImpl extends BaseConfigurationStrategy {
+
+ public void init(FilterConfig filterConfig, Class extends Filter> filterClazz) {
+ }
+
+ @Override
+ protected String get(ConfigurationKey configurationKey) {
+ return System.getProperty(configurationKey.getName());
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/WebXmlConfigurationStrategyImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/WebXmlConfigurationStrategyImpl.java
new file mode 100644
index 000000000..634b9f16e
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/WebXmlConfigurationStrategyImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.configuration;
+
+import org.jasig.cas.client.util.CommonUtils;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+
+/**
+ * Implementation of the {@link org.jasig.cas.client.configuration.ConfigurationStrategy} that first checks the {@link javax.servlet.FilterConfig} and
+ * then checks the {@link javax.servlet.ServletContext}, ultimately falling back to the defaultValue.
+ *
+ * @author Scott Battaglia
+ * @since 3.4.0
+ */
+public final class WebXmlConfigurationStrategyImpl extends BaseConfigurationStrategy {
+
+ private FilterConfig filterConfig;
+
+ protected String get(final ConfigurationKey configurationKey) {
+ final String value = this.filterConfig.getInitParameter(configurationKey.getName());
+
+ if (CommonUtils.isNotBlank(value)) {
+ CommonUtils.assertFalse(ConfigurationKeys.RENEW.equals(configurationKey), "Renew MUST be specified via context parameter or JNDI environment to avoid misconfiguration.");
+ logger.info("Property [{}] loaded from FilterConfig.getInitParameter with value [{}]", configurationKey, value);
+ return value;
+ }
+
+ final String value2 = filterConfig.getServletContext().getInitParameter(configurationKey.getName());
+
+ if (CommonUtils.isNotBlank(value2)) {
+ logger.info("Property [{}] loaded from ServletContext.getInitParameter with value [{}]", configurationKey,
+ value2);
+ return value2;
+ }
+
+ return null;
+ }
+
+ public void init(final FilterConfig filterConfig, final Class extends Filter> clazz) {
+ this.filterConfig = filterConfig;
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java
index 6abeff65b..8ae58947c 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/AssertionPrincipal.java
@@ -1,26 +1,24 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jaas;
import java.io.Serializable;
-
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.validation.Assertion;
@@ -33,7 +31,7 @@
*
*/
public class AssertionPrincipal extends SimplePrincipal implements Serializable {
-
+
/** AssertionPrincipal.java */
private static final long serialVersionUID = 2288520214366461693L;
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java
index 58a3e0e36..5deb87e81 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/CasLoginModule.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jaas;
import java.beans.BeanInfo;
@@ -27,26 +26,19 @@
import java.security.Principal;
import java.security.acl.Group;
import java.util.*;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
+import java.util.concurrent.TimeUnit;
import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.authentication.SimpleGroup;
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* JAAS login module that delegates to a CAS {@link TicketValidator} component
@@ -79,6 +71,8 @@
* for JAAS providers that attempt to periodically reauthenticate to renew principal.
* Since CAS tickets are one-time-use, a cached assertion must be provided on reauthentication.
* cacheTimeout (optional) - Assertion cache timeout in minutes.
+ * cacheTimeoutUnit (optional) - Assertion cache timeout unit. Must be one of {@link TimeUnit} enumeration
+ * names, e.g. DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS. Default unit is MINUTES.
*
*
*
@@ -109,14 +103,14 @@
public class CasLoginModule implements LoginModule {
/** Constant for login name stored in shared state. */
public static final String LOGIN_NAME = "javax.security.auth.login.name";
-
+
/**
* Default group name for storing caller principal.
* The default value supports JBoss, but is configurable to hopefully
* support other JEE containers.
*/
public static final String DEFAULT_PRINCIPAL_GROUP_NAME = "CallerPrincipal";
-
+
/**
* Default group name for storing role membership data.
* The default value supports JBoss, but is configurable to hopefully
@@ -129,50 +123,50 @@ public class CasLoginModule implements LoginModule {
*/
public static final int DEFAULT_CACHE_TIMEOUT = 480;
+ /** Default assertion cache timeout unit is minutes. */
+ public static final TimeUnit DEFAULT_CACHE_TIMEOUT_UNIT = TimeUnit.MINUTES;
+
/**
* Stores mapping of ticket to assertion to support JAAS providers that
* attempt to periodically re-authenticate to renew principal. Since
* CAS tickets are one-time-use, a cached assertion must be provided on
* re-authentication.
*/
- protected static final Map ASSERTION_CACHE = new HashMap();
+ protected static final Map ASSERTION_CACHE = new HashMap();
- /** Executor responsible for assertion cache cleanup */
- protected static Executor cacheCleanerExecutor = Executors.newSingleThreadExecutor();
-
/** Logger instance */
- protected final Log log = LogFactory.getLog(getClass());
-
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
/** JAAS authentication subject */
protected Subject subject;
-
+
/** JAAS callback handler */
protected CallbackHandler callbackHandler;
-
+
/** CAS ticket validator */
protected TicketValidator ticketValidator;
-
+
/** CAS service parameter used if no service is provided via TextCallback on login */
protected String service;
-
+
/** CAS assertion */
protected Assertion assertion;
-
+
/** CAS ticket credential */
protected TicketCredential ticket;
-
+
/** Login module shared state */
- protected Map sharedState;
-
+ protected Map sharedState;
+
/** Roles to be added to all authenticated principals by default */
protected String[] defaultRoles;
-
+
/** Names of attributes in the CAS assertion that should be used for role data */
protected Set roleAttributeNames = new HashSet();
-
+
/** Name of JAAS Group containing caller principal */
protected String principalGroupName = DEFAULT_PRINCIPAL_GROUP_NAME;
-
+
/** Name of JAAS Group containing role data */
protected String roleGroupName = DEFAULT_ROLE_GROUP_NAME;
@@ -182,8 +176,12 @@ public class CasLoginModule implements LoginModule {
/** Assertion cache timeout in minutes */
protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT;
+ /** Units of cache timeout. */
+ protected TimeUnit cacheTimeoutUnit = DEFAULT_CACHE_TIMEOUT_UNIT;
+
/**
* Initializes the CAS login module.
+ *
* @param subject Authentication subject.
* @param handler Callback handler.
* @param state Shared state map.
@@ -201,55 +199,60 @@ public class CasLoginModule implements LoginModule {
* which by default are single use, reauthentication fails. Assertion caching addresses this
* behavior.
* cacheTimeout (optional) - assertion cache timeout in minutes.
+ * cacheTimeoutUnit (optional) - Assertion cache timeout unit. Must be one of {@link TimeUnit} enumeration
+ * names, e.g. DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS. Default unit is MINUTES.
*
*/
+ public final void initialize(final Subject subject, final CallbackHandler handler, final Map state,
+ final Map options) {
-
- public final void initialize(final Subject subject, final CallbackHandler handler, final Map state, final Map options) {
this.assertion = null;
this.callbackHandler = handler;
this.subject = subject;
- this.sharedState = (Map) state;
+ this.sharedState = (Map) state;
this.sharedState = new HashMap(state);
-
+
String ticketValidatorClass = null;
for (final String key : options.keySet()) {
- log.trace("Processing option " + key);
+ logger.trace("Processing option {}", key);
if ("service".equals(key)) {
this.service = (String) options.get(key);
- log.debug("Set service=" + this.service);
+ logger.debug("Set service={}", this.service);
} else if ("ticketValidatorClass".equals(key)) {
ticketValidatorClass = (String) options.get(key);
- log.debug("Set ticketValidatorClass=" + ticketValidatorClass);
+ logger.debug("Set ticketValidatorClass={}", ticketValidatorClass);
} else if ("defaultRoles".equals(key)) {
final String roles = (String) options.get(key);
- log.trace("Got defaultRoles value " + roles);
+ logger.trace("Got defaultRoles value {}", roles);
this.defaultRoles = roles.split(",\\s*");
- log.debug("Set defaultRoles=" + Arrays.asList(this.defaultRoles));
+ logger.debug("Set defaultRoles={}", Arrays.asList(this.defaultRoles));
} else if ("roleAttributeNames".equals(key)) {
final String attrNames = (String) options.get(key);
- log.trace("Got roleAttributeNames value " + attrNames);
+ logger.trace("Got roleAttributeNames value {}", attrNames);
final String[] attributes = attrNames.split(",\\s*");
this.roleAttributeNames.addAll(Arrays.asList(attributes));
- log.debug("Set roleAttributeNames=" + this.roleAttributeNames);
+ logger.debug("Set roleAttributeNames={}", this.roleAttributeNames);
} else if ("principalGroupName".equals(key)) {
this.principalGroupName = (String) options.get(key);
- log.debug("Set principalGroupName=" + this.principalGroupName);
+ logger.debug("Set principalGroupName={}", this.principalGroupName);
} else if ("roleGroupName".equals(key)) {
this.roleGroupName = (String) options.get(key);
- log.debug("Set roleGroupName=" + this.roleGroupName);
+ logger.debug("Set roleGroupName={}", this.roleGroupName);
} else if ("cacheAssertions".equals(key)) {
this.cacheAssertions = Boolean.parseBoolean((String) options.get(key));
- log.debug("Set cacheAssertions=" + this.cacheAssertions);
+ logger.debug("Set cacheAssertions={}", this.cacheAssertions);
} else if ("cacheTimeout".equals(key)) {
this.cacheTimeout = Integer.parseInt((String) options.get(key));
- log.debug("Set cacheTimeout=" + this.cacheTimeout);
+ logger.debug("Set cacheTimeout={}", this.cacheTimeout);
+ } else if ("cacheTimeoutUnit".equals(key)) {
+ this.cacheTimeoutUnit = Enum.valueOf(TimeUnit.class, (String) options.get(key));
+ logger.debug("Set cacheTimeoutUnit={}", this.cacheTimeoutUnit);
}
}
if (this.cacheAssertions) {
- cacheCleanerExecutor.execute(new CacheCleaner());
+ cleanCache();
}
CommonUtils.assertNotNull(ticketValidatorClass, "ticketValidatorClass is required.");
@@ -275,10 +278,10 @@ protected void postLogin(final boolean result) {
}
public final boolean login() throws LoginException {
- log.debug("Performing login.");
+ logger.debug("Performing login.");
if (!preLogin()) {
- log.debug("preLogin failed.");
+ logger.debug("preLogin failed.");
return false;
}
@@ -289,46 +292,46 @@ public final boolean login() throws LoginException {
try {
this.callbackHandler.handle(new Callback[] { ticketCallback, serviceCallback });
} catch (final IOException e) {
- log.info("Login failed due to IO exception in callback handler: " + e);
+ logger.info("Login failed due to IO exception in callback handler: {}", e);
throw (LoginException) new LoginException("IO exception in callback handler: " + e).initCause(e);
} catch (final UnsupportedCallbackException e) {
- log.info("Login failed due to unsupported callback: " + e);
- throw (LoginException) new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e);
+ logger.info("Login failed due to unsupported callback: {}", e);
+ throw (LoginException) new LoginException(
+ "Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e);
}
if (ticketCallback.getPassword() != null) {
this.ticket = new TicketCredential(new String(ticketCallback.getPassword()));
- final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service;
+ final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName()
+ : this.service;
if (this.cacheAssertions) {
- synchronized(ASSERTION_CACHE) {
- if (ASSERTION_CACHE.get(ticket) != null) {
- log.debug("Assertion found in cache.");
- this.assertion = ASSERTION_CACHE.get(ticket);
- }
+ this.assertion = ASSERTION_CACHE.get(ticket);
+ if (this.assertion != null) {
+ logger.debug("Assertion found in cache.");
}
}
if (this.assertion == null) {
- log.debug("CAS assertion is null; ticket validation required.");
+ logger.debug("CAS assertion is null; ticket validation required.");
if (CommonUtils.isBlank(service)) {
- log.info("Login failed because required CAS service parameter not provided.");
- throw new LoginException("Neither login module nor callback handler provided required service parameter.");
+ logger.info("Login failed because required CAS service parameter not provided.");
+ throw new LoginException(
+ "Neither login module nor callback handler provided required service parameter.");
}
try {
- if (log.isDebugEnabled()) {
- log.debug("Attempting ticket validation with service=" + service + " and ticket=" + ticket);
- }
+ logger.debug("Attempting ticket validation with service={} and ticket={}", service,
+ this.ticket);
this.assertion = this.ticketValidator.validate(this.ticket.getName(), service);
} catch (final Exception e) {
- log.info("Login failed due to CAS ticket validation failure: " + e);
+ logger.info("Login failed due to CAS ticket validation failure: {}", e);
throw (LoginException) new LoginException("CAS ticket validation failed: " + e).initCause(e);
}
}
- log.info("Login succeeded.");
+ logger.info("Login succeeded.");
} else {
- log.info("Login failed because callback handler did not provide CAS ticket.");
+ logger.info("Login failed because callback handler did not provide CAS ticket.");
throw new LoginException("Callback handler did not provide CAS ticket.");
}
result = true;
@@ -380,7 +383,8 @@ public final boolean commit() throws LoginException {
throw new LoginException("Ticket credential not found.");
}
- final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion);
+ final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(),
+ this.assertion);
this.subject.getPrincipals().add(casPrincipal);
// Add group containing principal as sole member
@@ -396,7 +400,7 @@ public final boolean commit() throws LoginException {
roleGroup.addMember(new SimplePrincipal(defaultRole));
}
- final Map attributes = this.assertion.getPrincipal().getAttributes();
+ final Map attributes = this.assertion.getPrincipal().getAttributes();
for (final String key : attributes.keySet()) {
if (this.roleAttributeNames.contains(key)) {
// Attribute value is Object if singular or Collection if plural
@@ -415,16 +419,10 @@ public final boolean commit() throws LoginException {
// Place principal name in shared state for downstream JAAS modules (module chaining use case)
this.sharedState.put(LOGIN_NAME, assertion.getPrincipal().getName());
- if (log.isDebugEnabled()) {
- if (log.isDebugEnabled()) {
- log.debug("Created JAAS subject with principals: " + subject.getPrincipals());
- }
- }
+ logger.debug("Created JAAS subject with principals: {}", subject.getPrincipals());
if (this.cacheAssertions) {
- if (log.isDebugEnabled()) {
- log.debug("Caching assertion for principal " + this.assertion.getPrincipal());
- }
+ logger.debug("Caching assertion for principal {}", this.assertion.getPrincipal());
ASSERTION_CACHE.put(this.ticket, this.assertion);
}
} else {
@@ -442,21 +440,29 @@ public final boolean commit() throws LoginException {
}
public final boolean logout() throws LoginException {
- log.debug("Performing logout.");
+ logger.debug("Performing logout.");
if (!preLogout()) {
return false;
}
+ // Remove cache entry if assertion caching is enabled
+ if (this.cacheAssertions) {
+ for (final TicketCredential ticket : this.subject.getPrivateCredentials(TicketCredential.class)) {
+ logger.debug("Removing cached assertion for {}", ticket);
+ ASSERTION_CACHE.remove(ticket);
+ }
+ }
+
// Remove all CAS principals
removePrincipalsOfType(AssertionPrincipal.class);
removePrincipalsOfType(SimplePrincipal.class);
removePrincipalsOfType(SimpleGroup.class);
-
+
// Remove all CAS credentials
removeCredentialsOfType(TicketCredential.class);
- log.info("Logout succeeded.");
+ logger.info("Logout succeeded.");
postLogout();
return true;
@@ -478,39 +484,40 @@ protected void postLogout() {
// template method
}
-
/**
* Creates a {@link TicketValidator} instance from a class name and map of property name/value pairs.
* @param className Fully-qualified name of {@link TicketValidator} concrete class.
* @param propertyMap Map of property name/value pairs to set on validator instance.
* @return Ticket validator with properties set.
*/
- private TicketValidator createTicketValidator(final String className, final Map propertyMap) {
- CommonUtils.assertTrue(propertyMap.containsKey("casServerUrlPrefix"), "Required property casServerUrlPrefix not found.");
+ private TicketValidator createTicketValidator(final String className, final Map propertyMap) {
+ CommonUtils.assertTrue(propertyMap.containsKey("casServerUrlPrefix"),
+ "Required property casServerUrlPrefix not found.");
final Class validatorClass = ReflectUtils.loadClass(className);
- final TicketValidator validator = ReflectUtils.newInstance(validatorClass, propertyMap.get("casServerUrlPrefix"));
+ final TicketValidator validator = ReflectUtils.newInstance(validatorClass,
+ propertyMap.get("casServerUrlPrefix"));
try {
final BeanInfo info = Introspector.getBeanInfo(validatorClass);
for (final String property : propertyMap.keySet()) {
if (!"casServerUrlPrefix".equals(property)) {
- log.debug("Attempting to set TicketValidator property " + property);
+ logger.debug("Attempting to set TicketValidator property {}", property);
final String value = (String) propertyMap.get(property);
final PropertyDescriptor pd = ReflectUtils.getPropertyDescriptor(info, property);
if (pd != null) {
- ReflectUtils.setProperty(property, convertIfNecessary(pd, value), validator, info);
- log.debug("Set " + property + "=" + value);
+ ReflectUtils.setProperty(property, convertIfNecessary(pd, value), validator, info);
+ logger.debug("Set {} = {}", property, value);
} else {
- log.warn("Cannot find property " + property + " on " + className);
+ logger.warn("Cannot find property {} on {}", property, className);
}
}
}
} catch (final IntrospectionException e) {
throw new RuntimeException("Error getting bean info for " + validatorClass, e);
}
-
+
return validator;
}
@@ -534,7 +541,8 @@ private static Object convertIfNecessary(final PropertyDescriptor pd, final Stri
} else if (long.class.equals(pd.getPropertyType())) {
return new Long(value);
} else {
- throw new IllegalArgumentException("No conversion strategy exists for property " + pd.getName() + " of type " + pd.getPropertyType());
+ throw new IllegalArgumentException("No conversion strategy exists for property " + pd.getName()
+ + " of type " + pd.getPropertyType());
}
}
@@ -554,26 +562,21 @@ private void removeCredentialsOfType(final Class extends Principal> clazz) {
this.subject.getPrivateCredentials().removeAll(this.subject.getPrivateCredentials(clazz));
}
- /** Removes expired entries from the assertion cache. */
- private class CacheCleaner implements Runnable {
- public void run() {
- if (log.isDebugEnabled()) {
- log.debug("Cleaning assertion cache of size " + CasLoginModule.ASSERTION_CACHE.size());
- }
- final Iterator> iter =
- CasLoginModule.ASSERTION_CACHE.entrySet().iterator();
- final Calendar cutoff = Calendar.getInstance();
- cutoff.add(Calendar.MINUTE, -CasLoginModule.this.cacheTimeout);
- while (iter.hasNext()) {
- final Assertion assertion = iter.next().getValue();
- final Calendar created = Calendar.getInstance();
- created.setTime(assertion.getValidFromDate());
- if (created.before(cutoff)) {
- if (log.isDebugEnabled()) {
- log.debug("Removing expired assertion for principal " + assertion.getPrincipal());
- }
- iter.remove();
- }
+ /**
+ * Removes expired entries from the assertion cache.
+ */
+ private void cleanCache() {
+ logger.debug("Cleaning assertion cache of size {}", ASSERTION_CACHE.size());
+ final Iterator> iter = ASSERTION_CACHE.entrySet().iterator();
+ final Calendar cutoff = Calendar.getInstance();
+ cutoff.setTimeInMillis(System.currentTimeMillis() - this.cacheTimeoutUnit.toMillis(this.cacheTimeout));
+ while (iter.hasNext()) {
+ final Assertion assertion = iter.next().getValue();
+ final Calendar created = Calendar.getInstance();
+ created.setTime(assertion.getValidFromDate());
+ if (created.before(cutoff)) {
+ logger.debug("Removing expired assertion for principal {}", assertion.getPrincipal());
+ iter.remove();
}
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/ServiceAndTicketCallbackHandler.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/ServiceAndTicketCallbackHandler.java
index 62bced724..7fc88ef4f 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/ServiceAndTicketCallbackHandler.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/ServiceAndTicketCallbackHandler.java
@@ -1,31 +1,25 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jaas;
import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.callback.*;
/**
* Callback handler that provides the CAS service and ticket to a
@@ -41,10 +35,10 @@ public class ServiceAndTicketCallbackHandler implements CallbackHandler {
/** CAS service URL */
private final String service;
-
+
/** CAS service ticket */
private final String ticket;
-
+
/**
* Creates a new instance with the given service and ticket.
*
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/Servlet3AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/Servlet3AuthenticationFilter.java
new file mode 100644
index 000000000..e64b57897
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/Servlet3AuthenticationFilter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.jaas;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.util.AbstractCasFilter;
+import org.jasig.cas.client.util.CommonUtils;
+
+/**
+ * Servlet filter performs a programmatic JAAS login using the Servlet 3.0 HttpServletRequest#login() facility.
+ * This component should be compatible with any servlet container that supports the Servlet 3.0/JEE6 specification.
+ *
+ * The filter executes when it receives a CAS ticket and expects the
+ * {@link CasLoginModule} JAAS module to perform the CAS
+ * ticket validation in order to produce an {@link org.jasig.cas.client.jaas.AssertionPrincipal} from which
+ * the CAS assertion is obtained and inserted into the session to enable SSO.
+ *
+ * If a service init-param is specified for this filter, it supersedes
+ * the service defined for the {@link CasLoginModule}.
+ *
+ * @author Daniel Fisher
+ * @author Marvin S. Addison
+ * @since 3.3
+ */
+public final class Servlet3AuthenticationFilter extends AbstractCasFilter {
+
+ public Servlet3AuthenticationFilter() {
+ super(Protocol.CAS2);
+ }
+
+ public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain chain) throws IOException, ServletException {
+ final HttpServletRequest request = (HttpServletRequest) servletRequest;
+ final HttpServletResponse response = (HttpServletResponse) servletResponse;
+ final HttpSession session = request.getSession();
+ final String ticket = CommonUtils.safeGetParameter(request, getProtocol().getArtifactParameterName());
+
+ if (session != null && session.getAttribute(CONST_CAS_ASSERTION) == null && ticket != null) {
+ try {
+ final String service = constructServiceUrl(request, response);
+ logger.debug("Attempting CAS ticket validation with service={} and ticket={}", service, ticket);
+ request.login(service, ticket);
+ if (request.getUserPrincipal() instanceof AssertionPrincipal) {
+ final AssertionPrincipal principal = (AssertionPrincipal) request.getUserPrincipal();
+ logger.debug("Installing CAS assertion into session.");
+ request.getSession().setAttribute(CONST_CAS_ASSERTION, principal.getAssertion());
+ } else {
+ logger.debug("Aborting -- principal is not of type AssertionPrincipal");
+ throw new GeneralSecurityException("JAAS authentication did not produce CAS AssertionPrincipal.");
+ }
+ } catch (final ServletException e) {
+ logger.debug("JAAS authentication failed.");
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
+ } catch (final GeneralSecurityException e) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
+ }
+ } else if (session != null && request.getUserPrincipal() == null) {
+ // There is evidence that in some cases the principal can disappear
+ // in JBoss despite a valid session.
+ // This block forces consistency between principal and assertion.
+ logger.info("User principal not found. Removing CAS assertion from session to force re-authentication.");
+ session.removeAttribute(CONST_CAS_ASSERTION);
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/TicketCredential.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/TicketCredential.java
index 2f7e92e5f..621da53fd 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/jaas/TicketCredential.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/TicketCredential.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jaas;
import java.security.Principal;
@@ -33,7 +32,7 @@ public final class TicketCredential implements Principal {
/** Hash code seed value */
private static final int HASHCODE_SEED = 17;
-
+
/** Ticket ID string */
private String ticket;
@@ -54,12 +53,15 @@ public String toString() {
}
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
final TicketCredential that = (TicketCredential) o;
- if (ticket != null ? !ticket.equals(that.ticket) : that.ticket != null) return false;
+ if (ticket != null ? !ticket.equals(that.ticket) : that.ticket != null)
+ return false;
return true;
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/AbstractEncryptedProxyGrantingTicketStorageImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/AbstractEncryptedProxyGrantingTicketStorageImpl.java
index 7842ac33d..24826cfc3 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/AbstractEncryptedProxyGrantingTicketStorageImpl.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/AbstractEncryptedProxyGrantingTicketStorageImpl.java
@@ -1,12 +1,32 @@
+/*
+ * 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.proxy;
-import javax.crypto.Cipher;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.DESedeKeySpec;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
+
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
/**
* Provides encryption capabilities. Not entirely safe to configure since we have no way of controlling the
@@ -18,13 +38,12 @@
*/
public abstract class AbstractEncryptedProxyGrantingTicketStorageImpl implements ProxyGrantingTicketStorage {
- public static final String DEFAULT_ENCRYPTION_ALGORITHM = "DESede";
-
private Key key;
- private String cipherAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
+ private String cipherAlgorithm = ConfigurationKeys.CIPHER_ALGORITHM.getDefaultValue();
- public final void setSecretKey(final String key) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public final void setSecretKey(final String key) throws NoSuchAlgorithmException, InvalidKeyException,
+ InvalidKeySpecException {
this.key = SecretKeyFactory.getInstance(this.cipherAlgorithm).generateSecret(new DESedeKeySpec(key.getBytes()));
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java
index 45adbaed4..e80304a29 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java
@@ -1,54 +1,49 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.proxy;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import java.net.URL;
+import java.net.URLEncoder;
+import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Implementation of a ProxyRetriever that follows the CAS 2.0 specification.
* For more information on the CAS 2.0 specification, please see the specification
+ * href="http://www.jasig.org/cas/protocol">specification
* document.
*
* In general, this class will make a call to the CAS server with some specified
* parameters and receive an XML response to parse.
*
* @author Scott Battaglia
- * @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class Cas20ProxyRetriever implements ProxyRetriever {
/** Unique Id for serialization. */
- private static final long serialVersionUID = 560409469568911791L;
+ private static final long serialVersionUID = 560409469568911792L;
- /**
- * Instance of Commons Logging.
- */
- private final Log log = LogFactory.getLog(this.getClass());
+ private static final Logger logger = LoggerFactory.getLogger(Cas20ProxyRetriever.class);
/**
* Url to CAS server.
@@ -57,39 +52,56 @@ public final class Cas20ProxyRetriever implements ProxyRetriever {
private final String encoding;
+ /** Url connection factory to use when communicating with the server **/
+ private final HttpURLConnectionFactory urlConnectionFactory;
+
+ @Deprecated
+ public Cas20ProxyRetriever(final String casServerUrl, final String encoding) {
+ this(casServerUrl, encoding, null);
+ }
+
/**
* Main Constructor.
*
* @param casServerUrl the URL to the CAS server (i.e. http://localhost/cas/)
* @param encoding the encoding to use.
+ * @param urlFactory url connection factory use when retrieving proxy responses from the server
*/
- public Cas20ProxyRetriever(final String casServerUrl, final String encoding) {
+ public Cas20ProxyRetriever(final String casServerUrl, final String encoding,
+ final HttpURLConnectionFactory urlFactory) {
CommonUtils.assertNotNull(casServerUrl, "casServerUrl cannot be null.");
this.casServerUrl = casServerUrl;
this.encoding = encoding;
+ this.urlConnectionFactory = urlFactory;
}
- public String getProxyTicketIdFor(final String proxyGrantingTicketId,
- final String targetService) {
+ public String getProxyTicketIdFor(final String proxyGrantingTicketId, final String targetService) {
+ CommonUtils.assertNotNull(proxyGrantingTicketId, "proxyGrantingTicketId cannot be null.");
+ CommonUtils.assertNotNull(targetService, "targetService cannot be null.");
+
+ final URL url = constructUrl(proxyGrantingTicketId, targetService);
+ final String response;
- final String url = constructUrl(proxyGrantingTicketId, targetService);
- final String response = CommonUtils.getResponseFromServer(url, this.encoding);
+ if (this.urlConnectionFactory != null) {
+ response = CommonUtils.getResponseFromServer(url, this.urlConnectionFactory, this.encoding);
+ } else {
+ response = CommonUtils.getResponseFromServer(url, this.encoding);
+ }
final String error = XmlUtils.getTextForElement(response, "proxyFailure");
if (CommonUtils.isNotEmpty(error)) {
- log.debug(error);
+ logger.debug(error);
return null;
}
return XmlUtils.getTextForElement(response, "proxyTicket");
}
- private String constructUrl(final String proxyGrantingTicketId, final String targetService) {
+ private URL constructUrl(final String proxyGrantingTicketId, final String targetService) {
try {
- return this.casServerUrl + (this.casServerUrl.endsWith("/") ? "" : "/") + "proxy" + "?pgt="
- + proxyGrantingTicketId + "&targetService="
- + URLEncoder.encode(targetService, "UTF-8");
- } catch (final UnsupportedEncodingException e) {
+ return new URL(this.casServerUrl + (this.casServerUrl.endsWith("/") ? "" : "/") + "proxy" + "?pgt="
+ + proxyGrantingTicketId + "&targetService=" + URLEncoder.encode(targetService, "UTF-8"));
+ } catch (final Exception e) {
throw new RuntimeException(e);
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/CleanUpTimerTask.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/CleanUpTimerTask.java
index e48986c4e..ce31b4b24 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/CleanUpTimerTask.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/CleanUpTimerTask.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.proxy;
import java.util.TimerTask;
@@ -39,6 +38,7 @@ public final class CleanUpTimerTask extends TimerTask {
public CleanUpTimerTask(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
}
+
public void run() {
this.proxyGrantingTicketStorage.cleanUp();
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java
index d4bf58ae9..0dc15c611 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.proxy;
/**
@@ -47,7 +46,7 @@ public interface ProxyGrantingTicketStorage {
* @return the ProxyGrantingTicket Id or null if it can't be found
*/
public String retrieve(String proxyGrantingTicketIou);
-
+
/**
* Called on a regular basis by an external timer,
* giving implementations a chance to remove stale data.
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java
index a1d13941e..727eedef5 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java
@@ -1,30 +1,29 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.proxy;
-import java.util.*;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Implementation of {@link ProxyGrantingTicketStorage} that is backed by a
@@ -39,8 +38,8 @@
* @since 3.0
*/
public final class ProxyGrantingTicketStorageImpl implements ProxyGrantingTicketStorage {
-
- private final Log log = LogFactory.getLog(getClass());
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Default timeout in milliseconds.
@@ -50,7 +49,7 @@ public final class ProxyGrantingTicketStorageImpl implements ProxyGrantingTicket
/**
* Map that stores the PGTIOU to PGT mappings.
*/
- private final ConcurrentMap cache = new ConcurrentHashMap();
+ private final ConcurrentMap cache = new ConcurrentHashMap();
/**
* time, in milliseconds, before a {@link ProxyGrantingTicketHolder}
@@ -58,7 +57,7 @@ public final class ProxyGrantingTicketStorageImpl implements ProxyGrantingTicket
*
* @see ProxyGrantingTicketStorageImpl#DEFAULT_TIMEOUT
*/
- private long timeout;
+ private long timeout;
/**
* Constructor set the timeout to the default value.
@@ -74,7 +73,7 @@ public ProxyGrantingTicketStorageImpl() {
* @param timeout the time to hold on to the ProxyGrantingTicket
*/
public ProxyGrantingTicketStorageImpl(final long timeout) {
- this.timeout = timeout;
+ this.timeout = timeout;
}
/**
@@ -82,27 +81,28 @@ public ProxyGrantingTicketStorageImpl(final long timeout) {
* Its removed after retrieval.
*/
public String retrieve(final String proxyGrantingTicketIou) {
+ if (CommonUtils.isBlank(proxyGrantingTicketIou)) {
+ return null;
+ }
+
final ProxyGrantingTicketHolder holder = this.cache.get(proxyGrantingTicketIou);
if (holder == null) {
- log.info("No Proxy Ticket found for [" + proxyGrantingTicketIou + "].");
+ logger.info("No Proxy Ticket found for [{}].", proxyGrantingTicketIou);
return null;
}
this.cache.remove(proxyGrantingTicketIou);
- if (log.isDebugEnabled()) {
- log.debug("Returned ProxyGrantingTicket of [" + holder.getProxyGrantingTicket() + "]");
- }
+ logger.debug("Returned ProxyGrantingTicket of [{}]", holder.getProxyGrantingTicket());
return holder.getProxyGrantingTicket();
}
public void save(final String proxyGrantingTicketIou, final String proxyGrantingTicket) {
final ProxyGrantingTicketHolder holder = new ProxyGrantingTicketHolder(proxyGrantingTicket);
- if (log.isDebugEnabled()) {
- log.debug("Saving ProxyGrantingTicketIOU and ProxyGrantingTicket combo: [" + proxyGrantingTicketIou + ", " + proxyGrantingTicket + "]");
- }
+ logger.debug("Saving ProxyGrantingTicketIOU and ProxyGrantingTicket combo: [{}, {}]", proxyGrantingTicketIou,
+ proxyGrantingTicket);
this.cache.put(proxyGrantingTicketIou, holder);
}
@@ -111,13 +111,13 @@ public void save(final String proxyGrantingTicketIou, final String proxyGranting
* called regularly via an external thread or timer.
*/
public void cleanUp() {
- for (final Map.Entry holder : this.cache.entrySet()) {
+ for (final Map.Entry holder : this.cache.entrySet()) {
if (holder.getValue().isExpired(this.timeout)) {
this.cache.remove(holder.getKey());
}
}
}
-
+
private static final class ProxyGrantingTicketHolder {
private final String proxyGrantingTicket;
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java
index 14c006f7b..07e01a48d 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.proxy;
import java.io.Serializable;
@@ -38,6 +37,5 @@ public interface ProxyRetriever extends Serializable {
* @param targetService the service we want to proxy.
* @return the ProxyTicket Id if Granted, null otherwise.
*/
- String getProxyTicketIdFor(String proxyGrantingTicketId,
- String targetService);
+ String getProxyTicketIdFor(String proxyGrantingTicketId, String targetService);
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html
index fa0cb0635..17c3c9c5c 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html
@@ -5,20 +5,19 @@
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:
+ 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
+ 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
+ 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.
-->
-
The proxy package includes a servlet to act as a proxy receptor,
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/HashMapBackedSessionMappingStorage.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/HashMapBackedSessionMappingStorage.java
index c58e3a47b..27a6c6018 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/HashMapBackedSessionMappingStorage.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/HashMapBackedSessionMappingStorage.java
@@ -1,31 +1,28 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.session;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import java.util.HashMap;
import java.util.Map;
-
import javax.servlet.http.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* HashMap backed implementation of SessionMappingStorage.
@@ -36,50 +33,48 @@
*
*/
public final class HashMapBackedSessionMappingStorage implements SessionMappingStorage {
-
+
/**
* Maps the ID from the CAS server to the Session.
*/
- private final Map MANAGED_SESSIONS = new HashMap();
+ private final Map MANAGED_SESSIONS = new HashMap();
/**
* Maps the Session ID to the key from the CAS Server.
*/
- private final Map ID_TO_SESSION_KEY_MAPPING = new HashMap();
+ private final Map ID_TO_SESSION_KEY_MAPPING = new HashMap();
- private final Log log = LogFactory.getLog(getClass());
+ private final Logger logger = LoggerFactory.getLogger(getClass());
- public synchronized void addSessionById(String mappingId, HttpSession session) {
+ public synchronized void addSessionById(String mappingId, HttpSession session) {
ID_TO_SESSION_KEY_MAPPING.put(session.getId(), mappingId);
MANAGED_SESSIONS.put(mappingId, session);
- }
+ }
- public synchronized void removeBySessionById(String sessionId) {
- if (log.isDebugEnabled()) {
- log.debug("Attempting to remove Session=[" + sessionId + "]");
- }
+ public synchronized void removeBySessionById(final String sessionId) {
+ logger.debug("Attempting to remove Session=[{}]", sessionId);
final String key = ID_TO_SESSION_KEY_MAPPING.get(sessionId);
- if (log.isDebugEnabled()) {
+ if (logger.isDebugEnabled()) {
if (key != null) {
- log.debug("Found mapping for session. Session Removed.");
+ logger.debug("Found mapping for session. Session Removed.");
} else {
- log.debug("No mapping for session found. Ignoring.");
+ logger.debug("No mapping for session found. Ignoring.");
}
}
MANAGED_SESSIONS.remove(key);
ID_TO_SESSION_KEY_MAPPING.remove(sessionId);
- }
+ }
- public synchronized HttpSession removeSessionByMappingId(String mappingId) {
- final HttpSession session = MANAGED_SESSIONS.get(mappingId);
+ public synchronized HttpSession removeSessionByMappingId(String mappingId) {
+ final HttpSession session = MANAGED_SESSIONS.get(mappingId);
if (session != null) {
- removeBySessionById(session.getId());
+ removeBySessionById(session.getId());
}
return session;
- }
+ }
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SessionMappingStorage.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SessionMappingStorage.java
index 32e53a133..ab0dce4b0 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SessionMappingStorage.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SessionMappingStorage.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.session;
import javax.servlet.http.HttpSession;
@@ -30,26 +29,26 @@
*
*/
public interface SessionMappingStorage {
-
- /**
- * Remove the HttpSession based on the mappingId.
- *
- * @param mappingId the id the session is keyed under.
- * @return the HttpSession if it exists.
- */
- HttpSession removeSessionByMappingId(String mappingId);
-
- /**
- * Remove a session by its Id.
- * @param sessionId the id of the session.
- */
- void removeBySessionById(String sessionId);
-
- /**
- * Add a session by its mapping Id.
- * @param mappingId the id to map the session to.
- * @param session the HttpSession.
- */
- void addSessionById(String mappingId, HttpSession session);
+
+ /**
+ * Remove the HttpSession based on the mappingId.
+ *
+ * @param mappingId the id the session is keyed under.
+ * @return the HttpSession if it exists.
+ */
+ HttpSession removeSessionByMappingId(String mappingId);
+
+ /**
+ * Remove a session by its Id.
+ * @param sessionId the id of the session.
+ */
+ void removeBySessionById(String sessionId);
+
+ /**
+ * Add a session by its mapping Id.
+ * @param mappingId the id to map the session to.
+ * @param session the HttpSession.
+ */
+ void addSessionById(String mappingId, HttpSession session);
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
index a9b439026..5720a6907 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java
@@ -1,33 +1,32 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.session;
-import org.jasig.cas.client.util.AbstractConfigurationFilter;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.jasig.cas.client.configuration.ConfigurationKeys;
+import org.jasig.cas.client.util.AbstractConfigurationFilter;
/**
* Implements the Single Sign Out protocol. It handles registering the session and destroying the session.
@@ -38,50 +37,72 @@
*/
public final class SingleSignOutFilter extends AbstractConfigurationFilter {
- private static final SingleSignOutHandler handler = new SingleSignOutHandler();
+ private static final SingleSignOutHandler HANDLER = new SingleSignOutHandler();
+
+ private AtomicBoolean handlerInitialized = new AtomicBoolean(false);
public void init(final FilterConfig filterConfig) throws ServletException {
+ super.init(filterConfig);
if (!isIgnoreInitConfiguration()) {
- handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
- handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
- handler.setArtifactParameterOverPost(parseBoolean(getPropertyFromInitParams(filterConfig, "artifactParameterOverPost", "false")));
+ setArtifactParameterName(getString(ConfigurationKeys.ARTIFACT_PARAMETER_NAME));
+ setLogoutParameterName(getString(ConfigurationKeys.LOGOUT_PARAMETER_NAME));
+ setFrontLogoutParameterName(getString(ConfigurationKeys.FRONT_LOGOUT_PARAMETER_NAME));
+ setRelayStateParameterName(getString(ConfigurationKeys.RELAY_STATE_PARAMETER_NAME));
+ setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
+ HANDLER.setArtifactParameterOverPost(getBoolean(ConfigurationKeys.ARTIFACT_PARAMETER_OVER_POST));
+ HANDLER.setEagerlyCreateSessions(getBoolean(ConfigurationKeys.EAGERLY_CREATE_SESSIONS));
}
- handler.init();
+ HANDLER.init();
+ handlerInitialized.set(true);
}
public void setArtifactParameterName(final String name) {
- handler.setArtifactParameterName(name);
+ HANDLER.setArtifactParameterName(name);
}
-
+
public void setLogoutParameterName(final String name) {
- handler.setLogoutParameterName(name);
+ HANDLER.setLogoutParameterName(name);
+ }
+
+ public void setFrontLogoutParameterName(final String name) {
+ HANDLER.setFrontLogoutParameterName(name);
+ }
+
+ public void setRelayStateParameterName(final String name) {
+ HANDLER.setRelayStateParameterName(name);
+ }
+
+ public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
+ HANDLER.setCasServerUrlPrefix(casServerUrlPrefix);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
- handler.setSessionMappingStorage(storage);
+ HANDLER.setSessionMappingStorage(storage);
}
-
- public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+
+ public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
+ final HttpServletResponse response = (HttpServletResponse) servletResponse;
- if (handler.isTokenRequest(request)) {
- handler.recordSession(request);
- } else if (handler.isLogoutRequest(request)) {
- handler.destroySession(request);
- // Do not continue up filter chain
- return;
- } else {
- log.trace("Ignoring URI " + request.getRequestURI());
+ /**
+ * Workaround for now for the fact that Spring Security will fail since it doesn't call {@link #init(javax.servlet.FilterConfig)}.
+ * Ultimately we need to allow deployers to actually inject their fully-initialized {@link org.jasig.cas.client.session.SingleSignOutHandler}.
+ */
+ if (!this.handlerInitialized.getAndSet(true)) {
+ HANDLER.init();
}
- filterChain.doFilter(servletRequest, servletResponse);
+ if (HANDLER.process(request, response)) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
}
public void destroy() {
// nothing to do
}
-
+
protected static SingleSignOutHandler getSingleSignOutHandler() {
- return handler;
+ return HANDLER;
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
index 31f3e24d7..5d07095cc 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java
@@ -1,34 +1,39 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.session;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.Inflater;
+
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.apache.commons.codec.binary.Base64;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
-
-import java.util.Arrays;
-import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Performs CAS single sign-out operations in an API-agnostic fashion.
@@ -40,22 +45,36 @@
*/
public final class SingleSignOutHandler {
+ private final static int DECOMPRESSION_FACTOR = 10;
+
/** Logger instance */
- private final Log log = LogFactory.getLog(getClass());
+ private final Logger logger = LoggerFactory.getLogger(getClass());
/** Mapping of token IDs and session IDs to HTTP sessions */
private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();
-
+
/** The name of the artifact parameter. This is used to capture the session identifier. */
- private String artifactParameterName = "ticket";
+ private String artifactParameterName = Protocol.CAS2.getArtifactParameterName();
+
+ /** Parameter name that stores logout request for back channel SLO */
+ private String logoutParameterName = ConfigurationKeys.LOGOUT_PARAMETER_NAME.getDefaultValue();
- /** Parameter name that stores logout request */
- private String logoutParameterName = "logoutRequest";
+ /** Parameter name that stores logout request for front channel SLO */
+ private String frontLogoutParameterName = ConfigurationKeys.FRONT_LOGOUT_PARAMETER_NAME.getDefaultValue();
+
+ /** Parameter name that stores the state of the CAS server webflow for the callback */
+ private String relayStateParameterName = ConfigurationKeys.RELAY_STATE_PARAMETER_NAME.getDefaultValue();
+
+ /** The prefix url of the CAS server */
+ private String casServerUrlPrefix = "";
private boolean artifactParameterOverPost = false;
+ private boolean eagerlyCreateSessions = true;
+
private List safeParameters;
+ private LogoutStrategy logoutStrategy = isServlet30() ? new Servlet30LogoutStrategy() : new Servlet25LogoutStrategy();
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.sessionMappingStorage = storage;
@@ -77,27 +96,61 @@ public void setArtifactParameterName(final String name) {
}
/**
- * @param name Name of parameter containing CAS logout request message.
+ * @param name Name of parameter containing CAS logout request message for back channel SLO.
*/
public void setLogoutParameterName(final String name) {
this.logoutParameterName = name;
}
+ /**
+ * @param casServerUrlPrefix The prefix url of the CAS server.
+ */
+ public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
+ this.casServerUrlPrefix = casServerUrlPrefix;
+ }
+
+ /**
+ * @param name Name of parameter containing CAS logout request message for front channel SLO.
+ */
+ public void setFrontLogoutParameterName(final String name) {
+ this.frontLogoutParameterName = name;
+ }
+
+ /**
+ * @param name Name of parameter containing the state of the CAS server webflow.
+ */
+ public void setRelayStateParameterName(final String name) {
+ this.relayStateParameterName = name;
+ }
+
+ public void setEagerlyCreateSessions(final boolean eagerlyCreateSessions) {
+ this.eagerlyCreateSessions = eagerlyCreateSessions;
+ }
+
/**
* Initializes the component for use.
*/
- public void init() {
- CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
- CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
- CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null.");
+ public synchronized void init() {
+ if (this.safeParameters == null) {
+ CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
+ CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
+ CommonUtils.assertNotNull(this.frontLogoutParameterName, "frontLogoutParameterName cannot be null.");
+ CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null.");
+ CommonUtils.assertNotNull(this.relayStateParameterName, "relayStateParameterName cannot be null.");
+ CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null.");
- if (this.artifactParameterOverPost) {
- this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName);
- } else {
- this.safeParameters = Arrays.asList(this.logoutParameterName);
+ if (CommonUtils.isBlank(this.casServerUrlPrefix)) {
+ logger.warn("Front Channel single sign out redirects are disabled when the 'casServerUrlPrefix' value is not set.");
+ }
+
+ if (this.artifactParameterOverPost) {
+ this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName);
+ } else {
+ this.safeParameters = Arrays.asList(this.logoutParameterName);
+ }
}
}
-
+
/**
* Determines whether the given request contains an authentication token.
*
@@ -105,20 +158,70 @@ public void init() {
*
* @return True if request contains authentication token, false otherwise.
*/
- public boolean isTokenRequest(final HttpServletRequest request) {
- return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters));
+ private boolean isTokenRequest(final HttpServletRequest request) {
+ return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName,
+ this.safeParameters));
}
/**
- * Determines whether the given request is a CAS logout request.
+ * Determines whether the given request is a CAS back channel logout request.
*
* @param request HTTP request.
*
* @return True if request is logout request, false otherwise.
*/
- public boolean isLogoutRequest(final HttpServletRequest request) {
- return "POST".equals(request.getMethod()) && !isMultipartRequest(request) &&
- CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters));
+ private boolean isBackChannelLogoutRequest(final HttpServletRequest request) {
+ return "POST".equals(request.getMethod())
+ && !isMultipartRequest(request)
+ && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,
+ this.safeParameters));
+ }
+
+ /**
+ * Determines whether the given request is a CAS front channel logout request. Front Channel log out requests are only supported
+ * when the 'casServerUrlPrefix' value is set.
+ *
+ * @param request HTTP request.
+ *
+ * @return True if request is logout request, false otherwise.
+ */
+ private boolean isFrontChannelLogoutRequest(final HttpServletRequest request) {
+ return "GET".equals(request.getMethod()) && CommonUtils.isNotBlank(this.casServerUrlPrefix)
+ && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.frontLogoutParameterName));
+ }
+
+ /**
+ * Process a request regarding the SLO process: record the session or destroy it.
+ *
+ * @param request the incoming HTTP request.
+ * @param response the HTTP response.
+ * @return if the request should continue to be processed.
+ */
+ public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
+ if (isTokenRequest(request)) {
+ logger.trace("Received a token request");
+ recordSession(request);
+ return true;
+
+ } else if (isBackChannelLogoutRequest(request)) {
+ logger.trace("Received a back channel logout request");
+ destroySession(request);
+ return false;
+
+ } else if (isFrontChannelLogoutRequest(request)) {
+ logger.trace("Received a front channel logout request");
+ destroySession(request);
+ // redirection url to the CAS server
+ final String redirectionUrl = computeRedirectionToServer(request);
+ if (redirectionUrl != null) {
+ CommonUtils.sendRedirect(response, redirectionUrl);
+ }
+ return false;
+
+ } else {
+ logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
+ return true;
+ }
}
/**
@@ -127,14 +230,17 @@ public boolean isLogoutRequest(final HttpServletRequest request) {
*
* @param request HTTP request containing an authentication token.
*/
- public void recordSession(final HttpServletRequest request) {
- final HttpSession session = request.getSession(true);
+ private void recordSession(final HttpServletRequest request) {
+ final HttpSession session = request.getSession(this.eagerlyCreateSessions);
- final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
- if (log.isDebugEnabled()) {
- log.debug("Recording session for token " + token);
+ if (session == null) {
+ logger.debug("No session currently exists (and none created). Cannot record session information for single sign out.");
+ return;
}
+ final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
+ logger.debug("Recording session for token {}", token);
+
try {
this.sessionMappingStorage.removeBySessionById(session.getId());
} catch (final Exception e) {
@@ -142,38 +248,134 @@ public void recordSession(final HttpServletRequest request) {
}
sessionMappingStorage.addSessionById(token, session);
}
-
+
+ /**
+ * Uncompress a logout message (base64 + deflate).
+ *
+ * @param originalMessage the original logout message.
+ * @return the uncompressed logout message.
+ */
+ private String uncompressLogoutMessage(final String originalMessage) {
+ final byte[] binaryMessage = Base64.decodeBase64(originalMessage);
+
+ Inflater decompresser = null;
+ try {
+ // decompress the bytes
+ decompresser = new Inflater();
+ decompresser.setInput(binaryMessage);
+ final byte[] result = new byte[binaryMessage.length * DECOMPRESSION_FACTOR];
+
+ final int resultLength = decompresser.inflate(result);
+
+ // decode the bytes into a String
+ return new String(result, 0, resultLength, "UTF-8");
+ } catch (final Exception e) {
+ logger.error("Unable to decompress logout message", e);
+ throw new RuntimeException(e);
+ } finally {
+ if (decompresser != null) {
+ decompresser.end();
+ }
+ }
+ }
+
/**
* Destroys the current HTTP session for the given CAS logout request.
*
* @param request HTTP request containing a CAS logout message.
*/
- public void destroySession(final HttpServletRequest request) {
- final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
- if (log.isTraceEnabled()) {
- log.trace ("Logout request:\n" + logoutMessage);
+ private void destroySession(final HttpServletRequest request) {
+ final String logoutMessage;
+ // front channel logout -> the message needs to be base64 decoded + decompressed
+ if (isFrontChannelLogoutRequest(request)) {
+ logoutMessage = uncompressLogoutMessage(CommonUtils.safeGetParameter(request,
+ this.frontLogoutParameterName));
+ } else {
+ logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
}
-
+ logger.trace("Logout request:\n{}", logoutMessage);
+
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (CommonUtils.isNotBlank(token)) {
final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
if (session != null) {
- String sessionID = session.getId();
+ final String sessionID = session.getId();
+ logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);
- if (log.isDebugEnabled()) {
- log.debug ("Invalidating session [" + sessionID + "] for token [" + token + "]");
- }
try {
session.invalidate();
} catch (final IllegalStateException e) {
- log.debug("Error invalidating session.", e);
+ logger.debug("Error invalidating session.", e);
}
+ this.logoutStrategy.logout(request);
}
}
}
+ /**
+ * Compute the redirection url to the CAS server when it's a front channel SLO
+ * (depending on the relay state parameter).
+ *
+ * @param request The HTTP request.
+ * @return the redirection url to the CAS server.
+ */
+ private String computeRedirectionToServer(final HttpServletRequest request) {
+ final String relayStateValue = CommonUtils.safeGetParameter(request, this.relayStateParameterName);
+ // if we have a state value -> redirect to the CAS server to continue the logout process
+ if (CommonUtils.isNotBlank(relayStateValue)) {
+ final StringBuilder buffer = new StringBuilder();
+ buffer.append(casServerUrlPrefix);
+ if (!this.casServerUrlPrefix.endsWith("/")) {
+ buffer.append("/");
+ }
+ buffer.append("logout?_eventId=next&");
+ buffer.append(this.relayStateParameterName);
+ buffer.append("=");
+ buffer.append(CommonUtils.urlEncode(relayStateValue));
+ final String redirectUrl = buffer.toString();
+ logger.debug("Redirection url to the CAS server: {}", redirectUrl);
+ return redirectUrl;
+ }
+ return null;
+ }
+
private boolean isMultipartRequest(final HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
}
+
+ private static boolean isServlet30() {
+ try {
+ return HttpServletRequest.class.getMethod("logout") != null;
+ } catch (final NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Abstracts the ways we can force logout with the Servlet spec.
+ */
+ private interface LogoutStrategy {
+
+ void logout(HttpServletRequest request);
+ }
+
+ private class Servlet25LogoutStrategy implements LogoutStrategy {
+
+ public void logout(final HttpServletRequest request) {
+ // nothing additional to do here
+ }
+ }
+
+ private class Servlet30LogoutStrategy implements LogoutStrategy {
+
+ public void logout(final HttpServletRequest request) {
+ try {
+ request.logout();
+ } catch (final ServletException e) {
+ logger.debug("Error performing request.logout.");
+ }
+ }
+ }
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java
index 7d28bd16c..d1f3b5d11 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.session;
import javax.servlet.http.HttpSession;
@@ -35,16 +34,16 @@
*/
public final class SingleSignOutHttpSessionListener implements HttpSessionListener {
- private SessionMappingStorage sessionMappingStorage;
-
+ private SessionMappingStorage sessionMappingStorage;
+
public void sessionCreated(final HttpSessionEvent event) {
// nothing to do at the moment
}
public void sessionDestroyed(final HttpSessionEvent event) {
- if (sessionMappingStorage == null) {
- sessionMappingStorage = getSessionMappingStorage();
- }
+ if (sessionMappingStorage == null) {
+ sessionMappingStorage = getSessionMappingStorage();
+ }
final HttpSession session = event.getSession();
sessionMappingStorage.removeBySessionById(session.getId());
}
@@ -56,6 +55,6 @@ public void sessionDestroyed(final HttpSessionEvent event) {
* @return the SessionMappingStorage
*/
protected static SessionMappingStorage getSessionMappingStorage() {
- return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
+ return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/AnyHostnameVerifier.java b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/AnyHostnameVerifier.java
index 14fe4ec99..c9672ce27 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/AnyHostnameVerifier.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/AnyHostnameVerifier.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.ssl;
import javax.net.ssl.HostnameVerifier;
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpURLConnectionFactory.java b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpURLConnectionFactory.java
new file mode 100644
index 000000000..8ea298f09
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpURLConnectionFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ssl;
+
+import java.io.Serializable;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * A factory to prepare and configure {@link java.net.URLConnection} instances.
+ *
+ * @author Misagh Moayyed
+ * @since 3.3
+ */
+public interface HttpURLConnectionFactory extends Serializable {
+
+ /**
+ * Receives a {@link URLConnection} instance typically as a result of a {@link URL}
+ * opening a connection to a remote resource. The received url connection is then
+ * configured and prepared appropriately depending on its type and is then returned to the caller
+ * to accommodate method chaining.
+ *
+ * @param url The url connection that needs to be configured
+ * @return The configured {@link HttpURLConnection} instance
+ *
+ * @see {@link HttpsURLConnectionFactory}
+ */
+ HttpURLConnection buildHttpURLConnection(final URLConnection url);
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpsURLConnectionFactory.java b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpsURLConnectionFactory.java
new file mode 100644
index 000000000..cede90858
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/HttpsURLConnectionFactory.java
@@ -0,0 +1,191 @@
+/*
+ * 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.ssl;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.security.KeyStore;
+import java.util.Properties;
+import javax.net.ssl.*;
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of the {@link HttpURLConnectionFactory} whose responsible to configure
+ * the underlying https connection, if needed, with a given hostname and SSL socket factory based on the
+ * configuration provided.
+ *
+ * @author Misagh Moayyed
+ * @since 3.3
+ * @see #setHostnameVerifier(HostnameVerifier)
+ * @see #setSSLConfiguration(Properties)
+ */
+public final class HttpsURLConnectionFactory implements HttpURLConnectionFactory {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HttpsURLConnectionFactory.class);
+
+ /**
+ * Hostname verifier used when making an SSL request to the CAS server.
+ * Defaults to {@link HttpsURLConnection#getDefaultHostnameVerifier()}
+ */
+ private HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+
+ /**
+ * Properties file that can contains key/trust info for Client Side Certificates
+ */
+ private Properties sslConfiguration = new Properties();
+
+ public HttpsURLConnectionFactory() {
+ }
+
+ public HttpsURLConnectionFactory(final HostnameVerifier verifier, final Properties config) {
+ setHostnameVerifier(verifier);
+ setSSLConfiguration(config);
+ }
+
+ public final void setSSLConfiguration(final Properties config) {
+ this.sslConfiguration = config;
+ }
+
+ /**
+ * Set the host name verifier for the https connection received.
+ *
+ * @see AnyHostnameVerifier
+ * @see RegexHostnameVerifier
+ * @see WhitelistHostnameVerifier
+ */
+ public final void setHostnameVerifier(final HostnameVerifier verifier) {
+ this.hostnameVerifier = verifier;
+ }
+
+ public HttpURLConnection buildHttpURLConnection(final URLConnection url) {
+ return this.configureHttpsConnectionIfNeeded(url);
+ }
+
+ /**
+ * Configures the connection with specific settings for secure http connections
+ * If the connection instance is not a {@link HttpsURLConnection},
+ * no additional changes will be made and the connection itself is simply returned.
+ *
+ * @param conn the http connection
+ */
+ private HttpURLConnection configureHttpsConnectionIfNeeded(final URLConnection conn) {
+ if (conn instanceof HttpsURLConnection) {
+ final HttpsURLConnection httpsConnection = (HttpsURLConnection) conn;
+ final SSLSocketFactory socketFactory = this.createSSLSocketFactory();
+ if (socketFactory != null) {
+ httpsConnection.setSSLSocketFactory(socketFactory);
+ }
+
+ if (this.hostnameVerifier != null) {
+ httpsConnection.setHostnameVerifier(this.hostnameVerifier);
+ }
+ }
+ return (HttpURLConnection) conn;
+ }
+
+ /**
+ * Creates a {@link SSLSocketFactory} based on the configuration specified
+ *
+ * Sample properties file:
+ *
+ * protocol=TLS
+ * keyStoreType=JKS
+ * keyStorePath=/var/secure/location/.keystore
+ * keyStorePass=changeit
+ * certificatePassword=aGoodPass
+ *
+ * @return the {@link SSLSocketFactory}
+ */
+ private SSLSocketFactory createSSLSocketFactory() {
+ InputStream keyStoreIS = null;
+ try {
+ final SSLContext sslContext = SSLContext.getInstance(this.sslConfiguration.getProperty("protocol", "SSL"));
+
+ if (this.sslConfiguration.getProperty("keyStoreType") != null) {
+ final KeyStore keyStore = KeyStore.getInstance(this.sslConfiguration.getProperty("keyStoreType"));
+ if (this.sslConfiguration.getProperty("keyStorePath") != null) {
+ keyStoreIS = new FileInputStream(this.sslConfiguration.getProperty("keyStorePath"));
+ if (this.sslConfiguration.getProperty("keyStorePass") != null) {
+ keyStore.load(keyStoreIS, this.sslConfiguration.getProperty("keyStorePass").toCharArray());
+ LOGGER.debug("Keystore has {} keys", keyStore.size());
+ final KeyManagerFactory keyManager = KeyManagerFactory.getInstance(this.sslConfiguration
+ .getProperty("keyManagerType", "SunX509"));
+ keyManager.init(keyStore, this.sslConfiguration.getProperty("certificatePassword")
+ .toCharArray());
+ sslContext.init(keyManager.getKeyManagers(), null, null);
+ return sslContext.getSocketFactory();
+ }
+ }
+ }
+
+ } catch (final Exception e) {
+ LOGGER.error(e.getMessage(), e);
+ } finally {
+ CommonUtils.closeQuietly(keyStoreIS);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final HttpsURLConnectionFactory that = (HttpsURLConnectionFactory) o;
+
+ if (!hostnameVerifier.equals(that.hostnameVerifier)) return false;
+ if (!sslConfiguration.equals(that.sslConfiguration)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = hostnameVerifier.hashCode();
+ result = 31 * result + sslConfiguration.hashCode();
+ return result;
+ }
+
+ private void writeObject(final ObjectOutputStream out) throws IOException {
+ if (this.hostnameVerifier == HttpsURLConnection.getDefaultHostnameVerifier()) {
+ out.writeObject(null);
+ } else {
+ out.writeObject(this.hostnameVerifier);
+ }
+
+ out.writeObject(this.sslConfiguration);
+
+ }
+
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ final Object internalHostNameVerifier = in.readObject();
+ if (internalHostNameVerifier == null) {
+ this.hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+ } else {
+ this.hostnameVerifier = (HostnameVerifier) internalHostNameVerifier;
+ }
+
+ this.sslConfiguration = (Properties) in.readObject();
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/RegexHostnameVerifier.java b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/RegexHostnameVerifier.java
index 6bb707406..a13c9f734 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/RegexHostnameVerifier.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/RegexHostnameVerifier.java
@@ -1,25 +1,26 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.ssl;
+import java.io.Serializable;
import java.util.regex.Pattern;
-
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
@@ -32,12 +33,13 @@
* @since 3.1.10
*
*/
-public final class RegexHostnameVerifier implements HostnameVerifier {
+public final class RegexHostnameVerifier implements HostnameVerifier, Serializable {
+
+ private static final long serialVersionUID = 1L;
/** Allowed hostname pattern */
private Pattern pattern;
-
-
+
/**
* Creates a new instance using the given regular expression.
*
@@ -47,7 +49,6 @@ public RegexHostnameVerifier(final String regex) {
this.pattern = Pattern.compile(regex);
}
-
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
return pattern.matcher(hostname).matches();
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/WhitelistHostnameVerifier.java b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/WhitelistHostnameVerifier.java
index c09002a70..c882225e4 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/ssl/WhitelistHostnameVerifier.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/ssl/WhitelistHostnameVerifier.java
@@ -1,26 +1,26 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
+import java.io.Serializable;
/**
* Verifies a SSL peer host name based on an explicit whitelist of allowed hosts.
@@ -30,12 +30,13 @@
* @since 3.1.10
*
*/
-public final class WhitelistHostnameVerifier implements HostnameVerifier {
+public final class WhitelistHostnameVerifier implements HostnameVerifier, Serializable {
+
+ private static final long serialVersionUID = 1L;
/** Allowed hosts */
private String[] allowedHosts;
-
/**
* Creates a new instance using the given array of allowed hosts.
*
@@ -45,7 +46,6 @@ public WhitelistHostnameVerifier(final String[] allowed) {
this.allowedHosts = allowed;
}
-
/**
* Creates a new instance using the given list of allowed hosts.
*
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 79c94b327..9b48296f1 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
@@ -1,26 +1,25 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@@ -38,23 +37,16 @@
* Please note that one of the two above parameters must be set.
*
* @author Scott Battaglia
- * @version $Revision$ $Date$
+ * @author Misagh Moayyed
* @since 3.1
*/
public abstract class AbstractCasFilter extends AbstractConfigurationFilter {
-
+
/** Represents the constant for where the assertion will be located in memory. */
public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_";
- /** Instance of commons logging for logging purposes. */
- protected final Log log = LogFactory.getLog(getClass());
+ private Protocol protocol;
- /** Defines the parameter to look for for the artifact. */
- private String artifactParameterName = "ticket";
-
- /** Defines the parameter to look for for the service. */
- private String serviceParameterName = "service";
-
/** Sets where response.encodeUrl should be called on service urls when constructed. */
private boolean encodeServiceUrl = true;
@@ -66,24 +58,23 @@ public abstract class AbstractCasFilter extends AbstractConfigurationFilter {
/** The exact url of the service. */
private String service;
+ protected AbstractCasFilter(final Protocol protocol) {
+ this.protocol = protocol;
+ }
+
public final void init(final FilterConfig filterConfig) throws ServletException {
+ super.init(filterConfig);
if (!isIgnoreInitConfiguration()) {
- setServerName(getPropertyFromInitParams(filterConfig, "serverName", null));
- log.trace("Loading serverName property: " + this.serverName);
- setService(getPropertyFromInitParams(filterConfig, "service", null));
- log.trace("Loading service property: " + this.service);
- setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
- log.trace("Loading artifact parameter name property: " + this.artifactParameterName);
- setServiceParameterName(getPropertyFromInitParams(filterConfig, "serviceParameterName", "service"));
- log.trace("Loading serviceParameterName property: " + this.serviceParameterName);
- setEncodeServiceUrl(parseBoolean(getPropertyFromInitParams(filterConfig, "encodeServiceUrl", "true")));
- log.trace("Loading encodeServiceUrl property: " + this.encodeServiceUrl);
-
+ setServerName(getString(ConfigurationKeys.SERVER_NAME));
+ setService(getString(ConfigurationKeys.SERVICE));
+ setEncodeServiceUrl(getBoolean(ConfigurationKeys.ENCODE_SERVICE_URL));
+
initInternal(filterConfig);
}
init();
}
+
/** Controls the ordering of filter initialization and checking by defining a method that runs before the init.
* @param filterConfig the original filter configuration.
* @throws ServletException if there is a problem.
@@ -98,10 +89,10 @@ protected void initInternal(final FilterConfig filterConfig) throws ServletExcep
* afterPropertiesSet();
*/
public void init() {
- CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
- CommonUtils.assertNotNull(this.serviceParameterName, "serviceParameterName cannot be null.");
- CommonUtils.assertTrue(CommonUtils.isNotEmpty(this.serverName) || CommonUtils.isNotEmpty(this.service), "serverName or service must be set.");
- CommonUtils.assertTrue(CommonUtils.isBlank(this.serverName) || CommonUtils.isBlank(this.service), "serverName and service cannot both be set. You MUST ONLY set one.");
+ CommonUtils.assertTrue(CommonUtils.isNotEmpty(this.serverName) || CommonUtils.isNotEmpty(this.service),
+ "serverName or service must be set.");
+ CommonUtils.assertTrue(CommonUtils.isBlank(this.serverName) || CommonUtils.isBlank(this.service),
+ "serverName and service cannot both be set. You MUST ONLY set one.");
}
// empty implementation as most filters won't need this.
@@ -110,7 +101,9 @@ public void destroy() {
}
protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
- return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName, this.artifactParameterName, this.encodeServiceUrl);
+ return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName,
+ this.protocol.getServiceParameterName(),
+ this.protocol.getArtifactParameterName(), this.encodeServiceUrl);
}
/**
@@ -121,8 +114,8 @@ protected final String constructServiceUrl(final HttpServletRequest request, fin
*/
public final void setServerName(final String serverName) {
if (serverName != null && serverName.endsWith("/")) {
- this.serverName = serverName.substring(0, serverName.length()-1);
- log.info(String.format("Eliminated extra slash from serverName [%s]. It is now [%s]", serverName, this.serverName));
+ this.serverName = serverName.substring(0, serverName.length() - 1);
+ logger.info("Eliminated extra slash from serverName [{}]. It is now [{}]", serverName, this.serverName);
} else {
this.serverName = serverName;
}
@@ -132,24 +125,12 @@ public final void setService(final String service) {
this.service = service;
}
- public final void setArtifactParameterName(final String artifactParameterName) {
- this.artifactParameterName = artifactParameterName;
- }
-
- public final void setServiceParameterName(final String serviceParameterName) {
- this.serviceParameterName = serviceParameterName;
- }
-
public final void setEncodeServiceUrl(final boolean encodeServiceUrl) {
- this.encodeServiceUrl = encodeServiceUrl;
- }
-
- public final String getArtifactParameterName() {
- return this.artifactParameterName;
+ this.encodeServiceUrl = encodeServiceUrl;
}
- public final String getServiceParameterName() {
- return this.serviceParameterName;
+ protected Protocol getProtocol() {
+ return this.protocol;
}
/**
@@ -159,6 +140,6 @@ public final String getServiceParameterName() {
* @return the ticket if its found, null otherwise.
*/
protected String retrieveTicketFromRequest(final HttpServletRequest request) {
- return CommonUtils.safeGetParameter(request,getArtifactParameterName());
+ return CommonUtils.safeGetParameter(request, this.protocol.getArtifactParameterName());
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java
index de6dca7e1..9d3e54293 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java
@@ -1,31 +1,32 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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 javax.naming.InitialContext;
-import javax.naming.NamingException;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.jasig.cas.client.configuration.ConfigurationKey;
+import org.jasig.cas.client.configuration.ConfigurationStrategy;
+import org.jasig.cas.client.configuration.ConfigurationStrategyName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Abstracts out the ability to configure the filters from the initial properties provided.
@@ -35,90 +36,42 @@
* @since 3.1
*/
public abstract class AbstractConfigurationFilter implements Filter {
-
- protected final Log log = LogFactory.getLog(getClass());
+
+ private static final String CONFIGURATION_STRATEGY_KEY = "configurationStrategy";
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
private boolean ignoreInitConfiguration = false;
- /**
- * Retrieves the property from the FilterConfig. First it checks the FilterConfig's initParameters to see if it
- * has a value.
- * If it does, it returns that, otherwise it retrieves the ServletContext's initParameters and returns that value if any.
- *
- * Finally, it will check JNDI if all other methods fail. All the JNDI properties should be stored under either java:comp/env/cas/SHORTFILTERNAME/{propertyName}
- * or java:comp/env/cas/{propertyName}
- *
- * Essentially the documented order is:
- *
- * - FilterConfig.getInitParameter
- * - ServletContext.getInitParameter
- * - java:comp/env/cas/SHORTFILTERNAME/{propertyName}
- * - java:comp/env/cas/{propertyName}
- * - Default Value
- *
- *
- *
- * @param filterConfig the Filter Configuration.
- * @param propertyName the property to retrieve.
- * @param defaultValue the default value if the property is not found.
- * @return the property value, following the above conventions. It will always return the more specific value (i.e.
- * filter vs. context).
- */
- protected final String getPropertyFromInitParams(final FilterConfig filterConfig, final String propertyName, final String defaultValue) {
- final String value = filterConfig.getInitParameter(propertyName);
+ private ConfigurationStrategy configurationStrategy;
- if (CommonUtils.isNotBlank(value)) {
- log.info("Property [" + propertyName + "] loaded from FilterConfig.getInitParameter with value [" + value + "]");
- return value;
- }
+ public void init(FilterConfig filterConfig) throws ServletException {
+ final String configurationStrategyName = filterConfig.getServletContext().getInitParameter(CONFIGURATION_STRATEGY_KEY);
+ this.configurationStrategy = ReflectUtils.newInstance(ConfigurationStrategyName.resolveToConfigurationStrategy(configurationStrategyName));
+ this.configurationStrategy.init(filterConfig, getClass());
+ }
- final String value2 = filterConfig.getServletContext().getInitParameter(propertyName);
+ protected final boolean getBoolean(final ConfigurationKey configurationKey) {
+ return this.configurationStrategy.getBoolean(configurationKey);
+ }
- if (CommonUtils.isNotBlank(value2)) {
- log.info("Property [" + propertyName + "] loaded from ServletContext.getInitParameter with value [" + value2 + "]");
- return value2;
- }
- InitialContext context;
- try {
- context = new InitialContext();
- } catch (final NamingException e) {
- log.warn(e,e);
- return defaultValue;
- }
-
-
- final String shortName = this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".")+1);
- final String value3 = loadFromContext(context, "java:comp/env/cas/" + shortName + "/" + propertyName);
-
- if (CommonUtils.isNotBlank(value3)) {
- log.info("Property [" + propertyName + "] loaded from JNDI Filter Specific Property with value [" + value3 + "]");
- return value3;
- }
-
- final String value4 = loadFromContext(context, "java:comp/env/cas/" + propertyName);
-
- if (CommonUtils.isNotBlank(value4)) {
- log.info("Property [" + propertyName + "] loaded from JNDI with value [" + value4 + "]");
- return value4;
- }
+ protected final String getString(final ConfigurationKey configurationKey) {
+ return this.configurationStrategy.getString(configurationKey);
+ }
- log.info("Property [" + propertyName + "] not found. Using default value [" + defaultValue + "]");
- return defaultValue;
+ protected final long getLong(final ConfigurationKey configurationKey) {
+ return this.configurationStrategy.getLong(configurationKey);
}
-
- protected final boolean parseBoolean(final String value) {
- return ((value != null) && value.equalsIgnoreCase("true"));
+
+ protected final int getInt(final ConfigurationKey configurationKey) {
+ return this.configurationStrategy.getInt(configurationKey);
}
-
- protected final String loadFromContext(final InitialContext context, final String path) {
- try {
- return (String) context.lookup(path);
- } catch (final NamingException e) {
- return null;
- }
+
+ protected final Class extends T> getClass(final ConfigurationKey> configurationKey) {
+ return this.configurationStrategy.getClass(configurationKey);
}
- public final void setIgnoreInitConfiguration(boolean ignoreInitConfiguration) {
+ public final void setIgnoreInitConfiguration(final boolean ignoreInitConfiguration) {
this.ignoreInitConfiguration = ignoreInitConfiguration;
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java
index 7959ca0ec..842a2686d 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jasig.cas.client.validation.Assertion;
@@ -35,7 +34,6 @@ public class AssertionHolder {
*/
private static final ThreadLocal threadLocal = new ThreadLocal();
-
/**
* Retrieve the assertion from the ThreadLocal.
*
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java
index d0af95e58..9cae17dce 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java
@@ -1,35 +1,28 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jasig.cas.client.validation.Assertion;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import java.io.IOException;
+import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
-import java.io.IOException;
+import org.jasig.cas.client.validation.Assertion;
/**
* Places the assertion in a ThreadLocal such that other resources can access it that do not have access to the web tier session.
@@ -44,10 +37,13 @@ public void init(final FilterConfig filterConfig) throws ServletException {
// nothing to do here
}
- public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+ public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpSession session = request.getSession(false);
- final Assertion assertion = (Assertion) (session == null ? request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
+ final Assertion assertion = (Assertion) (session == null ? request
+ .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session
+ .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
try {
AssertionHolder.setAssertion(assertion);
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 733393f3d..639b3b48b 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
@@ -1,47 +1,40 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
-import org.jasig.cas.client.validation.ProxyList;
-import org.jasig.cas.client.validation.ProxyListEditor;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.net.URLConnection;
-import java.net.URLEncoder;
-import java.net.URL;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.*;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
+import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
+import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
+import org.jasig.cas.client.validation.ProxyList;
+import org.jasig.cas.client.validation.ProxyListEditor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Common utilities so that we don't need to include Commons Lang.
@@ -52,9 +45,8 @@
*/
public final class CommonUtils {
- /** Instance of Commons Logging. */
- private static final Log LOG = LogFactory.getLog(CommonUtils.class);
-
+ private static final Logger LOGGER = LoggerFactory.getLogger(CommonUtils.class);
+
/**
* Constant representing the ProxyGrantingTicket IOU Request Parameter.
*/
@@ -65,16 +57,23 @@ public final class CommonUtils {
*/
private static final String PARAM_PROXY_GRANTING_TICKET = "pgtId";
+ private static final HttpURLConnectionFactory DEFAULT_URL_CONNECTION_FACTORY = new HttpsURLConnectionFactory();
+
+ private static final String SERVICE_PARAMETER_NAMES;
+
private CommonUtils() {
// nothing to do
}
- public static String formatForUtcTime(final Date date) {
- final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- return dateFormat.format(date);
+ static {
+ final Set serviceParameterSet = new HashSet(4);
+ for (final Protocol protocol : Protocol.values()) {
+ serviceParameterSet.add(protocol.getServiceParameterName());
+ }
+ SERVICE_PARAMETER_NAMES = serviceParameterSet.toString()
+ .replaceAll("\\[|\\]", "")
+ .replaceAll("\\s", "");
}
-
/**
* Check whether the object is null or not. If it is, throw an exception and
* display the message.
@@ -106,7 +105,7 @@ public static void assertNotEmpty(final Collection> c, final String message) {
* Assert that the statement is true, otherwise throw an exception with the
* provided message.
*
- * @param cond the codition to assert is true.
+ * @param cond the condition to assert is true.
* @param message the message to display if the condition is not true.
*/
public static void assertTrue(final boolean cond, final String message) {
@@ -115,6 +114,20 @@ public static void assertTrue(final boolean cond, final String message) {
}
}
+
+ /**
+ * Assert that the statement is true, otherwise throw an exception with the
+ * provided message.
+ *
+ * @param cond the condition to assert is false.
+ * @param message the message to display if the condition is not false.
+ */
+ public static void assertFalse(final boolean cond, final String message) {
+ if (cond) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
/**
* Determines whether the String is null or of length 0.
*
@@ -168,43 +181,48 @@ public static boolean isNotBlank(final String string) {
* @param gateway where we should send gateway or not.
* @return the fully constructed redirect url.
*/
- public static String constructRedirectUrl(final String casServerLoginUrl, final String serviceParameterName, final String serviceUrl, final boolean renew, final boolean gateway) {
+ public static String constructRedirectUrl(final String casServerLoginUrl, final String serviceParameterName,
+ final String serviceUrl, final boolean renew, final boolean gateway) {
+ return casServerLoginUrl + (casServerLoginUrl.contains("?") ? "&" : "?") + serviceParameterName + "="
+ + urlEncode(serviceUrl) + (renew ? "&renew=true" : "") + (gateway ? "&gateway=true" : "");
+ }
+
+ /**
+ * Url encode a value using UTF-8 encoding.
+ *
+ * @param value the value to encode.
+ * @return the encoded value.
+ */
+ public static String urlEncode(String value) {
try {
- return casServerLoginUrl + (casServerLoginUrl.contains("?") ? "&" : "?") + serviceParameterName + "="
- + URLEncoder.encode(serviceUrl, "UTF-8")
- + (renew ? "&renew=true" : "")
- + (gateway ? "&gateway=true" : "");
+ return URLEncoder.encode(value, "UTF-8");
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
-
- public static void readAndRespondToProxyReceptorRequest(final HttpServletRequest request, final HttpServletResponse response, final ProxyGrantingTicketStorage proxyGrantingTicketStorage) throws IOException {
+
+ public static void readAndRespondToProxyReceptorRequest(final HttpServletRequest request,
+ final HttpServletResponse response, final ProxyGrantingTicketStorage proxyGrantingTicketStorage)
+ throws IOException {
final String proxyGrantingTicketIou = request.getParameter(PARAM_PROXY_GRANTING_TICKET_IOU);
- final String proxyGrantingTicket = request.getParameter(PARAM_PROXY_GRANTING_TICKET);
+ final String proxyGrantingTicket = request.getParameter(PARAM_PROXY_GRANTING_TICKET);
- if (CommonUtils.isBlank(proxyGrantingTicket) || CommonUtils.isBlank(proxyGrantingTicketIou)) {
- response.getWriter().write("");
- return;
- }
+ if (CommonUtils.isBlank(proxyGrantingTicket) || CommonUtils.isBlank(proxyGrantingTicketIou)) {
+ response.getWriter().write("");
+ return;
+ }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Received proxyGrantingTicketId ["
- + proxyGrantingTicket + "] for proxyGrantingTicketIou ["
- + proxyGrantingTicketIou + "]");
- }
+ LOGGER.debug("Received proxyGrantingTicketId [{}] for proxyGrantingTicketIou [{}]", proxyGrantingTicket,
+ proxyGrantingTicketIou);
- proxyGrantingTicketStorage.save(proxyGrantingTicketIou, proxyGrantingTicket);
+ proxyGrantingTicketStorage.save(proxyGrantingTicketIou, proxyGrantingTicket);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Successfully saved proxyGrantingTicketId ["
- + proxyGrantingTicket + "] for proxyGrantingTicketIou ["
- + proxyGrantingTicketIou + "]");
- }
-
- response.getWriter().write("");
- response.getWriter().write("");
+ LOGGER.debug("Successfully saved proxyGrantingTicketId [{}] for proxyGrantingTicketIou [{}]",
+ proxyGrantingTicket, proxyGrantingTicketIou);
+
+ response.getWriter().write("");
+ response.getWriter().write("");
}
protected static String findMatchingServerName(final HttpServletRequest request, final String serverName) {
@@ -216,12 +234,12 @@ protected static String findMatchingServerName(final HttpServletRequest request,
final String host = request.getHeader("Host");
final String xHost = request.getHeader("X-Forwarded-Host");
-
+
final String comparisonHost;
if (xHost != null && host == "localhost") {
- comparisonHost = xHost;
+ comparisonHost = xHost;
} else {
- comparisonHost = host;
+ comparisonHost = host;
}
if (comparisonHost == null) {
@@ -238,70 +256,104 @@ protected static String findMatchingServerName(final HttpServletRequest request,
return serverNames[0];
}
-
-/**
+
+ private static boolean serverNameContainsPort(final boolean containsScheme, final String serverName) {
+ if (!containsScheme && serverName.contains(":")) {
+ return true;
+ }
+
+ final int schemeIndex = serverName.indexOf(":");
+ final int portIndex = serverName.lastIndexOf(":");
+ return schemeIndex != portIndex;
+ }
+
+ private static boolean requestIsOnStandardPort(final HttpServletRequest request) {
+ final int serverPort = request.getServerPort();
+ return serverPort == 80 || serverPort == 443;
+ }
+
+ /**
+ * Constructs a service url from the HttpServletRequest or from the given
+ * serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a
+ * serviceName. Compiles a list of all service parameters for supported protocols
+ * and removes them all from the query string.
+ *
+ * @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 construct 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.
+ */
+ @Deprecated
+ public static String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response,
+ final String service, final String serverNames,
+ final String artifactParameterName, final boolean encode) {
+ return constructServiceUrl(request, response, service, serverNames, SERVICE_PARAMETER_NAMES
+ , artifactParameterName, encode);
+ }
+
+ /**
* 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 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.
+ * @param serverNames the server name to use to construct 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).
+ * @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) {
+ public static String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response,
+ 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);
- buffer.append(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();
- if (LOG.isDebugEnabled()) {
- LOG.debug("serviceUrl generated: " + returnValue);
- }
- return returnValue;
- }
-
- buffer.append("?");
+ if (!serverNameContainsPort(containsScheme, serverName) && !requestIsOnStandardPort(request)) {
+ builder.setPort(request.getServerPort());
+ }
- if (location == -1) {
- buffer.append(request.getQueryString());
- } else if (location > 0) {
- final int actualLocation = request.getQueryString()
- .indexOf("&" + artifactParameterName + "=");
+ builder.setEncodedPath(request.getRequestURI());
- if (actualLocation == -1) {
- buffer.append(request.getQueryString());
- } else if (actualLocation > 0) {
- buffer.append(request.getQueryString().substring(0,
- actualLocation));
+ final List serviceParameterNames = Arrays.asList(serviceParameterName.split(","));
+ if (!serviceParameterNames.isEmpty() && !originalRequestUrl.getQueryParams().isEmpty()) {
+ for (final URIBuilder.BasicNameValuePair pair : originalRequestUrl.getQueryParams()) {
+ if (!pair.getName().equals(artifactParameterName) && !serviceParameterNames.contains(pair.getName())) {
+ builder.addParameter(pair.getName(), pair.getValue());
}
}
}
- final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString();
- if (LOG.isDebugEnabled()) {
- LOG.debug("serviceUrl generated: " + returnValue);
- }
+ final String result = builder.toString();
+ final String returnValue = encode ? response.encodeURL(result) : result;
+ LOGGER.debug("serviceUrl generated: {}", returnValue);
return returnValue;
}
@@ -321,18 +373,21 @@ public static String constructServiceUrl(final HttpServletRequest request, final
* @param parameter the parameter to look for.
* @return the value of the parameter.
*/
- public static String safeGetParameter(final HttpServletRequest request, final String parameter, final List parameters) {
+ public static String safeGetParameter(final HttpServletRequest request, final String parameter,
+ final List parameters) {
if ("POST".equals(request.getMethod()) && parameters.contains(parameter)) {
- LOG.debug("safeGetParameter called on a POST HttpServletRequest for Restricted Parameters. Cannot complete check safely. Reverting to standard behavior for this Parameter");
+ LOGGER.debug("safeGetParameter called on a POST HttpServletRequest for Restricted Parameters. Cannot complete check safely. Reverting to standard behavior for this Parameter");
return request.getParameter(parameter);
}
- return request.getQueryString() == null || !request.getQueryString().contains(parameter) ? null : request.getParameter(parameter);
+ return request.getQueryString() == null || !request.getQueryString().contains(parameter) ? null : request
+ .getParameter(parameter);
}
public static String safeGetParameter(final HttpServletRequest request, final String parameter) {
return safeGetParameter(request, parameter, Arrays.asList("logoutRequest"));
}
+
/**
* Contacts the remote URL and returns the response.
*
@@ -340,64 +395,58 @@ public static String safeGetParameter(final HttpServletRequest request, final St
* @param encoding the encoding to use.
* @return the response.
*/
+ @Deprecated
+ public static String getResponseFromServer(final String constructedUrl, final String encoding) {
+ try {
+ return getResponseFromServer(new URL(constructedUrl), DEFAULT_URL_CONNECTION_FACTORY, encoding);
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Deprecated
public static String getResponseFromServer(final URL constructedUrl, final String encoding) {
- return getResponseFromServer(constructedUrl, HttpsURLConnection.getDefaultHostnameVerifier(), encoding);
+ return getResponseFromServer(constructedUrl, DEFAULT_URL_CONNECTION_FACTORY, encoding);
}
/**
* Contacts the remote URL and returns the response.
*
* @param constructedUrl the url to contact.
- * @param hostnameVerifier Host name verifier to use for HTTPS connections.
+ * @param factory connection factory to prepare the URL connection instance
* @param encoding the encoding to use.
* @return the response.
*/
- public static String getResponseFromServer(final URL constructedUrl, final HostnameVerifier hostnameVerifier, final String encoding) {
- URLConnection conn = null;
+ public static String getResponseFromServer(final URL constructedUrl, final HttpURLConnectionFactory factory,
+ final String encoding) {
+
+ HttpURLConnection conn = null;
+ InputStreamReader in = null;
try {
- conn = constructedUrl.openConnection();
- if (conn instanceof HttpsURLConnection) {
- ((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
- }
- final BufferedReader in;
+ conn = factory.buildHttpURLConnection(constructedUrl.openConnection());
if (CommonUtils.isEmpty(encoding)) {
- in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ in = new InputStreamReader(conn.getInputStream());
} else {
- in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encoding));
+ in = new InputStreamReader(conn.getInputStream(), encoding);
}
- String line;
- final StringBuilder stringBuffer = new StringBuilder(255);
-
- while ((line = in.readLine()) != null) {
- stringBuffer.append(line);
- stringBuffer.append("\n");
+ final StringBuilder builder = new StringBuilder(255);
+ int byteRead;
+ while ((byteRead = in.read()) != -1) {
+ builder.append((char) byteRead);
}
- return stringBuffer.toString();
+
+ return builder.toString();
} catch (final Exception e) {
- LOG.error(e.getMessage(), e);
+ LOGGER.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
- if (conn != null && conn instanceof HttpURLConnection) {
- ((HttpURLConnection)conn).disconnect();
+ closeQuietly(in);
+ if (conn != null) {
+ conn.disconnect();
}
}
-
- }
- /**
- * Contacts the remote URL and returns the response.
- *
- * @param url the url to contact.
- * @param encoding the encoding to use.
- * @return the response.
- */
- public static String getResponseFromServer(final String url, String encoding) {
- try {
- return getResponseFromServer(new URL(url), encoding);
- } catch (final MalformedURLException e) {
- throw new IllegalArgumentException(e);
- }
}
public static ProxyList createProxyList(final String proxies) {
@@ -408,7 +457,7 @@ public static ProxyList createProxyList(final String proxies) {
final ProxyListEditor editor = new ProxyListEditor();
editor.setAsText(proxies);
return (ProxyList) editor.getValue();
- }
+ }
/**
* Sends the redirect message and captures the exceptions that we can't possibly do anything with.
@@ -420,8 +469,234 @@ public static void sendRedirect(final HttpServletResponse response, final String
try {
response.sendRedirect(url);
} catch (final Exception e) {
- LOG.warn(e.getMessage(), e);
+ LOGGER.warn(e.getMessage(), e);
+ }
+
+ }
+
+ /**
+ * Unconditionally close a {@link Closeable}. Equivalent to {@link java.io.Closeable#close()}close(), except any exceptions
+ * will be ignored. This is typically used in finally blocks.
+ * @param resource the resource to close
+ */
+ public static void closeQuietly(final Closeable resource) {
+ try {
+ if (resource != null) {
+ resource.close();
+ }
+ } catch (final IOException e) {
+ //ignore
+ }
+ }
+
+ /**
+ * Converts a String to a boolean (optimised for performance).
+ *
+ * {@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'}
+ * (case insensitive) will return {@code true}. Otherwise,
+ * {@code false} is returned.
+ *
+ * This method performs 4 times faster (JDK1.4) than
+ * {@code Boolean.valueOf(String)}. However, this method accepts
+ * 'on' and 'yes', 't', 'y' as true values.
+ *
+ *
+ * BooleanUtils.toBoolean(null) = false
+ * BooleanUtils.toBoolean("true") = true
+ * BooleanUtils.toBoolean("TRUE") = true
+ * BooleanUtils.toBoolean("tRUe") = true
+ * BooleanUtils.toBoolean("on") = true
+ * BooleanUtils.toBoolean("yes") = true
+ * BooleanUtils.toBoolean("false") = false
+ * BooleanUtils.toBoolean("x gti") = false
+ * BooleanUtils.toBooleanObject("y") = true
+ * BooleanUtils.toBooleanObject("n") = false
+ * BooleanUtils.toBooleanObject("t") = true
+ * BooleanUtils.toBooleanObject("f") = false
+ *
+ *
+ * @param str the String to check
+ * @return the boolean value of the string, {@code false} if no match or the String is null
+ */
+ public static boolean toBoolean(final String str) {
+ return toBooleanObject(str) == Boolean.TRUE;
+ }
+
+ /**
+ * Converts a String to a Boolean.
+ *
+ * {@code 'true'}, {@code 'on'}, {@code 'y'}, {@code 't'} or {@code 'yes'}
+ * (case insensitive) will return {@code true}.
+ * {@code 'false'}, {@code 'off'}, {@code 'n'}, {@code 'f'} or {@code 'no'}
+ * (case insensitive) will return {@code false}.
+ * Otherwise, {@code null} is returned.
+ *
+ * NOTE: This returns null and will throw a NullPointerException if autoboxed to a boolean.
+ *
+ *
+ * // N.B. case is not significant
+ * BooleanUtils.toBooleanObject(null) = null
+ * BooleanUtils.toBooleanObject("true") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("T") = Boolean.TRUE // i.e. T[RUE]
+ * BooleanUtils.toBooleanObject("false") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("f") = Boolean.FALSE // i.e. f[alse]
+ * BooleanUtils.toBooleanObject("No") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("n") = Boolean.FALSE // i.e. n[o]
+ * BooleanUtils.toBooleanObject("on") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("ON") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("off") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("oFf") = Boolean.FALSE
+ * BooleanUtils.toBooleanObject("yes") = Boolean.TRUE
+ * BooleanUtils.toBooleanObject("Y") = Boolean.TRUE // i.e. Y[ES]
+ * BooleanUtils.toBooleanObject("blue") = null
+ * BooleanUtils.toBooleanObject("true ") = null // trailing space (too long)
+ * BooleanUtils.toBooleanObject("ono") = null // does not match on or no
+ *
+ *
+ * @param str the String to check; upper and lower case are treated as the same
+ * @return the Boolean value of the string, {@code null} if no match or {@code null} input
+ */
+ public static Boolean toBooleanObject(final String str) {
+ // Previously used equalsIgnoreCase, which was fast for interned 'true'.
+ // Non interned 'true' matched 15 times slower.
+ //
+ // Optimisation provides same performance as before for interned 'true'.
+ // Similar performance for null, 'false', and other strings not length 2/3/4.
+ // 'true'/'TRUE' match 4 times slower, 'tRUE'/'True' 7 times slower.
+ if (str == "true") {
+ return Boolean.TRUE;
+ }
+ if (str == null) {
+ return null;
+ }
+ switch (str.length()) {
+ case 1: {
+ final char ch0 = str.charAt(0);
+ if (ch0 == 'y' || ch0 == 'Y' ||
+ ch0 == 't' || ch0 == 'T') {
+ return Boolean.TRUE;
+ }
+ if (ch0 == 'n' || ch0 == 'N' ||
+ ch0 == 'f' || ch0 == 'F') {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 2: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ if ((ch0 == 'o' || ch0 == 'O') &&
+ (ch1 == 'n' || ch1 == 'N') ) {
+ return Boolean.TRUE;
+ }
+ if ((ch0 == 'n' || ch0 == 'N') &&
+ (ch1 == 'o' || ch1 == 'O') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 3: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ if ((ch0 == 'y' || ch0 == 'Y') &&
+ (ch1 == 'e' || ch1 == 'E') &&
+ (ch2 == 's' || ch2 == 'S') ) {
+ return Boolean.TRUE;
+ }
+ if ((ch0 == 'o' || ch0 == 'O') &&
+ (ch1 == 'f' || ch1 == 'F') &&
+ (ch2 == 'f' || ch2 == 'F') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ case 4: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ final char ch3 = str.charAt(3);
+ if ((ch0 == 't' || ch0 == 'T') &&
+ (ch1 == 'r' || ch1 == 'R') &&
+ (ch2 == 'u' || ch2 == 'U') &&
+ (ch3 == 'e' || ch3 == 'E') ) {
+ return Boolean.TRUE;
+ }
+ break;
+ }
+ case 5: {
+ final char ch0 = str.charAt(0);
+ final char ch1 = str.charAt(1);
+ final char ch2 = str.charAt(2);
+ final char ch3 = str.charAt(3);
+ final char ch4 = str.charAt(4);
+ if ((ch0 == 'f' || ch0 == 'F') &&
+ (ch1 == 'a' || ch1 == 'A') &&
+ (ch2 == 'l' || ch2 == 'L') &&
+ (ch3 == 's' || ch3 == 'S') &&
+ (ch4 == 'e' || ch4 == 'E') ) {
+ return Boolean.FALSE;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * Convert a String to a long, returning a
+ * default value if the conversion fails.
+ *
+ * If the string is null, the default value is returned.
+ *
+ *
+ * NumberUtils.toLong(null, 1L) = 1L
+ * NumberUtils.toLong("", 1L) = 1L
+ * NumberUtils.toLong("1", 0L) = 1L
+ *
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the long represented by the string, or the default if conversion fails
+ */
+ public static long toLong(final String str, final long defaultValue) {
+ if (str == null) {
+ return defaultValue;
+ }
+ try {
+ return Long.parseLong(str);
+ } catch (final NumberFormatException nfe) {
+ return defaultValue;
}
+ }
+ /**
+ * Convert a String to an int, returning a
+ * default value if the conversion fails.
+ *
+ * If the string is null, the default value is returned.
+ *
+ *
+ * NumberUtils.toInt(null, 1) = 1
+ * NumberUtils.toInt("", 1) = 1
+ * NumberUtils.toInt("1", 0) = 1
+ *
+ *
+ * @param str the string to convert, may be null
+ * @param defaultValue the default value
+ * @return the int represented by the string, or the default if conversion fails
+ */
+ public static int toInt(final String str, final int defaultValue) {
+ if(str == null) {
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(str);
+ } catch (final NumberFormatException nfe) {
+ return defaultValue;
+ }
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/DelegatingFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/DelegatingFilter.java
index 6c71ef831..c25ff0caf 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/DelegatingFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/DelegatingFilter.java
@@ -1,36 +1,29 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A Delegating Filter looks up a parameter in the request object and matches
@@ -46,7 +39,7 @@ public final class DelegatingFilter implements Filter {
/**
* Instance of Commons Logging.
*/
- private final Log log = LogFactory.getLog(this.getClass());
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The request parameter to look for in the Request object.
@@ -56,7 +49,7 @@ public final class DelegatingFilter implements Filter {
/**
* The map of filters to delegate to and the criteria (as key).
*/
- private final Map delegators;
+ private final Map delegators;
/**
* The default filter to use if there is no match.
@@ -69,11 +62,13 @@ public final class DelegatingFilter implements Filter {
*/
private final boolean exactMatch;
- public DelegatingFilter(final String requestParameterName, final Map delegators, final boolean exactMatch) {
+ public DelegatingFilter(final String requestParameterName, final Map delegators,
+ final boolean exactMatch) {
this(requestParameterName, delegators, exactMatch, null);
}
- public DelegatingFilter(final String requestParameterName, final Map delegators, final boolean exactMatch, final Filter defaultFilter) {
+ public DelegatingFilter(final String requestParameterName, final Map delegators,
+ final boolean exactMatch, final Filter defaultFilter) {
CommonUtils.assertNotNull(requestParameterName, "requestParameterName cannot be null.");
CommonUtils.assertTrue(!delegators.isEmpty(), "delegators cannot be empty.");
@@ -87,7 +82,8 @@ public void destroy() {
// nothing to do here
}
- public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
final String parameter = CommonUtils.safeGetParameter((HttpServletRequest) request, this.requestParameterName);
@@ -95,19 +91,15 @@ public void doFilter(final ServletRequest request, final ServletResponse respons
for (final String key : this.delegators.keySet()) {
if ((parameter.equals(key) && this.exactMatch) || (parameter.matches(key) && !this.exactMatch)) {
final Filter filter = this.delegators.get(key);
- if (log.isDebugEnabled()) {
- log.debug("Match found for parameter ["
- + this.requestParameterName + "] with value ["
- + parameter + "]. Delegating to filter ["
- + filter.getClass().getName() + "]");
- }
+ logger.debug("Match found for parameter [{}] with value [{}]. Delegating to filter [{}]",
+ this.requestParameterName, parameter, filter.getClass().getName());
filter.doFilter(request, response, filterChain);
return;
}
}
}
- log.debug("No match found for parameter [" + this.requestParameterName + "] with value [" + parameter + "]");
+ logger.debug("No match found for parameter [{}] with value [{}]", this.requestParameterName, parameter);
if (this.defaultFilter != null) {
this.defaultFilter.doFilter(request, response, filterChain);
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/ErrorRedirectFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/ErrorRedirectFilter.java
index 9ad2862fb..b1425a4bb 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/ErrorRedirectFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/ErrorRedirectFilter.java
@@ -1,128 +1,136 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Filters that redirects to the supplied url based on an exception. Exceptions and the urls are configured via
* init filter name/param values.
- *
+ *
* If there is an exact match the filter uses that value. If there's a non-exact match (i.e. inheritance), then the filter
* uses the last value that matched.
- *
+ *
* If there is no match it will redirect to a default error page. The default exception is configured via the "defaultErrorRedirectPage" property.
- *
+ *
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1.4
- *
*/
public final class ErrorRedirectFilter implements Filter {
- private final Log log = LogFactory.getLog(getClass());
-
- private final List errors = new ArrayList();
-
- private String defaultErrorRedirectPage;
-
- public void destroy() {
- // nothing to do here
- }
-
- public void doFilter(final ServletRequest request, final ServletResponse response,
- final FilterChain filterChain) throws IOException, ServletException {
- final HttpServletResponse httpResponse = (HttpServletResponse) response;
- try {
- filterChain.doFilter(request, response);
- } catch (final ServletException e) {
- final Throwable t = e.getCause();
- ErrorHolder currentMatch = null;
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final List errors = new ArrayList();
+
+ private String defaultErrorRedirectPage;
+
+ public void destroy() {
+ // nothing to do here
+ }
+
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
+ final HttpServletResponse httpResponse = (HttpServletResponse) response;
+ try {
+ filterChain.doFilter(request, response);
+ } catch (final Exception e) {
+ final Throwable t = extractErrorToCompare(e);
+ ErrorHolder currentMatch = null;
for (final ErrorHolder errorHolder : this.errors) {
- if (errorHolder.exactMatch(t)) {
- currentMatch = errorHolder;
- break;
- } else if (errorHolder.inheritanceMatch(t)) {
- currentMatch = errorHolder;
- }
- }
-
- if (currentMatch != null) {
- httpResponse.sendRedirect(currentMatch.getUrl());
- } else {
- httpResponse.sendRedirect(defaultErrorRedirectPage);
- }
- }
- }
-
- public void init(final FilterConfig filterConfig) throws ServletException {
- this.defaultErrorRedirectPage = filterConfig.getInitParameter("defaultErrorRedirectPage");
-
- final Enumeration> enumeration = filterConfig.getInitParameterNames();
- while (enumeration.hasMoreElements()) {
- final String className = (String) enumeration.nextElement();
- try {
- if (!className.equals("defaultErrorRedirectPage")) {
- this.errors.add(new ErrorHolder(className, filterConfig.getInitParameter(className)));
- }
- } catch (final ClassNotFoundException e) {
- log.warn("Class [" + className + "] cannot be found in ClassLoader. Ignoring.");
- }
- }
- }
-
- protected final class ErrorHolder {
-
- private Class> className;
-
- private String url;
-
- protected ErrorHolder(final String className, final String url) throws ClassNotFoundException {
- this.className = Class.forName(className);
- this.url = url;
- }
-
- public boolean exactMatch(final Throwable e) {
- return this.className.equals(e.getClass());
- }
-
- public boolean inheritanceMatch(final Throwable e) {
- return className.isAssignableFrom(e.getClass());
- }
-
- public String getUrl() {
- return this.url;
- }
- }
+ if (errorHolder.exactMatch(t)) {
+ currentMatch = errorHolder;
+ break;
+ } else if (errorHolder.inheritanceMatch(t)) {
+ currentMatch = errorHolder;
+ }
+ }
+
+ if (currentMatch != null) {
+ httpResponse.sendRedirect(currentMatch.getUrl());
+ } else {
+ httpResponse.sendRedirect(defaultErrorRedirectPage);
+ }
+ }
+ }
+
+ /**
+ * Determine which error to use for comparison. If there is an {@link Throwable#getCause()} then that will be used. Otherwise, the original throwable is used.
+ *
+ * @param throwable the throwable to look for a root cause.
+ * @return the throwable to use for comparison. MUST NOT BE NULL.
+ */
+ private Throwable extractErrorToCompare(final Throwable throwable) {
+ final Throwable cause = throwable.getCause();
+
+ if (cause != null) {
+ return cause;
+ }
+
+ return throwable;
+ }
+
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ this.defaultErrorRedirectPage = filterConfig.getInitParameter("defaultErrorRedirectPage");
+
+ final Enumeration> enumeration = filterConfig.getInitParameterNames();
+ while (enumeration.hasMoreElements()) {
+ final String className = (String) enumeration.nextElement();
+ try {
+ if (!className.equals("defaultErrorRedirectPage")) {
+ this.errors.add(new ErrorHolder(className, filterConfig.getInitParameter(className)));
+ }
+ } catch (final ClassNotFoundException e) {
+ logger.warn("Class [{}] cannot be found in ClassLoader. Ignoring.", className);
+ }
+ }
+ }
+
+ protected final class ErrorHolder {
+
+ private Class> className;
+
+ private String url;
+
+ protected ErrorHolder(final String className, final String url) throws ClassNotFoundException {
+ this.className = Class.forName(className);
+ this.url = url;
+ }
+
+ public boolean exactMatch(final Throwable e) {
+ return this.className.equals(e.getClass());
+ }
+
+ public boolean inheritanceMatch(final Throwable e) {
+ return className.isAssignableFrom(e.getClass());
+ }
+
+ public String getUrl() {
+ return this.url;
+ }
+ }
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java
index 56920b0df..a474a83ac 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java
@@ -1,38 +1,33 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.jasig.cas.client.authentication.AttributePrincipal;
-import org.jasig.cas.client.validation.Assertion;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.security.Principal;
import java.util.Collection;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
+import org.jasig.cas.client.validation.Assertion;
/**
* Implementation of a filter that wraps the normal HttpServletRequest with a
@@ -56,7 +51,7 @@ public final class HttpServletRequestWrapperFilter extends AbstractConfiguration
/** Name of the attribute used to answer role membership queries */
private String roleAttribute;
-
+
/** Whether or not to ignore case in role membership queries */
private boolean ignoreCase;
@@ -69,23 +64,28 @@ public void destroy() {
* request.getRemoteUser to the underlying Assertion object
* stored in the user session.
*/
- public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+ public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);
- filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal), servletResponse);
+ filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal),
+ servletResponse);
}
protected AttributePrincipal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpSession session = request.getSession(false);
- final Assertion assertion = (Assertion) (session == null ? request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
+ final Assertion assertion = (Assertion) (session == null ? request
+ .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session
+ .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
return assertion == null ? null : assertion.getPrincipal();
}
public void init(final FilterConfig filterConfig) throws ServletException {
- this.roleAttribute = getPropertyFromInitParams(filterConfig, "roleAttribute", null);
- this.ignoreCase = Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "ignoreCase", "false"));
+ super.init(filterConfig);
+ this.roleAttribute = getString(ConfigurationKeys.ROLE_ATTRIBUTE);
+ this.ignoreCase = getBoolean(ConfigurationKeys.IGNORE_CASE);
}
final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {
@@ -107,36 +107,36 @@ public String getRemoteUser() {
public boolean isUserInRole(final String role) {
if (CommonUtils.isBlank(role)) {
- log.debug("No valid role provided. Returning false.");
+ logger.debug("No valid role provided. Returning false.");
return false;
}
if (this.principal == null) {
- log.debug("No Principal in Request. Returning false.");
+ logger.debug("No Principal in Request. Returning false.");
return false;
}
if (CommonUtils.isBlank(roleAttribute)) {
- log.debug("No Role Attribute Configured. Returning false.");
+ logger.debug("No Role Attribute Configured. Returning false.");
return false;
}
final Object value = this.principal.getAttributes().get(roleAttribute);
-
+
if (value instanceof Collection>) {
for (final Object o : (Collection>) value) {
if (rolesEqual(role, o)) {
- log.debug("User [" + getRemoteUser() + "] is in role [" + role + "]: " + true);
+ logger.debug("User [{}] is in role [{}]: true", getRemoteUser(), role);
return true;
}
}
}
final boolean isMember = rolesEqual(role, value);
- log.debug("User [" + getRemoteUser() + "] is in role [" + role + "]: " + isMember);
+ logger.debug("User [{}] is in role [{}]: {}", getRemoteUser(), role, isMember);
return isMember;
}
-
+
/**
* Determines whether the given role is equal to the candidate
* role attribute taking into account case sensitivity.
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java
new file mode 100644
index 000000000..d1d82f76f
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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 java.io.*;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * IO utility class.
+ *
+ * @author Marvin S. Addison
+ * @since 3.4
+ */
+public final class IOUtils {
+
+ /** UTF-8 character set. */
+ public static final Charset UTF8 = Charset.forName("UTF-8");
+
+
+ private IOUtils() { /** Utility class pattern. */ }
+
+ /**
+ * Reads all data from the given stream as UTF-8 character data and closes it on completion or errors.
+ *
+ * @param in Input stream containing character data.
+ *
+ * @return String of all data in stream.
+ *
+ * @throws IOException On IO errors.
+ */
+ public static String readString(final InputStream in) throws IOException {
+ return readString(in, UTF8);
+ }
+
+ /**
+ * Reads all data from the given stream as character data in the given character set and closes it on completion
+ * or errors.
+ *
+ * @param in Input stream containing character data.
+ * @param charset Character set of data in stream.
+ *
+ * @return String of all data in stream.
+ *
+ * @throws IOException On IO errors.
+ */
+ public static String readString(final InputStream in, final Charset charset) throws IOException {
+ final Reader reader = new InputStreamReader(in, charset);
+ final StringBuilder builder = new StringBuilder();
+ final CharBuffer buffer = CharBuffer.allocate(2048);
+ try {
+ while (reader.read(buffer) > -1) {
+ buffer.flip();
+ builder.append(buffer);
+ }
+ } finally {
+ closeQuietly(reader);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Unconditionally close a {@link Closeable} resource. Errors on close are ignored.
+ *
+ * @param resource Resource to close.
+ */
+ public static void closeQuietly(final Closeable resource) {
+ try {
+ if (resource != null) {
+ resource.close();
+ }
+ } catch (final IOException e) {
+ //ignore
+ }
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java
new file mode 100644
index 000000000..0afac39dc
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java
@@ -0,0 +1,80 @@
+/*
+ * 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 javax.xml.namespace.NamespaceContext;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Namespace context implementation backed by a map of XML prefixes to namespace URIs.
+ *
+ * @author Marvin S. Addison
+ * @since 3.4
+ */
+public class MapNamespaceContext implements NamespaceContext {
+
+ private final Map namespaceMap;
+
+ /**
+ * Creates a new instance from an array of namespace delcarations.
+ *
+ * @param namespaceDeclarations An array of namespace declarations of the form prefix->uri.
+ */
+ public MapNamespaceContext(final String ... namespaceDeclarations) {
+ namespaceMap = new HashMap();
+ int index;
+ String key;
+ String value;
+ for (final String decl : namespaceDeclarations) {
+ index = decl.indexOf('-');
+ key = decl.substring(0, index);
+ value = decl.substring(index + 2);
+ namespaceMap.put(key, value);
+ }
+ }
+
+ /**
+ * Creates a new instance from a map.
+ *
+ * @param namespaceMap Map of XML namespace prefixes (keys) to URIs (values).
+ */
+ public MapNamespaceContext(final Map namespaceMap) {
+ this.namespaceMap = namespaceMap;
+ }
+
+ public String getNamespaceURI(final String prefix) {
+ return namespaceMap.get(prefix);
+ }
+
+ public String getPrefix(final String namespaceURI) {
+ for (final Map.Entry entry : namespaceMap.entrySet()) {
+ if (entry.getValue().equalsIgnoreCase(namespaceURI)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ public Iterator getPrefixes(final String namespaceURI) {
+ return Collections.singleton(getPrefix(namespaceURI)).iterator();
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/ReflectUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/ReflectUtils.java
index 0b1f963ef..1ec21162e 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/ReflectUtils.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/ReflectUtils.java
@@ -1,22 +1,21 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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 java.beans.BeanInfo;
@@ -54,7 +53,6 @@ public static Class loadClass(final String className) throws IllegalArgum
}
}
-
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
@@ -62,10 +60,10 @@ public static Class loadClass(final String className) throws IllegalArgum
* @param args Constructor arguments.
* @return New instance of given class.
*/
- public static T newInstance(final String className, final Object ... args) {
- return newInstance(ReflectUtils.loadClass(className), args);
+ public static T newInstance(final String className, final Object... args) {
+ return newInstance(ReflectUtils. loadClass(className), args);
}
-
+
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
@@ -73,7 +71,7 @@ public static T newInstance(final String className, final Object ... args) {
* @param args Constructor arguments.
* @return New instance of given class.
*/
- public static T newInstance(final Class clazz, final Object ... args) {
+ public static T newInstance(final Class clazz, final Object... args) {
final Class>[] argClasses = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argClasses[i] = args[i].getClass();
@@ -139,7 +137,8 @@ public static void setProperty(final String propertyName, final Object value, fi
* @param target Target JavaBean on which to set property.
* @param info BeanInfo describing the target JavaBean.
*/
- public static void setProperty(final String propertyName, final Object value, final Object target, final BeanInfo info) {
+ public static void setProperty(final String propertyName, final Object value, final Object target,
+ final BeanInfo info) {
try {
final PropertyDescriptor pd = getPropertyDescriptor(info, propertyName);
pd.getWriteMethod().invoke(target, value);
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java
new file mode 100644
index 000000000..e0d9b5dc9
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java
@@ -0,0 +1,105 @@
+/*
+ * 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.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.xpath.*;
+
+/**
+ * Thread local XPath expression.
+ *
+ * @author Marvin S. Addison
+ * @since 3.4
+ */
+public class ThreadLocalXPathExpression extends ThreadLocal implements XPathExpression {
+
+ /** XPath expression */
+ private final String expression;
+
+ /** Namespace context. */
+ private final NamespaceContext context;
+
+ /**
+ * Creates a new instance from an XPath expression and namespace context.
+ *
+ * @param xPath XPath expression.
+ * @param context Namespace context for handling namespace prefix to URI mappings.
+ */
+ public ThreadLocalXPathExpression(final String xPath, final NamespaceContext context) {
+ this.expression = xPath;
+ this.context = context;
+ }
+
+ public Object evaluate(final Object o, final QName qName) throws XPathExpressionException {
+ return get().evaluate(o, qName);
+ }
+
+ public String evaluate(final Object o) throws XPathExpressionException {
+ return get().evaluate(o);
+ }
+
+ public Object evaluate(final InputSource inputSource, final QName qName) throws XPathExpressionException {
+ return get().evaluate(inputSource, qName);
+ }
+
+ public String evaluate(final InputSource inputSource) throws XPathExpressionException {
+ return get().evaluate(inputSource);
+ }
+
+ /**
+ * Evaluates the XPath expression and returns the result coerced to a string.
+ *
+ * @param o Object on which to evaluate the expression; typically a DOM node.
+ *
+ * @return Evaluation result as a string.
+ *
+ * @throws XPathExpressionException On XPath evaluation errors.
+ */
+ public String evaluateAsString(final Object o) throws XPathExpressionException {
+ return (String) evaluate(o, XPathConstants.STRING);
+ }
+
+ /**
+ * Evaluates the XPath expression and returns the result coerced to a node list.
+ *
+ * @param o Object on which to evaluate the expression; typically a DOM node.
+ *
+ * @return Evaluation result as a node list.
+ *
+ * @throws XPathExpressionException On XPath evaluation errors.
+ */
+ public NodeList evaluateAsNodeList(final Object o) throws XPathExpressionException {
+ return (NodeList) evaluate(o, XPathConstants.NODESET);
+ }
+
+ @Override
+ protected XPathExpression initialValue() {
+ try {
+ final XPath xPath = XPathFactory.newInstance().newXPath();
+ xPath.setNamespaceContext(context);
+ return xPath.compile(expression);
+ } catch (XPathExpressionException e) {
+ throw new IllegalArgumentException("Invalid XPath expression");
+ }
+ }
+}
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 000000000..7a6874a3b
--- /dev/null
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/URIBuilder.java
@@ -0,0 +1,665 @@
+/*
+ * 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 RuntimeException 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 the uri to digest
+ */
+ 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 new ArrayList();
+ }
+
+ /**
+ * 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 URIBuilder setEncode(boolean encode) {
+ this.encode = encode;
+ return this;
+ }
+
+ /**
+ * 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.isEmpty()) {
+ 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 URIBuilder setEncodedFragment(final String fragment) {
+ this.fragment = null;
+ this.encodedFragment = fragment;
+ return this;
+ }
+
+ public URIBuilder setEncodedQuery(final String query) {
+ this.query = null;
+ this.encodedFragment = query;
+ 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;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final URIBuilder that = (URIBuilder) o;
+
+ if (port != that.port) return false;
+ if (encode != that.encode) return false;
+ if (scheme != null ? !scheme.equals(that.scheme) : that.scheme != null) return false;
+ if (encodedSchemeSpecificPart != null ? !encodedSchemeSpecificPart.equals(that.encodedSchemeSpecificPart) : that.encodedSchemeSpecificPart != null)
+ return false;
+ if (encodedAuthority != null ? !encodedAuthority.equals(that.encodedAuthority) : that.encodedAuthority != null)
+ return false;
+ if (userInfo != null ? !userInfo.equals(that.userInfo) : that.userInfo != null) return false;
+ if (encodedUserInfo != null ? !encodedUserInfo.equals(that.encodedUserInfo) : that.encodedUserInfo != null)
+ return false;
+ if (host != null ? !host.equals(that.host) : that.host != null) return false;
+ if (path != null ? !path.equals(that.path) : that.path != null) return false;
+ if (encodedPath != null ? !encodedPath.equals(that.encodedPath) : that.encodedPath != null) return false;
+ if (encodedQuery != null ? !encodedQuery.equals(that.encodedQuery) : that.encodedQuery != null) return false;
+ if (queryParams != null ? !queryParams.equals(that.queryParams) : that.queryParams != null) return false;
+ if (query != null ? !query.equals(that.query) : that.query != null) return false;
+ if (fragment != null ? !fragment.equals(that.fragment) : that.fragment != null) return false;
+ return !(encodedFragment != null ? !encodedFragment.equals(that.encodedFragment) : that.encodedFragment != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = scheme != null ? scheme.hashCode() : 0;
+ result = 31 * result + (encodedSchemeSpecificPart != null ? encodedSchemeSpecificPart.hashCode() : 0);
+ result = 31 * result + (encodedAuthority != null ? encodedAuthority.hashCode() : 0);
+ result = 31 * result + (userInfo != null ? userInfo.hashCode() : 0);
+ result = 31 * result + (encodedUserInfo != null ? encodedUserInfo.hashCode() : 0);
+ result = 31 * result + (host != null ? host.hashCode() : 0);
+ result = 31 * result + port;
+ result = 31 * result + (path != null ? path.hashCode() : 0);
+ result = 31 * result + (encodedPath != null ? encodedPath.hashCode() : 0);
+ result = 31 * result + (encodedQuery != null ? encodedQuery.hashCode() : 0);
+ result = 31 * result + (queryParams != null ? queryParams.hashCode() : 0);
+ result = 31 * result + (query != null ? query.hashCode() : 0);
+ result = 31 * result + (encode ? 1 : 0);
+ result = 31 * result + (fragment != null ? fragment.hashCode() : 0);
+ result = 31 * result + (encodedFragment != null ? encodedFragment.hashCode() : 0);
+ return result;
+ }
+
+ 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/main/java/org/jasig/cas/client/util/XmlUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java
index 62abfdfe0..77831f495 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java
@@ -1,42 +1,39 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import java.io.StringReader;
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
-import org.xml.sax.helpers.XMLReaderFactory;
-import javax.xml.parsers.DocumentBuilder;
+import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
+import javax.xml.parsers.SAXParserFactory;
/**
* Common utilities for easily parsing XML without duplicating logic.
@@ -50,7 +47,35 @@ public final class XmlUtils {
/**
* Static instance of Commons Logging.
*/
- private final static Log LOG = LogFactory.getLog(XmlUtils.class);
+ private final static Logger LOGGER = LoggerFactory.getLogger(XmlUtils.class);
+
+
+ /**
+ * Creates a new namespace-aware DOM document object by parsing the given XML.
+ *
+ * @param xml XML content.
+ *
+ * @return DOM document.
+ */
+ public static Document newDocument(final String xml) {
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ final Map features = new HashMap();
+ features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ for (final Map.Entry entry : features.entrySet()) {
+ try {
+ factory.setFeature(entry.getKey(), entry.getValue());
+ } catch (ParserConfigurationException e) {
+ LOGGER.warn("Failed setting XML feature {}: {}", entry.getKey(), e);
+ }
+ }
+ factory.setNamespaceAware(true);
+ try {
+ return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
+ } catch (Exception e) {
+ throw new RuntimeException("XML parsing error: " + e);
+ }
+ }
/**
* Get an instance of an XML reader from the XMLReaderFactory.
@@ -59,14 +84,17 @@ public final class XmlUtils {
*/
public static XMLReader getXmlReader() {
try {
- final XMLReader reader = XMLReaderFactory.createXMLReader();
+ final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+ reader.setFeature("http://xml.org/sax/features/namespaces", true);
+ reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
return reader;
- } catch (final SAXException e) {
+ } catch (final Exception e) {
throw new RuntimeException("Unable to create XMLReader", e);
}
}
+
/**
* Retrieve the text for a group of elements. Each text element is an entry
* in a list.
@@ -76,8 +104,7 @@ public static XMLReader getXmlReader() {
* @param element the element to look for
* @return the list of text from the elements.
*/
- public static List getTextForElements(final String xmlAsString,
- final String element) {
+ public static List getTextForElements(final String xmlAsString, final String element) {
final List elements = new ArrayList(2);
final XMLReader reader = getXmlReader();
@@ -87,16 +114,14 @@ public static List getTextForElements(final String xmlAsString,
private StringBuilder buffer = new StringBuilder();
- public void startElement(final String uri, final String localName,
- final String qName, final Attributes attributes)
- throws SAXException {
+ public void startElement(final String uri, final String localName, final String qName,
+ final Attributes attributes) throws SAXException {
if (localName.equals(element)) {
this.foundElement = true;
}
}
- public void endElement(final String uri, final String localName,
- final String qName) throws SAXException {
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
if (localName.equals(element)) {
this.foundElement = false;
elements.add(this.buffer.toString());
@@ -104,8 +129,7 @@ public void endElement(final String uri, final String localName,
}
}
- public void characters(char[] ch, int start, int length)
- throws SAXException {
+ public void characters(char[] ch, int start, int length) throws SAXException {
if (this.foundElement) {
this.buffer.append(ch, start, length);
}
@@ -118,7 +142,7 @@ public void characters(char[] ch, int start, int length)
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
} catch (final Exception e) {
- LOG.error(e, e);
+ LOGGER.error(e.getMessage(), e);
return null;
}
@@ -133,8 +157,7 @@ public void characters(char[] ch, int start, int length)
* @param element the element to look for
* @return the text value of the element.
*/
- public static String getTextForElement(final String xmlAsString,
- final String element) {
+ public static String getTextForElement(final String xmlAsString, final String element) {
final XMLReader reader = getXmlReader();
final StringBuilder builder = new StringBuilder();
@@ -142,23 +165,20 @@ public static String getTextForElement(final String xmlAsString,
private boolean foundElement = false;
- public void startElement(final String uri, final String localName,
- final String qName, final Attributes attributes)
- throws SAXException {
+ public void startElement(final String uri, final String localName, final String qName,
+ final Attributes attributes) throws SAXException {
if (localName.equals(element)) {
this.foundElement = true;
}
}
- public void endElement(final String uri, final String localName,
- final String qName) throws SAXException {
+ public void endElement(final String uri, final String localName, final String qName) throws SAXException {
if (localName.equals(element)) {
this.foundElement = false;
}
}
- public void characters(char[] ch, int start, int length)
- throws SAXException {
+ public void characters(char[] ch, int start, int length) throws SAXException {
if (this.foundElement) {
builder.append(ch, start, length);
}
@@ -171,33 +191,10 @@ public void characters(char[] ch, int start, int length)
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
} catch (final Exception e) {
- LOG.error(e, e);
+ LOGGER.error(e.getMessage(), e);
return null;
}
return builder.toString();
}
-
- /**
- * Retrieve the child nodes from xml string, for a specific element.
- *
- * @param xmlAsString the xml response
- * @param tagName the element to look for
- * @return the {@link org.w3c.dom.NodeList NodeList} containing the child nodes.
- * @throws ParserConfigurationException
- * @throws IOException
- * @throws SAXException
- */
- public static NodeList getNodeListForElements(final String xmlAsString, final String tagName)
- throws ParserConfigurationException,
- IOException,
- SAXException {
-
- final DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
- final InputSource inStream = new InputSource();
- inStream.setCharacterStream(new StringReader(xmlAsString));
- final Document document = documentBuilder.parse(inStream);
-
- return document.getElementsByTagName(tagName).item(0).getChildNodes();
- }
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html b/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html
index 300a79b89..fcf85e2f9 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html
@@ -5,20 +5,19 @@
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:
+ 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
+ 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
+ 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.
-->
-
The validation package includes interfaces for validating Tickets, as well as the common implementations.
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java
index 84d9e6740..146280d7e 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java
@@ -1,27 +1,25 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.validation;
-import org.jasig.cas.client.util.CommonUtils;
-
import java.net.URL;
+import org.jasig.cas.client.util.CommonUtils;
/**
* Abstract class that knows the protocol for validating a CAS ticket.
@@ -36,18 +34,10 @@ protected AbstractCasProtocolUrlBasedTicketValidator(final String casServerUrlPr
super(casServerUrlPrefix);
}
- protected final void setDisableXmlSchemaValidation(final boolean disable) {
- // nothing to do
- }
-
/**
* Retrieves the response from the server by opening a connection and merely reading the response.
*/
protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
- if (this.hostnameVerifier != null) {
- return CommonUtils.getResponseFromServer(validationUrl, this.hostnameVerifier, getEncoding());
- } else {
- return CommonUtils.getResponseFromServer(validationUrl, getEncoding());
- }
+ return CommonUtils.getResponseFromServer(validationUrl, getURLConnectionFactory(), getEncoding());
}
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java
index 71b62e7ed..51df207b1 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java
@@ -1,47 +1,49 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.validation;
-import org.jasig.cas.client.util.AbstractCasFilter;
-import org.jasig.cas.client.util.CommonUtils;
-import org.jasig.cas.client.util.ReflectUtils;
-
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
import javax.net.ssl.HostnameVerifier;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
+
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
+import org.jasig.cas.client.util.AbstractCasFilter;
+import org.jasig.cas.client.util.CommonUtils;
+import org.jasig.cas.client.util.ReflectUtils;
/**
* The filter that handles all the work of validating ticket requests.
*
* This filter can be configured with the following values:
*
- * redirectAfterValidation - redirect the CAS client to the same URL without the ticket.
+ * redirectAfterValidation - redirect the CAS client to the same URL without the ticket.
+ * (default: true, Will be forced to false when {@link #useSession} is false.)
* exceptionOnValidationFailure - throw an exception if the validation fails. Otherwise, continue
- * processing.
- * useSession - store any of the useful information in a session attribute.
+ * processing. (default: true)
+ * useSession - store any of the useful information in a session attribute. (default: true)
+ * hostnameVerifier - name of class implementing a {@link HostnameVerifier}.
+ * hostnameVerifierConfig - name of configuration class (constructor argument of verifier).
*
*
* @author Scott Battaglia
@@ -58,13 +60,21 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {
* successful validation to remove the ticket parameter from the query
* string.
*/
- private boolean redirectAfterValidation = false;
+ private boolean redirectAfterValidation = true;
/** Determines whether an exception is thrown when there is a ticket validation failure. */
- private boolean exceptionOnValidationFailure = true;
+ private boolean exceptionOnValidationFailure = false;
+ /**
+ * Specify whether the Assertion should be stored in a session
+ * attribute {@link AbstractCasFilter#CONST_CAS_ASSERTION}.
+ */
private boolean useSession = true;
+ protected AbstractTicketValidationFilter(final Protocol protocol) {
+ super(protocol);
+ }
+
/**
* Template method to return the appropriate validator.
*
@@ -74,18 +84,39 @@ public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {
protected TicketValidator getTicketValidator(final FilterConfig filterConfig) {
return this.ticketValidator;
}
-
+
+ /**
+ * Gets the ssl config to use for HTTPS connections
+ * if one is configured for this filter.
+ * @return Properties that can contains key/trust info for Client Side Certificates
+ */
+ protected Properties getSSLConfig() {
+ final Properties properties = new Properties();
+ final String fileName = getString(ConfigurationKeys.SSL_CONFIG_FILE);
+
+ if (fileName != null) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(fileName);
+ properties.load(fis);
+ logger.trace("Loaded {} entries from {}", properties.size(), fileName);
+ } catch (final IOException ioe) {
+ logger.error(ioe.getMessage(), ioe);
+ } finally {
+ CommonUtils.closeQuietly(fis);
+ }
+ }
+ return properties;
+ }
+
/**
* Gets the configured {@link HostnameVerifier} to use for HTTPS connections
* if one is configured for this filter.
- * @param filterConfig Servlet filter configuration.
* @return Instance of specified host name verifier or null if none specified.
*/
- protected HostnameVerifier getHostnameVerifier(final FilterConfig filterConfig) {
- final String className = getPropertyFromInitParams(filterConfig, "hostnameVerifier", null);
- log.trace("Using hostnameVerifier parameter: " + className);
- final String config = getPropertyFromInitParams(filterConfig, "hostnameVerifierConfig", null);
- log.trace("Using hostnameVerifierConfig parameter: " + config);
+ protected HostnameVerifier getHostnameVerifier() {
+ final Class extends HostnameVerifier> className = getClass(ConfigurationKeys.HOSTNAME_VERIFIER);
+ final String config = getString(ConfigurationKeys.HOSTNAME_VERIFIER_CONFIG);
if (className != null) {
if (config != null) {
return ReflectUtils.newInstance(className, config);
@@ -97,12 +128,15 @@ protected HostnameVerifier getHostnameVerifier(final FilterConfig filterConfig)
}
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
- setExceptionOnValidationFailure(parseBoolean(getPropertyFromInitParams(filterConfig, "exceptionOnValidationFailure", "true")));
- log.trace("Setting exceptionOnValidationFailure parameter: " + this.exceptionOnValidationFailure);
- setRedirectAfterValidation(parseBoolean(getPropertyFromInitParams(filterConfig, "redirectAfterValidation", "true")));
- log.trace("Setting redirectAfterValidation parameter: " + this.redirectAfterValidation);
- setUseSession(parseBoolean(getPropertyFromInitParams(filterConfig, "useSession", "true")));
- log.trace("Setting useSession parameter: " + this.useSession);
+ setExceptionOnValidationFailure(getBoolean(ConfigurationKeys.EXCEPTION_ON_VALIDATION_FAILURE));
+ setRedirectAfterValidation(getBoolean(ConfigurationKeys.REDIRECT_AFTER_VALIDATION));
+ setUseSession(getBoolean(ConfigurationKeys.USE_SESSION));
+
+ if (!this.useSession && this.redirectAfterValidation) {
+ logger.warn("redirectAfterValidation parameter may not be true when useSession parameter is false. Resetting it to false in order to prevent infinite redirects.");
+ setRedirectAfterValidation(false);
+ }
+
setTicketValidator(getTicketValidator(filterConfig));
super.initInternal(filterConfig);
}
@@ -122,7 +156,8 @@ public void init() {
* @throws IOException if there is an I/O problem
* @throws ServletException if there is a servlet problem.
*/
- protected boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+ protected boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
return true;
}
@@ -135,8 +170,9 @@ protected boolean preFilter(final ServletRequest servletRequest, final ServletRe
* @param response the HttpServletResponse.
* @param assertion the successful Assertion from the server.
*/
- protected void onSuccessfulValidation(final HttpServletRequest request, final HttpServletResponse response, final Assertion assertion) {
- // nothing to do here.
+ protected void onSuccessfulValidation(final HttpServletRequest request, final HttpServletResponse response,
+ final Assertion assertion) {
+ // nothing to do here.
}
/**
@@ -150,7 +186,8 @@ protected void onFailedValidation(final HttpServletRequest request, final HttpSe
// nothing to do here.
}
- public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
+ public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException, ServletException {
if (!preFilter(servletRequest, servletResponse, filterChain)) {
return;
@@ -161,16 +198,13 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes
final String ticket = retrieveTicketFromRequest(request);
if (CommonUtils.isNotBlank(ticket)) {
- if (log.isDebugEnabled()) {
- log.debug("Attempting to validate ticket: " + ticket);
- }
+ logger.debug("Attempting to validate ticket: {}", ticket);
try {
- final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));
+ final Assertion assertion = this.ticketValidator.validate(ticket,
+ constructServiceUrl(request, response));
- if (log.isDebugEnabled()) {
- log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
- }
+ logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
request.setAttribute(CONST_CAS_ASSERTION, assertion);
@@ -180,13 +214,12 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes
onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
- log. debug("Redirecting after successful ticket validation.");
+ logger.debug("Redirecting after successful ticket validation.");
response.sendRedirect(constructServiceUrl(request, response));
return;
}
} catch (final TicketValidationException e) {
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- log.warn(e, e);
+ logger.debug(e.getMessage(), e);
onFailedValidation(request, response);
@@ -194,7 +227,7 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes
throw new ServletException(e);
}
- response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
@@ -205,8 +238,8 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes
}
public final void setTicketValidator(final TicketValidator ticketValidator) {
- this.ticketValidator = ticketValidator;
-}
+ this.ticketValidator = ticketValidator;
+ }
public final void setRedirectAfterValidation(final boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
@@ -219,4 +252,4 @@ public final void setExceptionOnValidationFailure(final boolean exceptionOnValid
public final void setUseSession(final boolean useSession) {
this.useSession = useSession;
}
-}
+}
\ No newline at end of file
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java
index 0012b5e86..59c4d88e1 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java
@@ -1,55 +1,50 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.validation;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.jasig.cas.client.util.CommonUtils;
-
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
-
-import javax.net.ssl.HostnameVerifier;
+import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
+import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Abstract validator implementation for tickets that must be validated against a server.
*
* @author Scott Battaglia
- * @version $Revision$ $Date$
* @since 3.1
*/
public abstract class AbstractUrlBasedTicketValidator implements TicketValidator {
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
/**
- * Commons Logging instance.
- */
- protected final Log log = LogFactory.getLog(getClass());
-
- /**
- * Hostname verifier used when making an SSL request to the CAS server.
+ * URLConnection factory instance to use when making validation requests to the CAS server.
+ * Defaults to {@link HttpsURLConnectionFactory}
*/
- protected HostnameVerifier hostnameVerifier;
+ private HttpURLConnectionFactory urlConnectionFactory = new HttpsURLConnectionFactory();
/**
* Prefix for the CAS server. Should be everything up to the url endpoint, including the /.
@@ -66,7 +61,7 @@ public abstract class AbstractUrlBasedTicketValidator implements TicketValidator
/**
* A map containing custom parameters to pass to the validation url.
*/
- private Map customParameters;
+ private Map customParameters;
private String encoding;
@@ -85,7 +80,7 @@ protected AbstractUrlBasedTicketValidator(final String casServerUrlPrefix) {
*
* @param urlParameters the map containing the parameters.
*/
- protected void populateUrlAttributeMap(final Map urlParameters) {
+ protected void populateUrlAttributeMap(final Map urlParameters) {
// nothing to do
}
@@ -95,13 +90,6 @@ protected void populateUrlAttributeMap(final Map urlParameters) {
*/
protected abstract String getUrlSuffix();
- /**
- * Disable XML Schema validation. Note, setting this to true may not be reversable. Defaults to false. Setting it to false
- * after setting it to true may not have any affect.
- *
- * @param disabled whether to disable or not.
- */
- protected abstract void setDisableXmlSchemaValidation(boolean disabled);
/**
* Constructs the URL to send the validation request to.
@@ -111,26 +99,27 @@ protected void populateUrlAttributeMap(final Map urlParameters) {
* @return the fully constructed URL.
*/
protected final String constructValidationUrl(final String ticket, final String serviceUrl) {
- final Map urlParameters = new HashMap();
+ final Map urlParameters = new HashMap();
- log.debug("Placing URL parameters in map.");
+ logger.debug("Placing URL parameters in map.");
urlParameters.put("ticket", ticket);
- urlParameters.put("service", encodeUrl(serviceUrl));
+ urlParameters.put("service", serviceUrl);
if (this.renew) {
urlParameters.put("renew", "true");
}
- log.debug("Calling template URL attribute map.");
+ logger.debug("Calling template URL attribute map.");
populateUrlAttributeMap(urlParameters);
- log.debug("Loading custom parameters from configuration.");
+ logger.debug("Loading custom parameters from configuration.");
if (this.customParameters != null) {
urlParameters.putAll(this.customParameters);
}
final String suffix = getUrlSuffix();
- final StringBuilder buffer = new StringBuilder(urlParameters.size()*10 + this.casServerUrlPrefix.length() + suffix.length() +1);
+ final StringBuilder buffer = new StringBuilder(urlParameters.size() * 10 + this.casServerUrlPrefix.length()
+ + suffix.length() + 1);
int i = 0;
@@ -140,7 +129,7 @@ protected final String constructValidationUrl(final String ticket, final String
}
buffer.append(suffix);
- for (Map.Entry entry : urlParameters.entrySet()) {
+ for (Map.Entry entry : urlParameters.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
@@ -148,7 +137,8 @@ protected final String constructValidationUrl(final String ticket, final String
buffer.append(i++ == 0 ? "?" : "&");
buffer.append(key);
buffer.append("=");
- buffer.append(value);
+ final String encodedValue = encodeUrl(value);
+ buffer.append(encodedValue);
}
}
@@ -163,10 +153,10 @@ protected final String constructValidationUrl(final String ticket, final String
* @return the encoded url, or the original url if "UTF-8" character encoding could not be found.
*/
protected final String encodeUrl(final String url) {
- if (url == null) {
- return null;
- }
-
+ if (url == null) {
+ return null;
+ }
+
try {
return URLEncoder.encode(url, "UTF-8");
} catch (final UnsupportedEncodingException e) {
@@ -195,24 +185,18 @@ protected final String encodeUrl(final String url) {
protected abstract String retrieveResponseFromServer(URL validationUrl, String ticket);
public final Assertion validate(final String ticket, final String service) throws TicketValidationException {
-
-
final String validationUrl = constructValidationUrl(ticket, service);
- if (log.isDebugEnabled()) {
- log.debug("Constructing validation url: " + validationUrl);
- }
+ logger.debug("Constructing validation url: {}", validationUrl);
try {
- log.debug("Retrieving response from server.");
+ logger.debug("Retrieving response from server.");
final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);
if (serverResponse == null) {
throw new TicketValidationException("The CAS server returned no response.");
}
-
- if (log.isDebugEnabled()) {
- log.debug("Server response: " + serverResponse);
- }
+
+ logger.debug("Server response: {}", serverResponse);
return parseResponseFromServer(serverResponse);
} catch (final MalformedURLException e) {
@@ -224,13 +208,9 @@ public final void setRenew(final boolean renew) {
this.renew = renew;
}
- public final void setCustomParameters(final Map customParameters) {
+ public final void setCustomParameters(final Map customParameters) {
this.customParameters = customParameters;
}
-
- public final void setHostnameVerifier(final HostnameVerifier verifier) {
- this.hostnameVerifier = verifier;
- }
public final void setEncoding(final String encoding) {
this.encoding = encoding;
@@ -251,4 +231,12 @@ protected final String getCasServerUrlPrefix() {
protected final Map getCustomParameters() {
return this.customParameters;
}
-}
\ No newline at end of file
+
+ protected HttpURLConnectionFactory getURLConnectionFactory() {
+ return this.urlConnectionFactory;
+ }
+
+ public void setURLConnectionFactory(final HttpURLConnectionFactory urlConnectionFactory) {
+ this.urlConnectionFactory = urlConnectionFactory;
+ }
+}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java
index 75ac70d40..06d98aa59 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java
@@ -1,29 +1,27 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.validation;
-import org.jasig.cas.client.authentication.AttributePrincipal;
-
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
+import org.jasig.cas.client.authentication.AttributePrincipal;
/**
* Represents a response to a validation request.
@@ -48,12 +46,20 @@ public interface Assertion extends Serializable {
*/
Date getValidUntilDate();
+ /**
+ * The date the authentication actually occurred on. If its unable to be determined, it should be set to the current
+ * time.
+ *
+ * @return the authentication date, or the current time if it can't be determined.
+ */
+ Date getAuthenticationDate();
+
/**
* The key/value pairs associated with this assertion.
*
* @return the map of attributes.
*/
- Map getAttributes();
+ Map getAttributes();
/**
* The principal for which this assertion is valid.
@@ -61,4 +67,12 @@ public interface Assertion extends Serializable {
* @return the principal.
*/
AttributePrincipal getPrincipal();
+
+ /**
+ * Determines whether an Assertion is considered usable or not. A naive implementation may just check the date validity.
+ *
+ * @return true if its valid, false otherwise.
+ * @since 3.3.0 (though in 3.3.0, no one actually calls this)
+ */
+ boolean isValid();
}
diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java
index a845fe871..86e286fd7 100644
--- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java
+++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java
@@ -1,31 +1,29 @@
-/**
+/*
* 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:
+ * 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
+ * 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
+ * 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.validation;
-import org.jasig.cas.client.authentication.AttributePrincipal;
-import org.jasig.cas.client.authentication.AttributePrincipalImpl;
-import org.jasig.cas.client.util.CommonUtils;
-
import java.util.Collections;
import java.util.Date;
import java.util.Map;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+import org.jasig.cas.client.authentication.AttributePrincipalImpl;
+import org.jasig.cas.client.util.CommonUtils;
/**
* Concrete Implementation of the {@link Assertion}.
@@ -38,16 +36,18 @@
public final class AssertionImpl implements Assertion {
/** Unique Id for serialization. */
- private static final long serialVersionUID = -7767943925833639221L;
+ private static final long serialVersionUID = -7767943925833639221L;
- /** The date from which the assertion is valid. */
+ /** The date from which the assertion is valid. */
private final Date validFromDate;
/** The date the assertion is valid until. */
private final Date validUntilDate;
+ private final Date authenticationDate;
+
/** Map of key/value pairs associated with this assertion. I.e. authentication type. */
- private final Map attributes;
+ private final Map attributes;
/** The principal for which this assertion is valid for. */
private final AttributePrincipal principal;
@@ -58,7 +58,7 @@ public final class AssertionImpl implements Assertion {
* @param name the name of the principal for which this assertion is valid.
*/
public AssertionImpl(final String name) {
- this(new AttributePrincipalImpl(name));
+ this(new AttributePrincipalImpl(name));
}
/**
@@ -67,7 +67,7 @@ public AssertionImpl(final String name) {
* @param principal the Principal to associate with the Assertion.
*/
public AssertionImpl(final AttributePrincipal principal) {
- this(principal, Collections.emptyMap());
+ this(principal, Collections. emptyMap());
}
/**
@@ -76,8 +76,8 @@ public AssertionImpl(final AttributePrincipal principal) {
* @param principal the Principal to associate with the Assertion.
* @param attributes the key/value pairs for this attribute.
*/
- public AssertionImpl(final AttributePrincipal principal, final Map attributes) {
- this(principal, new Date(), null, attributes);
+ public AssertionImpl(final AttributePrincipal principal, final Map attributes) {
+ this(principal, new Date(), null, new Date(), attributes);
}
/**
@@ -88,16 +88,23 @@ public AssertionImpl(final AttributePrincipal principal, final Map attributes) {
+ public AssertionImpl(final AttributePrincipal principal, final Date validFromDate, final Date validUntilDate,
+ final Date authenticationDate, final Map attributes) {
this.principal = principal;
this.validFromDate = validFromDate;
this.validUntilDate = validUntilDate;
this.attributes = attributes;
+ this.authenticationDate = authenticationDate;
CommonUtils.assertNotNull(this.principal, "principal cannot be null.");
CommonUtils.assertNotNull(this.validFromDate, "validFromDate cannot be null.");
CommonUtils.assertNotNull(this.attributes, "attributes cannot be null.");
}
+
+ public Date getAuthenticationDate() {
+ return this.authenticationDate;
+ }
+
public Date getValidFromDate() {
return this.validFromDate;
}
@@ -106,11 +113,20 @@ public Date getValidUntilDate() {
return this.validUntilDate;
}
- public Map getAttributes() {
+ public Map