/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.security;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.maverick.crypto.digests.Hash;
import com.maverick.crypto.digests.MD5Digest;
import com.adito.boot.Context;
import com.adito.boot.ContextHolder;
import com.adito.boot.Util;
import com.adito.core.CoreEvent;
import com.adito.core.CoreEventConstants;
import com.adito.core.CoreListener;
import com.adito.core.CoreServlet;
import com.adito.core.CoreUtil;
import com.adito.realms.Realm;
/**
* Encapsulates everything known about an Adito session.
*/
public class SessionInfo implements CoreListener {
final static Log log = LogFactory.getLog(SessionInfo.class);
private static List davUserAgents = null;
private static Context context = ContextHolder.getContext();
/* Logon type */
/**
* Originated from the user interface
*/
public final static int UI = 0;
/**
* Originated from the VPN client
*/
public final static int AGENT = 1;
/**
* Originated from a DAV client
*/
public final static int DAV_CLIENT = 2;
/* Navigation context */
/**
* User console for normal user.
*/
public static final int USER_CONSOLE_CONTEXT = 1;
/**
* Management console for super user or users that have been delegated
* responsibility for management tasks.
*/
public static final int MANAGEMENT_CONSOLE_CONTEXT = 2;
/**
* Setup console that may be accessed from the management console or when
* the server has been started in setup mode
*/
public static final int SETUP_CONSOLE_CONTEXT = 4;
/**
* Help context works slighly differently in that the sessions current
* context is not actually changed.
*/
public static final int HELP_CONTEXT = 8;
/**
* Convenience mask for all contexts
*/
public static final int ALL_CONTEXTS = 255;
/* Private instance variables */
private User user;
private InetAddress address;
private Calendar logonTime;
private int type;
private String logonTicket;
private int navigationContext;
private HttpSession session;
private int id;
private String userAgent;
private long lastAccessTime;
private boolean invalidating;
private Map<String, Object> attributes;
private List<SessionInfoListener> listeners = new ArrayList<SessionInfoListener>();
String uid = null;
private boolean temporary;
/* Private static variables */
private static HashMap sessions = new HashMap();
private static int nextId = 1;
/**
* Create a new {@link SessionInfo} object, assiging it the next Id.
*
* @param session {@link HttpSession} originator of this session
* @param logonTicket logon ticket
* @param user user object
* @param address address
* @param type client type
* @param userAgent user agent if known
* @return session
*/
public static SessionInfo nextSession(HttpSession session, String logonTicket, User user, InetAddress address, int type,
String userAgent) {
synchronized (sessions) {
SessionInfo info = new SessionInfo(nextId, session, logonTicket, user, address, type, userAgent);
if (LogonControllerFactory.getInstance().isAdministrator(user)) {
info.setNavigationContext(SessionInfo.MANAGEMENT_CONSOLE_CONTEXT);
}
sessions.put(String.valueOf(nextId), info);
nextId++;
return info;
}
}
/**
* Get a session given its Id.
*
* @param id session id
* @return session
*/
public static SessionInfo getSession(int id) {
return (SessionInfo) sessions.get(String.valueOf(id));
}
/**
* Release a session so its ID can be re-used
*/
public void release() {
CoreServlet.getServlet().removeCoreListener(this);
synchronized (sessions) {
sessions.remove(String.valueOf(id));
// TODO implement a more efficient way of getting the next session
// id
Map.Entry e;
int next = 1;
boolean found;
while (true) {
found = false;
for (Iterator i = sessions.entrySet().iterator(); i.hasNext();) {
e = (Map.Entry) i.next();
if (((SessionInfo) e.getValue()).getId() == next) {
found = true;
break;
}
}
if (!found) {
nextId = next;
break;
}
next++;
}
}
}
/* Private constructor to prevent instantiation */
private SessionInfo(int id, HttpSession session, String logonTicket, User user, InetAddress address, int type, String userAgent) {
attributes = new HashMap<String, Object>();
this.user = user;
this.id = id;
this.session = session;
this.logonTicket = logonTicket;
this.address = address;
navigationContext = USER_CONSOLE_CONTEXT;
this.type = type;
this.userAgent = userAgent;
logonTime = new GregorianCalendar();
lastAccessTime = System.currentTimeMillis();
/**
* Generate a unique session id
*/
Hash hash = new Hash(new MD5Digest());
hash.putString(String.valueOf(logonTime));
if(session != null) {
hash.putString(session.getId());
}
hash.putInt(id);
hash.putString(user.getPrincipalName());
hash.putString(address.getHostAddress());
byte[] tmp = hash.doFinal();
uid = Util.toHexString(tmp);
CoreServlet.getServlet().addCoreListener(this);
}
public Object setAttribute(String key, Object value) {
if(value instanceof SessionInfoListener &&
!listeners.contains(value)) {
listeners.add((SessionInfoListener)value);
}
return attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public Object removeAttribute(String key) {
Object val = attributes.remove(key);
if(val instanceof SessionInfoListener) {
listeners.remove((SessionInfoListener)val);
}
return val;
}
/**
* Get if this session is attached to a web session (i.e.
* returns non null from {@link #getHttpSession()}.
*
* @return attached to web session
*/
public boolean isAttachedToWebSession() {
return session != null;
}
public String getUniqueSessionId() {
return uid;
}
public void access() {
if(session != null) {
context.access(session);
}
else {
throw new IllegalStateException("Not attached to a web session.");
}
}
/**
* Get the sequential ID of this session
*
* @return Id
*/
public int getId() {
return id;
}
/**
* Get the {@link HttpSession} that originated this logon session
*
* @return {@link HttpSession} originator
*/
public HttpSession getHttpSession() {
return session;
}
/**
* Get the logon ticket for this session
*
* @return logon ticket
*/
public String getLogonTicket() {
return logonTicket;
}
/**
* Get the type of session. May be one of {@link #UI}, {@link #AGENT} or
* {@link #DAV_CLIENT}.
*
*
* @return session type
*/
public int getType() {
return type;
}
/**
* Set the type of session. May be one of {@link #UI}, {@link #AGENT} or
* {@link #DAV_CLIENT}.
*
*
* @param type session type
*/
public void setType(int type) {
this.type = type;
}
/**
* Get the internet address that this session originated from
*
* @return originating internet address
*/
public InetAddress getAddress() {
return address;
}
/**
* Get the time this session started
*
* @return logon time
*/
public Calendar getLogonTime() {
return logonTime;
}
/**
* Get the user that originated this session
*
* @return user
*/
public User getUser() {
return user;
}
/**
* Convenience method to get the {@link Realm} of the user that this
* originated this session
*
* @return realm
*/
public Realm getRealm() {
return user == null ? null : user.getRealm();
}
/**
* Convenience method to get the resource ID of the {@link Realm} of the
* user that this originated this session
*
* @return realm
*/
public int getRealmId() {
Realm r = user == null ? null : user.getRealm();
return r == null ? 0 : r.getResourceId();
}
/**
* Set the {@link HttpSession} that owns or now owns this session.
*
* @param session session
*/
public void setSession(HttpSession session) {
this.session = session;
}
/**
* Is the navigation context currently set to user console?
* @return <tt>true</tt> if the user is currently in the user console.
*/
public boolean isUserConsoleContext() {
return USER_CONSOLE_CONTEXT == getNavigationContext();
}
/**
* Is the navigation context currently set to management console?
* @return <tt>true</tt> if the user is currently in the management console.
*/
public boolean isManagementConsoleContext() {
return MANAGEMENT_CONSOLE_CONTEXT == getNavigationContext();
}
/**
* Get the navigation context the user is currently in. May be one of
* {@link #USER_CONSOLE_CONTEXT} or {@link #MANAGEMENT_CONSOLE_CONTEXT}.
*
* @return navigation context
*/
public int getNavigationContext() {
return navigationContext;
}
/**
* Set the navigation context the user is currently in. May be one of
* {@link #USER_CONSOLE_CONTEXT} or {@link #MANAGEMENT_CONSOLE_CONTEXT}.
*
* @param navigationContext new navigation context
*/
public void setNavigationContext(int navigationContext) {
this.navigationContext = navigationContext;
}
/**
* Get the scheme that was used to logon.
*
* @return logon scheme
*/
public AuthenticationScheme getCredentials() {
return (AuthenticationScheme) session.getAttribute(Constants.AUTH_SESSION);
}
/**
* Get the user agent.
*
* @return user agent
*/
public String getUserAgent() {
return userAgent;
}
/**
* Set the user for this session
*
* @param user user
*/
public void setUser(User user) {
this.user = user;
}
/**
* Try to determine what type of client is connecting based on the user
* agent. If <code>null</code> is supplied then the type will just be
* returned as {@link SessionInfo#UI}.
* <p>
* When not null, first the user agent is tested to see if it is the VPN
* client (agent). If so then {@link SessionInfo#AGENT} will be returned.
* <p>
* If not the VPN client, then the file <i>conf/dav.agents</i> will be
* examined to see if any of the patterns it contains match the supplied
* agent string. If so, the the session is of type
* {@link SessionInfo#DAV_CLIENT}.
* <p>
* If none of the conditions are met, then the default of
* {@link SessionInfo#UI} is returned.
*
* @param userAgent user agent string
* @return session type
* @see SessionInfo#UI
* @see SessionInfo#DAV_CLIENT
* @see SessionInfo#AGENT
*/
public static int getSessionTypeForUserAgent(String userAgent) {
if (userAgent == null) {
return UI;
}
if (userAgent.equals("Agent")) {
return AGENT;
}
if (davUserAgents == null) {
davUserAgents = new ArrayList();
File f = new File(context.getConfDirectory(), "dav.agents");
FileInputStream fin = null;
try {
fin = new FileInputStream(f);
BufferedReader br = new BufferedReader(new InputStreamReader(fin));
String line = null;
while ((line = br.readLine()) != null) {
line = Util.trimBoth(line);
if (!line.startsWith("#")) {
davUserAgents.add(line);
}
}
} catch (IOException ioe) {
log.warn("Failed to read " + f.getAbsolutePath() + ". Will not be able to identify DAV clients.");
} finally {
Util.closeStream(fin);
}
}
for (Iterator i = davUserAgents.iterator(); i.hasNext();) {
String us = (String) i.next();
if (userAgent.matches(us)) {
return SessionInfo.DAV_CLIENT;
}
}
return SessionInfo.UI;
}
public String toString() {
return session.getId() + "/" + user.getPrincipalName();
}
public boolean isInvalidating() {
return invalidating;
}
public void invalidate() {
if (session != null) {
invalidating = true;
session.invalidate();
invalidating = false;
}
for(SessionInfoListener l : listeners) {
l.invalidated();
}
}
public void coreEvent(CoreEvent evt) {
if (evt.getId() == CoreEventConstants.GRANT_POLICY_TO_PRINCIPAL
|| evt.getId() == CoreEventConstants.REVOKE_POLICY_FROM_PRINCIPAL
|| evt.getId() == CoreEventConstants.RESOURCE_DETACHED_FROM_POLICY
|| evt.getId() == CoreEventConstants.RESOURCE_ATTACHED_TO_POLICY) {
if (session != null) {
synchronized (session) {
CoreUtil.resetMainNavigation(getHttpSession());
}
}
}
}
/**
* Set whether this session is temporary. If it is, it will be destroyed when the request
* is completed. This is used for WebDAV clients that do not support cookies.
*
* @param temporary temporary
*/
public void setTemporary(boolean temporary) {
this.temporary = temporary;
}
/**
* Get whether this session is temporary. If it is, it will be destroyed when the request
* is completed. This is used for WebDAV clients that do not support cookies.
*
* @return temporary
*/
public boolean isTemporary() {
return temporary;
}
}