/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2016 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.util.CommonUtils; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.Cas20ProxyTicketValidator; import org.jasig.cas.client.validation.TicketValidationException; import org.springframework.security.cas.ServiceProperties; 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 Cas20ProxyTicketValidator validator; protected ServiceAuthenticationDetailsSource casAuthenticationDetailsSource ; 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; ServiceProperties props = new ServiceProperties(); props.setSendRenew(authConfig.isSendRenew()); // TODO, investigate in // props.setAuthenticateAllArtifacts(true); casAuthenticationDetailsSource= new ServiceAuthenticationDetailsSource(props,GeoServerCasConstants.ARTIFACT_PARAMETER); //validator = new GeoServerCas20ProxyTicketValidator(authConfig.getCasServerUrlPrefix()); validator = new Cas20ProxyTicketValidator(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().process(request, null); } 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 boolean handlerInitialized = false; protected static SingleSignOutHandler getHandler() { SingleSignOutHandler handler = GeoServerExtensions.bean(SingleSignOutHandler.class); if (!handlerInitialized) { handler.init(); handlerInitialized=true; } return handler; } @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 (isLogoutRequest(httpReq)) { if (singleSignOut) { // do we participate LOGGER.info("Single Sign Out received from CAS server --> starting log out"); LogoutFilterChain logOutChain = (LogoutFilterChain) getSecurityManager().getSecurityConfig().getFilterChain().getRequestChainByName("webLogout"); logOutChain.doLogout(getSecurityManager(), httpReq, httpRes,getName()); handler.process(httpReq, httpRes); } 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) { handler.process(httpReq, httpRes); if (LOGGER.isLoggable(Level.INFO)) LOGGER.info("Record HTTP Session "+session.getId()+ " for CAS single sign out"); } } } /** * 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()) && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, SingleSignOutHandler.DEFAULT_LOGOUT_PARAMETER_NAME)); } @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { request.setAttribute(GeoServerLogoutFilter.LOGOUT_REDIRECT_ATTR,casLogoutURL); } }