/*
* JOSSO: Java Open Single Sign-On
*
* Copyright 2004-2009, Atricore, Inc.
*
* 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.josso.gateway.session.service;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.josso.Lookup;
import org.josso.gateway.SecurityDomainRegistry;
import org.josso.gateway.session.SSOSession;
import org.josso.gateway.session.exceptions.NoSuchSessionException;
import org.josso.gateway.session.exceptions.SSOSessionException;
import org.josso.gateway.session.exceptions.TooManyOpenSessionsException;
import org.josso.gateway.session.service.store.SessionStore;
import javax.security.auth.Subject;
import java.util.*;
/**
* @org.apache.xbean.XBean element="session-manager"
*
* This is the default implementation of the SSO Session Manager.
*
* @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a>
* @version $Id: SSOSessionManagerImpl.java 613 2008-08-26 16:42:10Z sgonzalez $
*/
public class SSOSessionManagerImpl implements SSOSessionManager {
private static final Log logger = LogFactory.getLog(SSOSessionManagerImpl.class);
// Max inactive interval used for new sessions. Default is set to 30
private int _maxInactiveInterval = 30;
private int _maxSessionsPerUser = 1;
private long _sessionMonitorInterval = 5000;
private boolean _invalidateExceedingSessions = false;
private String _securityDomainName;
/**
* This implementation uses a MemoryStore and a defaylt Session Id generator.
*/
public SSOSessionManagerImpl() {
}
//-----------------------------------------------------
// Instance variables :
//-----------------------------------------------------
private SessionStore _store;
private SessionIdGenerator _idGen;
private SessionMonitor _monitor;
private Thread _monitorThread;
//------------------------------------------------------
// SSO Session Manager
//------------------------------------------------------
public void setSecurityDomainName(String securityDomainName) {
_securityDomainName = securityDomainName;
}
/**
* Initializes the manager.
*/
public synchronized void initialize() {
logger.info("[initialize()] : IdGenerator.................=" + _idGen.getClass().getName());
logger.info("[initialize()] : Store.......................=" + _store.getClass().getName());
logger.info("[initialize()] : MaxInactive.................=" + _maxInactiveInterval);
logger.info("[initialize()] : MaxSessionsPerUser..........=" + _maxSessionsPerUser);
logger.info("[initialize()] : InvalidateExceedingSessions.=" + _invalidateExceedingSessions);
logger.info("[initialize()] : SesisonMonitorInteval.......=" + _sessionMonitorInterval);
// Start session monitor.
_monitor = new SessionMonitor(this, getSessionMonitorInterval());
_monitorThread = new Thread(_monitor);
_monitorThread.setDaemon(true);
_monitorThread.setName("JOSSOSessionMonitor");
_monitorThread.start();
// Register sessions in security domain !
logger.info("[initialize()] : Restore Sec.Domain Registry.=" + _securityDomainName);
try {
SecurityDomainRegistry registry = Lookup.getInstance().lookupSecurityDomainRegistry();
BaseSession[] sessions = _store.loadAll();
for (int i = 0; i < sessions.length; i++) {
BaseSession session = sessions[i];
registry.registerToken(_securityDomainName, TOKEN_TYPE, session.getId());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* Destroy the manager and free resources (running threads).
*/
public synchronized void destroy() {
if (_monitor != null) {
_monitor.stop();
try {
_monitorThread.join();
} catch (InterruptedException e) {
logger.warn("[destroy()] : main thread interrupted.");
}
}
}
/**
* Initiates a new session. The new session id is returned.
*
* @return the new session identifier.
*/
public String initiateSession(String username, Subject subject) throws SSOSessionException {
// Invalidate sessions if necessary
BaseSession sessions[] = _store.loadByUsername(username);
// Check if we can open a new session for this user.
if (!_invalidateExceedingSessions &&
_maxSessionsPerUser != -1 &&
_maxSessionsPerUser <= sessions.length) {
throw new TooManyOpenSessionsException(sessions.length);
}
// Check if sessions should be auto-invalidated.
if (_invalidateExceedingSessions && _maxSessionsPerUser != -1) {
// Number of sessions to invalidate
int invalidate = sessions.length - _maxSessionsPerUser + 1;
if (logger.isDebugEnabled())
logger.debug("Auto-invalidating " + invalidate + " sessions for user : " + username);
for (int idx = 0; invalidate > 0; invalidate--) {
BaseSession session = sessions[idx];
if (logger.isDebugEnabled())
logger.debug("Auto-invalidating " + session.getId() + " session for user : " + username);
invalidate(session.getId());
}
}
// Build the new session.
BaseSession session = doMakeNewSession();
// Configure the new session ...
session.setId(_idGen.generateId());
session.setCreationTime(System.currentTimeMillis());
session.setValid(true);
session.setMaxInactiveInterval(getMaxInactiveInterval() * 60); // Convert minutes in seconds.
session.setUsername(username);
session.setSubject(subject);
// Store the session
_store.save(session);
try {
SecurityDomainRegistry registry = Lookup.getInstance().lookupSecurityDomainRegistry();
registry.registerToken(_securityDomainName, TOKEN_TYPE, session.getId());
} catch (Exception e) {
throw new SSOSessionException(e.getMessage(), e);
}
session.fireSessionEvent(BaseSession.SESSION_CREATED_EVENT, null);
// Return its id.
return session.getId();
}
/**
* Gets an SSO session based on its id.
*
* @param sessionId the session id previously returned by initiateSession.
* @throws NoSuchSessionException if the session id is not related to any sso session.
*/
public SSOSession getSession(String sessionId) throws NoSuchSessionException, SSOSessionException {
BaseSession s = _store.load(sessionId);
if (s == null) {
throw new NoSuchSessionException(sessionId);
}
return s;
}
/**
* Gets all SSO sessions.
*/
public Collection getSessions() throws SSOSessionException {
return Arrays.asList(_store.loadAll());
}
/**
* Gets an SSO session based on the associated user.
*
* @param username the username used when initiating the session.
* @throws org.josso.gateway.session.exceptions.NoSuchSessionException
* if the session id is not related to any sso session.
*/
public Collection getUserSessions(String username) throws NoSuchSessionException, SSOSessionException {
BaseSession s[] = _store.loadByUsername(username);
if (s.length < 1) {
throw new NoSuchSessionException(username);
}
// Build the result
List result = new ArrayList(s.length);
for (int i = 0; i < s.length; i++) {
result.add(s[i]);
}
return result;
}
/**
* This method accesss the session associated to the received id.
* This resets the session last access time and updates the access count.
*
* @param sessionId the session id previously returned by initiateSession.
* @throws NoSuchSessionException if the session id is not valid or the session is not valid.
*/
public void accessSession(String sessionId) throws NoSuchSessionException, SSOSessionException {
try {
if (logger.isDebugEnabled())
logger.debug("[accessSession()] trying session : " + sessionId);
// getSession will throw a NoSuchSessionException if not found.
BaseSession s = (BaseSession) getSession(sessionId);
if (!s.isValid()) {
if (logger.isDebugEnabled())
logger.debug("[accessSession()] invalid session : " + sessionId);
throw new NoSuchSessionException(sessionId);
}
s.access();
_store.save(s); // Update session information ...
try {
SecurityDomainRegistry registry = Lookup.getInstance().lookupSecurityDomainRegistry();
registry.registerToken(_securityDomainName, TOKEN_TYPE, s.getId());
} catch (Exception e) {
throw new SSOSessionException(e.getMessage(), e);
}
if (logger.isDebugEnabled())
logger.debug("[accessSession()] ok");
} finally {
if (logger.isDebugEnabled())
logger.debug("[accessSession()] ended for session : " + sessionId);
}
}
/**
* Invlalidates all open sessions.
*/
public void invalidateAll() throws SSOSessionException {
BaseSession[] sessions = _store.loadAll();
for (int i = 0; i < sessions.length; i++) {
BaseSession session = sessions[i];
// Mark session as expired (this will notify session listeners, if any)
session.expire();
}
}
/**
* Invalidates a session.
*
* @param sessionId the session id previously returned by initiateSession.
* @throws NoSuchSessionException if the session id is not related to any sso session.
*/
public void invalidate(String sessionId) throws NoSuchSessionException, SSOSessionException {
// Get current session.
BaseSession s = (BaseSession) getSession(sessionId);
// Remove it from the store
try {
_store.remove(sessionId);
SecurityDomainRegistry registry = Lookup.getInstance().lookupSecurityDomainRegistry();
registry.unregisterToken(_securityDomainName, TOKEN_TYPE, sessionId);
} catch (SSOSessionException e) {
logger.warn("Can't remove session from store\n" + e.getMessage() != null ? e.getMessage() : e.toString(), e);
} catch (Exception e) {
logger.error("Can't remove session from store\n" + e.getMessage() != null ? e.getMessage() : e.toString(), e);
}
// Mark session as expired (this will notify session listeners, if any)
s.expire(); // This will invalidate the session ...
}
/**
* Check all sessions and remove those that are not valid from the store.
* This method is invoked periodically to update sessions state.
*/
public void checkValidSessions() {
try {
//---------------------------------------------
// Verify invalid sessions ...
//---------------------------------------------
BaseSession sessions[] = _store.loadByValid(false);
if (logger.isDebugEnabled())
logger.debug("[checkValidSessions()] found " + sessions.length + " invalid sessions");
checkValidSessions(sessions);
//---------------------------------------------
// Verify old sessions ...
//---------------------------------------------
// Convert Max Inactive Interval to MS
long period = _maxInactiveInterval * 60L * 1000L;
Date from = new Date(System.currentTimeMillis() - period);
sessions = _store.loadByLastAccessTime(from);
if (logger.isDebugEnabled())
logger.debug("[checkValidSessions()] found " + sessions.length + " sessions last accessed before " + from);
checkValidSessions(sessions);
} catch (Exception e) {
logger.error("Can't process expired sessions : " + e.getMessage(), e);
}
}
protected void checkValidSessions(BaseSession[] sessions) {
for (int i = 0; i < sessions.length; i++) {
try {
// Ignore valid sessions, they have not expired yet.
BaseSession session = (BaseSession) sessions[i];
if (!session.isValid()) {
// Remove invalid session from the store.
_store.remove(session.getId());
SecurityDomainRegistry registry = Lookup.getInstance().lookupSecurityDomainRegistry();
registry.unregisterToken(_securityDomainName, TOKEN_TYPE, session.getId());
if (logger.isDebugEnabled())
logger.debug("[checkValidSessions()] Session expired : " + session.getId());
}
} catch (Exception e) {
logger.warn("Can't remove session [" + i + "]; " + e.getMessage() != null ? e.getMessage() : e.toString(), e);
}
}
}
/**
* @org.apache.xbean.Property alias="session-store"
* @param ss
*/
public void setSessionStore(SessionStore ss) {
_store = ss;
}
/**
* Dependency Injection of Session Id Generator.
*
* @org.apache.xbean.Property alias="session-id-generator"
*/
public void setSessionIdGenerator(SessionIdGenerator g) {
_idGen = g;
}
/**
* Number of sessions registered in the manager.
*
* @return the number of sessions registered in this manager.
*/
public int getSessionCount() throws SSOSessionException {
return _store.getSize();
}
// ---------------------------------------------------------------
// Properties
// ---------------------------------------------------------------
public int getMaxInactiveInterval() {
return _maxInactiveInterval;
}
/**
* @param maxInactiveInterval in minutes
*/
public void setMaxInactiveInterval(int maxInactiveInterval) {
_maxInactiveInterval = maxInactiveInterval;
}
public int getMaxSessionsPerUser() {
return _maxSessionsPerUser;
}
public void setMaxSessionsPerUser(int maxSessionsPerUser) {
_maxSessionsPerUser = maxSessionsPerUser;
}
public boolean isInvalidateExceedingSessions() {
return _invalidateExceedingSessions;
}
public void setInvalidateExceedingSessions(boolean invalidateExceedingSessions) {
_invalidateExceedingSessions = invalidateExceedingSessions;
}
public long getSessionMonitorInterval() {
return _sessionMonitorInterval;
}
public void setSessionMonitorInterval(long sessionMonitorInterval) {
_sessionMonitorInterval = sessionMonitorInterval;
if (_monitor != null) {
_monitor.setInterval(_sessionMonitorInterval);
}
}
// ---------------------------------------------------------------
// Protected utils.
// ---------------------------------------------------------------
/**
* Get new session class to be used in the doLoad() method.
*/
protected BaseSession doMakeNewSession() {
return new BaseSessionImpl();
}
// ---------------------------------------------------------------
// To expire threads periodically,
// ---------------------------------------------------------------
/**
* Checks for valid sessions every second.
*/
private class SessionMonitor implements Runnable {
private long _interval;
private SSOSessionManager _m;
private boolean _stop;
SessionMonitor(SSOSessionManager m) {
_m = m;
}
SessionMonitor(SSOSessionManager m, long interval) {
_interval = interval;
_m = m;
}
public long getInterval() {
return _interval;
}
public void setInterval(long interval) {
_interval = interval;
}
/**
* Check for valid sessions ...
*/
public void run() {
_stop = false;
do {
try {
if (logger.isDebugEnabled())
logger.debug("[run()] calling checkValidSessions ... ");
_m.checkValidSessions();
synchronized (this) {
try {
if (logger.isDebugEnabled())
logger.debug("[run()] waiting " + _interval + " ms");
wait(_interval);
} catch (InterruptedException e) {
logger.warn(e, e);
}
}
} catch (Exception e) {
logger.warn("Exception received : " + e.getMessage() != null ? e.getMessage() : e.toString(), e);
}
} while (!_stop);
}
public void stop() {
_stop = true;
synchronized (this) {
notify(); // wake the thread if it was in a wait.
}
}
}
}