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 0000000..14ec120
--- /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 {
+
+ ConfigurationKeyweb.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 0000000..6fcaa11
--- /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 0000000..a9af77c
--- /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 0000000..5dd7dd9
--- /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 0000000..49be2c5
--- /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 0000000..634b9f1
--- /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/Servlet3AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/jaas/Servlet3AuthenticationFilter.java
index d39027e..e64b578 100644
--- 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
@@ -29,6 +29,7 @@ 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;
@@ -50,12 +51,16 @@ import org.jasig.cas.client.util.CommonUtils;
*/
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, getArtifactParameterName());
+ final String ticket = CommonUtils.safeGetParameter(request, getProtocol().getArtifactParameterName());
if (session != null && session.getAttribute(CONST_CAS_ASSERTION) == null && ticket != null) {
try {
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 69cc75d..24826cf 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
@@ -18,6 +18,8 @@
*/
package org.jasig.cas.client.proxy;
+import org.jasig.cas.client.configuration.ConfigurationKeys;
+
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
@@ -36,11 +38,9 @@ import javax.crypto.spec.DESedeKeySpec;
*/
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 {
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 1146b48..e80304a 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
@@ -55,6 +55,11 @@ public final class Cas20ProxyRetriever implements ProxyRetriever {
/** 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.
*
@@ -75,7 +80,13 @@ public final class Cas20ProxyRetriever implements ProxyRetriever {
CommonUtils.assertNotNull(targetService, "targetService cannot be null.");
final URL url = constructUrl(proxyGrantingTicketId, targetService);
- final String response = CommonUtils.getResponseFromServer(url, this.urlConnectionFactory, this.encoding);
+ final String response;
+
+ 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)) {
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 33b2094..5720a69 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
@@ -19,10 +19,13 @@
package org.jasig.cas.client.session;
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;
/**
@@ -34,49 +37,47 @@ import org.jasig.cas.client.util.AbstractConfigurationFilter;
*/
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",
- SingleSignOutHandler.DEFAULT_ARTIFACT_PARAMETER_NAME));
- handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName",
- SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME));
- handler.setFrontLogoutParameterName(getPropertyFromInitParams(filterConfig, "frontLogoutParameterName",
- SingleSignOutHandler.DEFAULT_FRONT_LOGOUT_PARAMETER_NAME));
- handler.setRelayStateParameterName(getPropertyFromInitParams(filterConfig, "relayStateParameterName",
- SingleSignOutHandler.DEFAULT_RELAY_STATE_PARAMETER_NAME));
- handler.setCasServerUrlPrefix(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null));
- handler.setArtifactParameterOverPost(parseBoolean(getPropertyFromInitParams(filterConfig,
- "artifactParameterOverPost", "false")));
- handler.setEagerlyCreateSessions(parseBoolean(getPropertyFromInitParams(filterConfig,
- "eagerlyCreateSessions", "true")));
+ 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);
+ HANDLER.setFrontLogoutParameterName(name);
}
public void setRelayStateParameterName(final String name) {
- handler.setRelayStateParameterName(name);
+ HANDLER.setRelayStateParameterName(name);
}
public void setCasServerUrlPrefix(final String casServerUrlPrefix) {
- handler.setCasServerUrlPrefix(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,
@@ -84,7 +85,15 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
- if (handler.process(request, response)) {
+ /**
+ * 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(); + } + + if (HANDLER.process(request, response)) { filterChain.doFilter(servletRequest, servletResponse); } } @@ -94,6 +103,6 @@ public final class SingleSignOutFilter extends AbstractConfigurationFilter { } 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 c93d9b3..4efc018 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 @@ -28,7 +28,9 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; +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 org.slf4j.Logger; @@ -44,11 +46,6 @@ import org.slf4j.LoggerFactory; */ public final class SingleSignOutHandler { - public final static String DEFAULT_ARTIFACT_PARAMETER_NAME = "ticket"; - public final static String DEFAULT_LOGOUT_PARAMETER_NAME = "logoutRequest"; - public final static String DEFAULT_FRONT_LOGOUT_PARAMETER_NAME = "SAMLRequest"; - public final static String DEFAULT_RELAY_STATE_PARAMETER_NAME = "RelayState"; - private final static int DECOMPRESSION_FACTOR = 10; /** Logger instance */ @@ -58,19 +55,19 @@ public final class SingleSignOutHandler { private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage(); /** The name of the artifact parameter. This is used to capture the session identifier. */ - private String artifactParameterName = DEFAULT_ARTIFACT_PARAMETER_NAME; + private String artifactParameterName = Protocol.CAS2.getArtifactParameterName(); /** Parameter name that stores logout request for back channel SLO */ - private String logoutParameterName = DEFAULT_LOGOUT_PARAMETER_NAME; + private String logoutParameterName = ConfigurationKeys.LOGOUT_PARAMETER_NAME.getDefaultValue(); /** Parameter name that stores logout request for front channel SLO */ - private String frontLogoutParameterName = DEFAULT_FRONT_LOGOUT_PARAMETER_NAME; + 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 = DEFAULT_RELAY_STATE_PARAMETER_NAME; + private String relayStateParameterName = ConfigurationKeys.RELAY_STATE_PARAMETER_NAME.getDefaultValue(); /** The prefix url of the CAS server */ - private String casServerUrlPrefix; + private String casServerUrlPrefix = ""; private boolean artifactParameterOverPost = false; @@ -78,6 +75,8 @@ public final class SingleSignOutHandler { private List- * 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: - *
+ *
* 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 { @@ -58,8 +58,8 @@ public final class ErrorRedirectFilter implements Filter { final HttpServletResponse httpResponse = (HttpServletResponse) response; try { filterChain.doFilter(request, response); - } catch (final ServletException e) { - final Throwable t = e.getCause(); + } catch (final Exception e) { + final Throwable t = extractErrorToCompare(e); ErrorHolder currentMatch = null; for (final ErrorHolder errorHolder : this.errors) { if (errorHolder.exactMatch(t)) { @@ -78,6 +78,22 @@ public final class ErrorRedirectFilter implements Filter { } } + /** + * 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"); 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 3512f48..a474a83 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 @@ -26,6 +26,7 @@ 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; /** @@ -82,8 +83,9 @@ public final class HttpServletRequestWrapperFilter extends AbstractConfiguration } 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 { 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 0000000..b003775 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java @@ -0,0 +1,74 @@ +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 0000000..6eb5b62 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java @@ -0,0 +1,62 @@ +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 MapDeployers can provide the "casServerPrefix" and the "renew" attributes via the standard context or filter init * parameters. - * + * * @author Scott Battaglia * @version $Revision$ $Date$ * @since 3.1 */ public class Cas10TicketValidationFilter extends AbstractTicketValidationFilter { - protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) { - final String casServerUrlPrefix = getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null); - final Cas10TicketValidator validator = new Cas10TicketValidator(casServerUrlPrefix); - validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false"))); + public Cas10TicketValidationFilter() { + super(Protocol.CAS1); + } - final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(filterConfig), - getSSLConfig(filterConfig)); + protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) { + final String casServerUrlPrefix = getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX); + final Cas10TicketValidator validator = new Cas10TicketValidator(casServerUrlPrefix); + validator.setRenew(getBoolean(ConfigurationKeys.RENEW)); + + final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(), + getSSLConfig()); validator.setURLConnectionFactory(factory); - validator.setEncoding(getPropertyFromInitParams(filterConfig, "encoding", null)); + validator.setEncoding(getString(ConfigurationKeys.ENCODING)); return validator; } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java index e256755..5fe1337 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java @@ -23,16 +23,21 @@ import java.util.*; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import org.jasig.cas.client.Protocol; +import org.jasig.cas.client.configuration.ConfigurationKeys; import org.jasig.cas.client.proxy.*; import org.jasig.cas.client.ssl.HttpURLConnectionFactory; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; import org.jasig.cas.client.util.CommonUtils; import org.jasig.cas.client.util.ReflectUtils; +import static org.jasig.cas.client.configuration.ConfigurationKeys.*; + /** * Creates either a CAS20ProxyTicketValidator or a CAS20ServiceTicketValidator depending on whether any of the * proxy parameters are set. - *
+ *
* This filter can also pass additional parameters to the ticket validator. Any init parameter not included in the * reserved list {@link org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter#RESERVED_INIT_PARAMS}. * @@ -40,20 +45,20 @@ import org.jasig.cas.client.util.ReflectUtils; * @author Brad Cupit (brad [at] lsu {dot} edu) * @version $Revision$ $Date$ * @since 3.1 - * */ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketValidationFilter { - private static final String[] RESERVED_INIT_PARAMS = new String[] { "proxyGrantingTicketStorageClass", - "proxyReceptorUrl", "acceptAnyProxy", "allowedProxyChains", "casServerUrlPrefix", "proxyCallbackUrl", - "renew", "exceptionOnValidationFailure", "redirectAfterValidation", "useSession", "serverName", "service", - "artifactParameterName", "serviceParameterName", "encodeServiceUrl", "millisBetweenCleanUps", - "hostnameVerifier", "encoding", "config", "ticketValidatorClass" }; - - private static final int DEFAULT_MILLIS_BETWEEN_CLEANUPS = 60 * 1000; + private static final String[] RESERVED_INIT_PARAMS = new String[]{ARTIFACT_PARAMETER_NAME.getName(), SERVER_NAME.getName(), SERVICE.getName(), RENEW.getName(), LOGOUT_PARAMETER_NAME.getName(), + ARTIFACT_PARAMETER_OVER_POST.getName(), EAGERLY_CREATE_SESSIONS.getName(), ENCODE_SERVICE_URL.getName(), SSL_CONFIG_FILE.getName(), ROLE_ATTRIBUTE.getName(), IGNORE_CASE.getName(), + CAS_SERVER_LOGIN_URL.getName(), GATEWAY.getName(), AUTHENTICATION_REDIRECT_STRATEGY_CLASS.getName(), GATEWAY_STORAGE_CLASS.getName(), CAS_SERVER_URL_PREFIX.getName(), ENCODING.getName(), + TOLERANCE.getName(), IGNORE_PATTERN.getName(), IGNORE_URL_PATTERN_TYPE.getName(), HOSTNAME_VERIFIER.getName(), HOSTNAME_VERIFIER_CONFIG.getName(), + EXCEPTION_ON_VALIDATION_FAILURE.getName(), REDIRECT_AFTER_VALIDATION.getName(), USE_SESSION.getName(), SECRET_KEY.getName(), CIPHER_ALGORITHM.getName(), PROXY_RECEPTOR_URL.getName(), + PROXY_GRANTING_TICKET_STORAGE_CLASS.getName(), MILLIS_BETWEEN_CLEAN_UPS.getName(), ACCEPT_ANY_PROXY.getName(), ALLOWED_PROXY_CHAINS.getName(), TICKET_VALIDATOR_CLASS.getName(), + PROXY_CALLBACK_URL.getName(), FRONT_LOGOUT_PARAMETER_NAME.getName(), RELAY_STATE_PARAMETER_NAME.getName() + }; /** - * The URL to send to the CAS server as the URL that will process proxying requests on the CAS client. + * The URL to send to the CAS server as the URL that will process proxying requests on the CAS client. */ private String proxyReceptorUrl; @@ -63,25 +68,37 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal private int millisBetweenCleanUps; + protected Class extends Cas20ServiceTicketValidator> defaultServiceTicketValidatorClass; + + protected Class extends Cas20ProxyTicketValidator> defaultProxyTicketValidatorClass; + /** * Storage location of ProxyGrantingTickets and Proxy Ticket IOUs. */ private ProxyGrantingTicketStorage proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl(); - protected void initInternal(final FilterConfig filterConfig) throws ServletException { - setProxyReceptorUrl(getPropertyFromInitParams(filterConfig, "proxyReceptorUrl", null)); + public Cas20ProxyReceivingTicketValidationFilter() { + this(Protocol.CAS2); + this.defaultServiceTicketValidatorClass = Cas20ServiceTicketValidator.class; + this.defaultProxyTicketValidatorClass = Cas20ProxyTicketValidator.class; + } - final String proxyGrantingTicketStorageClass = getPropertyFromInitParams(filterConfig, - "proxyGrantingTicketStorageClass", null); + protected Cas20ProxyReceivingTicketValidationFilter(final Protocol protocol) { + super(protocol); + } + + protected void initInternal(final FilterConfig filterConfig) throws ServletException { + setProxyReceptorUrl(getString(ConfigurationKeys.PROXY_RECEPTOR_URL)); + + final Class extends ProxyGrantingTicketStorage> proxyGrantingTicketStorageClass = getClass(ConfigurationKeys.PROXY_GRANTING_TICKET_STORAGE_CLASS); if (proxyGrantingTicketStorageClass != null) { this.proxyGrantingTicketStorage = ReflectUtils.newInstance(proxyGrantingTicketStorageClass); if (this.proxyGrantingTicketStorage instanceof AbstractEncryptedProxyGrantingTicketStorageImpl) { final AbstractEncryptedProxyGrantingTicketStorageImpl p = (AbstractEncryptedProxyGrantingTicketStorageImpl) this.proxyGrantingTicketStorage; - final String cipherAlgorithm = getPropertyFromInitParams(filterConfig, "cipherAlgorithm", - AbstractEncryptedProxyGrantingTicketStorageImpl.DEFAULT_ENCRYPTION_ALGORITHM); - final String secretKey = getPropertyFromInitParams(filterConfig, "secretKey", null); + final String cipherAlgorithm = getString(ConfigurationKeys.CIPHER_ALGORITHM); + final String secretKey = getString(ConfigurationKeys.SECRET_KEY); p.setCipherAlgorithm(cipherAlgorithm); @@ -95,9 +112,7 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal } } - logger.trace("Setting proxyReceptorUrl parameter: {}", this.proxyReceptorUrl); - this.millisBetweenCleanUps = Integer.parseInt(getPropertyFromInitParams(filterConfig, "millisBetweenCleanUps", - Integer.toString(DEFAULT_MILLIS_BETWEEN_CLEANUPS))); + this.millisBetweenCleanUps = getInt(ConfigurationKeys.MILLIS_BETWEEN_CLEAN_UPS); super.initInternal(filterConfig); } @@ -115,13 +130,13 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal this.timer.schedule(this.timerTask, this.millisBetweenCleanUps, this.millisBetweenCleanUps); } - private