KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > tomcat > tc6 > sso > ClusteredSingleSignOn


1 /*
2  * Copyright 1999-2001,2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.jboss.web.tomcat.tc6.sso;
17
18
19 import org.jboss.web.tomcat.tc6.session.JBossManager;
20
21 import java.io.IOException JavaDoc;
22 import java.security.Principal JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Set JavaDoc;
26
27 import javax.management.ObjectName JavaDoc;
28 import javax.servlet.ServletException JavaDoc;
29 import javax.servlet.http.Cookie JavaDoc;
30
31 import org.apache.catalina.Lifecycle;
32 import org.apache.catalina.LifecycleEvent;
33 import org.apache.catalina.LifecycleException;
34 import org.apache.catalina.LifecycleListener;
35 import org.apache.catalina.Manager;
36 import org.apache.catalina.Session;
37 import org.apache.catalina.Realm;
38 import org.apache.catalina.SessionEvent;
39 import org.apache.catalina.authenticator.Constants;
40 import org.apache.catalina.connector.Request;
41 import org.apache.catalina.connector.Response;
42 import org.apache.catalina.session.ManagerBase;
43
44
45 /**
46  * A <strong>Valve</strong> that supports a "single sign on" user experience,
47  * where the security identity of a user who successfully authenticates to one
48  * web application is propogated to other web applications in the same
49  * security domain. For successful use, the following requirements must
50  * be met:
51  * <ul>
52  * <li>This Valve must be configured on the Container that represents a
53  * virtual host (typically an implementation of <code>Host</code>).</li>
54  * <li>The <code>Realm</code> that contains the shared user and role
55  * information must be configured on the same Container (or a higher
56  * one), and not overridden at the web application level.</li>
57  * <li>The web applications themselves must use one of the standard
58  * Authenticators found in the
59  * <code>org.apache.catalina.authenticator</code> package.</li>
60  * </ul>
61  *
62  * @author Brian E. Stansberry based on the work of Craig R. McClanahan
63  * @version $Revision: 58435 $ $Date: 2006-11-15 23:18:12 -0500 (Wed, 15 Nov 2006) $
64  */

65 public class ClusteredSingleSignOn
66    extends org.apache.catalina.authenticator.SingleSignOn
67    implements LifecycleListener
68 {
69
70    // Override the superclass value
71
static
72    {
73       info = ClusteredSingleSignOn.class.getName();
74    }
75     
76    // ----------------------------------------------------- Instance Variables
77

78  
79    /**
80     * Fully qualified name of a class implementing
81     * {@link SSOClusterManager SSOClusterManager} that will be used
82     * to manage SSOs across a cluster.
83     */

84    private String JavaDoc clusterManagerClass =
85       TreeCacheSSOClusterManager.class.getName();
86
87    /**
88     * Object used to provide cross-cluster support for single sign on.
89     */

90    private SSOClusterManager ssoClusterManager = null;
91
92    /**
93     * Object name of the tree cache used by SSOClusterManager.
94     * Only relevant if the SSOClusterManager implementation is
95     * TreeCacheSSOClusterManager.
96     */

97    private String JavaDoc treeCacheName = "jboss.cache:service=TomcatClusteringCache";
98    
99    private Set JavaDoc activeManagers = Collections.synchronizedSet(new HashSet JavaDoc());
100
101    // ------------------------------------------------------------- Properties
102

103    /**
104     * Gets the object that provides SSO support across a cluster.
105     *
106     * @return the object provided cluster support, or <code>null</code> if
107     * no such object has been configured.
108     */

109    public SSOClusterManager getClusterManager()
110    {
111       return this.ssoClusterManager;
112    }
113
114
115    /**
116     * Sets the object that provides SSO support across a cluster.
117     *
118     * @param clusterManager the object that provides SSO support.
119     * @throws IllegalStateException if this method is invoked after this valve
120     * has been started.
121     */

122    public void setClusterManager(SSOClusterManager clusterManager)
123    {
124       if (started && (clusterManager != ssoClusterManager))
125       {
126          throw new IllegalStateException JavaDoc("already started -- cannot set a " +
127             "new SSOClusterManager");
128       }
129
130       this.ssoClusterManager = clusterManager;
131
132       if (clusterManager != null)
133       {
134          clusterManagerClass = clusterManager.getClass().getName();
135       }
136    }
137
138
139    /**
140     * Gets the name of the class that will be used to provide SSO support
141     * across a cluster.
142     *
143     * @return Fully qualified name of a class implementing
144     * {@link SSOClusterManager SSOClusterManager}
145     * that is being used to manage SSOs across a cluster.
146     * May return <code>null</code> (the default) if clustered
147     * SSO support is not configured.
148     */

149    public String JavaDoc getClusterManagerClass()
150    {
151       return clusterManagerClass;
152    }
153
154
155    /**
156     * Sets the name of the class that will be used to provide SSO support
157     * across a cluster.
158     * <p><b>NOTE: </b>
159     * If this Valve has already started, and no SSOClusterManager has been
160     * configured for it, calling this method will
161     *
162     * @param managerClass Fully qualified name of a class implementing
163     * {@link SSOClusterManager SSOClusterManager}
164     * that will be used to manage SSOs across a cluster.
165     * Class must declare a public no-arguments
166     * constructor. <code>null</code> is allowed.
167     */

168    public void setClusterManagerClass(String JavaDoc managerClass)
169    {
170       if (!started)
171       {
172          clusterManagerClass = managerClass;
173       }
174       else if (ssoClusterManager == null)
175       {
176          try
177          {
178             createClusterManager(managerClass);
179          }
180          catch (LifecycleException e)
181          {
182             getContainer().getLogger().error("Exception creating SSOClusterManager " +
183                managerClass, e);
184          }
185       }
186       else
187       {
188           getContainer().getLogger().error("Cannot set clusterManagerClass to " + managerClass +
189             "; already started using " + clusterManagerClass);
190       }
191    }
192
193    /**
194     * Object name of the tree cache used by SSOClusterManager.
195     * Only relevant if the SSOClusterManager implementation is
196     * TreeCacheSSOClusterManager.
197     */

198    public String JavaDoc getTreeCacheName()
199    {
200       return treeCacheName;
201    }
202
203    /**
204     * Sets the object name of the tree cache used by SSOClusterManager.
205     * Only relevant if the SSOClusterManager implementation is
206     * TreeCacheSSOClusterManager.
207     */

208    public void setTreeCacheName(String JavaDoc cacheName)
209       throws Exception JavaDoc
210    {
211       this.treeCacheName = cacheName;
212       if (ssoClusterManager != null
213          && ssoClusterManager instanceof TreeCacheSSOClusterManager)
214       {
215          ((TreeCacheSSOClusterManager) ssoClusterManager).setCacheName(cacheName);
216       }
217    }
218
219
220    // ------------------------------------------------------ Lifecycle Methods
221

222
223    /**
224     * Prepare for the beginning of active use of the public methods of this
225     * component. This method should be called after <code>configure()</code>,
226     * and before any of the public methods of the component are utilized.
227     *
228     * @throws LifecycleException if this component detects a fatal error
229     * that prevents this component from being used
230     */

231    public void start() throws LifecycleException
232    {
233       // Validate and update our current component state
234
if (started)
235       {
236          throw new LifecycleException
237             (sm.getString("authenticator.alreadyStarted"));
238       }
239
240       // Attempt to create an SSOClusterManager
241
createClusterManager(getClusterManagerClass());
242
243       lifecycle.fireLifecycleEvent(START_EVENT, null);
244       started = true;
245
246       if (ssoClusterManager != null)
247       {
248          ssoClusterManager.start();
249       }
250
251    }
252
253
254    /**
255     * Gracefully terminate the active use of the public methods of this
256     * component. This method should be the last one called on a given
257     * instance of this component.
258     *
259     * @throws LifecycleException if this component detects a fatal error
260     * that needs to be reported
261     */

262    public void stop() throws LifecycleException
263    {
264       // Validate and update our current component state
265
if (!started)
266       {
267          throw new LifecycleException
268             (sm.getString("authenticator.notStarted"));
269       }
270
271       if (ssoClusterManager != null)
272       {
273          ssoClusterManager.stop();
274       }
275
276       lifecycle.fireLifecycleEvent(STOP_EVENT, null);
277       started = false;
278
279    }
280
281
282    // ------------------------------------------------ SessionListener Methods
283

284
285    /**
286     * Updates the state of a single sign on session to reflect the destruction
287     * of a standard HTTP session.
288     * <p/>
289     * If the given event is a {@link Session#SESSION_DESTROYED_EVENT
290     * Session destroyed event}, checks whether the session was destroyed due
291     * to timeout or user action (i.e. logout). If due to timeout, disassociates
292     * the Session from the single sign on session. If due to logout, invokes
293     * the {@link #logout} method.
294     *
295     * @param event SessionEvent that has occurred
296     */

297    public void sessionEvent(SessionEvent event)
298    {
299       // We only care about session destroyed events
300
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()))
301          return;
302
303       // Look up the single session id associated with this session (if any)
304
Session session = event.getSession();
305       if (getContainer().getLogger().isDebugEnabled())
306           getContainer().getLogger().debug("Process session destroyed on " + session);
307
308       String JavaDoc ssoId = null;
309       synchronized (reverse)
310       {
311          ssoId = (String JavaDoc) reverse.get(session);
312       }
313       if (ssoId == null)
314          return;
315
316       // Was the session destroyed as the result of a timeout or
317
// the undeployment of the containing webapp?
318
// If so, we'll just remove the expired session from the
319
// SSO. If the session was logged out, we'll log out
320
// of all sessions associated with the SSO.
321
if (isSessionTimedOut(session) || isManagerStopped(session))
322       {
323          removeSession(ssoId, session);
324       }
325       else
326       {
327          // The session was logged out.
328
logout(ssoId);
329       }
330
331    }
332
333    private boolean isSessionTimedOut(Session session)
334    {
335       return (session.getMaxInactiveInterval() > 0)
336                && (System.currentTimeMillis() - session.getLastAccessedTime() >=
337                   session.getMaxInactiveInterval() * 1000);
338    }
339    
340    private boolean isManagerStopped(Session session)
341    {
342       boolean stopped = false;
343       
344       Manager manager = session.getManager();
345       
346       if (manager instanceof ManagerBase)
347       {
348          ObjectName JavaDoc mgrName = ((ManagerBase)manager).getObjectName();
349          stopped = (!activeManagers.contains(mgrName));
350       }
351       else if (manager instanceof JBossManager)
352       {
353          ObjectName JavaDoc mgrName = ((JBossManager)manager).getObjectName();
354          stopped = (!activeManagers.contains(mgrName));
355       }
356       else if (manager instanceof Lifecycle)
357       {
358          stopped = (!activeManagers.contains(manager));
359       }
360       // else we have no way to tell, so assume not
361

362       return stopped;
363    }
364
365    // ---------------------------------------------- LifecycleListener Methods
366

367
368    public void lifecycleEvent(LifecycleEvent event)
369    {
370       String JavaDoc type = event.getType();
371       if (Lifecycle.BEFORE_STOP_EVENT.equals(type)
372             || Lifecycle.STOP_EVENT.equals(type)
373             || Lifecycle.AFTER_STOP_EVENT.equals(type))
374       {
375          Lifecycle source = event.getLifecycle();
376          boolean removed;
377          if (source instanceof ManagerBase)
378          {
379             removed = activeManagers.remove(((ManagerBase)source).getObjectName());
380          }
381          else if (source instanceof JBossManager)
382          {
383             removed = activeManagers.remove(((JBossManager)source).getObjectName());
384          }
385          else
386          {
387             removed = activeManagers.remove(source);
388          }
389          
390          if (removed)
391          {
392             source.removeLifecycleListener(this);
393             
394             if (getContainer().getLogger().isDebugEnabled())
395             {
396                 getContainer().getLogger().debug("ClusteredSSO: removed " +
397                         "stopped manager " + source.toString());
398             }
399          }
400          
401          // TODO consider getting the sessions and removing any from our sso's
402
// Idea is to cleanup after managers that don't destroy sessions
403

404       }
405    }
406    
407
408    // ---------------------------------------------------------- Valve Methods
409

410
411    /**
412     * Perform single-sign-on support processing for this request.
413     * <p/>
414     * Overrides the superclass version by handling the fact that a
415     * single sign on may have been originated on another cluster node and
416     * thus may not have a <code>Principal</code> object associated with it
417     * on this node.
418     *
419     * @param request The servlet request we are processing
420     * @param response The servlet response we are creating
421     * @param context The valve context used to invoke the next valve
422     * in the current processing pipeline
423     * @throws IOException if an input/output error occurs
424     * @throws ServletException if a servlet error occurs
425     */

426    public void invoke(Request request, Response response)
427       throws IOException JavaDoc, ServletException JavaDoc
428    {
429       request.removeNote(Constants.REQ_SSOID_NOTE);
430
431       // Has a valid user already been authenticated?
432
if (getContainer().getLogger().isDebugEnabled())
433          getContainer().getLogger().debug("Process request for '" + request.getRequestURI() + "'");
434       if (request.getUserPrincipal() != null)
435       {
436          if (getContainer().getLogger().isDebugEnabled())
437             getContainer().getLogger().debug(" Principal '" + request.getUserPrincipal().getName() +
438                "' has already been authenticated");
439          getNext().invoke(request, response);
440          return;
441       }
442
443       // Check for the single sign on cookie
444
Cookie JavaDoc cookie = null;
445       Cookie JavaDoc cookies[] = request.getCookies();
446       if (cookies == null)
447          cookies = new Cookie JavaDoc[0];
448       for (int i = 0; i < cookies.length; i++)
449       {
450          if (Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName()))
451          {
452             cookie = cookies[i];
453             break;
454          }
455       }
456       if (cookie == null)
457       {
458          if (getContainer().getLogger().isDebugEnabled())
459             getContainer().getLogger().debug(" SSO cookie is not present");
460          getNext().invoke(request, response);
461          return;
462       }
463
464       // Look up the cached Principal associated with this cookie value
465
if (getContainer().getLogger().isDebugEnabled())
466           getContainer().getLogger().debug(" Checking for cached principal for " + cookie.getValue());
467       SingleSignOnEntry entry = getSingleSignOnEntry(cookie.getValue());
468       if (entry != null)
469       {
470          Principal JavaDoc ssoPrinc = entry.getPrincipal();
471          // have to deal with the fact that the entry may not have an
472
// associated Principal. SSO entries retrieved via a lookup from a
473
// cluster will not have a Principal, as Principal is not Serializable
474
if (getContainer().getLogger().isDebugEnabled())
475          {
476              getContainer().getLogger().debug(" Found cached principal '" +
477                (ssoPrinc == null ? "NULL" : ssoPrinc.getName()) +
478                "' with auth type '" + entry.getAuthType() + "'");
479          }
480          request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
481          // Only set security elements if per-request reauthentication is
482
// not required AND the SSO entry had a Principal.
483
if (!getRequireReauthentication() && ssoPrinc != null)
484          {
485             request.setAuthType(entry.getAuthType());
486             request.setUserPrincipal(ssoPrinc);
487          }
488       }
489       else
490       {
491          if (getContainer().getLogger().isDebugEnabled())
492             getContainer().getLogger().debug(" No cached principal found, erasing SSO cookie");
493          cookie.setMaxAge(0);
494          response.addCookie(cookie);
495       }
496
497       // Invoke the next Valve in our pipeline
498
getNext().invoke(request, response);
499    }
500
501
502    // ------------------------------------------------------ Protected Methods
503

504
505    /**
506     * Associate the specified single sign on identifier with the
507     * specified Session.
508     * <p/>
509     * Differs from the superclass version in that it notifies the cluster
510     * of any new association of SSO and Session.
511     *
512     * @param ssoId Single sign on identifier
513     * @param session Session to be associated
514     */

515    protected void associate(String JavaDoc ssoId, Session session)
516    {
517       if (getContainer().getLogger().isDebugEnabled())
518           getContainer().getLogger().debug("Associate sso id " + ssoId + " with session " + session);
519
520       SingleSignOnEntry sso = getSingleSignOnEntry(ssoId);
521       boolean added = false;
522       if (sso != null)
523          added = sso.addSession(this, session);
524
525       synchronized (reverse)
526       {
527          reverse.put(session, ssoId);
528       }
529
530       // If we made a change, track the manager and notify any cluster
531
if (added)
532       {
533          Manager manager = session.getManager();
534          
535          // Prefer to cache an ObjectName to avoid risk of leaking a manager,
536
// so if the manager exposes one, use it
537
Object JavaDoc mgrKey = null;
538          if (manager instanceof ManagerBase)
539          {
540             mgrKey = ((ManagerBase)manager).getObjectName();
541          }
542          else if (manager instanceof JBossManager)
543          {
544             mgrKey = ((JBossManager)manager).getObjectName();
545          }
546          else if (manager instanceof Lifecycle)
547          {
548             mgrKey = manager;
549          }
550          else {
551             getContainer().getLogger().warn("Manager for session " +
552                       session.getIdInternal() +
553                       " does not implement Lifecycle; web app shutdown may " +
554                       " lead to incorrect SSO invalidations");
555          }
556          
557          if (mgrKey != null)
558          {
559             synchronized (activeManagers)
560             {
561                if (!activeManagers.contains(mgrKey))
562                {
563                   activeManagers.add(mgrKey);
564                   ((Lifecycle) manager).addLifecycleListener(this);
565                }
566             }
567          }
568          
569          if (ssoClusterManager != null)
570             ssoClusterManager.addSession(ssoId, session);
571       }
572    }
573
574
575    /**
576     * Deregister the specified session. If it is the last session,
577     * then also get rid of the single sign on identifier.
578     * <p/>
579     * Differs from the superclass version in that it notifies the cluster
580     * of any disassociation of SSO and Session.
581     *
582     * @param ssoId Single sign on identifier
583     * @param session Session to be deregistered
584     */

585    protected void deregister(String JavaDoc ssoId, Session session)
586    {
587       synchronized (reverse)
588       {
589          reverse.remove(session);
590       }
591
592       SingleSignOnEntry sso = getSingleSignOnEntry(ssoId);
593       if (sso == null)
594          return;
595
596       boolean removed = sso.removeSession(session);
597       // If we changed anything, notify any cluster
598
if (removed && ssoClusterManager != null)
599       {
600          ssoClusterManager.removeSession(ssoId, session);
601       }
602
603       // see if this was the last session on this node,
604
// if remove sso entry from our local cache
605
if (sso.getSessionCount() == 0)
606       {
607          synchronized (cache)
608          {
609             sso = (SingleSignOnEntry) cache.remove(ssoId);
610          }
611       }
612    }
613
614
615    /**
616     * Deregister the specified single sign on identifier, and invalidate
617     * any associated sessions.
618     *
619     * @param ssoId Single sign on identifier to deregister
620     */

621    protected void deregister(String JavaDoc ssoId)
622    {
623       if (getContainer().getLogger().isDebugEnabled())
624           getContainer().getLogger().debug("Deregistering sso id '" + ssoId + "'");
625
626       // Look up and remove the corresponding SingleSignOnEntry
627
SingleSignOnEntry sso = null;
628       synchronized (cache)
629       {
630          sso = (SingleSignOnEntry) cache.remove(ssoId);
631       }
632
633       if (sso == null)
634          return;
635
636       // Expire any associated sessions
637
Session sessions[] = sso.findSessions();
638       for (int i = 0; i < sessions.length; i++)
639       {
640          if (getContainer().getLogger().isTraceEnabled())
641              getContainer().getLogger().trace(" Invalidating session " + sessions[i]);
642          // Remove from reverse cache first to avoid recursion
643
synchronized (reverse)
644          {
645             reverse.remove(sessions[i]);
646          }
647          // Invalidate this session
648
sessions[i].expire();
649       }
650
651       // NOTE: Clients may still possess the old single sign on cookie,
652
// but it will be removed on the next request since it is no longer
653
// in the cache
654
}
655
656
657    /**
658     * Deregister the given SSO, invalidating any associated sessions, then
659     * notify any cluster of the logout.
660     *
661     * @param ssoId the id of the SSO session
662     */

663    protected void logout(String JavaDoc ssoId)
664    {
665       deregister(ssoId);
666       // broadcast logout to any cluster
667
if (ssoClusterManager != null)
668          ssoClusterManager.logout(ssoId);
669    }
670
671
672    /**
673     * Look up and return the cached SingleSignOn entry associated with this
674     * sso id value, if there is one; otherwise return <code>null</code>.
675     *
676     * @param ssoId Single sign on identifier to look up
677     */

678    protected SingleSignOnEntry getSingleSignOnEntry(String JavaDoc ssoId)
679    {
680       SingleSignOnEntry sso = localLookup(ssoId);
681       // If we don't have one locally and there is a cluster,
682
// query the cluster for the SSO
683
if (sso == null && ssoClusterManager != null)
684       {
685          sso = ssoClusterManager.lookup(ssoId);
686          if (sso != null)
687          {
688             // Store it locally
689
synchronized (cache)
690             {
691                cache.put(ssoId, sso);
692             }
693          }
694       }
695
696       return sso;
697    }
698
699
700    /**
701     * Attempts reauthentication to the given <code>Realm</code> using
702     * the credentials associated with the single sign-on session
703     * identified by argument <code>ssoId</code>.
704     * <p/>
705     * If reauthentication is successful, the <code>Principal</code> and
706     * authorization type associated with the SSO session will be bound
707     * to the given <code>HttpRequest</code> object via calls to
708     * {@link HttpRequest#setAuthType HttpRequest.setAuthType()} and
709     * {@link HttpRequest#setUserPrincipal HttpRequest.setUserPrincipal()}
710     * </p>
711     *
712     * @param ssoId identifier of SingleSignOn session with which the
713     * caller is associated
714     * @param realm Realm implementation against which the caller is to
715     * be authenticated
716     * @param request the request that needs to be authenticated
717     * @return <code>true</code> if reauthentication was successful,
718     * <code>false</code> otherwise.
719     */

720    protected boolean reauthenticate(String JavaDoc ssoId, Realm realm,
721       Request request)
722    {
723       if (ssoId == null || realm == null)
724          return false;
725
726       boolean reauthenticated = false;
727
728       SingleSignOnEntry entry = getSingleSignOnEntry(ssoId);
729       if (entry != null && entry.getCanReauthenticate())
730       {
731
732          String JavaDoc username = entry.getUsername();
733          if (username != null)
734          {
735             Principal JavaDoc reauthPrincipal =
736                realm.authenticate(username, entry.getPassword());
737             if (reauthPrincipal != null)
738             {
739                reauthenticated = true;
740                // Bind the authorization credentials to the request
741
request.setAuthType(entry.getAuthType());
742                request.setUserPrincipal(reauthPrincipal);
743                // JBAS-2314 -- bind principal to the entry as well
744
entry.setPrincipal(reauthPrincipal);
745             }
746          }
747       }
748
749       return reauthenticated;
750    }
751
752
753    /**
754     * Register the specified Principal as being associated with the specified
755     * value for the single sign on identifier.
756     * <p/>
757     * Differs from the superclass version in that it notifies the cluster
758     * of the registration.
759     *
760     * @param ssoId Single sign on identifier to register
761     * @param principal Associated user principal that is identified
762     * @param authType Authentication type used to authenticate this
763     * user principal
764     * @param username Username used to authenticate this user
765     * @param password Password used to authenticate this user
766     */

767    protected void register(String JavaDoc ssoId, Principal JavaDoc principal, String JavaDoc authType,
768       String JavaDoc username, String JavaDoc password)
769    {
770       registerLocal(ssoId, principal, authType, username, password);
771
772       // broadcast change to any cluster
773
if (ssoClusterManager != null)
774          ssoClusterManager.register(ssoId, authType, username, password);
775    }
776
777
778    /**
779     * Remove a single Session from a SingleSignOn. Called when
780     * a session is timed out and no longer active.
781     * <p/>
782     * Differs from the superclass version in that it notifies the cluster
783     * of any disassociation of SSO and Session.
784     *
785     * @param ssoId Single sign on identifier from which to remove the session.
786     * @param session the session to be removed.
787     */

788    protected void removeSession(String JavaDoc ssoId, Session session)
789    {
790       if (getContainer().getLogger().isDebugEnabled())
791           getContainer().getLogger().debug("Removing session " + session.toString() +
792             " from sso id " + ssoId);
793
794       // Get a reference to the SingleSignOn
795
SingleSignOnEntry entry = getSingleSignOnEntry(ssoId);
796       if (entry == null)
797          return;
798
799       // Remove the inactive session from SingleSignOnEntry
800
boolean removed = entry.removeSession(session);
801       // If we changed anything, notify any cluster
802
if (removed && ssoClusterManager != null)
803       {
804          ssoClusterManager.removeSession(ssoId, session);
805       }
806
807       // Remove the inactive session from the 'reverse' Map.
808
synchronized (reverse)
809       {
810          reverse.remove(session);
811       }
812
813       // If there are no sessions left in the SingleSignOnEntry,
814
// deregister the entry.
815
if (entry.getSessionCount() == 0)
816       {
817          deregister(ssoId);
818       }
819    }
820
821
822    /**
823     * Updates any <code>SingleSignOnEntry</code> found under key
824     * <code>ssoId</code> with the given authentication data.
825     * <p/>
826     * The purpose of this method is to allow an SSO entry that was
827     * established without a username/password combination (i.e. established
828     * following DIGEST or CLIENT-CERT authentication) to be updated with
829     * a username and password if one becomes available through a subsequent
830     * BASIC or FORM authentication. The SSO entry will then be usable for
831     * reauthentication.
832     * <p/>
833     * <b>NOTE:</b> Only updates the SSO entry if a call to
834     * <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
835     * <code>false</code>; otherwise, it is assumed that the SSO entry already
836     * has sufficient information to allow reauthentication and that no update
837     * is needed.
838     * <p/>
839     * Differs from the superclass version in that it notifies the cluster
840     * of any update.
841     *
842     * @param ssoId identifier of Single sign to be updated
843     * @param principal the <code>Principal</code> returned by the latest
844     * call to <code>Realm.authenticate</code>.
845     * @param authType the type of authenticator used (BASIC, CLIENT-CERT,
846     * DIGEST or FORM)
847     * @param username the username (if any) used for the authentication
848     * @param password the password (if any) used for the authentication
849     */

850    protected void update(String JavaDoc ssoId, Principal JavaDoc principal, String JavaDoc authType,
851       String JavaDoc username, String JavaDoc password)
852    {
853       boolean needToBroadcast = updateLocal(ssoId, principal, authType,
854          username, password);
855                                     
856       // if there was a change, broadcast it to any cluster
857
if (needToBroadcast && ssoClusterManager != null)
858       {
859          ssoClusterManager.updateCredentials(ssoId, authType,
860             username, password);
861       }
862    }
863
864    //---------------------------------------------- Package-Protected Methods
865

866    /**
867     * Search in our local cache for an SSO entry.
868     *
869     * @param ssoId the id of the SSO session
870     * @return any SingleSignOnEntry associated with the given id, or
871     * <code>null</code> if there is none.
872     */

873    SingleSignOnEntry localLookup(String JavaDoc ssoId)
874    {
875       synchronized (cache)
876       {
877          return ((SingleSignOnEntry) cache.get(ssoId));
878       }
879
880    }
881
882    /**
883     * Create a SingleSignOnEntry using the passed configuration parameters and
884     * register it in the local cache, bound to the given id.
885     *
886     * @param ssoId the id of the SSO session
887     * @param principal the <code>Principal</code> returned by the latest
888     * call to <code>Realm.authenticate</code>.
889     * @param authType the type of authenticator used (BASIC, CLIENT-CERT,
890     * DIGEST or FORM)
891     * @param username the username (if any) used for the authentication
892     * @param password the password (if any) used for the authentication
893     */

894    void registerLocal(String JavaDoc ssoId, Principal JavaDoc principal, String JavaDoc authType,
895       String JavaDoc username, String JavaDoc password)
896    {
897       if (getContainer().getLogger().isDebugEnabled())
898       {
899           getContainer().getLogger().debug("Registering sso id '" + ssoId + "' for user '" +
900             principal.getName() + "' with auth type '" + authType + "'");
901       }
902
903       synchronized (cache)
904       {
905          cache.put(ssoId, new SingleSignOnEntry(principal, authType,
906             username, password));
907       }
908    }
909
910    /**
911     * Updates any <code>SingleSignOnEntry</code> found under key
912     * <code>ssoId</code> with the given authentication data.
913     *
914     * @param ssoId identifier of Single sign to be updated
915     * @param principal the <code>Principal</code> returned by the latest
916     * call to <code>Realm.authenticate</code>.
917     * @param authType the type of authenticator used (BASIC, CLIENT-CERT,
918     * DIGEST or FORM)
919     * @param username the username (if any) used for the authentication
920     * @param password the password (if any) used for the authentication
921     * @return <code>true</code> if the update resulted in an actual change
922     * to the entry's authType, username or principal properties
923     */

924    boolean updateLocal(String JavaDoc ssoId, Principal JavaDoc principal, String JavaDoc authType,
925       String JavaDoc username, String JavaDoc password)
926    {
927       boolean shouldBroadcast = false;
928
929       SingleSignOnEntry sso = getSingleSignOnEntry(ssoId);
930       // Only update if the entry is missing information
931
if (sso != null)
932       {
933          if (sso.getCanReauthenticate() == false)
934          {
935             if (getContainer().getLogger().isDebugEnabled())
936                 getContainer().getLogger().debug("Update sso id " + ssoId + " to auth type " + authType);
937
938             synchronized (sso)
939             {
940                shouldBroadcast = sso.updateCredentials(principal, authType,
941                   username, password);
942             }
943          }
944          else if (sso.getPrincipal() == null && principal != null)
945          {
946             if (getContainer().getLogger().isDebugEnabled())
947                 getContainer().getLogger().debug("Update sso id " + ssoId + " with principal " +
948                   principal.getName());
949
950             synchronized (sso)
951             {
952                sso.setPrincipal(principal);
953                // No need to notify cluster; Principals don't replicate
954
}
955          }
956
957       }
958
959       return shouldBroadcast;
960
961    }
962
963    void remoteUpdate(String JavaDoc ssoId, String JavaDoc authType,
964       String JavaDoc username, String JavaDoc password)
965    {
966       SingleSignOnEntry sso = getSingleSignOnEntry(ssoId);
967       // Only update if the entry is missing information
968
if (sso != null && sso.getCanReauthenticate() == false)
969       {
970          if (getContainer().getLogger().isDebugEnabled())
971              getContainer().getLogger().debug("Update sso id " + ssoId + " to auth type " + authType);
972
973          synchronized (sso)
974          {
975             // Use the existing principal
976
Principal JavaDoc p = sso.getPrincipal();
977             sso.updateCredentials(p, authType, username, password);
978          }
979       }
980
981    }
982
983    
984    // ------------------------------------------------------- Private Methods
985

986    
987    /**
988     * Instantiates an instance of the given class, making it this valve's
989     * SSOClusterManager.
990     * <p/>
991     * If this valve has been started and the given class implements
992     * <code>Lifecycle</code>, starts the new SSOClusterManager.
993     *
994     * @param className fully qualified class name of an implementation
995     * of {@link SSOClusterManager SSOClusterManager}.
996     * @throws LifecycleException if there is any problem instantiating or
997     * starting the object, or if the created
998     * object does not implement
999     * <code>SSOClusterManger</code>
1000    */

1001   private void createClusterManager(String JavaDoc className)
1002      throws LifecycleException
1003   {
1004      if (ssoClusterManager != null)
1005         return;
1006
1007      if (className != null)
1008      {
1009         SSOClusterManager mgr = null;
1010         try
1011         {
1012            ClassLoader JavaDoc tcl =
1013               Thread.currentThread().getContextClassLoader();
1014            Class JavaDoc clazz = tcl.loadClass(className);
1015            mgr = (SSOClusterManager) clazz.newInstance();
1016            mgr.setSingleSignOnValve(this);
1017            if (mgr instanceof TreeCacheSSOClusterManager)
1018            {
1019               ((TreeCacheSSOClusterManager) mgr).setCacheName(getTreeCacheName());
1020            }
1021            ssoClusterManager = mgr;
1022            clusterManagerClass = className;
1023         }
1024         catch (Throwable JavaDoc t)
1025         {
1026            throw new LifecycleException("Cannot create " +
1027               "SSOClusterManager using " +
1028               className, t);
1029         }
1030
1031         if (started)
1032         {
1033            ssoClusterManager.start();
1034         }
1035      }
1036   }
1037
1038}
1039
Popular Tags