/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.cas;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.logging.Level;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.security.LogoutFilterChain;
import org.geoserver.security.config.SecurityNamedServiceConfig;
import org.geoserver.security.filter.GeoServerLogoutFilter;
import org.geoserver.security.filter.GeoServerPreAuthenticatedUserNameFilter;
import org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.session.SingleSignOutHandler;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.StringUtils;
/**
* CAS Authentication filter receiving/validating proxy tickets
* and service tickets.
*
* If {@link #singleSignOut} is <code>true</code>, this filter
* handles logout requests sent from the CAS server.
*
* This filter implements the {@link LogoutHandler} interface
* for log out requests triggered by GeoServer
*
* @author mcr
*
*/
public class GeoServerCasAuthenticationFilter extends GeoServerPreAuthenticatedUserNameFilter implements LogoutHandler {
protected GeoServerCas20ProxyTicketValidator validator;
protected ServiceAuthenticationDetailsSource casAuthenticationDetailsSource = new ServiceAuthenticationDetailsSource();
protected String casLogoutURL;
protected String urlInCasLogoutPage;
protected boolean singleSignOut;
protected ProxyGrantingTicketStorage pgtStorageFilter;
public GeoServerCasAuthenticationFilter(ProxyGrantingTicketStorage pgtStorageFilter) {
this.pgtStorageFilter = pgtStorageFilter;
}
@Override
public void initializeFromConfig(SecurityNamedServiceConfig config) throws IOException {
super.initializeFromConfig(config);
CasAuthenticationFilterConfig authConfig =
(CasAuthenticationFilterConfig) config;
validator = new GeoServerCas20ProxyTicketValidator(authConfig.getCasServerUrlPrefix());
validator.setAcceptAnyProxy(true);
validator.setProxyGrantingTicketStorage(pgtStorageFilter);
validator.setRenew(authConfig.isSendRenew());
if (StringUtils.hasLength(authConfig.getProxyCallbackUrlPrefix()))
validator.setProxyCallbackUrl(GeoServerCasConstants.createProxyCallBackURl(authConfig.getProxyCallbackUrlPrefix()));
casLogoutURL=GeoServerCasConstants.createCasURl(authConfig.getCasServerUrlPrefix(), GeoServerCasConstants.LOGOUT_URI);
if (StringUtils.hasLength(authConfig.getUrlInCasLogoutPage()))
casLogoutURL+="?"+GeoServerCasConstants.LOGOUT_URL_PARAM+"="+URLEncoder.encode(authConfig.getUrlInCasLogoutPage(),"utf-8");
singleSignOut=authConfig.isSingleSignOut();
aep = new GeoServerCasAuthenticationEntryPoint(authConfig);
}
protected Assertion getCASAssertion(HttpServletRequest request) {
String ticket = request.getParameter(GeoServerCasConstants.ARTIFACT_PARAMETER);
if (ticket==null) return null;
if ((ticket.startsWith(GeoServerCasConstants.PROXY_TICKET_PREFIX) ||
ticket.startsWith(GeoServerCasConstants.SERVICE_TICKET_PREFIX))==false)
return null;
try {
String service = retrieveService(request);
return validator.validate(ticket,service );
} catch (TicketValidationException e) {
LOGGER.warning(e.getMessage());
}
return null;
}
protected static String retrieveService(HttpServletRequest request) {
String serviceBaseUrl = null;
String proxyBaseUrl = GeoServerExtensions.getProperty("PROXY_BASE_URL");
if (StringUtils.hasLength(proxyBaseUrl)) {
serviceBaseUrl = proxyBaseUrl;
} else {
serviceBaseUrl = request.getRequestURL().toString();
}
StringBuffer buff = new StringBuffer(serviceBaseUrl);
if (StringUtils.hasLength(request.getQueryString())) {
String query = request.getQueryString();
String[] params = query.split("&");
boolean firsttime=true;
for (String param : params)
{
String[] keyValue = param.split("=");
if (keyValue.length == 0) continue;
String name = keyValue[0];
if (GeoServerCasConstants.ARTIFACT_PARAMETER.equals(name.trim()))
continue;
if (GeoServerCasAuthenticationEntryPoint.CAS_REDIRECT.equals(name.trim()))
continue;
if (firsttime) {
buff.append("?");
firsttime=false;
} else {
buff.append("&");
}
buff.append(param);
}
}
String serviceUrl = buff.toString();
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("CAS Service URL: "+serviceUrl);
return serviceUrl;
}
protected String getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = super.getPreAuthenticatedPrincipal(request);
HttpSession session = request.getSession(false);
if (principal!=null && session!=null) {
session.setAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY,
request.getAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY));
request.removeAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY);
getHandler().recordSession(request);
}
if (principal==null) {
request.removeAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY);
}
return principal;
}
/**
*/
@Override
protected String getPreAuthenticatedPrincipalName(HttpServletRequest request) {
Assertion assertion = getCASAssertion(request);
if (assertion==null) return null;
request.setAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY,assertion);
return assertion.getPrincipal().getName();
}
protected static SingleSignOutHandler getHandler() {
return GeoServerExtensions.bean(SingleSignOutHandler.class);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq= (HttpServletRequest) req;
HttpServletResponse httpRes= (HttpServletResponse) res;
SingleSignOutHandler handler = getHandler();
// check for sign out request from cas server
if (handler.isLogoutRequest(httpReq)) {
if (singleSignOut) { // do we participate
LOGGER.info("Single Sign Out received from CAS server --> starting log out");
handler.destroySession(httpReq);
LogoutFilterChain logOutChain = (LogoutFilterChain)
getSecurityManager().getSecurityConfig().getFilterChain().getRequestChainByName("webLogout");
logOutChain.doLogout(getSecurityManager(), httpReq, httpRes,getName());
} else
LOGGER.info("Single Sign Out received from CAS server --> ignoring");
return;
}
super.doFilter(req, res, chain);
if (SecurityContextHolder.getContext().getAuthentication()!=null) {
HttpSession session = httpReq.getSession(false);
if (session !=null &&
session.getAttribute(GeoServerCasConstants.CAS_ASSERTION_KEY)!=null && singleSignOut) {
getHandler().recordSession(httpReq);
if (LOGGER.isLoggable(Level.INFO))
LOGGER.info("Record HTTP Session "+session.getId()+ " for CAS single sign out");
}
}
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
request.setAttribute(GeoServerLogoutFilter.LOGOUT_REDIRECT_ATTR,casLogoutURL);
}
}