/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/api/src/main/java/org/sakaiproject/util/TrustedLoginFilter.java $
* $Id: TrustedLoginFilter.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 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.opensource.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.util;
import java.io.IOException;
import java.security.MessageDigest;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.api.UserNotDefinedException;
/**
* <pre>
* A filter to come after the standard sakai request fileter to allow services
* to encode a token containing the user id accessing the service.
*
* The filter must be configured with a shared secret and requests contain a parameter 't'
* or a header X-SAKAI-TOKEN. This token is used to validate the Request and
* associate a user with the request.
*
* The token contains:
* hash;user;other
*
* hash is a SHA1 hash, user is the username to assocoiate with the request and other is some random
* data to make the hash change per request.
*
* The hash is created by performing
* SHA1(sharedSecret;user;other)
*
* This is encoded as a string of hex bytes eg
* 0123432ABCEF11131D etc
*
* The shared secret must be known by both ends of the conversation, and must not be distributed outside a trusted zone.
*
* To use this filter add it AFTER the Sakai Request Filter in you web.xml like
*
*
* <!--
* The Sakai Request Hander
* -->
* <filter>
* <filter-name>sakai.request</filter-name>
* <filter-class>org.sakaiproject.util.RequestFilter</filter-class>
* </filter>
* <filter>
* <filter-name>sakai.trusted</filter-name>
* <filter-class>org.sakaiproject.util.TrustedLoginFilter</filter-class>
* <init-param>
* <param-name>shared.secret</param-name>
* <param-value>The Snow on the Volga falls only under the bridges</param-value>
* </init-param>
* </filter>
*
* <!--
* Mapped onto Handler
* -->
* <filter-mapping>
* <filter-name>sakai.request</filter-name>
* <servlet-name>sakai.mytoolservlet</servlet-name>
* <dispatcher>REQUEST</dispatcher>
* <dispatcher>FORWARD</dispatcher>
* <dispatcher>INCLUDE</dispatcher>
* </filter-mapping>
*
* <filter-mapping>
* <filter-name>sakai.trusted</filter-name>
* <servlet-name>sakai.mytoolservlet</servlet-name>
* <dispatcher>REQUEST</dispatcher>
* <dispatcher>FORWARD</dispatcher>
* <dispatcher>INCLUDE</dispatcher>
* </filter-mapping>
*
* </pre>
*
* @author ieb
*/
public class TrustedLoginFilter implements Filter
{
private final static Log log = LogFactory.getLog(TrustedLoginFilter.class);
private SessionManager sessionManager;
private String sharedSecret;
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#destroy()
*/
public void destroy()
{
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException
{
HttpServletRequest hreq = (HttpServletRequest) req;
String token = hreq.getHeader("X-SAKAI-TOKEN");
if (token == null)
{
token = hreq.getParameter("t");
}
Session currentSession = null;
Session requestSession = null;
String user = decodeToken(token);
if (user != null)
{
currentSession = sessionManager.getCurrentSession();
if (!user.equals(currentSession.getUserEid()))
{
requestSession = sessionManager.startSession();
org.sakaiproject.user.api.User usr;
try {
usr = org.sakaiproject.user.cover.UserDirectoryService.getUserByEid(user);
requestSession.setUserEid(usr.getEid());
requestSession.setUserId(usr.getId());
requestSession.setActive();
} catch (UserNotDefinedException e) {
e.printStackTrace();
}
sessionManager.setCurrentSession(requestSession);
}
}
try
{
chain.doFilter(req, resp);
}
finally
{
if (requestSession != null)
{
if (currentSession != null)
{
sessionManager.setCurrentSession(currentSession);
}
requestSession.invalidate();
}
}
}
/**
* @param token
* @return
*/
protected String decodeToken(String token)
{
try
{
int sep = token.indexOf(";");
if (sep > 0)
{
String hash = token.substring(0, sep);
String data = token.substring(sep+1);
String key = sharedSecret + ";" + data;
String computedHash;
computedHash = byteArrayToHexStr(MessageDigest.getInstance("SHA1")
.digest(key.getBytes("UTF-8")));
if (hash.equals(computedHash))
{
sep = data.indexOf(";");
return data.substring(0,sep);
}
}
}
catch (Exception ex)
{
log.warn("Failed to decode token " + token + " :" + ex.getMessage());
}
return null;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig config) throws ServletException
{
sessionManager = org.sakaiproject.tool.cover.SessionManager.getInstance();
sharedSecret = config.getInitParameter("shared.secret");
}
protected String byteArrayToHexStr(byte[] data)
{
char[] chars = new char[data.length * 2];
for (int i = 0; i < data.length; i++)
{
byte current = data[i];
int hi = (current & 0xF0) >> 4;
int lo = current & 0x0F;
chars[2 * i] = (char) (hi < 10 ? ('0' + hi) : ('A' + hi - 10));
chars[2 * i + 1] = (char) (lo < 10 ? ('0' + lo) : ('A' + lo - 10));
}
return new String(chars);
}
/**
* @param sharedSecret the sharedSecret to set
*/
protected void setSharedSecret(String sharedSecret)
{
this.sharedSecret = sharedSecret;
}
}