001: /*
002: * JOSSO: Java Open Single Sign-On
003: *
004: * Copyright 2004-2008, Atricore, Inc.
005: *
006: * This is free software; you can redistribute it and/or modify it
007: * under the terms of the GNU Lesser General Public License as
008: * published by the Free Software Foundation; either version 2.1 of
009: * the License, or (at your option) any later version.
010: *
011: * This software is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this software; if not, write to the Free
018: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
020: */
021: package org.josso.gateway.session.service;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.josso.gateway.session.SSOSession;
026: import org.josso.gateway.session.exceptions.NoSuchSessionException;
027: import org.josso.gateway.session.exceptions.SSOSessionException;
028: import org.josso.gateway.session.exceptions.TooManyOpenSessionsException;
029: import org.josso.gateway.session.service.store.SessionStore;
030: import org.josso.gateway.assertion.AssertionManager;
031: import org.josso.gateway.assertion.AuthenticationAssertion;
032: import org.josso.gateway.assertion.exceptions.AssertionNotValidException;
033: import org.josso.Lookup;
034:
035: import java.util.*;
036:
037: /**
038: * This is the default implementation of the SSO Session Manager.
039: *
040: * @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a>
041: * @version $Id: SSOSessionManagerImpl.java 508 2008-02-18 13:32:29Z sgonzalez $
042: */
043:
044: public class SSOSessionManagerImpl implements SSOSessionManager {
045:
046: private static final Log logger = LogFactory
047: .getLog(SSOSessionManagerImpl.class);
048:
049: // Max inactive interval used for new sessions. Default is set to 30
050: private int _maxInactiveInterval = 30;
051:
052: private int _maxSessionsPerUser = 1;
053:
054: private long _sessionMonitorInterval = 5000;
055:
056: private boolean _invalidateExceedingSessions = false;
057:
058: /**
059: * This implementation uses a MemoryStore and a defaylt Session Id generator.
060: */
061: public SSOSessionManagerImpl() {
062: }
063:
064: //-----------------------------------------------------
065: // Instance variables :
066: //-----------------------------------------------------
067:
068: private SessionStore _store;
069: private SessionIdGenerator _idGen;
070: private SessionMonitor _monitor;
071:
072: //------------------------------------------------------
073: // SSO Session Manager
074: //------------------------------------------------------
075:
076: /**
077: * Initializes the manager.
078: */
079: public synchronized void initialize() {
080:
081: logger.info("[initialize()] : IdGenerator.................="
082: + _idGen.getClass().getName());
083: logger.info("[initialize()] : Store.......................="
084: + _idGen.getClass().getName());
085: logger.info("[initialize()] : MaxInactive.................="
086: + _maxInactiveInterval);
087: logger.info("[initialize()] : MaxSessionsPerUser..........="
088: + _maxSessionsPerUser);
089: logger.info("[initialize()] : InvalidateExceedingSessions.="
090: + _invalidateExceedingSessions);
091: logger.info("[initialize()] : SesisonMonitorInteval.......="
092: + _sessionMonitorInterval);
093:
094: // Start session monitor.
095: _monitor = new SessionMonitor(this , getSessionMonitorInterval());
096: Thread t = new Thread(_monitor);
097: t.setDaemon(true);
098: t.setName("JOSSOSessionMonitor");
099: t.start();
100:
101: }
102:
103: /**
104: * Initiates a new session. The new session id is returned.
105: *
106: * @return the new session identifier.
107: */
108: public String initiateSession(String username)
109: throws SSOSessionException {
110:
111: // Invalidate sessions if necessary
112: BaseSession sessions[] = _store.loadByUsername(username);
113:
114: // Check if we can open a new session for this user.
115: if (!_invalidateExceedingSessions && _maxSessionsPerUser != -1
116: && _maxSessionsPerUser <= sessions.length) {
117: throw new TooManyOpenSessionsException(sessions.length);
118: }
119:
120: // Check if sessions should be auto-invalidated.
121: if (_invalidateExceedingSessions && _maxSessionsPerUser != -1) {
122:
123: // Number of sessions to invalidate
124: int invalidate = sessions.length - _maxSessionsPerUser + 1;
125: if (logger.isDebugEnabled())
126: logger.debug("Auto-invalidating " + invalidate
127: + " sessions for user : " + username);
128:
129: for (int idx = 0; invalidate > 0; invalidate--) {
130: BaseSession session = sessions[idx];
131:
132: if (logger.isDebugEnabled())
133: logger.debug("Auto-invalidating " + session.getId()
134: + " session for user : " + username);
135:
136: invalidate(session.getId());
137: }
138: }
139:
140: // Build the new session.
141: BaseSession session = doMakeNewSession();
142:
143: // Configure the new session ...
144: session.setId(_idGen.generateId());
145: session.setCreationTime(System.currentTimeMillis());
146: session.setValid(true);
147: session.setMaxInactiveInterval(getMaxInactiveInterval() * 60); // Convert minutes in seconds.
148: session.setUsername(username);
149:
150: // Store the session
151: _store.save(session);
152:
153: // Return its id.
154: return session.getId();
155:
156: }
157:
158: /**
159: * Gets an SSO session based on its id.
160: *
161: * @param sessionId the session id previously returned by initiateSession.
162: * @throws NoSuchSessionException if the session id is not related to any sso session.
163: */
164: public SSOSession getSession(String sessionId)
165: throws NoSuchSessionException, SSOSessionException {
166: BaseSession s = _store.load(sessionId);
167: if (s == null) {
168: throw new NoSuchSessionException(sessionId);
169: }
170: return s;
171:
172: }
173:
174: /**
175: * Gets all SSO sessions.
176: */
177: public Collection getSessions() throws SSOSessionException {
178: return Arrays.asList(_store.loadAll());
179: }
180:
181: /**
182: * Gets an SSO session based on the associated user.
183: *
184: * @param username the username used when initiating the session.
185: * @throws org.josso.gateway.session.exceptions.NoSuchSessionException
186: * if the session id is not related to any sso session.
187: */
188: public Collection getUserSessions(String username)
189: throws NoSuchSessionException, SSOSessionException {
190: BaseSession s[] = _store.loadByUsername(username);
191: if (s.length < 1) {
192: throw new NoSuchSessionException(username);
193: }
194:
195: // Build the result
196: List result = new ArrayList(s.length);
197: for (int i = 0; i < s.length; i++) {
198: result.add(s[i]);
199: }
200:
201: return result;
202:
203: }
204:
205: /**
206: * This method accesss the session associated to the received id.
207: * This resets the session last access time and updates the access count.
208: *
209: * @param sessionId the session id previously returned by initiateSession.
210: * @throws NoSuchSessionException if the session id is not valid or the session is not valid.
211: */
212: public void accessSession(String sessionId)
213: throws NoSuchSessionException, SSOSessionException {
214:
215: // getCurrentSession will throw a NoSuchSessionException if not found.
216: BaseSession s = (BaseSession) getSession(sessionId);
217: if (!s.isValid()) {
218: if (logger.isDebugEnabled())
219: logger.debug("[accessSession()] invalid session : "
220: + sessionId);
221: throw new NoSuchSessionException(sessionId);
222: }
223:
224: s.access();
225: _store.save(s); // Update session information ...
226:
227: if (logger.isDebugEnabled())
228: logger.debug("[accessSession()] ok");
229:
230: }
231:
232: /**
233: * Invlalidates all open sessions.
234: */
235: public void invalidateAll() throws SSOSessionException {
236: BaseSession[] sessions = _store.loadAll();
237: for (int i = 0; i < sessions.length; i++) {
238: BaseSession session = sessions[i];
239:
240: // Mark session as expired (this will notify session listeners, if any)
241: session.expire();
242: }
243: }
244:
245: /**
246: * Invalidates a session.
247: *
248: * @param sessionId the session id previously returned by initiateSession.
249: * @throws NoSuchSessionException if the session id is not related to any sso session.
250: */
251: public void invalidate(String sessionId)
252: throws NoSuchSessionException, SSOSessionException {
253:
254: // Get current session.
255: BaseSession s = (BaseSession) getSession(sessionId);
256:
257: // Remove it from the store
258: try {
259: _store.remove(sessionId);
260: } catch (SSOSessionException e) {
261: logger.warn("Can't remove session from store\n"
262: + e.getMessage() != null ? e.getMessage() : e
263: .toString(), e);
264: }
265:
266: // Mark session as expired (this will notify session listeners, if any)
267: s.expire(); // This will invalidate the session ...
268:
269: }
270:
271: /**
272: * Check all sessions and remove those that are not valid from the store.
273: * This method is invoked periodically to update sessions state.
274: */
275: public void checkValidSessions() {
276:
277: try {
278:
279: //---------------------------------------------
280: // Verify invalid sessions ...
281: //---------------------------------------------
282: BaseSession sessions[] = _store.loadByValid(false);
283: if (logger.isDebugEnabled())
284: logger.debug("[checkValidSessions()] found "
285: + sessions.length + " invalid sessions");
286:
287: checkValidSessions(sessions);
288:
289: //---------------------------------------------
290: // Verify old sessions ...
291: //---------------------------------------------
292:
293: // Convert Max Inactive Interval to MS
294: long period = _maxInactiveInterval * 60L * 1000L;
295: Date from = new Date(System.currentTimeMillis() - period);
296: sessions = _store.loadByLastAccessTime(from);
297: if (logger.isDebugEnabled())
298: logger.debug("[checkValidSessions()] found "
299: + sessions.length
300: + " sessions last accessed before " + from);
301:
302: checkValidSessions(sessions);
303:
304: } catch (Exception e) {
305: logger.error("Can't process expired sessions : "
306: + e.getMessage(), e);
307: }
308:
309: }
310:
311: protected void checkValidSessions(BaseSession[] sessions) {
312: for (int i = 0; i < sessions.length; i++) {
313: try {
314:
315: // Ignore valid sessions, they have not expired yet.
316: BaseSession session = (BaseSession) sessions[i];
317:
318: if (!session.isValid()) {
319: // Remove invalid session from the store.
320: _store.remove(session.getId());
321: if (logger.isDebugEnabled())
322: logger
323: .debug("[checkValidSessions()] Session expired : "
324: + session.getId());
325: }
326:
327: } catch (Exception e) {
328: logger.warn("Can't remove session [" + i + "]; "
329: + e.getMessage() != null ? e.getMessage() : e
330: .toString(), e);
331: }
332: }
333:
334: }
335:
336: public void setSessionStore(SessionStore ss) {
337: _store = ss;
338: }
339:
340: /**
341: * Dependency Injection of Session Id Generator.
342: */
343: public void setSessionIdGenerator(SessionIdGenerator g) {
344: _idGen = g;
345: }
346:
347: /**
348: * Number of sessions registered in the manager.
349: *
350: * @return the number of sessions registered in this manager.
351: */
352: public int getSessionCount() throws SSOSessionException {
353: return _store.getSize();
354: }
355:
356: // ---------------------------------------------------------------
357: // Properties
358: // ---------------------------------------------------------------
359:
360: public int getMaxInactiveInterval() {
361: return _maxInactiveInterval;
362: }
363:
364: /**
365: * Used by config utils. Interval in minutes.
366: */
367: public void setMaxInactiveInterval(String i) {
368: setMaxInactiveInterval(Integer.parseInt(i));
369: }
370:
371: /**
372: * @param maxInactiveInterval in minutes
373: */
374: public void setMaxInactiveInterval(int maxInactiveInterval) {
375: _maxInactiveInterval = maxInactiveInterval;
376: }
377:
378: public int getMaxSessionsPerUser() {
379: return _maxSessionsPerUser;
380: }
381:
382: /**
383: * Used by config utils.
384: */
385: public void setMaxSessionsPerUser(String i) {
386: setMaxSessionsPerUser(Integer.parseInt(i));
387: }
388:
389: public void setMaxSessionsPerUser(int maxSessionsPerUser) {
390: _maxSessionsPerUser = maxSessionsPerUser;
391: }
392:
393: public boolean isInvalidateExceedingSessions() {
394: return _invalidateExceedingSessions;
395: }
396:
397: /**
398: * Just for JMX compatibility.
399: */
400: public boolean getInvalidateExceedingSessions() {
401: return _invalidateExceedingSessions;
402: }
403:
404: public void setInvalidateExceedingSessions(
405: boolean invalidateExceedingSessions) {
406: _invalidateExceedingSessions = invalidateExceedingSessions;
407: }
408:
409: /**
410: * Used by config utils.
411: */
412: public void setInvalidateExceedingSessions(String s) {
413: setInvalidateExceedingSessions(Boolean.valueOf(s)
414: .booleanValue());
415: }
416:
417: public long getSessionMonitorInterval() {
418: return _sessionMonitorInterval;
419: }
420:
421: public void setSessionMonitorInterval(long sessionMonitorInterval) {
422: _sessionMonitorInterval = sessionMonitorInterval;
423: if (_monitor != null) {
424: _monitor.setInterval(_sessionMonitorInterval);
425: }
426:
427: }
428:
429: /**
430: * Used by config utils.
431: */
432: public void setSessionMonitorInterval(String sessionMonitorInterval) {
433: setSessionMonitorInterval(Long
434: .parseLong(sessionMonitorInterval));
435: }
436:
437: // ---------------------------------------------------------------
438: // Protected utils.
439: // ---------------------------------------------------------------
440:
441: /**
442: * Get new session class to be used in the doLoad() method.
443: */
444: protected BaseSession doMakeNewSession() {
445: return new BaseSessionImpl();
446: }
447:
448: // ---------------------------------------------------------------
449: // To expire threads periodically,
450: // TODO : use a configured scheduler ...
451: // ---------------------------------------------------------------
452:
453: /**
454: * Checks for valid sessions every second.
455: */
456: private class SessionMonitor implements Runnable {
457:
458: private long _interval;
459:
460: private SSOSessionManager _m;
461:
462: SessionMonitor(SSOSessionManager m) {
463: _m = m;
464: }
465:
466: SessionMonitor(SSOSessionManager m, long interval) {
467: _interval = interval;
468: _m = m;
469: }
470:
471: public long getInterval() {
472: return _interval;
473: }
474:
475: public void setInterval(long interval) {
476: _interval = interval;
477: }
478:
479: /**
480: * Check for valid sessions ...
481: */
482: public void run() {
483:
484: do {
485: try {
486:
487: if (logger.isDebugEnabled())
488: logger
489: .debug("[run()] calling checkValidSessions ... ");
490:
491: _m.checkValidSessions();
492:
493: synchronized (this ) {
494: try {
495:
496: if (logger.isDebugEnabled())
497: logger.debug("[run()] waiting "
498: + _interval + " ms");
499:
500: wait(_interval);
501:
502: } catch (InterruptedException e) {
503: logger.warn(e, e);
504: }
505: }
506: } catch (Exception e) {
507: logger.warn("Exception received : "
508: + e.getMessage() != null ? e.getMessage()
509: : e.toString(), e);
510: }
511:
512: } while (true);
513: }
514: }
515:
516: }
|