/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/login/branches/sakai-2.8.1/login-tool/tool/src/java/org/sakaiproject/login/filter/NakamuraAuthenticationFilter.java $
* $Id: NakamuraAuthenticationFilter.java 86544 2010-12-15 19:16:49Z lance@indiana.edu $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community 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.osedu.org/licenses/ECL-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.sakaiproject.login.filter;
import java.io.IOException;
import java.security.Principal;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.component.api.ComponentManager;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.event.api.UsageSessionService;
import org.sakaiproject.hybrid.util.NakamuraAuthenticationHelper;
import org.sakaiproject.hybrid.util.NakamuraAuthenticationHelper.AuthInfo;
import org.sakaiproject.hybrid.util.XSakaiToken;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.api.UserDirectoryService;
/**
* A simple {@link Filter} which can be used for container authentication (e.g.
* like CAS) against a Nakamura instance. Because Nakamura does not currently
* have a redirecting authentication URL, the user must already be authenticated
* to Nakamura.
*/
public class NakamuraAuthenticationFilter implements Filter {
private static final Log LOG = LogFactory
.getLog(NakamuraAuthenticationFilter.class);
// dependencies
private SessionManager sessionManager;
private UserDirectoryService userDirectoryService;
private UsageSessionService usageSessionService;
private EventTrackingService eventTrackingService;
private AuthzGroupService authzGroupService;
protected NakamuraAuthenticationHelper nakamuraAuthenticationHelper;
private ComponentManager componentManager;
private ServerConfigurationService serverConfigurationService;
/**
* All sakai.properties settings will be prefixed with this string.
*/
public static final String CONFIG_PREFIX = "org.sakaiproject.login.filter.NakamuraAuthenticationFilter";
/**
* Is the filtered enabled? true == enabled; default = false;
*/
public static final String CONFIG_ENABLED = CONFIG_PREFIX + ".enabled";
/**
* The principal that will be used when connecting to Nakamura REST
* end-point. Must have permissions to read /var/cluster/user.cookie.json.
*
* @see XSakaiToken#createToken(String, String)
*/
public static final String CONFIG_PRINCIPAL = CONFIG_PREFIX + ".principal";
/**
* The hostname we will use to lookup the sharedSecret for access to
* validateUrl.
*
* @see XSakaiToken#createToken(String, String)
*/
public static final String CONFIG_HOST_NAME = CONFIG_PREFIX + ".hostname";
/**
* The Nakamura REST end-point we will use to validate the cookie
*/
public static final String CONFIG_VALIDATE_URL = CONFIG_PREFIX
+ ".validateUrl";
/**
* Filter will be disabled by default.
*
* @see #CONFIG_ENABLED
*/
protected boolean filterEnabled = false;
/**
* The Nakamura RESTful service to validate authenticated users. A good
* default for common hybrid implementations is supplied.
*
* @see #CONFIG_VALIDATE_URL
*/
protected String validateUrl = "http://localhost/var/cluster/user.cookie.json?c=";
/**
* The nakamura user that has permissions to GET
* /var/cluster/user.cookie.json. A good default for common hybrid
* implementations is supplied.
*
* @see XSakaiToken#createToken(String, String)
* @see #CONFIG_PRINCIPAL
*/
protected String principal = "admin";
/**
* The hostname we will use to lookup the sharedSecret for access to
* validateUrl. A good default for common hybrid implementations is
* supplied.
*
* @see XSakaiToken#createToken(String, String)
* @see #CONFIG_HOST_NAME
*/
protected String hostname = "localhost";
/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
if (LOG.isDebugEnabled()) {
LOG.debug("doFilter(ServletRequest " + servletRequest
+ ", ServletResponse " + servletResponse + ", FilterChain "
+ chain + ")");
}
if (filterEnabled && servletRequest instanceof HttpServletRequest) {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final AuthInfo authInfo = nakamuraAuthenticationHelper
.getPrincipalLoggedIntoNakamura(request);
if (authInfo != null && authInfo.getPrincipal() != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Authenticated to Nakamura proceeding with chain: "
+ authInfo.getPrincipal());
}
chain.doFilter(new NakamuraHttpServletRequestWrapper(request,
authInfo.getPrincipal()), servletResponse);
return;
}
}
/*
* not enabled or not authenticated to nakamura - just proceed with
* normal chain
*/
chain.doFilter(servletRequest, servletResponse);
return;
}
/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig filterConfig) throws ServletException {
LOG.debug("init(FilterConfig filterConfig)");
if (componentManager == null) { // may be in a test case
componentManager = org.sakaiproject.component.cover.ComponentManager
.getInstance();
}
if (componentManager == null) {
throw new IllegalStateException("componentManager == null");
}
serverConfigurationService = (ServerConfigurationService) componentManager
.get(ServerConfigurationService.class);
if (serverConfigurationService == null) {
throw new IllegalStateException(
"ServerConfigurationService == null");
}
sessionManager = (SessionManager) componentManager
.get(SessionManager.class);
if (sessionManager == null) {
throw new IllegalStateException("SessionManager == null");
}
userDirectoryService = (UserDirectoryService) componentManager
.get(UserDirectoryService.class);
if (userDirectoryService == null) {
throw new IllegalStateException("UserDirectoryService == null");
}
usageSessionService = (UsageSessionService) componentManager
.get(UsageSessionService.class);
if (usageSessionService == null) {
throw new IllegalStateException("UsageSessionService == null");
}
eventTrackingService = (EventTrackingService) componentManager
.get(EventTrackingService.class);
if (eventTrackingService == null) {
throw new IllegalStateException("EventTrackingService == null");
}
authzGroupService = (AuthzGroupService) componentManager
.get(AuthzGroupService.class);
if (authzGroupService == null) {
throw new IllegalStateException("AuthzGroupService == null");
}
filterEnabled = serverConfigurationService.getBoolean(CONFIG_ENABLED,
filterEnabled);
if (filterEnabled) {
LOG.info("NakamuraAuthenticationFilter ENABLED.");
validateUrl = serverConfigurationService.getString(
CONFIG_VALIDATE_URL, validateUrl);
LOG.info("vaildateUrl=" + validateUrl);
principal = serverConfigurationService.getString(CONFIG_PRINCIPAL,
principal);
LOG.info("principal=" + principal);
hostname = serverConfigurationService.getString(CONFIG_HOST_NAME,
hostname);
LOG.info("hostname=" + hostname);
// make sure container.login is turned on as well
final boolean containerLogin = serverConfigurationService
.getBoolean("container.login", false);
if (!containerLogin) {
LOG.error("container.login must be enabled in sakai.properties for hybrid authentication!");
throw new IllegalStateException(
"container.login must be enabled in sakai.properties for hybrid authentication!");
}
// what about top.login = false ?
/**
* Whether or not to provide a login form on the portal itself
* (typically on the top).
*/
final boolean topLogin = serverConfigurationService.getBoolean(
"top.login", false);
if (topLogin) {
LOG.warn("top.login is usually disabled in sakai.properties for container authentication scenarios");
}
if (nakamuraAuthenticationHelper == null) { // may be in a test case
nakamuraAuthenticationHelper = new NakamuraAuthenticationHelper(
componentManager, validateUrl, principal, hostname);
}
}
}
/**
* Only used for unit testing setup.
*
* @param componentManager
*/
protected void setupTestCase(ComponentManager componentManager,
NakamuraAuthenticationHelper nakamuraAuthenticationHelper) {
if (componentManager == null) {
throw new IllegalArgumentException("componentManager == null");
}
this.componentManager = componentManager;
if (nakamuraAuthenticationHelper == null) {
throw new IllegalArgumentException(
"nakamuraAuthenticationHelper == null");
}
this.nakamuraAuthenticationHelper = nakamuraAuthenticationHelper;
}
/**
* @see javax.servlet.Filter#destroy()
*/
public void destroy() {
LOG.debug("destroy()");
// nothing to do here
}
/**
* A simple implementation of {@link Principal} which will be used by
* {@link NakamuraHttpServletRequestWrapper}.
*/
public static final class NakamuraPrincipal implements Principal {
private static final Log LOG = LogFactory
.getLog(NakamuraPrincipal.class);
private final String name;
/**
* Create a {@link NakamuraPrincipal} with given name (eid).
*
* @param name
* i.e. eid or username.
* @throws IllegalArgumentException
*/
public NakamuraPrincipal(String name) {
if (LOG.isDebugEnabled()) {
LOG.debug("new NakamuraPrincipal(String " + name + ")");
}
if (name == null || "".equals(name)) {
throw new IllegalArgumentException("name == null OR empty");
}
this.name = name;
}
/**
* @see Principal#getName()
*/
public String getName() {
LOG.debug("getName()");
return name;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (LOG.isDebugEnabled()) {
LOG.debug("equals(Object " + obj + ")");
}
if (obj == this) {
return true;
}
if (obj instanceof NakamuraPrincipal) {
return name.equals(((NakamuraPrincipal) obj).getName());
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
LOG.debug("hashCode()");
return name.hashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
LOG.debug("toString()");
return name;
}
}
/**
* A {@link HttpServletRequestWrapper} which uses the passed
* {@link NakamuraPrincipal} as the current user when completing the servlet
* request.
*/
public static class NakamuraHttpServletRequestWrapper extends
HttpServletRequestWrapper {
private static final Log LOG = LogFactory
.getLog(NakamuraHttpServletRequestWrapper.class);
private final Principal principal;
/**
* @param request
* @param principal
* @throws IllegalArgumentException
*/
public NakamuraHttpServletRequestWrapper(
final HttpServletRequest request, final String principal) {
super(request);
if (LOG.isDebugEnabled()) {
LOG.debug("new NakamuraHttpServletRequestWrapper(HttpServletRequest "
+ request + ", String " + principal + ")");
}
if (principal == null || "".equals(principal)) {
throw new IllegalArgumentException("principal == null OR empty");
}
this.principal = new NakamuraPrincipal(principal);
}
/**
* @see javax.servlet.http.HttpServletRequestWrapper#getRemoteUser()
*/
@Override
public String getRemoteUser() {
LOG.debug("getRemoteUser()");
return principal != null ? this.principal.getName() : null;
}
/**
* @see javax.servlet.http.HttpServletRequestWrapper#getUserPrincipal()
*/
@Override
public Principal getUserPrincipal() {
LOG.debug("getUserPrincipal()");
return this.principal;
}
/**
* @see javax.servlet.http.HttpServletRequestWrapper#isUserInRole(java.lang.String)
*/
@Override
public boolean isUserInRole(String role) {
if (LOG.isDebugEnabled()) {
LOG.debug("isUserInRole(String " + role + ")");
}
// not needed for this filter
return false;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (LOG.isDebugEnabled()) {
LOG.debug("equals(Object " + obj + ")");
}
if (obj instanceof NakamuraHttpServletRequestWrapper) {
final NakamuraHttpServletRequestWrapper other = (NakamuraHttpServletRequestWrapper) obj;
return this.principal.equals(other.principal);
} else {
return super.equals(obj);
}
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
LOG.debug("hashCode()");
return principal.hashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
LOG.debug("toString()");
return principal.toString();
}
}
}