/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.security.handler.cas;
import java.io.IOException;
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.HttpSession;
import org.codice.ddf.security.handler.api.AuthenticationHandler;
import org.codice.ddf.security.handler.api.BaseAuthenticationToken;
import org.codice.ddf.security.handler.api.HandlerResult;
import org.codice.ddf.security.handler.cas.filter.ProxyFilter;
import org.codice.ddf.security.handler.cas.filter.ProxyFilterChain;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import ddf.security.sts.client.configuration.STSClientConfiguration;
/**
* Authentication Handler for CAS. Runs through CAS filter chain if no CAS ticket is present.
*/
public class CasHandler implements AuthenticationHandler {
/**
* CAS type to use when configuring context policy.
*/
public static final String AUTH_TYPE = "CAS";
public static final String SOURCE = "CASHandler";
private static final Logger LOGGER = LoggerFactory.getLogger(CasHandler.class);
protected String realm = BaseAuthenticationToken.DEFAULT_REALM;
private STSClientConfiguration clientConfiguration;
private ProxyFilter proxyFilter;
@Override
public String getAuthenticationType() {
return AUTH_TYPE;
}
@Override
public HandlerResult getNormalizedToken(ServletRequest request, ServletResponse response,
FilterChain chain, boolean resolve) throws ServletException {
// Default to NO_ACTION and set the source as this handler
HandlerResult handlerResult = new HandlerResult(HandlerResult.Status.NO_ACTION, null);
handlerResult.setSource(realm + "-" + SOURCE);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getServletPath();
LOGGER.debug("Doing CAS authentication and authorization for path {}", path);
// if the request contains the principal, return it
Assertion assertion = getAssertion(httpRequest);
try {
if (resolve && assertion == null) {
proxyFilter.doFilter(request, response, new ProxyFilterChain(null));
}
} catch (IOException e) {
throw new ServletException(e);
}
if (assertion != null) {
LOGGER.debug("Found previous CAS attribute, using that same session.");
CASAuthenticationToken token = getAuthenticationToken(assertion);
if (token != null) {
handlerResult.setToken(token);
handlerResult.setStatus(HandlerResult.Status.COMPLETED);
//update cache with new information
LOGGER.debug("Adding new CAS assertion for session {}",
httpRequest.getSession(false)
.getId());
httpRequest.getSession(false)
.setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, assertion);
LOGGER.debug("Successfully set authentication token, returning result with token.");
} else {
LOGGER.debug("Could not create authentication token, returning NO_ACTION result.");
}
} else {
if (resolve) {
LOGGER.debug(
"Calling cas authentication and validation filters to perform redirects.");
handlerResult.setStatus(HandlerResult.Status.REDIRECTED);
} else {
LOGGER.debug(
"No cas authentication information found and resolve is not enabled, returning NO_ACTION.");
}
}
return handlerResult;
}
@Override
public HandlerResult handleError(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws ServletException {
HandlerResult handlerResult;
LOGGER.debug("handleError was called on the CasHandler, cannot do anything.");
handlerResult = new HandlerResult(HandlerResult.Status.NO_ACTION, null);
return handlerResult;
}
/**
* Gets the CAS proxy ticket that will be used by the STS to get a SAML assertion.
*
* @param assertion The CAS assertion object.
* @return Returns the CAS proxy ticket that will be used by the STS to get a SAML assertion.
*/
private CASAuthenticationToken getAuthenticationToken(Assertion assertion) {
CASAuthenticationToken token = null;
AttributePrincipal attributePrincipal = assertion.getPrincipal();
LOGGER.debug("Got the following attributePrincipal: {}", attributePrincipal);
if (attributePrincipal != null) {
LOGGER.debug("Getting proxy ticket for {}", clientConfiguration.getAddress());
String proxyTicket =
attributePrincipal.getProxyTicketFor(clientConfiguration.getAddress());
if (proxyTicket == null || proxyTicket.equals("null")) {
LOGGER.debug("Couldn't get proxy ticket for CAS authentication.");
} else {
LOGGER.debug("proxy ticket: {}", proxyTicket);
LOGGER.debug("Creating AuthenticationToken with {}|{} as the credentials.",
proxyTicket,
clientConfiguration.getAddress());
token = new CASAuthenticationToken(attributePrincipal,
proxyTicket,
clientConfiguration.getAddress(),
realm);
}
} else {
LOGGER.debug("Couldn't get attribute principle for CAS authentication.");
}
return token;
}
/**
* Retrieves the CAS assertion associated with an incoming request.
*
* @param request Incoming request that should be checked.
* @return The CAS assertion if there is one, or null if no assertion could be found.
*/
private Assertion getAssertion(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
if (session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) != null) {
LOGGER.debug("Found CAS assertion in session.");
return (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
}
}
return null;
}
public STSClientConfiguration getClientConfiguration() {
return clientConfiguration;
}
public void setClientConfiguration(STSClientConfiguration clientConfiguration) {
this.clientConfiguration = clientConfiguration;
}
public ProxyFilter getProxyFilter() {
return proxyFilter;
}
public void setProxyFilter(ProxyFilter proxyFilter) {
this.proxyFilter = proxyFilter;
}
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
/**
* Listens for removal notifications from the cache and logs each time a removal is performed.
*/
private static class RemovalListenerLogger implements RemovalListener<String, Assertion> {
@Override
public void onRemoval(RemovalNotification<String, Assertion> notification) {
if (notification.getCause()
.equals(RemovalCause.EXPIRED)) {
LOGGER.debug("Cached CAS assertion for session with id {} has expired.",
notification.getKey(),
notification.getValue());
}
}
}
}