open module cas
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
* <cas:attributes>
|
||||
* <cas:attribute1>value</cas:attribute1>
|
||||
* <cas:attribute2>value</cas:attribute2>
|
||||
* </cas:attributes>
|
||||
* <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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user