open module cas

This commit is contained in:
thinkgem
2023-06-28 08:53:48 +08:00
parent 74f72fb2b8
commit b3b5d53763
64 changed files with 6468 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 com.jeesite.common.shiro.cas;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* This filter validates the CAS service ticket to authenticate the user. It must be configured on the URL recognized
* by the CAS server. For example, in {@code shiro.ini}:
* <pre>
* [main]
* casFilter = org.apache.shiro.cas.CasFilter
* ...
*
* [urls]
* /shiro-cas = casFilter
* ...
* </pre>
* (example : http://host:port/mycontextpath/shiro-cas)
*
* @since 1.2
* @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
* @ deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
*/
public class CasBaseFilter extends AuthenticatingFilter {
private static Logger logger = LoggerFactory.getLogger(CasBaseFilter.class);
// the name of the parameter service ticket in url
private static final String TICKET_PARAMETER = "ticket";
// the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
private String failureUrl;
/**
* The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
* the filter must be configured).
*
* @param request the incoming request
* @param response the outgoing response
* @throws Exception if there is an error processing the request.
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String ticket = httpRequest.getParameter(TICKET_PARAMETER);
return new CasToken(ticket);
}
/**
* Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
* with this token.
*
* @param request the incoming request
* @param response the outgoing response
* @throws Exception if there is an error processing the request.
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
return executeLogin(request, response);
}
/**
* Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
*
* @param request the incoming request
* @param response the outgoing response
* @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
* @return <code>false</code>
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
/**
* If login has been successful, redirect user to the original protected url.
*
* @param token the token representing the current authentication
* @param subject the current authenticated subjet
* @param request the incoming request
* @param response the outgoing response
* @throws Exception if there is an error processing the request.
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
return false;
}
/**
* If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
* authenticated, in which case redirect to the default success url.
*
* @param token the token representing the current authentication
* @param ae the current authentication exception
* @param request the incoming request
* @param response the outgoing response
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
ServletResponse response) {
if (logger.isDebugEnabled()) {
logger.debug( "Authentication exception", ae );
}
// is user authenticated or in remember me mode ?
Subject subject = getSubject(request, response);
if (subject.isAuthenticated() || subject.isRemembered()) {
try {
issueSuccessRedirect(request, response);
} catch (Exception e) {
logger.error("Cannot redirect to the default success url", e);
}
} else {
try {
WebUtils.issueRedirect(request, response, failureUrl);
} catch (IOException e) {
logger.error("Cannot redirect to failure url : {}", failureUrl, e);
}
}
return false;
}
public void setFailureUrl(String failureUrl) {
this.failureUrl = failureUrl;
}
}

View File

@@ -0,0 +1,18 @@
package com.jeesite.common.shiro.cas;
import com.jeesite.modules.sys.entity.User;
import java.util.Map;
/**
* Cas登录时本项目没有账号时调用方法
* @author ThinkGem
*/
public interface CasCreateUser {
/**
* Cas登录时本项目没有账号时调用方法
*/
void createUser(User user, Map<String, Object> attributes);
}

View File

@@ -0,0 +1,205 @@
/**
* 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 com.jeesite.common.shiro.cas;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import com.jeesite.common.shiro.realm.LoginInfo;
/**
* Performs CAS single sign-out operations in an API-agnostic fashion.
*
* @author Marvin S. Addison 2015-12-6 ThinkGem 增加手动指定ticket和logoutRequest参数
* @version $Revision: 24094 $ $Date: 2011-06-20 21:39:49 -0400 (Mon, 20 Jun 2011) $
* @since 3.1.12
*
*/
public final class CasOutHandler {
/** Logger instance */
private final Log log = LogFactory.getLog(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";
/** Parameter name that stores logout request */
private String logoutParameterName = "logoutRequest";
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.sessionMappingStorage = storage;
}
public SessionMappingStorage getSessionMappingStorage() {
return this.sessionMappingStorage;
}
/**
* @param name Name of the authentication token parameter.
*/
public void setArtifactParameterName(final String name) {
this.artifactParameterName = name;
}
/**
* @param name Name of parameter containing CAS logout request message.
*/
public void setLogoutParameterName(final String name) {
this.logoutParameterName = name;
}
/**
* 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 cannote be null.");
}
/**
* Determines whether the given request contains an authentication token.
*
* @param request HTTP reqest.
*
* @return True if request contains authentication token, false otherwise.
*/
public boolean isTokenRequest(final HttpServletRequest request) {
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName));
}
/**
* Determines whether the given request is a CAS 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));
}
/**
* Associates a token request with the current HTTP session by recording the mapping
* in the the configured {@link SessionMappingStorage} container.
*
* @param request HTTP request containing an authentication token.
*/
public void recordSession(final HttpServletRequest request) {
recordSession(request, null);
}
/**
* Associates a token request with the current HTTP session by recording the mapping
* in the the configured {@link SessionMappingStorage} container.
*
* @param request HTTP request containing an authentication token.
*/
public void recordSession(final HttpServletRequest request, String ticket) {
// final HttpSession session = request.getSession(true);
final HttpSession session = request.getSession();
final String token;
if (ticket != null){
token = ticket;
}else{
token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
}
if (log.isDebugEnabled()) {
log.debug("Recording session for token " + token);
}
try {
this.sessionMappingStorage.removeBySessionById(session.getId());
} catch (final Exception e) {
// ignore if the session is already marked as invalid. Nothing we can do!
}
sessionMappingStorage.addSessionById(token, session);
}
/**
* Destroys the current HTTP session for the given CAS logout request.
*
* @param request HTTP request containing a CAS logout message.
*/
public LoginInfo destroySession(final HttpServletRequest request) {
final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName);
if (log.isTraceEnabled()) {
log.trace("Logout request:\n" + logoutMessage);
}
LoginInfo loginInfo = null;
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();
if (log.isDebugEnabled()) {
log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]");
}
try {
PrincipalCollection pc = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
loginInfo = (pc != null ? (LoginInfo) pc.getPrimaryPrincipal() : null);
session.invalidate();
} catch (final IllegalStateException e) {
log.debug("Error invalidating session.", e);
}
}
}
return loginInfo;
}
// /**
// * Destroys the current HTTP session for the given CAS logout request.
// *
// * @param request HTTP request containing a CAS logout message.
// * v4.0.5之后替代方法onLogoutSuccess(loginInfo, request)
// */
// @Deprecated
// public User destroySession(final HttpServletRequest request, String logoutRequest) {
// this.logoutParameterName = logoutRequest;
// LoginInfo loginInfo = destroySession(request);
// if (loginInfo != null) {
// return UserUtils.get(loginInfo.getId());
// }
// return null;
// }
private boolean isMultipartRequest(final HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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 com.jeesite.common.shiro.cas;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.jasig.cas.client.session.SessionMappingStorage;
import com.jeesite.common.utils.SpringUtils;
/**
* Listener to detect when an HTTP session is destroyed and remove it from the map of
* managed sessions. Also allows for the programmatic removal of sessions.
* <p>
* Enables the CAS Single Sign out feature.
*
* Scott Battaglia
* @version $Revision$ Date$ 2015-12-6
* @since 3.1
*/
public final class CasOutSessionListener implements HttpSessionListener {
private CasOutHandler casOutHandler;
@Override
public void sessionCreated(final HttpSessionEvent event) {
// nothing to do at the moment
}
@Override
public void sessionDestroyed(final HttpSessionEvent event) {
final HttpSession session = event.getSession();
getSessionMappingStorage().removeBySessionById(session.getId());
}
public SessionMappingStorage getSessionMappingStorage() {
if (casOutHandler == null){
casOutHandler = SpringUtils.getBean(CasOutHandler.class);
}
return casOutHandler.getSessionMappingStorage();
}
}

View File

@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 com.jeesite.common.shiro.cas;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
/**
* {@link org.apache.shiro.mgt.SubjectFactory Subject} implementation to be used in CAS-enabled applications.
*
* @since 1.2
* @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
* @ deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
*/
public class CasSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
//the authenticated flag is only set by the SecurityManager after a successful authentication attempt.
boolean authenticated = context.isAuthenticated();
//although the SecurityManager 'sees' the submission as a successful authentication, in reality, the
//login might have been just a CAS rememberMe login. If so, set the authenticated flag appropriately:
if (authenticated) {
AuthenticationToken token = context.getAuthenticationToken();
if (token != null && token instanceof CasToken) {
CasToken casToken = (CasToken) token;
// set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
if (casToken.isRememberMe()) {
context.setAuthenticated(false);
}
}
}
return super.createSubject(context);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 com.jeesite.common.shiro.cas;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
/**
* This class represents a token for a CAS authentication (service ticket + user id + remember me).
*
* @since 1.2
* @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
* @ deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
*/
public class CasToken implements RememberMeAuthenticationToken {
private static final long serialVersionUID = 8587329689973009598L;
// the service ticket returned by the CAS server
private String ticket = null;
// the user identifier
private String userId = null;
// is the user in a remember me mode ?
private boolean isRememberMe = false;
public CasToken(String ticket) {
this.ticket = ticket;
}
public Object getPrincipal() {
return userId;
}
public Object getCredentials() {
return ticket;
}
public void setUserId(String userId) {
this.userId = userId;
}
public boolean isRememberMe() {
return isRememberMe;
}
public void setRememberMe(boolean isRememberMe) {
this.isRememberMe = isRememberMe;
}
}

View File

@@ -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:
*
* 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.Serializable;
import java.security.Principal;
import java.util.Map;
/**
* Extension to the standard Java Principal that includes a way to retrieve proxy tickets for a particular user
* and attributes.
* <p>
* Developer's who don't want their code tied to CAS merely need to work with the Java Principal then. Working with
* the CAS-specific features requires knowledge of the AttributePrincipal class.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public interface AttributePrincipal extends Principal, Serializable {
/**
* Retrieves a CAS proxy ticket for this specific principal.
*
* @param service the service we wish to proxy this user to.
* @return a String representing the proxy ticket.
*/
String getProxyTicketFor(String service);
/**
* The Map of key/value pairs associated with this principal.
* @return the map of key/value pairs associated with this principal.
*/
Map<String,Object> getAttributes();
}

View File

@@ -0,0 +1,112 @@
/**
* 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.proxy.ProxyRetriever;
import org.jasig.cas.client.util.CommonUtils;
import java.util.Collections;
import java.util.Map;
/**
* Concrete implementation of the AttributePrincipal interface.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public class AttributePrincipalImpl extends SimplePrincipal implements AttributePrincipal {
private static final Log LOG = LogFactory.getLog(AttributePrincipalImpl.class);
/** Unique Id for Serialization */
private static final long serialVersionUID = -1443182634624927187L;
/** Map of key/value pairs about this principal. */
private final Map<String,Object> attributes;
/** The CAS 2 ticket used to retrieve a proxy ticket. */
private final String proxyGrantingTicket;
/** The method to retrieve a proxy ticket from a CAS server. */
private final ProxyRetriever proxyRetriever;
/**
* Constructs a new principal with an empty map of attributes.
*
* @param name the unique identifier for the principal.
*/
public AttributePrincipalImpl(final String name) {
this(name, Collections.<String, Object>emptyMap());
}
/**
* Constructs a new principal with the supplied name and attributes.
*
* @param name the unique identifier for the principal.
* @param attributes the key/value pairs for this principal.
*/
public AttributePrincipalImpl(final String name, final Map<String,Object> attributes) {
this(name, attributes, null, null);
}
/**
* Constructs a new principal with the supplied name and the proxying capabilities.
*
* @param name the unique identifier for the principal.
* @param proxyGrantingTicket the ticket associated with this principal.
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server.
*/
public AttributePrincipalImpl(final String name, final String proxyGrantingTicket, final ProxyRetriever proxyRetriever) {
this(name, Collections.<String, Object>emptyMap(), proxyGrantingTicket, proxyRetriever);
}
/**
* Constructs a new principal witht he supplied name, attributes, and proxying capabilities.
*
* @param name the unique identifier for the principal.
* @param attributes the key/value pairs for this principal.
* @param proxyGrantingTicket the ticket associated with this principal.
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server.
*/
public AttributePrincipalImpl(final String name, final Map<String,Object> attributes, final String proxyGrantingTicket, final ProxyRetriever proxyRetriever) {
super(name);
this.attributes = attributes;
this.proxyGrantingTicket = proxyGrantingTicket;
this.proxyRetriever = proxyRetriever;
CommonUtils.assertNotNull(this.attributes, "attributes cannot be null.");
}
public Map<String,Object> getAttributes() {
return this.attributes;
}
public String getProxyTicketFor(String service) {
if (proxyGrantingTicket != null) {
return this.proxyRetriever.getProxyTicketIdFor(this.proxyGrantingTicket, service);
}
LOG.debug("No ProxyGrantingTicket was supplied, so no Proxy Ticket can be retrieved.");
return null;
}
}

View File

@@ -0,0 +1,158 @@
/**
* 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 org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.validation.Assertion;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
/**
* Filter implementation to intercept all requests and attempt to authenticate
* the user by redirecting them to CAS (unless the user has a ticket).
* <p>
* This filter allows you to specify the following parameters (at either the context-level or the filter-level):
* <ul>
* <li><code>casServerLoginUrl</code> - the url to log into CAS, i.e. https://cas.rutgers.edu/login</li>
* <li><code>renew</code> - true/false on whether to use renew or not.</li>
* <li><code>gateway</code> - true/false on whether to use gateway or not.</li>
* </ul>
*
* <p>Please see AbstractCasFilter for additional properties.</p>
*
* @author Scott Battaglia
* @version $Revision: 11768 $ $Date: 2007-02-07 15:44:16 -0500 (Wed, 07 Feb 2007) $
* @since 3.0
*/
public class AuthenticationFilter extends AbstractCasFilter {
/**
* The URL to the CAS Server login.
*/
private String casServerLoginUrl;
/**
* Whether to send the renew request or not.
*/
private boolean renew = false;
/**
* Whether to send the gateway request or not.
*/
private boolean gateway = false;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
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);
if (gatewayStorageClass != null) {
try {
this.gatewayStorage = (GatewayResolver) Class.forName(gatewayStorageClass).newInstance();
} catch (final Exception e) {
log.error(e,e);
throw new ServletException(e);
}
}
}
}
public void init() {
super.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 {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final HttpSession session = request.getSession(false);
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
final String serviceUrl = constructServiceUrl(request, response);
final String ticket = CommonUtils.safeGetParameter(request,getArtifactParameterName());
final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
filterChain.doFilter(request, response);
return;
}
final String modifiedServiceUrl;
log.debug("no ticket and no assertion found");
if (this.gateway) {
log.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
if (log.isDebugEnabled()) {
log.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 + "\"");
}
response.sendRedirect(urlToRedirectTo);
}
public final void setRenew(final boolean renew) {
this.renew = renew;
}
public final void setGateway(final boolean gateway) {
this.gateway = gateway;
}
public final void setCasServerLoginUrl(final String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
this.gatewayStorage = gatewayStorage;
}
}

View File

@@ -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:
*
* 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 jakarta.servlet.http.HttpServletRequest;
import jakarta.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 String storeGatewayInformation(final HttpServletRequest request,
final String serviceUrl) {
request.getSession(true).setAttribute(CONST_CAS_GATEWAY, "yes");
return serviceUrl;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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 jakarta.servlet.http.HttpServletRequest;
/**
* Implementations of this should only have a default constructor if
* you plan on constructing them via the web.xml.
*
* @author Scott Battaglia
* @version $Revision$
* @since 1.0
*
*/
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);
}

View File

@@ -0,0 +1,45 @@
/**
* 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 jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
/**
* Extension to the default Authentication filter that sets the required SAML1.1 artifact parameter name and service parameter name.
* <p>
* Note, the "final" on this class helps ensure the compliance required in the initInternal method.
*
* @author Scott Battaglia
* @since 3.1.12
* @version $Revision$ $Date$
*/
public final class Saml11AuthenticationFilter extends AuthenticationFilter {
protected 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");
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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 java.security.Principal;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* Simple security group implementation
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public final class SimpleGroup extends SimplePrincipal {
/** SimpleGroup.java */
private static final long serialVersionUID = 4382154818494550205L;
/** Group members */
private final Set<Principal> members = new HashSet<Principal>();
/**
* Creates a new group with the given name.
* @param name Group name.
*/
public SimpleGroup(final String name) {
super(name);
}
public boolean addMember(final Principal user) {
return this.members.add(user);
}
public boolean isMember(final Principal member) {
return this.members.contains(member);
}
public Enumeration<? extends Principal> members() {
return Collections.enumeration(this.members);
}
public boolean removeMember(final Principal user) {
return this.members.remove(user);
}
public String toString() {
return super.toString() + ": " + members.toString();
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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 java.io.Serializable;
import java.security.Principal;
import org.jasig.cas.client.util.CommonUtils;
/**
* Simple security principal implementation.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class SimplePrincipal implements Principal, Serializable {
/** SimplePrincipal.java */
private static final long serialVersionUID = -5645357206342793145L;
/** The unique identifier for this principal. */
private final String name;
/**
* Creates a new principal with the given name.
* @param name Principal name.
*/
public SimplePrincipal(final String name) {
this.name = name;
CommonUtils.assertNotNull(this.name, "name cannot be null.");
}
public final String getName() {
return this.name;
}
public String toString() {
return getName();
}
public boolean equals(final Object o) {
if (o == null) {
return false;
} else if (!(o instanceof SimplePrincipal)) {
return false;
} else {
return getName().equals(((SimplePrincipal)o).getName());
}
}
public int hashCode() {
return 37 * getName().hashCode();
}
}

View File

@@ -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:
*
* 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.Serializable;
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.validation.Assertion;
/**
* Principal implementation that contains the CAS ticket validation assertion.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class AssertionPrincipal extends SimplePrincipal implements Serializable {
/** AssertionPrincipal.java */
private static final long serialVersionUID = 2288520214366461693L;
/** CAS assertion describing authenticated state */
private Assertion assertion;
/**
* Creates a new principal containing the CAS assertion.
*
* @param name Principal name.
* @param assertion CAS assertion.
*/
public AssertionPrincipal(final String name, final Assertion assertion) {
super(name);
this.assertion = assertion;
}
/**
* @return CAS ticket validation assertion.
*/
public Assertion getAssertion() {
return this.assertion;
}
}

View File

@@ -0,0 +1,493 @@
/**
* 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.jaas;
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 javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.security.Principal;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* JAAS login module that delegates to a CAS {@link TicketValidator} component
* for authentication, and on success populates a {@link Subject} with principal
* data including NetID and principal attributes. The module expects to be provided
* with the CAS ticket (required) and service (optional) parameters via
* {@link PasswordCallback} and {@link NameCallback}, respectively, by the
* {@link CallbackHandler} that is part of the JAAS framework in which the servlet
* resides.
*
* <p>
* Module configuration options:
* <ul>
* <li>ticketValidatorClass - Fully-qualified class name of CAS ticket validator class.</li>
* <li>casServerUrlPrefix - URL to root of CAS Web application context.</li>
* <li>service (optional) - CAS service parameter that may be overridden by callback handler.
* NOTE: service must be specified by at least one component such that it is available at
* service ticket validation time</li>
* <li>defaultRoles (optional) - Comma-delimited list of static roles applied to all
* authenticated principals.</li>
* <li>roleAttributeNames (optional) - Comma-delimited list of attribute names that describe
* role data delivered to CAS in the service-ticket validation response that should be
* applied to the current authenticated principal.</li>
* <li>principalGroupName (optional) - The name of a group principal containing the
* primary principal name of the current JAAS subject. The default value is "CallerPrincipal",
* which is suitable for JBoss.</li>
* <li>roleGroupName (optional) - The name of a group principal containing all role data.
* The default value is "Roles", which is suitable for JBoss.</li>
* <li>cacheAssertions (optional) - Flag to enable assertion caching. This may be needed
* 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.</li>
* <li>cacheTimeout (optional) - Assertion cache timeout in minutes.</li>
* </ul>
*
* <p>
* Module options not explicitly listed above are treated as attributes of the
* given ticket validator class, e.g. <code>tolerance</code> in the following example.
*
* <p>
* Sample jaas.config file entry for this module:
* <pre>
* cas {
* org.jasig.cas.client.jaas.CasLoginModule required
* ticketValidatorClass="org.jasig.cas.client.validation.Saml11TicketValidator"
* casServerUrlPrefix="https://cas.example.com/cas"
* tolerance="20000"
* service="https://webapp.example.com/webapp"
* defaultRoles="admin,operator"
* roleAttributeNames="memberOf,eduPersonAffiliation"
* principalGroupName="CallerPrincipal"
* roleGroupName="Roles";
* }
* </pre>
*
* @author Marvin S. Addison
* @version $Revision$ $Date$
* @since 3.1.11
*
*/
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
* support other JEE containers.
*/
public static final String DEFAULT_ROLE_GROUP_NAME = "Roles";
/**
* Default assertion cache timeout in minutes. Default is 8 hours.
*/
public static final int DEFAULT_CACHE_TIMEOUT = 480;
/**
* 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<TicketCredential,Assertion> ASSERTION_CACHE = new HashMap<TicketCredential,Assertion>();
/** Executor responsible for assertion cache cleanup */
protected static Executor cacheCleanerExecutor = Executors.newSingleThreadExecutor();
/** Logger instance */
protected final Log log = LogFactory.getLog(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<String,Object> 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<String> roleAttributeNames = new HashSet<String>();
/** 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;
/** Enables or disable assertion caching */
protected boolean cacheAssertions;
/** Assertion cache timeout in minutes */
protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT;
/**
* Initializes the CAS login module.
* @param subject Authentication subject.
* @param handler Callback handler.
* @param state Shared state map.
* @param options Login module options. The following are supported:
* <ul>
* <li>service - CAS service URL used for service ticket validation.</li>
* <li>ticketValidatorClass - fully-qualified class name of service ticket validator component.</li>
* <li>defaultRoles (optional) - comma-delimited list of roles to be added to all authenticated principals.</li>
* <li>roleAttributeNames (optional) - comma-delimited list of attributes in the CAS assertion that contain role data.</li>
* <li>principalGroupName (optional) - name of JAAS Group containing caller principal.</li>
* <li>roleGroupName (optional) - name of JAAS Group containing role data</li>
* <li>cacheAssertions (optional) - whether or not to cache assertions.
* Some JAAS providers attempt to reauthenticate users after an indeterminate
* period of time. Since the credential used for authentication is a CAS ticket,
* which by default are single use, reauthentication fails. Assertion caching addresses this
* behavior.</li>
* <li>cacheTimeout (optional) - assertion cache timeout in minutes.</li>
* </ul>
*/
public void initialize(final Subject subject, final CallbackHandler handler, final Map<String,?> state, final Map<String, ?> options) {
this.assertion = null;
this.callbackHandler = handler;
this.subject = subject;
this.sharedState = new HashMap<String, Object>(state);
String ticketValidatorClass = null;
for (final String key : options.keySet()) {
log.trace("Processing option " + key);
if ("service".equals(key)) {
this.service = (String) options.get(key);
log.debug("Set service=" + this.service);
} else if ("ticketValidatorClass".equals(key)) {
ticketValidatorClass = (String) options.get(key);
log.debug("Set ticketValidatorClass=" + ticketValidatorClass);
} else if ("defaultRoles".equals(key)) {
final String roles = (String) options.get(key);
log.trace("Got defaultRoles value " + roles);
this.defaultRoles = roles.split(",\\s*");
log.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);
final String[] attributes = attrNames.split(",\\s*");
this.roleAttributeNames.addAll(Arrays.asList(attributes));
log.debug("Set roleAttributeNames=" + this.roleAttributeNames);
} else if ("principalGroupName".equals(key)) {
this.principalGroupName = (String) options.get(key);
log.debug("Set principalGroupName=" + this.principalGroupName);
} else if ("roleGroupName".equals(key)) {
this.roleGroupName = (String) options.get(key);
log.debug("Set roleGroupName=" + this.roleGroupName);
} else if ("cacheAssertions".equals(key)) {
this.cacheAssertions = Boolean.parseBoolean((String) options.get(key));
log.debug("Set cacheAssertions=" + this.cacheAssertions);
} else if ("cacheTimeout".equals(key)) {
this.cacheTimeout = Integer.parseInt((String) options.get(key));
log.debug("Set cacheTimeout=" + this.cacheTimeout);
}
}
if (this.cacheAssertions) {
cacheCleanerExecutor.execute(new CacheCleaner());
}
CommonUtils.assertNotNull(ticketValidatorClass, "ticketValidatorClass is required.");
this.ticketValidator = createTicketValidator(ticketValidatorClass, options);
}
public boolean login() throws LoginException {
log.debug("Performing login.");
final NameCallback serviceCallback = new NameCallback("service");
final PasswordCallback ticketCallback = new PasswordCallback("ticket", false);
try {
this.callbackHandler.handle(new Callback[] { ticketCallback, serviceCallback });
} catch (final IOException e) {
log.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);
}
if (ticketCallback.getPassword() != null) {
this.ticket = new TicketCredential(new String(ticketCallback.getPassword()));
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);
}
}
}
if (this.assertion == null) {
log.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.");
}
try {
if (log.isDebugEnabled()) {
log.debug("Attempting ticket validation with service=" + service + " and ticket=" + 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);
throw (LoginException) new LoginException("CAS ticket validation failed: " + e).initCause(e);
}
}
log.info("Login succeeded.");
} else {
log.info("Login failed because callback handler did not provide CAS ticket.");
throw new LoginException("Callback handler did not provide CAS ticket.");
}
return true;
}
public boolean abort() throws LoginException {
if (this.ticket != null) {
this.ticket = null;
}
if (this.assertion != null) {
this.assertion = null;
}
return true;
}
public boolean commit() throws LoginException {
if (this.assertion != null) {
if (this.ticket != null) {
this.subject.getPrivateCredentials().add(this.ticket);
} else {
throw new LoginException("Ticket credential not found.");
}
final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion);
this.subject.getPrincipals().add(casPrincipal);
// Add group containing principal as sole member
// Supports JBoss JAAS use case
final SimpleGroup principalGroup = new SimpleGroup(this.principalGroupName);
principalGroup.addMember(casPrincipal);
this.subject.getPrincipals().add(principalGroup);
// Add group principal containing role data
final SimpleGroup roleGroup = new SimpleGroup(this.roleGroupName);
for (final String defaultRole : defaultRoles) {
roleGroup.addMember(new SimplePrincipal(defaultRole));
}
final Map<String,Object> 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
final Object value = attributes.get(key);
if (value instanceof Collection<?>) {
for (final Object o : (Collection<?>) value) {
roleGroup.addMember(new SimplePrincipal(o.toString()));
}
} else {
roleGroup.addMember(new SimplePrincipal(value.toString()));
}
}
}
this.subject.getPrincipals().add(roleGroup);
// Place principal name in shared state for downstream JAAS modules (module chaining use case)
this.sharedState.put(LOGIN_NAME, new Object()); // casPrincipal.getName());
if (log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug("Created JAAS subject with principals: " + subject.getPrincipals());
}
}
if (this.cacheAssertions) {
if (log.isDebugEnabled()) {
log.debug("Caching assertion for principal " + this.assertion.getPrincipal());
}
ASSERTION_CACHE.put(this.ticket, this.assertion);
}
} else {
// Login must have failed if there is no assertion defined
// Need to clean up state
if (this.ticket != null) {
this.ticket = null;
}
}
return true;
}
public boolean logout() throws LoginException {
log.debug("Performing logout.");
// Remove all CAS principals
removePrincipalsOfType(AssertionPrincipal.class);
removePrincipalsOfType(SimplePrincipal.class);
removePrincipalsOfType(SimpleGroup.class);
// Remove all CAS credentials
removeCredentialsOfType(TicketCredential.class);
log.info("Logout succeeded.");
return true;
}
/**
* 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<String,?> propertyMap) {
CommonUtils.assertTrue(propertyMap.containsKey("casServerUrlPrefix"), "Required property casServerUrlPrefix not found.");
final Class<TicketValidator> validatorClass = ReflectUtils.loadClass(className);
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);
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);
} else {
log.warn("Cannot find property " + property + " on " + className);
}
}
}
} catch (final IntrospectionException e) {
throw new RuntimeException("Error getting bean info for " + validatorClass, e);
}
return validator;
}
/**
* Attempts to do simple type conversion from a string value to the type expected
* by the given property.
*
* Currently only conversion to int, long, and boolean are supported.
*
* @param pd Property descriptor of target property to set.
* @param value Property value as a string.
* @return Value converted to type expected by property if a conversion strategy exists.
*/
private static Object convertIfNecessary(final PropertyDescriptor pd, final String value) {
if (String.class.equals(pd.getPropertyType())) {
return value;
} else if (boolean.class.equals(pd.getPropertyType())) {
return Boolean.valueOf(value);
} else if (int.class.equals(pd.getPropertyType())) {
return Integer.valueOf(value);
} else if (long.class.equals(pd.getPropertyType())) {
return Long.valueOf(value);
} else {
throw new IllegalArgumentException("No conversion strategy exists for property " + pd.getName() + " of type " + pd.getPropertyType());
}
}
/**
* Removes all principals of the given type from the JAAS subject.
* @param clazz Type of principal to remove.
*/
private void removePrincipalsOfType(final Class<? extends Principal> clazz) {
this.subject.getPrincipals().removeAll(this.subject.getPrincipals(clazz));
}
/**
* Removes all credentials of the given type from the JAAS subject.
* @param clazz Type of principal to remove.
*/
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<Map.Entry<TicketCredential,Assertion>> 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();
}
}
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.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;
/**
* Callback handler that provides the CAS service and ticket to a
* {@link NameCallback} and {@link PasswordCallback} respectively,
* which meets the requirements of the {@link CasLoginModule} JAAS module.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
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.
*
* @param service CAS service URL.
* @param ticket CAS service ticket.
*/
public ServiceAndTicketCallbackHandler(final String service, final String ticket) {
this.service = service;
this.ticket = ticket;
}
public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (final Callback callback : callbacks) {
if (callback instanceof NameCallback) {
((NameCallback) callback).setName(this.service);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback) callback).setPassword(this.ticket.toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "Callback not supported.");
}
}
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.jaas;
import java.security.Principal;
/**
* Strongly-typed wrapper for a ticket credential.
*
* @author Marvin S. Addison
* @version $Revision$ $Date$
* @since 3.1.12
*
*/
public final class TicketCredential implements Principal {
/** Hash code seed value */
private static final int HASHCODE_SEED = 17;
/** Ticket ID string */
private String ticket;
/**
* Creates a new instance that wraps the given ticket.
* @param ticket Ticket identifier string.
*/
public TicketCredential(final String ticket) {
this.ticket = ticket;
}
public String getName() {
return this.ticket;
}
public String toString() {
return this.ticket;
}
public boolean equals(Object o) {
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;
return true;
}
public int hashCode() {
int hash = HASHCODE_SEED;
hash = hash * 31 + (ticket == null ? 0 : ticket.hashCode());
return hash;
}
}

View File

@@ -0,0 +1,92 @@
package org.jasig.cas.client.proxy;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
/**
* Provides encryption capabilities. Not entirely safe to configure since we have no way of controlling the
* key and cipher being set.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.2.0
*/
public abstract class AbstractEncryptedProxyGrantingTicketStorageImpl implements ProxyGrantingTicketStorage {
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "DESede";
private Key key;
private String cipherAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
public final void setSecretKey(final String key) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
this.key = SecretKeyFactory.getInstance(this.cipherAlgorithm).generateSecret(new DESedeKeySpec(key.getBytes()));
}
public final void setSecretKey(final Key key) {
this.key = key;
}
/**
* Note: you MUST call this method before calling setSecretKey if you're not using the default algorithm. You've been warned.
*
* @param cipherAlgorithm the cipher algorithm.
*/
public final void setCipherAlgorithm(final String cipherAlgorithm) {
this.cipherAlgorithm = cipherAlgorithm;
}
public final void save(final String proxyGrantingTicketIou, final String proxyGrantingTicket) {
saveInternal(proxyGrantingTicketIou, encrypt(proxyGrantingTicket));
}
public final String retrieve(final String proxyGrantingTicketIou) {
return decrypt(retrieveInternal(proxyGrantingTicketIou));
}
protected abstract void saveInternal(String proxyGrantingTicketIou, String proxyGrantingTicket);
protected abstract String retrieveInternal(String proxyGrantingTicketIou);
private String encrypt(final String value) {
if (this.key == null) {
return value;
}
if (value == null) {
return null;
}
try {
final Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, this.key);
return new String(cipher.doFinal(value.getBytes()));
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
private String decrypt(final String value) {
if (this.key == null) {
return value;
}
if (value == null) {
return null;
}
try {
final Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, this.key);
return new String(cipher.doFinal(value.getBytes()));
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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.proxy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Implementation of a ProxyRetriever that follows the CAS 2.0 specification.
* For more information on the CAS 2.0 specification, please see the <a
* href="http://www.ja-sig.org/products/cas/overview/protocol/index.html">specification
* document</a>.
* <p/>
* 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;
/**
* Instance of Commons Logging.
*/
private final Log log = LogFactory.getLog(this.getClass());
/**
* Url to CAS server.
*/
private final String casServerUrl;
private final String encoding;
/**
* Main Constructor.
*
* @param casServerUrl the URL to the CAS server (i.e. http://localhost/cas/)
* @param encoding the encoding to use.
*/
public Cas20ProxyRetriever(final String casServerUrl, final String encoding) {
CommonUtils.assertNotNull(casServerUrl, "casServerUrl cannot be null.");
this.casServerUrl = casServerUrl;
this.encoding = encoding;
}
public String getProxyTicketIdFor(final String proxyGrantingTicketId,
final String targetService) {
final String url = constructUrl(proxyGrantingTicketId, targetService);
final String response = CommonUtils.getResponseFromServer(url, this.encoding);
final String error = XmlUtils.getTextForElement(response, "proxyFailure");
if (CommonUtils.isNotEmpty(error)) {
log.debug(error);
return null;
}
return XmlUtils.getTextForElement(response, "proxyTicket");
}
private String 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) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.proxy;
import java.util.TimerTask;
/**
* A {@link TimerTask} implementation which performs the
* actual 'cleaning' by calling {@link ProxyGrantingTicketStorage#cleanUp()}.
* <p>
* By default, the {@link org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter} configures
* a task that cleans up the {@link org.jasig.cas.client.proxy.ProxyGrantingTicketStorage} associated with it.
*
* @author Brad Cupit (brad [at] lsu {dot} edu)
* @version $Revision$ $Date$
* @since 3.1.6
*/
public final class CleanUpTimerTask extends TimerTask {
private final ProxyGrantingTicketStorage proxyGrantingTicketStorage;
public CleanUpTimerTask(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
}
public void run() {
this.proxyGrantingTicketStorage.cleanUp();
}
}

View File

@@ -0,0 +1,56 @@
/**
* 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.proxy;
/**
* Interface for the storage and retrieval of ProxyGrantingTicketIds by mapping
* them to a specific ProxyGrantingTicketIou.
*
* @author Scott Battaglia
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public interface ProxyGrantingTicketStorage {
/**
* Method to save the ProxyGrantingTicket to the backing storage facility.
*
* @param proxyGrantingTicketIou used as the key
* @param proxyGrantingTicket used as the value
*/
public void save(String proxyGrantingTicketIou, String proxyGrantingTicket);
/**
* Method to retrieve a ProxyGrantingTicket based on the
* ProxyGrantingTicketIou. Note that implementations are not guaranteed to
* return the same result if retrieve is called twice with the same
* proxyGrantingTicketIou.
*
* @param proxyGrantingTicketIou used as the key
* @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.
*/
public void cleanUp();
}

View File

@@ -0,0 +1,140 @@
/**
* 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.proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implementation of {@link ProxyGrantingTicketStorage} that is backed by a
* HashMap that keeps a ProxyGrantingTicket for a specified amount of time.
* <p>
* {@link ProxyGrantingTicketStorage#cleanUp()} must be called on a regular basis to
* keep the HashMap from growing indefinitely.
*
* @author Scott Battaglia
* @author Brad Cupit (brad [at] lsu {dot} edu)
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class ProxyGrantingTicketStorageImpl implements ProxyGrantingTicketStorage {
private final Log log = LogFactory.getLog(getClass());
/**
* Default timeout in milliseconds.
*/
private static final long DEFAULT_TIMEOUT = 60000;
/**
* Map that stores the PGTIOU to PGT mappings.
*/
private final ConcurrentMap<String,ProxyGrantingTicketHolder> cache = new ConcurrentHashMap<String,ProxyGrantingTicketHolder>();
/**
* time, in milliseconds, before a {@link ProxyGrantingTicketHolder}
* is considered expired and ready for removal.
*
* @see ProxyGrantingTicketStorageImpl#DEFAULT_TIMEOUT
*/
private long timeout;
/**
* Constructor set the timeout to the default value.
*/
public ProxyGrantingTicketStorageImpl() {
this(DEFAULT_TIMEOUT);
}
/**
* Sets the amount of time to hold on to a ProxyGrantingTicket if its never
* been retrieved.
*
* @param timeout the time to hold on to the ProxyGrantingTicket
*/
public ProxyGrantingTicketStorageImpl(final long timeout) {
this.timeout = timeout;
}
/**
* NOTE: you can only retrieve a ProxyGrantingTicket once with this method.
* Its removed after retrieval.
*/
public String retrieve(final String proxyGrantingTicketIou) {
final ProxyGrantingTicketHolder holder = this.cache.get(proxyGrantingTicketIou);
if (holder == null) {
log.info("No Proxy Ticket found for [" + proxyGrantingTicketIou + "].");
return null;
}
this.cache.remove(proxyGrantingTicketIou);
if (log.isDebugEnabled()) {
log.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 + "]");
}
this.cache.put(proxyGrantingTicketIou, holder);
}
/**
* Cleans up old, expired proxy tickets. This method must be
* called regularly via an external thread or timer.
*/
public void cleanUp() {
for (final Map.Entry<String,ProxyGrantingTicketHolder> holder : this.cache.entrySet()) {
if (holder.getValue().isExpired(this.timeout)) {
this.cache.remove(holder.getKey());
}
}
}
private static final class ProxyGrantingTicketHolder {
private final String proxyGrantingTicket;
private final long timeInserted;
protected ProxyGrantingTicketHolder(final String proxyGrantingTicket) {
this.proxyGrantingTicket = proxyGrantingTicket;
this.timeInserted = System.currentTimeMillis();
}
public String getProxyGrantingTicket() {
return this.proxyGrantingTicket;
}
final boolean isExpired(final long timeout) {
return System.currentTimeMillis() - this.timeInserted > timeout;
}
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.proxy;
import java.io.Serializable;
/**
* Interface to abstract the retrieval of a proxy ticket to make the
* implementation a black box to the client.
*
* @author Scott Battaglia
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public interface ProxyRetriever extends Serializable {
/**
* Retrieves a proxy ticket for a specific targetService.
*
* @param proxyGrantingTicketId the ProxyGrantingTicketId
* @param targetService the service we want to proxy.
* @return the ProxyTicket Id if Granted, null otherwise.
*/
String getProxyTicketIdFor(String proxyGrantingTicketId,
String targetService);
}

View File

@@ -0,0 +1,85 @@
/**
* 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.session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpSession;
/**
* HashMap backed implementation of SessionMappingStorage.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*
*/
public final class HashMapBackedSessionMappingStorage implements SessionMappingStorage {
/**
* Maps the ID from the CAS server to the Session.
*/
private final Map<String,HttpSession> MANAGED_SESSIONS = new HashMap<String,HttpSession>();
/**
* Maps the Session ID to the key from the CAS Server.
*/
private final Map<String,String> ID_TO_SESSION_KEY_MAPPING = new HashMap<String,String>();
private final Log log = LogFactory.getLog(getClass());
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 + "]");
}
final String key = ID_TO_SESSION_KEY_MAPPING.get(sessionId);
if (log.isDebugEnabled()) {
if (key != null) {
log.debug("Found mapping for session. Session Removed.");
} else {
log.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);
if (session != null) {
removeBySessionById(session.getId());
}
return session;
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.session;
import jakarta.servlet.http.HttpSession;
/**
* Stores the mapping between sessions and keys to be retrieved later.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*
*/
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);
}

View File

@@ -0,0 +1,86 @@
/**
* 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.session;
import org.jasig.cas.client.util.AbstractConfigurationFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Implements the Single Sign Out protocol. It handles registering the session and destroying the session.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public final class SingleSignOutFilter extends AbstractConfigurationFilter {
private static final SingleSignOutHandler handler = new SingleSignOutHandler();
public void init(final FilterConfig filterConfig) throws ServletException {
if (!isIgnoreInitConfiguration()) {
handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
}
handler.init();
}
public void setArtifactParameterName(final String name) {
handler.setArtifactParameterName(name);
}
public void setLogoutParameterName(final String name) {
handler.setLogoutParameterName(name);
}
public void setSessionMappingStorage(final SessionMappingStorage storage) {
handler.setSessionMappingStorage(storage);
}
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
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());
}
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
// nothing to do
}
protected static SingleSignOutHandler getSingleSignOutHandler() {
return handler;
}
}

View File

@@ -0,0 +1,162 @@
/**
* 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.session;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
/**
* Performs CAS single sign-out operations in an API-agnostic fashion.
*
* @author Marvin S. Addison
* @version $Revision$ $Date$
* @since 3.1.12
*
*/
public final class SingleSignOutHandler {
/** Logger instance */
private final Log log = LogFactory.getLog(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";
/** Parameter name that stores logout request */
private String logoutParameterName = "logoutRequest";
public void setSessionMappingStorage(final SessionMappingStorage storage) {
this.sessionMappingStorage = storage;
}
public SessionMappingStorage getSessionMappingStorage() {
return this.sessionMappingStorage;
}
/**
* @param name Name of the authentication token parameter.
*/
public void setArtifactParameterName(final String name) {
this.artifactParameterName = name;
}
/**
* @param name Name of parameter containing CAS logout request message.
*/
public void setLogoutParameterName(final String name) {
this.logoutParameterName = name;
}
/**
* 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 cannote be null.");
}
/**
* Determines whether the given request contains an authentication token.
*
* @param request HTTP reqest.
*
* @return True if request contains authentication token, false otherwise.
*/
public boolean isTokenRequest(final HttpServletRequest request) {
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName));
}
/**
* Determines whether the given request is a CAS 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));
}
/**
* Associates a token request with the current HTTP session by recording the mapping
* in the the configured {@link SessionMappingStorage} container.
*
* @param request HTTP request containing an authentication token.
*/
public void recordSession(final HttpServletRequest request) {
final HttpSession session = request.getSession(true);
final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
if (log.isDebugEnabled()) {
log.debug("Recording session for token " + token);
}
try {
this.sessionMappingStorage.removeBySessionById(session.getId());
} catch (final Exception e) {
// ignore if the session is already marked as invalid. Nothing we can do!
}
sessionMappingStorage.addSessionById(token, session);
}
/**
* 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);
if (log.isTraceEnabled()) {
log.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();
if (log.isDebugEnabled()) {
log.debug ("Invalidating session [" + sessionID + "] for token [" + token + "]");
}
try {
session.invalidate();
} catch (final IllegalStateException e) {
log.debug("Error invalidating session.", e);
}
}
}
}
private boolean isMultipartRequest(final HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.session;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
/**
* Listener to detect when an HTTP session is destroyed and remove it from the map of
* managed sessions. Also allows for the programmatic removal of sessions.
* <p>
* Enables the CAS Single Sign out feature.
*
* Scott Battaglia
* @version $Revision$ Date$
* @since 3.1
*/
public final class SingleSignOutHttpSessionListener implements HttpSessionListener {
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();
}
final HttpSession session = event.getSession();
sessionMappingStorage.removeBySessionById(session.getId());
}
/**
* Obtains a {@link SessionMappingStorage} object. Assumes this method will always return the same
* instance of the object. It assumes this because it generally lazily calls the method.
*
* @return the SessionMappingStorage
*/
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
}

View File

@@ -0,0 +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:
*
* 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 javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Hostname verifier that performs no host name verification for an SSL peer
* such that all hosts are allowed.
*
* @author Marvin Addison
* @version $Revision$ $Date$
* @since 3.1.10
*/
public final class AnyHostnameVerifier implements HostnameVerifier {
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
return true;
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.ssl;
import java.util.regex.Pattern;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Validates an SSL peer's hostname using a regular expression that a candidate
* host must match in order to be verified.
*
* @author Marvin Addison
* @version $Revision$ $Date$
* @since 3.1.10
*
*/
public final class RegexHostnameVerifier implements HostnameVerifier {
/** Allowed hostname pattern */
private Pattern pattern;
/**
* Creates a new instance using the given regular expression.
*
* @param regex Regular expression describing allowed hosts.
*/
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();
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Verifies a SSL peer host name based on an explicit whitelist of allowed hosts.
*
* @author Marvin Addison
* @version $Revision$ $Date$
* @since 3.1.10
*
*/
public final class WhitelistHostnameVerifier implements HostnameVerifier {
/** Allowed hosts */
private String[] allowedHosts;
/**
* Creates a new instance using the given array of allowed hosts.
*
* @param allowed Array of allowed hosts.
*/
public WhitelistHostnameVerifier(final String[] allowed) {
this.allowedHosts = allowed;
}
/**
* Creates a new instance using the given list of allowed hosts.
*
* @param allowedList Comma-separated list of allowed hosts.
*/
public WhitelistHostnameVerifier(final String allowedList) {
this.allowedHosts = allowedList.split(",\\s*");
}
/** {@inheritDoc} */
public boolean verify(final String hostname, final SSLSession session) {
for (final String allowedHost : this.allowedHosts) {
if (hostname.equalsIgnoreCase(allowedHost)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,154 @@
/**
* 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.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Abstract filter that contains code that is common to all CAS filters.
* <p>
* The following filter options can be configured (either at the context-level or filter-level).
* <ul>
* <li><code>serverName</code> - the name of the CAS client server, in the format: localhost:8080 or localhost:8443 or localhost or https://localhost:8443</li>
* <li><code>service</code> - the completely qualified service url, i.e. https://localhost/cas-client/app</li>
* </ul>
* <p>Please note that one of the two above parameters must be set.</p>
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @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());
/** 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;
/**
* The name of the server. Should be in the following format: {protocol}:{hostName}:{port}.
* Standard ports can be excluded. */
private String serverName;
/** The exact url of the service. */
private String service;
public final void init(final FilterConfig filterConfig) throws ServletException {
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);
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.
*
*/
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
// template method
}
/**
* Initialization method. Called by Filter's init method or by Spring. Similar in concept to the InitializingBean interface's
* 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.");
}
// empty implementation as most filters won't need this.
public void destroy() {
// nothing to do
}
protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName, this.artifactParameterName, this.encodeServiceUrl);
}
/**
* Note that trailing slashes should not be used in the serverName. As a convenience for this common misconfiguration, we strip them from the provided
* value.
*
* @param serverName the serverName. If this method is called, this should not be null. This AND service should not be both configured.
*/
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));
} else {
this.serverName = serverName;
}
}
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;
}
public final String getServiceParameterName() {
return this.serviceParameterName;
}
}

View File

@@ -0,0 +1,128 @@
/**
* 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.util;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Abstracts out the ability to configure the filters from the initial properties provided.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public abstract class AbstractConfigurationFilter implements Filter {
protected final Log log = LogFactory.getLog(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.
* <p>
* 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}
* <p>
* Essentially the documented order is:
* <ol>
* <li>FilterConfig.getInitParameter</li>
* <li>ServletContext.getInitParameter</li>
* <li>java:comp/env/cas/SHORTFILTERNAME/{propertyName}</li>
* <li>java:comp/env/cas/{propertyName}</li>
* <li>Default Value</li>
* </ol>
*
*
* @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);
if (CommonUtils.isNotBlank(value)) {
log.info("Property [" + propertyName + "] loaded from FilterConfig.getInitParameter with value [" + value + "]");
return value;
}
final String value2 = filterConfig.getServletContext().getInitParameter(propertyName);
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;
}
log.info("Property [" + propertyName + "] not found. Using default value [" + defaultValue + "]");
return defaultValue;
}
protected final boolean parseBoolean(final String value) {
return ((value != null) && value.equalsIgnoreCase("true"));
}
protected final String loadFromContext(final InitialContext context, final String path) {
try {
return (String) context.lookup(path);
} catch (final NamingException e) {
return null;
}
}
public final void setIgnoreInitConfiguration(boolean ignoreInitConfiguration) {
this.ignoreInitConfiguration = ignoreInitConfiguration;
}
protected final boolean isIgnoreInitConfiguration() {
return this.ignoreInitConfiguration;
}
}

View File

@@ -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:
*
* 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.jasig.cas.client.validation.Assertion;
/**
* Static holder that places Assertion in a ThreadLocal.
*
* @author Scott Battaglia
* @version $Revision: 11728 $ $Date: 2007-09-26 14:20:43 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public class AssertionHolder {
/**
* ThreadLocal to hold the Assertion for Threads to access.
*/
private static final ThreadLocal<Assertion> threadLocal = new ThreadLocal<Assertion>();
/**
* Retrieve the assertion from the ThreadLocal.
*
* @return the Asssertion associated with this thread.
*/
public static Assertion getAssertion() {
return threadLocal.get();
}
/**
* Add the Assertion to the ThreadLocal.
*
* @param assertion the assertion to add.
*/
public static void setAssertion(final Assertion assertion) {
threadLocal.set(assertion);
}
/**
* Clear the ThreadLocal.
*/
public static void clear() {
threadLocal.set(null);
}
}

View File

@@ -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:
*
* 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.jasig.cas.client.validation.Assertion;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
/**
* Places the assertion in a ThreadLocal such that other resources can access it that do not have access to the web tier session.
*
* @author Scott Battaglia
* @version $Revision: 11728 $ $Date: 2007-09-26 14:20:43 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class AssertionThreadLocalFilter implements Filter {
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 {
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));
try {
AssertionHolder.setAssertion(assertion);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
AssertionHolder.clear();
}
}
public void destroy() {
// nothing to do
}
}

View File

@@ -0,0 +1,388 @@
/**
* 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.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 jakarta.servlet.http.HttpServletRequest;
import jakarta.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.Collection;
import java.util.Date;
import java.util.TimeZone;
/**
* Common utilities so that we don't need to include Commons Lang.
*
* @author Scott Battaglia
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class CommonUtils {
/** Instance of Commons Logging. */
private static final Log LOG = LogFactory.getLog(CommonUtils.class);
/**
* Constant representing the ProxyGrantingTicket IOU Request Parameter.
*/
private static final String PARAM_PROXY_GRANTING_TICKET_IOU = "pgtIou";
/**
* Constant representing the ProxyGrantingTicket Request Parameter.
*/
private static final String PARAM_PROXY_GRANTING_TICKET = "pgtId";
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);
}
/**
* Check whether the object is null or not. If it is, throw an exception and
* display the message.
*
* @param object the object to check.
* @param message the message to display if the object is null.
*/
public static void assertNotNull(final Object object, final String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
/**
* Check whether the collection is null or empty. If it is, throw an
* exception and display the message.
*
* @param c the collecion to check.
* @param message the message to display if the object is null.
*/
public static void assertNotEmpty(final Collection<?> c, final String message) {
assertNotNull(c, message);
if (c.isEmpty()) {
throw new IllegalArgumentException(message);
}
}
/**
* Assert that the statement is true, otherwise throw an exception with the
* provided message.
*
* @param cond the codition 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) {
if (!cond) {
throw new IllegalArgumentException(message);
}
}
/**
* Determines whether the String is null or of length 0.
*
* @param string the string to check
* @return true if its null or length of 0, false otherwise.
*/
public static boolean isEmpty(final String string) {
return string == null || string.length() == 0;
}
/**
* Determines if the String is not empty. A string is not empty if it is not
* null and has a length > 0.
*
* @param string the string to check
* @return true if it is not empty, false otherwise.
*/
public static boolean isNotEmpty(final String string) {
return !isEmpty(string);
}
/**
* Determines if a String is blank or not. A String is blank if its empty or
* if it only contains spaces.
*
* @param string the string to check
* @return true if its blank, false otherwise.
*/
public static boolean isBlank(final String string) {
return isEmpty(string) || string.trim().length() == 0;
}
/**
* Determines if a string is not blank. A string is not blank if it contains
* at least one non-whitespace character.
*
* @param string the string to check.
* @return true if its not blank, false otherwise.
*/
public static boolean isNotBlank(final String string) {
return !isBlank(string);
}
/**
* Constructs the URL to use to redirect to the CAS server.
*
* @param casServerLoginUrl the CAS Server login url.
* @param serviceParameterName the name of the parameter that defines the service.
* @param serviceUrl the actual service's url.
* @param renew whether we should send renew or not.
* @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) {
try {
return casServerLoginUrl + (casServerLoginUrl.indexOf("?") != -1 ? "&" : "?") + serviceParameterName + "="
+ URLEncoder.encode(serviceUrl, "UTF-8")
+ (renew ? "&renew=true" : "")
+ (gateway ? "&gateway=true" : "");
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
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);
if (CommonUtils.isBlank(proxyGrantingTicket) || CommonUtils.isBlank(proxyGrantingTicketIou)) {
response.getWriter().write("");
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Received proxyGrantingTicketId ["
+ proxyGrantingTicket + "] for proxyGrantingTicketIou ["
+ proxyGrantingTicketIou + "]");
}
proxyGrantingTicketStorage.save(proxyGrantingTicketIou, proxyGrantingTicket);
if (LOG.isDebugEnabled()) {
LOG.debug("Successfully saved proxyGrantingTicketId ["
+ proxyGrantingTicket + "] for proxyGrantingTicketIou ["
+ proxyGrantingTicketIou + "]");
}
response.getWriter().write("<?xml version=\"1.0\"?>");
response.getWriter().write("<casClient:proxySuccess xmlns:casClient=\"http://www.yale.edu/tp/casClient\" />");
}
/**
* Constructs a service url from the HttpServletRequest or from the given
* serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a
* serviceName.
*
* @param request the HttpServletRequest
* @param response the HttpServletResponse
* @param service the configured service url (this will be used if not null)
* @param serverName the server name to use to constuct the service url if the service param is empty
* @param artifactParameterName the artifact parameter name to remove (i.e. ticket)
* @param encode whether to encode the url or not (i.e. Jsession).
* @return the service url to use.
*/
public static String constructServiceUrl(final HttpServletRequest request,
final HttpServletResponse response, final String service, final String serverName, final String artifactParameterName, final boolean encode) {
if (CommonUtils.isNotBlank(service)) {
return encode ? response.encodeURL(service) : service;
}
final StringBuilder buffer = new StringBuilder();
if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) {
buffer.append(request.isSecure() ? "https://" : "http://");
}
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 (location == -1) {
buffer.append(request.getQueryString());
} else if (location > 0) {
final int actualLocation = request.getQueryString()
.indexOf("&" + artifactParameterName + "=");
if (actualLocation == -1) {
buffer.append(request.getQueryString());
} else if (actualLocation > 0) {
buffer.append(request.getQueryString().substring(0,
actualLocation));
}
}
}
final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString();
if (LOG.isDebugEnabled()) {
LOG.debug("serviceUrl generated: " + returnValue);
}
return returnValue;
}
/**
* Safe method for retrieving a parameter from the request without disrupting the reader UNLESS the parameter
* actually exists in the query string.
* <p>
* Note, this does not work for POST Requests for "logoutRequest". It works for all other CAS POST requests because the
* parameter is ALWAYS in the GET request.
* <p>
* If we see the "logoutRequest" parameter we MUST treat it as if calling the standard request.getParameter.
*
* @param request the request to check.
* @param parameter the parameter to look for.
* @return the value of the parameter.
*/
public static String safeGetParameter(final HttpServletRequest request, final String parameter) {
if ("POST".equals(request.getMethod()) && "logoutRequest".equals(parameter)) {
LOG.debug("safeGetParameter called on a POST HttpServletRequest for LogoutRequest. Cannot complete check safely. Reverting to standard behavior for this Parameter");
return request.getParameter(parameter);
}
return request.getQueryString() == null || request.getQueryString().indexOf(parameter) == -1 ? null : request.getParameter(parameter);
}
/**
* Contacts the remote URL and returns the response.
*
* @param constructedUrl the url to contact.
* @param encoding the encoding to use.
* @return the response.
*/
public static String getResponseFromServer(final URL constructedUrl, final String encoding) {
return getResponseFromServer(constructedUrl, HttpsURLConnection.getDefaultHostnameVerifier(), 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 encoding the encoding to use.
* @return the response.
*/
public static String getResponseFromServer(final URL constructedUrl, final HostnameVerifier hostnameVerifier, final String encoding) {
URLConnection conn = null;
try {
conn = constructedUrl.openConnection();
if (conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(hostnameVerifier);
}
final BufferedReader in;
if (CommonUtils.isEmpty(encoding)) {
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encoding));
}
String line;
final StringBuilder stringBuffer = new StringBuilder(255);
while ((line = in.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append("\n");
}
return stringBuffer.toString();
} catch (final Exception e) {
LOG.error(e.getMessage(), e);
throw new RuntimeException(e);
} finally {
if (conn != null && conn instanceof HttpURLConnection) {
((HttpURLConnection)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) {
if (CommonUtils.isBlank(proxies)) {
return new ProxyList();
}
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.
*
* @param response the HttpServletResponse. CANNOT be NULL.
* @param url the url to redirect to.
*/
public static void sendRedirect(final HttpServletResponse response, final String url) {
try {
response.sendRedirect(url);
} catch (final Exception e) {
LOG.warn(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,122 @@
/**
* 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.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
/**
* A Delegating Filter looks up a parameter in the request object and matches
* (either exact or using Regular Expressions) the value. If there is a match,
* the associated filter is executed. Otherwise, the normal chain is executed.
*
* @author Scott Battaglia
* @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $
* @since 3.0
*/
public final class DelegatingFilter implements Filter {
/**
* Instance of Commons Logging.
*/
private final Log log = LogFactory.getLog(this.getClass());
/**
* The request parameter to look for in the Request object.
*/
private final String requestParameterName;
/**
* The map of filters to delegate to and the criteria (as key).
*/
private final Map<String,Filter> delegators;
/**
* The default filter to use if there is no match.
*/
private final Filter defaultFilter;
/**
* Whether the key in the delegators map is an exact match or a regular
* expression.
*/
private final boolean exactMatch;
public DelegatingFilter(final String requestParameterName, final Map<String,Filter> delegators, final boolean exactMatch) {
this(requestParameterName, delegators, exactMatch, null);
}
public DelegatingFilter(final String requestParameterName, final Map<String,Filter> delegators, final boolean exactMatch, final Filter defaultFilter) {
CommonUtils.assertNotNull(requestParameterName, "requestParameterName cannot be null.");
CommonUtils.assertTrue(!delegators.isEmpty(), "delegators cannot be empty.");
this.requestParameterName = requestParameterName;
this.delegators = delegators;
this.defaultFilter = defaultFilter;
this.exactMatch = exactMatch;
}
public void destroy() {
// nothing to do here
}
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
final String parameter = CommonUtils.safeGetParameter((HttpServletRequest) request, this.requestParameterName);
if (CommonUtils.isNotEmpty(parameter)) {
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() + "]");
}
filter.doFilter(request, response, filterChain);
return;
}
}
}
log.debug("No match found for parameter [" + this.requestParameterName + "] with value [" + parameter + "]");
if (this.defaultFilter != null) {
this.defaultFilter.doFilter(request, response, filterChain);
} else {
filterChain.doFilter(request, response);
}
}
public void init(final FilterConfig filterConfig) throws ServletException {
// nothing to do here.
}
}

View File

@@ -0,0 +1,128 @@
/**
* 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.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Filters that redirects to the supplied url based on an exception. Exceptions and the urls are configured via
* init filter name/param values.
* <p>
* 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.
* <p>
* 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<ErrorHolder> errors = new ArrayList<ErrorHolder>();
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;
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;
}
}
}

View File

@@ -0,0 +1,153 @@
/**
* 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.util;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.security.Principal;
import java.util.Collection;
/**
* Implementation of a filter that wraps the normal HttpServletRequest with a
* wrapper that overrides the following methods to provide data from the
* CAS Assertion:
* <ul>
* <li>{@link HttpServletRequest#getUserPrincipal()}</li>
* <li>{@link HttpServletRequest#getRemoteUser()}</li>
* <li>{@link HttpServletRequest#isUserInRole(String)}</li>
* </ul>
* <p/>
* This filter needs to be configured in the chain so that it executes after
* both the authentication and the validation filters.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class HttpServletRequestWrapperFilter extends AbstractConfigurationFilter {
/** 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;
public void destroy() {
// nothing to do
}
/**
* Wraps the HttpServletRequest in a wrapper class that delegates
* <code>request.getRemoteUser</code> 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 {
final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);
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));
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"));
}
final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final AttributePrincipal principal;
CasHttpServletRequestWrapper(final HttpServletRequest request, final AttributePrincipal principal) {
super(request);
this.principal = principal;
}
public Principal getUserPrincipal() {
return this.principal;
}
public String getRemoteUser() {
return principal != null ? this.principal.getName() : null;
}
public boolean isUserInRole(final String role) {
if (CommonUtils.isBlank(role)) {
log.debug("No valid role provided. Returning false.");
return false;
}
if (this.principal == null) {
log.debug("No Principal in Request. Returning false.");
return false;
}
if (CommonUtils.isBlank(roleAttribute)) {
log.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);
return true;
}
}
}
final boolean isMember = rolesEqual(role, value);
log.debug("User [" + getRemoteUser() + "] is in role [" + role + "]: " + isMember);
return isMember;
}
/**
* Determines whether the given role is equal to the candidate
* role attribute taking into account case sensitivity.
*
* @param given Role under consideration.
* @param candidate Role that the current user possesses.
*
* @return True if roles are equal, false otherwise.
*/
private boolean rolesEqual(final String given, final Object candidate) {
return ignoreCase ? given.equalsIgnoreCase(candidate.toString()) : given.equals(candidate);
}
}
}

View File

@@ -0,0 +1,152 @@
/**
* 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.util;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
/**
* Helper class with reflection utility methods.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public final class ReflectUtils {
private ReflectUtils() {
// private constructor to prevent instanciation.
}
/**
* Attempts to create a class from a String.
* @param className the name of the class to create.
* @return the class. CANNOT be NULL.
* @throws IllegalArgumentException if the className does not exist.
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> loadClass(final String className) throws IllegalArgumentException {
try {
return (Class<T>) Class.forName(className);
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(className + " class not found.");
}
}
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
* @param className Name of class to be created.
* @param args Constructor arguments.
* @return New instance of given class.
*/
public static <T> T newInstance(final String className, final Object ... args) {
return newInstance(ReflectUtils.<T>loadClass(className), args);
}
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
* @param clazz Class of instance to be created.
* @param args Constructor arguments.
* @return New instance of given class.
*/
public static <T> T newInstance(final Class<T> clazz, final Object ... args) {
final Class<?>[] argClasses = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argClasses[i] = args[i].getClass();
}
try {
return clazz.getConstructor(argClasses).newInstance(args);
} catch (final Exception e) {
throw new IllegalArgumentException("Error creating new instance of " + clazz, e);
}
}
/**
* Gets the property descriptor for the named property on the given class.
* @param clazz Class to which property belongs.
* @param propertyName Name of property.
* @return Property descriptor for given property or null if no property with given
* name exists in given class.
*/
public static PropertyDescriptor getPropertyDescriptor(final Class<?> clazz, final String propertyName) {
try {
return getPropertyDescriptor(Introspector.getBeanInfo(clazz), propertyName);
} catch (final IntrospectionException e) {
throw new RuntimeException("Failed getting bean info for " + clazz, e);
}
}
/**
* Gets the property descriptor for the named property from the bean info describing
* a particular class to which property belongs.
* @param info Bean info describing class to which property belongs.
* @param propertyName Name of property.
* @return Property descriptor for given property or null if no property with given
* name exists.
*/
public static PropertyDescriptor getPropertyDescriptor(final BeanInfo info, final String propertyName) {
for (int i = 0; i < info.getPropertyDescriptors().length; i++) {
final PropertyDescriptor pd = info.getPropertyDescriptors()[i];
if (pd.getName().equals(propertyName)) {
return pd;
}
}
return null;
}
/**
* Sets the given property on the target JavaBean using bean instrospection.
* @param propertyName Property to set.
* @param value Property value to set.
* @param target Target java bean on which to set property.
*/
public static void setProperty(final String propertyName, final Object value, final Object target) {
try {
setProperty(propertyName, value, target, Introspector.getBeanInfo(target.getClass()));
} catch (final IntrospectionException e) {
throw new RuntimeException("Failed getting bean info on target JavaBean " + target, e);
}
}
/**
* Sets the given property on the target JavaBean using bean instrospection.
* @param propertyName Property to set.
* @param value Property value to set.
* @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) {
try {
final PropertyDescriptor pd = getPropertyDescriptor(info, propertyName);
pd.getWriteMethod().invoke(target, value);
} catch (final InvocationTargetException e) {
throw new RuntimeException("Error setting property " + propertyName, e.getCause());
} catch (final Exception e) {
throw new RuntimeException("Error setting property " + propertyName, e);
}
}
}

View File

@@ -0,0 +1,172 @@
/**
* 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.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
/**
* Common utilities for easily parsing XML without duplicating logic.
*
* @author Scott Battaglia
* @version $Revision: 11729 $ $Date: 2007-09-26 14:22:30 -0400 (Tue, 26 Sep 2007) $
* @since 3.0
*/
public final class XmlUtils {
/**
* Static instance of Commons Logging.
*/
private final static Log LOG = LogFactory.getLog(XmlUtils.class);
/**
* Get an instance of an XML reader from the XMLReaderFactory.
*
* @return the XMLReader.
*/
public static XMLReader getXmlReader() {
try {
return XMLReaderFactory.createXMLReader();
} catch (final SAXException 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.
* <p>This method is currently optimized for the use case of two elements in a list.
*
* @param xmlAsString the xml response
* @param element the element to look for
* @return the list of text from the elements.
*/
public static List<String> getTextForElements(final String xmlAsString,
final String element) {
final List<String> elements = new ArrayList<String>(2);
final XMLReader reader = getXmlReader();
final DefaultHandler handler = new DefaultHandler() {
private boolean foundElement = false;
private StringBuilder buffer = new StringBuilder();
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 {
if (localName.equals(element)) {
this.foundElement = false;
elements.add(this.buffer.toString());
this.buffer = new StringBuilder();
}
}
public void characters(char[] ch, int start, int length)
throws SAXException {
if (this.foundElement) {
this.buffer.append(ch, start, length);
}
}
};
reader.setContentHandler(handler);
reader.setErrorHandler(handler);
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
} catch (final Exception e) {
LOG.error(e, e);
return null;
}
return elements;
}
/**
* Retrieve the text for a specific element (when we know there is only
* one).
*
* @param xmlAsString the xml response
* @param element the element to look for
* @return the text value of the element.
*/
public static String getTextForElement(final String xmlAsString,
final String element) {
final XMLReader reader = getXmlReader();
final StringBuilder builder = new StringBuilder();
final DefaultHandler handler = new DefaultHandler() {
private boolean foundElement = false;
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 {
if (localName.equals(element)) {
this.foundElement = false;
}
}
public void characters(char[] ch, int start, int length)
throws SAXException {
if (this.foundElement) {
builder.append(ch, start, length);
}
}
};
reader.setContentHandler(handler);
reader.setErrorHandler(handler);
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
} catch (final Exception e) {
LOG.error(e, e);
return null;
}
return builder.toString();
}
}

View File

@@ -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:
*
* 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.validation;
import org.jasig.cas.client.util.CommonUtils;
import java.net.URL;
/**
* Abstract class that knows the protocol for validating a CAS ticket.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public abstract class AbstractCasProtocolUrlBasedTicketValidator extends AbstractUrlBasedTicketValidator {
protected AbstractCasProtocolUrlBasedTicketValidator(final String casServerUrlPrefix) {
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());
}
}
}

View File

@@ -0,0 +1,224 @@
/**
* 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.validation;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import javax.net.ssl.HostnameVerifier;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* The filter that handles all the work of validating ticket requests.
* <p>
* This filter can be configured with the following values:
* <ul>
* <li><code>redirectAfterValidation</code> - redirect the CAS client to the same URL without the ticket.</li>
* <li><code>exceptionOnValidationFailure</code> - throw an exception if the validation fails. Otherwise, continue
* processing.</li>
* <li><code>useSession</code> - store any of the useful information in a session attribute.</li>
* </ul>
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {
/** The TicketValidator we will use to validate tickets. */
private TicketValidator ticketValidator;
/**
* Specify whether the filter should redirect the user agent after a
* successful validation to remove the ticket parameter from the query
* string.
*/
private boolean redirectAfterValidation = false;
/** Determines whether an exception is thrown when there is a ticket validation failure. */
private boolean exceptionOnValidationFailure = true;
private boolean useSession = true;
/**
* Template method to return the appropriate validator.
*
* @param filterConfig the FilterConfiguration that may be needed to construct a validator.
* @return the ticket validator.
*/
protected TicketValidator getTicketValidator(final FilterConfig filterConfig) {
return this.ticketValidator;
}
/**
* 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);
if (className != null) {
if (config != null) {
return ReflectUtils.newInstance(className, config);
} else {
return ReflectUtils.newInstance(className);
}
}
return null;
}
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);
setTicketValidator(getTicketValidator(filterConfig));
super.initInternal(filterConfig);
}
public void init() {
super.init();
CommonUtils.assertNotNull(this.ticketValidator, "ticketValidator cannot be null.");
}
/**
* Pre-process the request before the normal filter process starts. This could be useful for pre-empting code.
*
* @param servletRequest The servlet request.
* @param servletResponse The servlet response.
* @param filterChain the filter chain.
* @return true if processing should continue, false otherwise.
* @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 {
return true;
}
/**
* Template method that gets executed if ticket validation succeeds. Override if you want additional behavior to occur
* if ticket validation succeeds. This method is called after all ValidationFilter processing required for a successful authentication
* occurs.
*
* @param request the HttpServletRequest.
* @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.
}
/**
* Template method that gets executed if validation fails. This method is called right after the exception is caught from the ticket validator
* but before any of the processing of the exception occurs.
*
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
*/
protected void onFailedValidation(final HttpServletRequest request, final HttpServletResponse response) {
// nothing to do here.
}
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
if (!preFilter(servletRequest, servletResponse, filterChain)) {
return;
}
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());
if (CommonUtils.isNotBlank(ticket)) {
if (log.isDebugEnabled()) {
log.debug("Attempting to validate ticket: " + ticket);
}
try {
final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));
if (log.isDebugEnabled()) {
log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
}
request.setAttribute(CONST_CAS_ASSERTION, assertion);
if (this.useSession) {
request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
}
onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
log. 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);
onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(e);
}
return;
}
}
filterChain.doFilter(request, response);
}
public final void setTicketValidator(final TicketValidator ticketValidator) {
this.ticketValidator = ticketValidator;
}
public final void setRedirectAfterValidation(final boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
}
public final void setExceptionOnValidationFailure(final boolean exceptionOnValidationFailure) {
this.exceptionOnValidationFailure = exceptionOnValidationFailure;
}
public final void setUseSession(final boolean useSession) {
this.useSession = useSession;
}
}

View File

@@ -0,0 +1,243 @@
/**
* 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.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;
/**
* 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 {
/**
* Commons Logging instance.
*/
protected final Log log = LogFactory.getLog(getClass());
/**
* Hostname verifier used when making an SSL request to the CAS server.
*/
protected HostnameVerifier hostnameVerifier;
/**
* Prefix for the CAS server. Should be everything up to the url endpoint, including the /.
*
* i.e. https://cas.rutgers.edu/
*/
private final String casServerUrlPrefix;
/**
* Whether the request include a renew or not.
*/
private boolean renew;
/**
* A map containing custom parameters to pass to the validation url.
*/
private Map<String,String> customParameters;
private String encoding;
/**
* Constructs a new TicketValidator with the casServerUrlPrefix.
*
* @param casServerUrlPrefix the location of the CAS server.
*/
protected AbstractUrlBasedTicketValidator(final String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null.");
}
/**
* Template method for ticket validators that need to provide additional parameters to the validation url.
*
* @param urlParameters the map containing the parameters.
*/
protected void populateUrlAttributeMap(final Map<String,String> urlParameters) {
// nothing to do
}
/**
* The endpoint of the validation URL. Should be relative (i.e. not start with a "/"). I.e. validate or serviceValidate.
* @return the endpoint of the validation URL.
*/
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.
*
* @param ticket the ticket to be validated.
* @param serviceUrl the service identifier.
* @return the fully constructed URL.
*/
protected final String constructValidationUrl(final String ticket, final String serviceUrl) {
final Map<String,String> urlParameters = new HashMap<String,String>();
log.debug("Placing URL parameters in map.");
urlParameters.put("ticket", ticket);
urlParameters.put("service", serviceUrl);
if (this.renew) {
urlParameters.put("renew", "true");
}
log.debug("Calling template URL attribute map.");
populateUrlAttributeMap(urlParameters);
log.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);
int i = 0;
buffer.append(this.casServerUrlPrefix);
if (!this.casServerUrlPrefix.endsWith("/")) {
buffer.append("/");
}
buffer.append(suffix);
for (Map.Entry<String,String> entry : urlParameters.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
if (value != null) {
buffer.append(i++ == 0 ? "?" : "&");
buffer.append(key);
buffer.append("=");
final String encodedValue = encodeUrl(value);
buffer.append(encodedValue);
}
}
return buffer.toString();
}
/**
* Encodes a URL using the URLEncoder format.
*
* @param url the url to encode.
* @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;
}
try {
return URLEncoder.encode(url, "UTF-8");
} catch (final UnsupportedEncodingException e) {
return url;
}
}
/**
* Parses the response from the server into a CAS Assertion.
*
* @param response the response from the server, in any format.
* @return the CAS assertion if one could be parsed from the response.
* @throws TicketValidationException if an Assertion could not be created.
*
*/
protected abstract Assertion parseResponseFromServer(final String response) throws TicketValidationException;
/**
* Contacts the CAS Server to retrieve the response for the ticket validation.
*
* @param validationUrl the url to send the validation request to.
* @param ticket the ticket to validate.
* @return the response from the CAS server.
*/
protected abstract String retrieveResponseFromServer(URL validationUrl, String ticket);
public 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);
}
try {
log.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);
}
return parseResponseFromServer(serverResponse);
} catch (final MalformedURLException e) {
throw new TicketValidationException(e);
}
}
public final void setRenew(final boolean renew) {
this.renew = renew;
}
public final void setCustomParameters(final Map<String,String> customParameters) {
this.customParameters = customParameters;
}
public final void setHostnameVerifier(final HostnameVerifier verifier) {
this.hostnameVerifier = verifier;
}
public final void setEncoding(final String encoding) {
this.encoding = encoding;
}
protected final String getEncoding() {
return this.encoding;
}
}

View File

@@ -0,0 +1,64 @@
/**
* 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.validation;
import org.jasig.cas.client.authentication.AttributePrincipal;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* Represents a response to a validation request.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public interface Assertion extends Serializable {
/**
* The date from which the assertion is valid from.
*
* @return the valid from date.
*/
Date getValidFromDate();
/**
* The date which the assertion is valid until.
*
* @return the valid until date.
*/
Date getValidUntilDate();
/**
* The key/value pairs associated with this assertion.
*
* @return the map of attributes.
*/
Map<String,Object> getAttributes();
/**
* The principal for which this assertion is valid.
*
* @return the principal.
*/
AttributePrincipal getPrincipal();
}

View File

@@ -0,0 +1,116 @@
/**
* 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.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;
/**
* Concrete Implementation of the {@link Assertion}.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*
*/
public final class AssertionImpl implements Assertion {
/** Unique Id for serialization. */
private static final long serialVersionUID = -7767943925833639221L;
/** The date from which the assertion is valid. */
private final Date validFromDate;
/** The date the assertion is valid until. */
private final Date validUntilDate;
/** Map of key/value pairs associated with this assertion. I.e. authentication type. */
private final Map<String,Object> attributes;
/** The principal for which this assertion is valid for. */
private final AttributePrincipal principal;
/**
* Constructs a new Assertion with a Principal of the supplied name, a valid from date of now, no valid until date, and no attributes.
*
* @param name the name of the principal for which this assertion is valid.
*/
public AssertionImpl(final String name) {
this(new AttributePrincipalImpl(name));
}
/**
* Creates a new Assertion with the supplied Principal.
*
* @param principal the Principal to associate with the Assertion.
*/
public AssertionImpl(final AttributePrincipal principal) {
this(principal, Collections.<String, Object>emptyMap());
}
/**
* Create a new Assertion with the supplied principal and Assertion attributes.
*
* @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<String,Object> attributes) {
this(principal, new Date(), null, attributes);
}
/**
* Creates a new Assertion with the supplied principal, Assertion attributes, and start and valid until dates.
*
* @param principal the Principal to associate with the Assertion.
* @param validFromDate when the assertion is valid from.
* @param validUntilDate when the assertion is valid to.
* @param attributes the key/value pairs for this attribute.
*/
public AssertionImpl(final AttributePrincipal principal, final Date validFromDate, final Date validUntilDate, final Map<String,Object> attributes) {
this.principal = principal;
this.validFromDate = validFromDate;
this.validUntilDate = validUntilDate;
this.attributes = attributes;
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 getValidFromDate() {
return this.validFromDate;
}
public Date getValidUntilDate() {
return this.validUntilDate;
}
public Map<String,Object> getAttributes() {
return this.attributes;
}
public AttributePrincipal getPrincipal() {
return this.principal;
}
}

View File

@@ -0,0 +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:
*
* 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.validation;
import jakarta.servlet.FilterConfig;
/**
* Implementation of AbstractTicketValidatorFilter that instanciates a Cas10TicketValidator.
* <p>Deployers 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")));
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
validator.setEncoding(getPropertyFromInitParams(filterConfig, "encoding", null));
return validator;
}
}

View File

@@ -0,0 +1,58 @@
/**
* 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.validation;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
/**
* Implementation of a Ticket Validator that can validate tickets conforming to the CAS 1.0 specification.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public final class Cas10TicketValidator extends AbstractCasProtocolUrlBasedTicketValidator {
public Cas10TicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
}
protected String getUrlSuffix() {
return "validate";
}
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
if (!response.startsWith("yes")) {
throw new TicketValidationException("CAS Server could not validate ticket.");
}
try {
final BufferedReader reader = new BufferedReader(new StringReader(response));
reader.readLine();
final String name = reader.readLine();
return new AssertionImpl(name);
} catch (final IOException e) {
throw new TicketValidationException("Unable to parse response.", e);
}
}
}

View File

@@ -0,0 +1,205 @@
/**
* 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.validation;
import java.io.IOException;
import java.util.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jasig.cas.client.proxy.*;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
/**
* Creates either a CAS20ProxyTicketValidator or a CAS20ServiceTicketValidator depending on whether any of the
* proxy parameters are set.
* <p>
* 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}.
*
* @author Scott Battaglia
* @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"};
private static final int DEFAULT_MILLIS_BETWEEN_CLEANUPS = 60 * 1000;
/**
* The URL to send to the CAS server as the URL that will process proxying requests on the CAS client.
*/
private String proxyReceptorUrl;
private Timer timer;
private TimerTask timerTask;
private int millisBetweenCleanUps;
/**
* 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));
final String proxyGrantingTicketStorageClass = getPropertyFromInitParams(filterConfig, "proxyGrantingTicketStorageClass", null);
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);
p.setCipherAlgorithm(cipherAlgorithm);
try {
if (secretKey != null) {
p.setSecretKey(secretKey);
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}
log.trace("Setting proxyReceptorUrl parameter: " + this.proxyReceptorUrl);
this.millisBetweenCleanUps = Integer.parseInt(getPropertyFromInitParams(filterConfig, "millisBetweenCleanUps", Integer.toString(DEFAULT_MILLIS_BETWEEN_CLEANUPS)));
super.initInternal(filterConfig);
}
public void init() {
super.init();
CommonUtils.assertNotNull(this.proxyGrantingTicketStorage, "proxyGrantingTicketStorage cannot be null.");
if (this.timer == null) {
this.timer = new Timer(true);
}
if (this.timerTask == null) {
this.timerTask = new CleanUpTimerTask(this.proxyGrantingTicketStorage);
}
this.timer.schedule(this.timerTask, this.millisBetweenCleanUps, this.millisBetweenCleanUps);
}
/**
* Constructs a Cas20ServiceTicketValidator or a Cas20ProxyTicketValidator based on supplied parameters.
*
* @param filterConfig the Filter Configuration object.
* @return a fully constructed TicketValidator.
*/
protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) {
final String allowAnyProxy = getPropertyFromInitParams(filterConfig, "acceptAnyProxy", null);
final String allowedProxyChains = getPropertyFromInitParams(filterConfig, "allowedProxyChains", null);
final String casServerUrlPrefix = getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null);
final Cas20ServiceTicketValidator validator;
if (CommonUtils.isNotBlank(allowAnyProxy) || CommonUtils.isNotBlank(allowedProxyChains)) {
final Cas20ProxyTicketValidator v = new Cas20ProxyTicketValidator(casServerUrlPrefix);
v.setAcceptAnyProxy(parseBoolean(allowAnyProxy));
v.setAllowedProxyChains(CommonUtils.createProxyList(allowedProxyChains));
validator = v;
} else {
validator = new Cas20ServiceTicketValidator(casServerUrlPrefix);
}
validator.setProxyCallbackUrl(getPropertyFromInitParams(filterConfig, "proxyCallbackUrl", null));
validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage);
validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix, getPropertyFromInitParams(filterConfig, "encoding", null)));
validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
validator.setEncoding(getPropertyFromInitParams(filterConfig, "encoding", null));
final Map<String,String> additionalParameters = new HashMap<String,String>();
final List<String> params = Arrays.asList(RESERVED_INIT_PARAMS);
for (final Enumeration<?> e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {
final String s = (String) e.nextElement();
if (!params.contains(s)) {
additionalParameters.put(s, filterConfig.getInitParameter(s));
}
}
validator.setCustomParameters(additionalParameters);
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
return validator;
}
public void destroy() {
super.destroy();
this.timer.cancel();
}
/**
* This processes the ProxyReceptor request before the ticket validation code executes.
*/
protected final boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final String requestUri = request.getRequestURI();
if (CommonUtils.isEmpty(this.proxyReceptorUrl) || !requestUri.endsWith(this.proxyReceptorUrl)) {
return true;
}
try {
CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
} catch (final RuntimeException e) {
log.error(e.getMessage(), e);
throw e;
}
return false;
}
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
this.proxyReceptorUrl = proxyReceptorUrl;
}
public void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage storage) {
this.proxyGrantingTicketStorage = storage;
}
public void setTimer(final Timer timer) {
this.timer = timer;
}
public void setTimerTask(final TimerTask timerTask) {
this.timerTask = timerTask;
}
public void setMillisBetweenCleanUps(final int millisBetweenCleanUps) {
this.millisBetweenCleanUps = millisBetweenCleanUps;
}
}

View File

@@ -0,0 +1,75 @@
/**
* 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.validation;
import org.jasig.cas.client.util.XmlUtils;
import java.util.List;
/**
* Extension to the traditional Service Ticket validation that will validate service tickets and proxy tickets.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public class Cas20ProxyTicketValidator extends Cas20ServiceTicketValidator {
private boolean acceptAnyProxy;
/** This should be a list of an array of Strings */
private ProxyList allowedProxyChains = new ProxyList();
public Cas20ProxyTicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
}
public ProxyList getAllowedProxyChains() {
return this.allowedProxyChains;
}
protected String getUrlSuffix() {
return "proxyValidate";
}
protected void customParseResponse(final String response, final Assertion assertion) throws TicketValidationException {
final List<String> proxies = XmlUtils.getTextForElements(response, "proxy");
final String[] proxiedList = proxies.toArray(new String[proxies.size()]);
// this means there was nothing in the proxy chain, which is okay
if (proxies.isEmpty() || this.acceptAnyProxy) {
return;
}
if (this.allowedProxyChains.contains(proxiedList)) {
return;
}
throw new InvalidProxyChainTicketValidationException("Invalid proxy chain: " + proxies.toString());
}
public void setAcceptAnyProxy(final boolean acceptAnyProxy) {
this.acceptAnyProxy = acceptAnyProxy;
}
public void setAllowedProxyChains(final ProxyList allowedProxyChains) {
this.allowedProxyChains = allowedProxyChains;
}
}

View File

@@ -0,0 +1,188 @@
/**
* 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.validation;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.proxy.Cas20ProxyRetriever;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.proxy.ProxyRetriever;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implementation of the TicketValidator that will validate Service Tickets in compliance with the CAS 2.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTicketValidator {
/** The CAS 2.0 protocol proxy callback url. */
private String proxyCallbackUrl;
/** The storage location of the proxy granting tickets. */
private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
/** Implementation of the proxy retriever. */
private ProxyRetriever proxyRetriever;
/**
* Constructs an instance of the CAS 2.0 Service Ticket Validator with the supplied
* CAS server url prefix.
*
* @param casServerUrlPrefix the CAS Server URL prefix.
*/
public Cas20ServiceTicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
this.proxyRetriever = new Cas20ProxyRetriever(casServerUrlPrefix, getEncoding());
}
/**
* Adds the pgtUrl to the list of parameters to pass to the CAS server.
*
* @param urlParameters the Map containing the existing parameters to send to the server.
*/
protected final void populateUrlAttributeMap(final Map<String, String> urlParameters) {
urlParameters.put("pgtUrl", this.proxyCallbackUrl);
}
protected String getUrlSuffix() {
return "serviceValidate";
}
protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException {
final String error = XmlUtils.getTextForElement(response,
"authenticationFailure");
if (CommonUtils.isNotBlank(error)) {
throw new TicketValidationException(error);
}
final String principal = XmlUtils.getTextForElement(response, "user");
final String proxyGrantingTicketIou = XmlUtils.getTextForElement(response, "proxyGrantingTicket");
final String proxyGrantingTicket = this.proxyGrantingTicketStorage != null ? this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou) : null;
if (CommonUtils.isEmpty(principal)) {
throw new TicketValidationException("No principal was found in the response from the CAS server.");
}
final Assertion assertion;
final Map<String,Object> attributes = extractCustomAttributes(response);
if (CommonUtils.isNotBlank(proxyGrantingTicket)) {
final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever);
assertion = new AssertionImpl(attributePrincipal);
} else {
assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
}
customParseResponse(response, assertion);
return assertion;
}
/**
* Default attribute parsing of attributes that look like the following:
* &lt;cas:attributes&gt;
* &lt;cas:attribute1&gt;value&lt;/cas:attribute1&gt;
* &lt;cas:attribute2&gt;value&lt;/cas:attribute2&gt;
* &lt;/cas:attributes&gt;
* <p>
* This code is here merely for sample/demonstration purposes for those wishing to modify the CAS2 protocol. You'll
* probably want a more robust implementation or to use SAML 1.1
*
* @param xml the XML to parse.
* @return the map of attributes.
*/
protected Map<String,Object> extractCustomAttributes(final String xml) {
final int pos1 = xml.indexOf("<cas:attributes>");
final int pos2 = xml.indexOf("</cas:attributes>");
if (pos1 == -1) {
return Collections.emptyMap();
}
final String attributesText = xml.substring(pos1+16, pos2);
final Map<String,Object> attributes = new HashMap<String,Object>();
final BufferedReader br = new BufferedReader(new StringReader(attributesText));
String line;
final List<String> attributeNames = new ArrayList<String>();
try {
while ((line = br.readLine()) != null) {
final String trimmedLine = line.trim();
if (trimmedLine.length() > 0) {
final int leftPos = trimmedLine.indexOf(":");
final int rightPos = trimmedLine.indexOf(">");
attributeNames.add(trimmedLine.substring(leftPos+1, rightPos));
}
}
br.close();
} catch (final IOException e) {
//ignore
}
for (final String name : attributeNames) {
final List<String> values = XmlUtils.getTextForElements(xml, name);
if (values.size() == 1) {
attributes.put(name, values.get(0));
} else {
attributes.put(name, values);
}
}
return attributes;
}
/**
* Template method if additional custom parsing (such as Proxying) needs to be done.
*
* @param response the original response from the CAS server.
* @param assertion the partially constructed assertion.
* @throws TicketValidationException if there is a problem constructing the Assertion.
*/
protected void customParseResponse(final String response, final Assertion assertion) throws TicketValidationException {
// nothing to do
}
public final void setProxyCallbackUrl(final String proxyCallbackUrl) {
this.proxyCallbackUrl = proxyCallbackUrl;
}
public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
}
public final void setProxyRetriever(final ProxyRetriever proxyRetriever) {
this.proxyRetriever = proxyRetriever;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.validation;
/**
* Exception denotes that an invalid proxy chain was sent from the CAS server to the local application.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public final class InvalidProxyChainTicketValidationException extends TicketValidationException {
/**
* Unique Id for Serialization
*/
private static final long serialVersionUID = -7736653266370691534L;
/**
* Constructs an exception with the supplied message.
* @param string the supplied message.
*/
public InvalidProxyChainTicketValidationException(final String string) {
super(string);
}
/**
* Constructs an exception with the supplied message and chained throwable.
* @param string the message.
* @param throwable the root exception.
*/
public InvalidProxyChainTicketValidationException(final String string, final Throwable throwable) {
super(string, throwable);
}
/**
* Constructs an exception with the chained throwable.
* @param throwable the root exception.
*/
public InvalidProxyChainTicketValidationException(final Throwable throwable) {
super(throwable);
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.validation;
import org.jasig.cas.client.util.CommonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
/**
* Holding class for the proxy list to make Spring configuration easier.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1.3
*/
public final class ProxyList {
private final List<String[]> proxyChains;
public ProxyList(final List<String[]> proxyChains) {
CommonUtils.assertNotNull(proxyChains, "List of proxy chains cannot be null.");
this.proxyChains = proxyChains;
}
public ProxyList() {
this(new ArrayList<String[]>());
}
public boolean contains(String[] proxiedList) {
for (final String[] list : this.proxyChains) {
if (Arrays.equals(proxiedList, list)) {
return true;
}
}
return false;
}
public String toString() {
return this.proxyChains.toString();
}
}

View File

@@ -0,0 +1,64 @@
/**
* 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.validation;
import org.jasig.cas.client.util.CommonUtils;
import java.beans.PropertyEditorSupport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
/**
* Convert a String-formatted list of acceptable proxies to an array.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*
*/
public final class ProxyListEditor extends PropertyEditorSupport {
public void setAsText(final String text) throws IllegalArgumentException {
final BufferedReader reader = new BufferedReader(new StringReader(text));
final List<String[]> proxyChains = new ArrayList<String[]>();
try {
String line;
while ((line = reader.readLine()) != null) {
if (CommonUtils.isNotBlank(line)) {
proxyChains.add(line.trim().split(" "));
}
}
} catch (final IOException e) {
// ignore this
} finally {
try {
reader.close();
} catch (final IOException e) {
// nothing to do
}
}
setValue(new ProxyList(proxyChains));
}
}

View File

@@ -0,0 +1,64 @@
/**
* 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.validation;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
/**
* Implementation of TicketValidationFilter that can instanciate a SAML 1.1 Ticket Validator.
* <p>
* Deployers can provide the "casServerUrlPrefix" and "tolerance" properties of the Saml11TicketValidator via the
* context or filter init parameters.
* <p>
* Note, the "final" on this class helps ensure the compliance required in the initInternal method.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public final class Saml11TicketValidationFilter extends AbstractTicketValidationFilter {
public Saml11TicketValidationFilter() {
setArtifactParameterName("SAMLart");
setServiceParameterName("TARGET");
}
protected 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");
}
protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) {
final Saml11TicketValidator validator = new Saml11TicketValidator(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null));
final String tolerance = getPropertyFromInitParams(filterConfig, "tolerance", "1000");
validator.setTolerance(Long.parseLong(tolerance));
validator.setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
validator.setHostnameVerifier(getHostnameVerifier(filterConfig));
validator.setEncoding(getPropertyFromInitParams(filterConfig, "encoding", null));
validator.setDisableXmlSchemaValidation(parseBoolean(getPropertyFromInitParams(filterConfig, "disableXmlSchemaValidation", "false")));
return validator;
}
}

View File

@@ -0,0 +1,242 @@
/**
* 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.validation;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.util.CommonUtils;
import org.opensaml.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
/**
* TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator {
/** Time tolerance to allow for time drifting. */
private long tolerance = 1000L;
public Saml11TicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
}
protected String getUrlSuffix() {
return "samlValidate";
}
protected void populateUrlAttributeMap(final Map<String, String> urlParameters) {
final String service = urlParameters.get("service");
urlParameters.remove("service");
urlParameters.remove("ticket");
urlParameters.put("TARGET", service);
}
@Override
protected void setDisableXmlSchemaValidation(final boolean disabled) {
if (disabled) {
// according to our reading of the SAML 1.1 code, this should disable the schema checking. However, there may be a couple
// of error messages that slip through on start up!
XML.parserPool.setDefaultSchemas(null, null);
}
}
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
try {
final String removeStartOfSoapBody = response.substring(response.indexOf("<SOAP-ENV:Body>") + 15);
final String removeEndOfSoapBody = removeStartOfSoapBody.substring(0, removeStartOfSoapBody.indexOf("</SOAP-ENV:Body>"));
final SAMLResponse samlResponse = new SAMLResponse(new ByteArrayInputStream(CommonUtils.isNotBlank(getEncoding()) ? removeEndOfSoapBody.getBytes(Charset.forName(getEncoding())) : removeEndOfSoapBody.getBytes()));
if (!samlResponse.getAssertions().hasNext()) {
throw new TicketValidationException("No assertions found.");
}
for (final Iterator<?> iter = samlResponse.getAssertions(); iter.hasNext();) {
final SAMLAssertion assertion = (SAMLAssertion) iter.next();
if (!isValidAssertion(assertion)) {
continue;
}
final SAMLAuthenticationStatement authenticationStatement = getSAMLAuthenticationStatement(assertion);
if (authenticationStatement == null) {
throw new TicketValidationException("No AuthentiationStatement found in SAML Assertion.");
}
final SAMLSubject subject = authenticationStatement.getSubject();
if (subject == null) {
throw new TicketValidationException("No Subject found in SAML Assertion.");
}
final SAMLAttribute[] attributes = getAttributesFor(assertion, subject);
final Map<String,Object> personAttributes = new HashMap<String,Object>();
for (final SAMLAttribute samlAttribute : attributes) {
final List<?> values = getValuesFrom(samlAttribute);
personAttributes.put(samlAttribute.getName(), values.size() == 1 ? values.get(0) : values);
}
final AttributePrincipal principal = new AttributePrincipalImpl(subject.getNameIdentifier().getName(), personAttributes);
final Map<String,Object> authenticationAttributes = new HashMap<String,Object>();
authenticationAttributes.put("samlAuthenticationStatement::authMethod", authenticationStatement.getAuthMethod());
return new AssertionImpl(principal, authenticationAttributes);
}
} catch (final SAMLException e) {
throw new TicketValidationException(e);
}
throw new TicketValidationException("No Assertion found within valid time range. Either there's a replay of the ticket or there's clock drift. Check tolerance range, or server/client synchronization.");
}
private boolean isValidAssertion(final SAMLAssertion assertion) {
final Date notBefore = assertion.getNotBefore();
final Date notOnOrAfter = assertion.getNotOnOrAfter();
if (assertion.getNotBefore() == null || assertion.getNotOnOrAfter() == null) {
log.debug("Assertion has no bounding dates. Will not process.");
return false;
}
final long currentTime = getCurrentTimeInUtc().getTime();
if (currentTime + tolerance < notBefore.getTime()) {
log.debug("skipping assertion that's not yet valid...");
return false;
}
if (notOnOrAfter.getTime() <= currentTime - tolerance) {
log.debug("skipping expired assertion...");
return false;
}
return true;
}
private SAMLAuthenticationStatement getSAMLAuthenticationStatement(final SAMLAssertion assertion) {
for (final Iterator<?> iter = assertion.getStatements(); iter.hasNext();) {
final SAMLStatement statement = (SAMLStatement) iter.next();
if (statement instanceof SAMLAuthenticationStatement) {
return (SAMLAuthenticationStatement) statement;
}
}
return null;
}
private SAMLAttribute[] getAttributesFor(final SAMLAssertion assertion, final SAMLSubject subject) {
final List<SAMLAttribute> attributes = new ArrayList<SAMLAttribute>();
for (final Iterator<?> iter = assertion.getStatements(); iter.hasNext();) {
final SAMLStatement statement = (SAMLStatement) iter.next();
if (statement instanceof SAMLAttributeStatement) {
final SAMLAttributeStatement attributeStatement = (SAMLAttributeStatement) statement;
// used because SAMLSubject does not implement equals
if (subject.getNameIdentifier().getName().equals(attributeStatement.getSubject().getNameIdentifier().getName())) {
for (final Iterator<?> iter2 = attributeStatement.getAttributes(); iter2.hasNext();)
attributes.add((SAMLAttribute) iter2.next());
}
}
}
return attributes.toArray(new SAMLAttribute[attributes.size()]);
}
private List<?> getValuesFrom(final SAMLAttribute attribute) {
final List<Object> list = new ArrayList<Object>();
for (final Iterator<?> iter = attribute.getValues(); iter.hasNext();) {
list.add(iter.next());
}
return list;
}
private Date getCurrentTimeInUtc() {
final Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("UTC"));
return c.getTime();
}
protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
String MESSAGE_TO_SEND;
try {
MESSAGE_TO_SEND = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\"" + SAMLIdentifierFactory.getInstance().getIdentifier() + "\" IssueInstant=\"" + CommonUtils.formatForUtcTime(new Date()) + "\">"
+ "<samlp:AssertionArtifact>" + ticket
+ "</samlp:AssertionArtifact></samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
} catch (final SAMLException e) {
throw new RuntimeException(e);
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) validationUrl.openConnection();
if (this.hostnameVerifier != null && conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setHostnameVerifier(this.hostnameVerifier);
}
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Content-Length", Integer.toString(MESSAGE_TO_SEND.length()));
conn.setRequestProperty("SOAPAction", "http://www.oasis-open.org/committees/security");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
final DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.writeBytes(MESSAGE_TO_SEND);
out.flush();
out.close();
final BufferedReader in = new BufferedReader(CommonUtils.isNotBlank(getEncoding()) ? new InputStreamReader(conn.getInputStream(), Charset.forName(getEncoding())) : new InputStreamReader(conn.getInputStream()));
final StringBuilder buffer = new StringBuilder(256);
String line;
while ((line = in.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
} catch (final IOException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
public void setTolerance(final long tolerance) {
this.tolerance = tolerance;
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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.validation;
/**
* Generic exception to be thrown when ticket validation fails.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public class TicketValidationException extends Exception {
/**
* Unique Id for Serialization
*/
private static final long serialVersionUID = -7036248720402711806L;
/**
* Constructs an exception with the supplied message.
*
* @param string the message
*/
public TicketValidationException(final String string) {
super(string);
}
/**
* Constructs an exception with the supplied message and chained throwable.
*
* @param string the message
* @param throwable the original exception
*/
public TicketValidationException(final String string, final Throwable throwable) {
super(string, throwable);
}
/**
* Constructs an exception with the chained throwable.
* @param throwable the original exception.
*/
public TicketValidationException(final Throwable throwable) {
super(throwable);
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.validation;
/**
* Contract for a validator that will confirm the validity of a supplied ticket.
* <p>
* Validator makes no statement about how to validate the ticket or the format of the ticket (other than that it must be a String).
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.1
*/
public interface TicketValidator {
/**
* Attempts to validate a ticket for the provided service.
*
* @param ticket the ticket to attempt to validate.
* @param service the service this ticket is valid for.
* @return an assertion from the ticket.
* @throws TicketValidationException if the ticket cannot be validated.
*
*/
Assertion validate(String ticket, String service) throws TicketValidationException;
}