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.agent;
022:
023: import org.josso.gateway.GatewayServiceLocator;
024: import org.josso.gateway.assertion.exceptions.AssertionNotValidException;
025: import org.josso.gateway.identity.service.SSOIdentityManager;
026: import org.josso.gateway.identity.service.SSOIdentityProvider;
027: import org.josso.gateway.session.exceptions.NoSuchSessionException;
028: import org.josso.gateway.session.service.SSOSessionManager;
029: import org.josso.gateway.session.SSOSession;
030:
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpUtils;
033: import javax.servlet.http.Cookie;
034: import java.security.Principal;
035: import java.util.HashMap;
036: import java.util.List;
037:
038: /**
039: * Represents a partial implementation of an Single Sign-on Agent.
040: * An Agent stands in between the Gateway and the Security Domain were partner application reside.
041: * It provides transparent security context to partner applications, providing user and role information
042: * by invoking the gateway through JAAS.
043: *
044: * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
045: * @version CVS $Id: AbstractSSOAgent.java 508 2008-02-18 13:32:29Z sgonzalez $
046: */
047:
048: public abstract class AbstractSSOAgent implements SSOAgent {
049:
050: /**
051: * The name of the cookie that holds the JOSSO Session id.
052: */
053: private static final String JOSSO_SINGLE_SIGN_ON_COOKIE = "JOSSO_SESSIONID";
054:
055: public static final long DEFAULT_SESSION_ACCESS_MIN_INTERVAL = 1000;
056:
057: // ----------------------------------------------------- Instance Variables
058: /**
059: * The cache of SingleSignOnEntry instances for authenticated Principals,
060: * keyed by the cookie value that is used to select them.
061: */
062: protected HashMap cache = new HashMap();
063:
064: /**
065: * The cache of single sign on identifiers, keyed by the Session that is
066: * associated with them.
067: */
068: protected HashMap reverse = new HashMap();
069: protected boolean started = false;
070: protected int debug = 0;
071: protected SSOAgentConfiguration _cfg;
072:
073: private GatewayServiceLocator _gsl;
074: private SSOSessionManager _sm;
075: private SSOIdentityManager _im;
076: private SSOIdentityProvider _ip;
077:
078: private String _gatewayLoginUrl;
079: private String _gatewayLogoutUrl;
080:
081: private String _gatewayLoginErrorUrl;
082:
083: private String _singlePointOfAccess;
084:
085: private long _sessionAccessMinInterval = DEFAULT_SESSION_ACCESS_MIN_INTERVAL;
086:
087: // Some statistical information :
088: private long _requestCount;
089: private long _l1CacheHits;
090: private long _l2CacheHits;
091:
092: // ----------------------------------------------------- Properties
093:
094: /**
095: * Configures the Gateway Service Locator to be used by the SSOAgent.
096: *
097: * @param gsl
098: */
099: public void setGatewayServiceLocator(GatewayServiceLocator gsl) {
100: _gsl = gsl;
101: }
102:
103: /**
104: * Obtains the Gateway Service Locator used by the SSOAgent to build
105: * the concrete clients needed to query the Gateway services.
106: *
107: * This getter is need by the JAASLoginModule to know which Gateway Service Locator to use.
108: *
109: * @return the configured gateway service locator
110: */
111: public GatewayServiceLocator getGatewayServiceLocator() {
112: return _gsl;
113: }
114:
115: public SSOSessionManager getSSOSessionManager() {
116: return _sm;
117: }
118:
119: public SSOIdentityManager getSSOIdentityManager() {
120: return _im;
121: }
122:
123: /**
124: * Sets the Login Form Url of the Gateway.
125: */
126: public void setGatewayLoginUrl(String gatewayLoginUrl) {
127: _gatewayLoginUrl = gatewayLoginUrl;
128: }
129:
130: /**
131: * Returns the Login Form Url of the Gateway.
132: *
133: * @return the gateway login url
134: */
135: public String getGatewayLoginUrl() {
136: return _gatewayLoginUrl;
137: }
138:
139: /**
140: * Returns the Error Login Url of the Gateway.
141: *
142: * @return the gateway login url
143: */
144: public String getGatewayLoginErrorUrl() {
145: return _gatewayLoginErrorUrl;
146: }
147:
148: /**
149: * Sets the Error Login Url of the Gateway.
150: */
151: public void setGatewayLoginErrorUrl(String gatewayLoginErrorUrl) {
152: _gatewayLoginErrorUrl = gatewayLoginErrorUrl;
153: }
154:
155: /**
156: * Sets the Logout Form Url of the Gateway.
157: */
158: public void setGatewayLogoutUrl(String gatewayLogoutUrl) {
159: _gatewayLogoutUrl = gatewayLogoutUrl;
160: }
161:
162: /**
163: * Returns the Logout Form Url of the Gateway.
164: *
165: * @return the gateway login url
166: */
167: public String getGatewayLogoutUrl() {
168: return _gatewayLogoutUrl;
169: }
170:
171: /**
172: * Used by the configuraiton, to set the session access min interval.
173: */
174: public void setSessionAccessMinInterval(String v) {
175: setSessionAccessMinInterval(Long.parseLong(v));
176: }
177:
178: /**
179: * Gets the session access min interval.
180: */
181: public long getSessionAccessMinInterval() {
182: return _sessionAccessMinInterval;
183: }
184:
185: /**
186: * Sets the session access min interval.
187: */
188: public void setSessionAccessMinInterval(
189: long sessionAccessMinInterval) {
190: _sessionAccessMinInterval = sessionAccessMinInterval;
191: }
192:
193: /**
194: * Single Point of Access to the SSO infrastructure. Useful when working in N-Tier mode behind a reverse proxy or
195: * load balancer
196: */
197: public String getSinglePointOfAccess() {
198: return _singlePointOfAccess;
199: }
200:
201: /**
202: * Single Point of Access to the SSO infrastructure. Useful when working in N-Tier mode behind a reverse proxy or
203: * load balancer
204: * @param singlePointOfAccess
205: */
206: public void setSinglePointOfAccess(String singlePointOfAccess) {
207: _singlePointOfAccess = singlePointOfAccess;
208: }
209:
210: /**
211: * Returns true if the received context should be processed by this agent. It means that
212: * the context belongs to a partner application.
213: * @param contextPath the web application context to be checked.
214: * @return true if this context belongs to a josso partner app.
215: */
216: public boolean isPartnerApp(String contextPath) {
217: return getPartnerAppConfig(contextPath) != null;
218: }
219:
220: /**
221: * Returns the partner application configuration definition associtated with the given context.
222: * If no partner application is defined for the context, returns null.
223: */
224: public SSOPartnerAppConfig getPartnerAppConfig(String contextPath) {
225: List papps = _cfg.getSSOPartnerApps();
226: for (int i = 0; i < papps.size(); i++) {
227: SSOPartnerAppConfig ssoPartnerAppConfig = (SSOPartnerAppConfig) papps
228: .get(i);
229: if (contextPath.equals(ssoPartnerAppConfig.getContext()))
230: return ssoPartnerAppConfig;
231: }
232: return null;
233:
234: }
235:
236: /**
237: * Starts the Agent.
238: */
239: public void start() {
240: try {
241: _sm = _gsl.getSSOSessionManager();
242: _im = _gsl.getSSOIdentityManager();
243: _ip = _gsl.getSSOIdentityProvider();
244:
245: if (debug > 0)
246: log("Agent Started");
247: } catch (Exception e) {
248: log("Can't create session/identity managers : \n"
249: + e.getMessage(), e);
250: }
251:
252: }
253:
254: /**
255: * Authenticated a user session previously authenticated by the gateway.
256: *
257: * @param request the JOSSO Agent request
258: * @return the logged user identity.
259: */
260: public SingleSignOnEntry processRequest(SSOAgentRequest request) {
261:
262: // Count this request.
263: _requestCount++;
264:
265: int action = request.getAction();
266: String jossoSessionId = request.getSessionId();
267: LocalSession localSession = request.getLocalSession();
268:
269: if (action == SSOAgentRequest.ACTION_RELAY) {
270: String assertionId = request.getAssertionId();
271: jossoSessionId = resolveAssertion(assertionId);
272: request.setSessionId(jossoSessionId);
273: }
274:
275: // Look up the cached Principal associated with this cookie value
276: if (debug > 0)
277: log("Checking for cached principal for " + jossoSessionId);
278:
279: SingleSignOnEntry entry = lookup(jossoSessionId);
280: if (entry != null) {
281:
282: if (debug > 0)
283: log(" Found cached principal '"
284: + entry.principal.getName()
285: + "' with auth type '" + entry.authType + "'");
286:
287: // Count the cache hit.
288: _l1CacheHits++;
289:
290: entry = accessSession(entry, jossoSessionId);
291: return entry;
292: }
293:
294: // Make the agent receive local session events.
295: localSession.addSessionListener(this );
296:
297: // Associated local session to the SSO Session
298: associateLocalSession(jossoSessionId, localSession);
299:
300: // Invoke the JAAS Gateway Login Module to obtain user information
301: Principal ssoUserPrincipal = authenticate(request);
302:
303: if (ssoUserPrincipal != null) {
304: if (debug > 0)
305: log("Principal checked for SSO Session '"
306: + jossoSessionId + "' : " + ssoUserPrincipal);
307:
308: register(jossoSessionId, ssoUserPrincipal, "JOSSO");
309: entry = lookup(jossoSessionId);
310: entry = accessSession(entry, jossoSessionId);
311: return entry;
312:
313: }
314:
315: if (debug > 0)
316: log("There is no associated principal for SSO Session '"
317: + jossoSessionId + "'");
318:
319: return null;
320:
321: }
322:
323: /**
324: * Dereference assertion id by invoking the corresponding operation using the back-channel
325: *
326: *
327: * @param assertionId
328: * @return null if the authentication assertion is invalid
329: */
330: protected String resolveAssertion(String assertionId) {
331:
332: try {
333:
334: if (debug > 0)
335: log("Dereferencing assertion for id '" + assertionId
336: + "'");
337:
338: String ssoSessionId = _ip
339: .resolveAuthenticationAssertion(assertionId);
340: return ssoSessionId;
341: } catch (AssertionNotValidException e) {
342: if (debug > 0)
343: log("Invalid Assertion");
344:
345: return null;
346:
347: } catch (Exception e) {
348: log(e.getMessage() != null ? e.getMessage() : e.toString(),
349: e);
350: return null;
351: }
352:
353: }
354:
355: /**
356: * Access sso session related with given entry. If sso session is no longer valid,
357: * deregisters the session and return null.
358: *
359: * @param entry
360: * @return null if the sso session is no longer valid.
361: */
362: protected SingleSignOnEntry accessSession(SingleSignOnEntry entry,
363: String jossoSessionId) {
364:
365: // Just in case
366: if (entry == null)
367: return entry;
368:
369: // Do not access server more than once in a second ...
370: long now = System.currentTimeMillis();
371: if ((now - entry.lastAccessTime) < getSessionAccessMinInterval()) {
372: _l2CacheHits++;
373: return entry;
374: }
375:
376: try {
377: // send a keep-alive event for the SSO session
378:
379: if (debug > 0)
380: log("Notifying keep-alive event for session '"
381: + jossoSessionId + "'");
382:
383: _sm.accessSession(jossoSessionId);
384: entry.lastAccessTime = now;
385: return entry;
386:
387: } catch (NoSuchSessionException e) {
388: if (debug > 0)
389: log("SSO Session is no longer valid");
390:
391: deregister(entry.ssoId);
392: return null;
393:
394: } catch (Exception e) {
395: log(e.getMessage() != null ? e.getMessage() : e.toString(),
396: e);
397: deregister(entry.ssoId);
398: return null;
399: }
400:
401: }
402:
403: /**
404: * Template method used by the agent to obtain a principal from a SSO Agent request.
405: *
406: * @param request
407: * @return the authenticated principal.
408: */
409: abstract protected Principal authenticate(SSOAgentRequest request);
410:
411: /**
412: * Log a message on the Logger associated with our Container (if any).
413: *
414: * @param message Message to be logged
415: */
416: abstract protected void log(String message);
417:
418: /**
419: * Log a message on the Logger associated with our Container (if any).
420: *
421: * @param message Message to be logged
422: * @param throwable Associated exception
423: */
424: abstract protected void log(String message, Throwable throwable);
425:
426: /**
427: * Stop the Agent.
428: */
429: public void stop() {
430: if (debug > 0)
431: log("Agent Stopped");
432:
433: }
434:
435: // ------------------------------------------------ LocalSessionListener Methods
436:
437: /**
438: * Acknowledge the occurrence of the specified event.
439: *
440: * @param event SessionEvent that has occurred
441: */
442: public void localSessionEvent(LocalSessionEvent event) {
443:
444: // We only care about session destroyed events
445: if (!LocalSession.LOCAL_SESSION_DESTROYED_EVENT.equals(event
446: .getType()))
447: return;
448:
449: // Look up the single session id associated with this session (if any)
450: LocalSession session = event.getLocalSession();
451:
452: if (debug > 0)
453: log("Local session destroyed on " + session);
454:
455: // notify gateway that the session was destroyed.
456: localSessionDestroyedEvent(session);
457:
458: }
459:
460: public void setConfiguration(SSOAgentConfiguration cfg) {
461: _cfg = cfg;
462: }
463:
464: public SSOAgentConfiguration getConfiguration() {
465: return _cfg;
466: }
467:
468: /**
469: * Disassociates a Local Session from the Single Sign-on session since the Local Session
470: * was destroyed.
471: *
472: * @param session
473: */
474: protected void localSessionDestroyedEvent(LocalSession session) {
475: String ssoId = null;
476: synchronized (reverse) {
477: ssoId = (String) reverse.get(session);
478: }
479: if (ssoId == null)
480: return;
481:
482: // Deregister this single sso session id, invalidating associated sessions
483: deregister(ssoId);
484: }
485:
486: /**
487: * Associate the specified single sign on identifier with the
488: * specified Session.
489: *
490: * @param ssoId Single sign on identifier
491: * @param localSession Local Session to be associated to the SSO Session.
492: */
493: protected void associateLocalSession(String ssoId,
494: LocalSession localSession) {
495:
496: SingleSignOnEntry sso = lookup(ssoId);
497: if (sso != null)
498: sso.addSession(localSession);
499: synchronized (reverse) {
500: reverse.put(localSession, ssoId);
501: }
502:
503: }
504:
505: /**
506: * Deregister the specified single sign on identifier, and invalidate
507: * any associated sessions.
508: *
509: * @param ssoId Single sign on identifier to deregister
510: */
511: protected void deregister(String ssoId) {
512:
513: // Look up and remove the corresponding SingleSignOnEntry
514: SingleSignOnEntry sso = null;
515: synchronized (cache) {
516: sso = (SingleSignOnEntry) cache.remove(ssoId);
517: }
518: if (sso == null)
519: return;
520:
521: // Expire any associated sessions
522: LocalSession localSessions[] = sso.findSessions();
523: for (int i = 0; i < localSessions.length; i++) {
524:
525: // Remove from reverse cache first to avoid recursion
526: synchronized (reverse) {
527: reverse.remove(localSessions[i]);
528: }
529: // Invalidate this session
530: localSessions[i].exipre();
531:
532: }
533:
534: }
535:
536: /**
537: * Register the specified Principal as being associated with the specified
538: * value for the single sign on identifier.
539: *
540: * @param ssoId Single sign on identifier to register
541: * @param principal Associated user principal that is identified
542: * @param authType Authentication type used to authenticate this
543: * user principal
544: */
545: protected void register(String ssoId, Principal principal,
546: String authType) {
547:
548: synchronized (cache) {
549: cache.put(ssoId, new SingleSignOnEntry(ssoId, principal,
550: authType));
551: }
552:
553: }
554:
555: /**
556: * Look up and return the cached SingleSignOn entry associated with this
557: * sso id value, if there is one; otherwise return <code>null</code>.
558: *
559: * @param ssoId Single sign on identifier to look up
560: */
561: protected SingleSignOnEntry lookup(String ssoId) {
562: synchronized (cache) {
563: return ((SingleSignOnEntry) cache.get(ssoId));
564: }
565: }
566:
567: public int getDebug() {
568: return debug;
569: }
570:
571: public void setDebug(int debug) {
572: this .debug = debug;
573: }
574:
575: public long getRequestCount() {
576: return _requestCount;
577: }
578:
579: public long getL1CacheHits() {
580: return _l1CacheHits;
581: }
582:
583: public long getL2CacheHits() {
584: return _l2CacheHits;
585: }
586:
587: /**
588: * This method builds the back_to URL value pointing to the given URI.
589: *
590: * The determines the host used to build the back_to URL in the following order :
591: *
592: * First, checks the singlePointOfAccess agent's configuration property.
593: * Then checks the reverse-proxy-host HTTP header value from the request.
594: * Finally uses current host name.
595: *
596: */
597: public String buildBackToURL(HttpServletRequest hreq, String uri) {
598: String backto = null;
599:
600: // Build the back to url.
601: String contextPath = hreq.getContextPath();
602:
603: // This is the root context
604: if (contextPath == null || "".equals(contextPath))
605: contextPath = "/";
606:
607: String reverseProxyHost = hreq
608: .getHeader(org.josso.gateway.Constants.JOSSO_REVERSE_PROXY_HEADER);
609: String singlePointOfAccess = getSinglePointOfAccess();
610:
611: if (singlePointOfAccess != null) {
612: // Using single-point of access configuration.
613: if (debug >= 1)
614: log("josso_back_to option : singlePointOfAccess: "
615: + singlePointOfAccess);
616: backto = singlePointOfAccess + contextPath + uri;
617:
618: } else if (reverseProxyHost != null) {
619: // Using reverse proxy host header.
620: if (debug >= 1)
621: log("josso_back_to option : reverse-proxy-host: "
622: + reverseProxyHost);
623: backto = reverseProxyHost + contextPath + uri;
624:
625: } else {
626: // Using default host
627: StringBuffer mySelf = HttpUtils.getRequestURL(hreq);
628:
629: try {
630: java.net.URL url = new java.net.URL(mySelf.toString());
631: backto = url.getProtocol()
632: + "://"
633: + url.getHost()
634: + ((url.getPort() > 0) ? ":" + url.getPort()
635: : "");
636: } catch (java.net.MalformedURLException e) {
637: throw new RuntimeException(e);
638: }
639:
640: backto += contextPath + uri;
641:
642: }
643:
644: if (debug >= 1)
645: log("Using josso_back_to : " + backto);
646:
647: return backto;
648: }
649:
650: /**
651: * This creates a new JOSSO Cookie for the given path and value.
652: * @param path the path associated with the cookie, normaly the partner application context.
653: * @param value the SSO Session ID
654: * @return
655: */
656: public Cookie newJossoCookie(String path, String value) {
657: Cookie ssoCookie = new Cookie(JOSSO_SINGLE_SIGN_ON_COOKIE,
658: value);
659: ssoCookie.setMaxAge(-1);
660: ssoCookie.setPath(path);
661:
662: // TODO : Check domain / secure ?
663: //ssoCookie.setDomain(cfg.getSessionTokenScope());
664: //ssoCookie.setSecure(true);
665:
666: return ssoCookie;
667: }
668:
669: }
|