001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.ha.authenticator;
019:
020: import java.security.Principal;
021:
022: import org.apache.catalina.Container;
023: import org.apache.catalina.Cluster;
024: import org.apache.catalina.Engine;
025: import org.apache.catalina.Host;
026: import org.apache.catalina.LifecycleException;
027: import org.apache.catalina.Manager;
028: import org.apache.catalina.Session;
029: import org.apache.catalina.authenticator.SingleSignOn;
030: import org.apache.catalina.ha.CatalinaCluster;
031: import org.apache.catalina.ha.ClusterManager;
032:
033: /**
034: * A <strong>Valve</strong> that supports a "single sign on" user experience on
035: * each nodes of a cluster, where the security identity of a user who successfully
036: * authenticates to one web application is propogated to other web applications and
037: * to other nodes cluster in the same security domain. For successful use, the following
038: * requirements must be met:
039: * <ul>
040: * <li>This Valve must be configured on the Container that represents a
041: * virtual host (typically an implementation of <code>Host</code>).</li>
042: * <li>The <code>Realm</code> that contains the shared user and role
043: * information must be configured on the same Container (or a higher
044: * one), and not overridden at the web application level.</li>
045: * <li>The web applications themselves must use one of the standard
046: * Authenticators found in the
047: * <code>org.apache.catalina.authenticator</code> package.</li>
048: * </ul>
049: *
050: * @author Fabien Carrion
051: */
052:
053: public class ClusterSingleSignOn extends SingleSignOn {
054:
055: // ----------------------------------------------------- Instance Variables
056:
057: /**
058: * Descriptive information about this Valve implementation.
059: */
060: protected static String info = "org.apache.catalina.cluster.authenticator.ClusterSingleSignOn";
061:
062: protected int messageNumber = 0;
063:
064: private ClusterSingleSignOnListener clusterSSOListener = null;
065:
066: // ------------------------------------------------------------- Properties
067:
068: private CatalinaCluster cluster = null;
069:
070: /**
071: * Return descriptive information about this Valve implementation.
072: */
073: public String getInfo() {
074:
075: return (info);
076:
077: }
078:
079: public CatalinaCluster getCluster() {
080:
081: return cluster;
082:
083: }
084:
085: public void setCluster(CatalinaCluster cluster) {
086:
087: this .cluster = cluster;
088:
089: }
090:
091: // ------------------------------------------------------ Lifecycle Methods
092:
093: /**
094: * Prepare for the beginning of active use of the public methods of this
095: * component. This method should be called after <code>configure()</code>,
096: * and before any of the public methods of the component are utilized.
097: *
098: * @exception LifecycleException if this component detects a fatal error
099: * that prevents this component from being used
100: */
101: public void start() throws LifecycleException {
102:
103: super .start();
104:
105: clusterSSOListener = new ClusterSingleSignOnListener();
106: clusterSSOListener.setClusterSSO(this );
107:
108: // Load the cluster component, if any
109: try {
110: //the channel is already running
111: Cluster cluster = getCluster();
112: // stop remove cluster binding
113: if (cluster == null) {
114: Container host = getContainer();
115: if (host != null && host instanceof Host) {
116: cluster = host.getCluster();
117: if (cluster != null
118: && cluster instanceof CatalinaCluster) {
119: setCluster((CatalinaCluster) cluster);
120: getCluster().addClusterListener(
121: clusterSSOListener);
122: } else {
123: Container engine = host.getParent();
124: if (engine != null && engine instanceof Engine) {
125: cluster = engine.getCluster();
126: if (cluster != null
127: && cluster instanceof CatalinaCluster) {
128: setCluster((CatalinaCluster) cluster);
129: getCluster().addClusterListener(
130: clusterSSOListener);
131: }
132: } else {
133: cluster = null;
134: }
135: }
136: }
137: }
138: if (cluster == null) {
139: throw new LifecycleException(
140: "There is no Cluster for ClusterSingleSignOn");
141: }
142:
143: } catch (Throwable t) {
144: throw new LifecycleException(
145: "ClusterSingleSignOn exception during clusterLoad "
146: + t);
147: }
148:
149: }
150:
151: /**
152: * Gracefully terminate the active use of the public methods of this
153: * component. This method should be the last one called on a given
154: * instance of this component.
155: *
156: * @exception LifecycleException if this component detects a fatal error
157: * that needs to be reported
158: */
159: public void stop() throws LifecycleException {
160:
161: super .stop();
162:
163: if (getCluster() != null
164: && getCluster() instanceof CatalinaCluster) {
165: getCluster().removeClusterListener(clusterSSOListener);
166: }
167:
168: }
169:
170: // --------------------------------------------------------- Public Methods
171:
172: /**
173: * Return a String rendering of this object.
174: */
175: public String toString() {
176:
177: StringBuffer sb = new StringBuffer("ClusterSingleSignOn[");
178: if (container == null)
179: sb.append("Container is null");
180: else
181: sb.append(container.getName());
182: sb.append("]");
183: return (sb.toString());
184:
185: }
186:
187: // ------------------------------------------------------ Protected Methods
188:
189: /**
190: * Notify the cluster of the addition of a Session to
191: * an SSO session and associate the specified single
192: * sign on identifier with the specified Session on the
193: * local node.
194: *
195: * @param ssoId Single sign on identifier
196: * @param session Session to be associated
197: */
198: protected void associate(String ssoId, Session session) {
199:
200: if (cluster != null) {
201: messageNumber++;
202: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
203: .getLocalMember(), ssoId, session.getId());
204: Manager mgr = session.getManager();
205: if ((mgr != null) && (mgr instanceof ClusterManager))
206: msg.setContextName(((ClusterManager) mgr).getName());
207:
208: msg.setAction(SingleSignOnMessage.ADD_SESSION);
209:
210: cluster.sendClusterDomain(msg);
211:
212: if (containerLog.isDebugEnabled())
213: containerLog
214: .debug("SingleSignOnMessage Send with action "
215: + msg.getAction());
216: }
217:
218: associateLocal(ssoId, session);
219:
220: }
221:
222: protected void associateLocal(String ssoId, Session session) {
223:
224: super .associate(ssoId, session);
225:
226: }
227:
228: /**
229: * Notify the cluster of the removal of a Session from an
230: * SSO session and deregister the specified session. If it is the last
231: * session, then also get rid of the single sign on identifier on the
232: * local node.
233: *
234: * @param ssoId Single sign on identifier
235: * @param session Session to be deregistered
236: */
237: protected void deregister(String ssoId, Session session) {
238:
239: if (cluster != null) {
240: messageNumber++;
241: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
242: .getLocalMember(), ssoId, session.getId());
243: Manager mgr = session.getManager();
244: if ((mgr != null) && (mgr instanceof ClusterManager))
245: msg.setContextName(((ClusterManager) mgr).getName());
246:
247: msg.setAction(SingleSignOnMessage.DEREGISTER_SESSION);
248:
249: cluster.sendClusterDomain(msg);
250: if (containerLog.isDebugEnabled())
251: containerLog
252: .debug("SingleSignOnMessage Send with action "
253: + msg.getAction());
254: }
255:
256: deregisterLocal(ssoId, session);
257:
258: }
259:
260: protected void deregisterLocal(String ssoId, Session session) {
261:
262: super .deregister(ssoId, session);
263:
264: }
265:
266: /**
267: * Notifies the cluster that a single sign on session
268: * has been terminated due to a user logout, deregister
269: * the specified single sign on identifier, and invalidate
270: * any associated sessions on the local node.
271: *
272: * @param ssoId Single sign on identifier to deregister
273: */
274: protected void deregister(String ssoId) {
275:
276: if (cluster != null) {
277: messageNumber++;
278: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
279: .getLocalMember(), ssoId, null);
280: msg.setAction(SingleSignOnMessage.LOGOUT_SESSION);
281:
282: cluster.sendClusterDomain(msg);
283: if (containerLog.isDebugEnabled())
284: containerLog
285: .debug("SingleSignOnMessage Send with action "
286: + msg.getAction());
287: }
288:
289: deregisterLocal(ssoId);
290:
291: }
292:
293: protected void deregisterLocal(String ssoId) {
294:
295: super .deregister(ssoId);
296:
297: }
298:
299: /**
300: * Notifies the cluster of the creation of a new SSO entry
301: * and register the specified Principal as being associated
302: * with the specified value for the single sign on identifier.
303: *
304: * @param ssoId Single sign on identifier to register
305: * @param principal Associated user principal that is identified
306: * @param authType Authentication type used to authenticate this
307: * user principal
308: * @param username Username used to authenticate this user
309: * @param password Password used to authenticate this user
310: */
311: protected void register(String ssoId, Principal principal,
312: String authType, String username, String password) {
313:
314: if (cluster != null) {
315: messageNumber++;
316: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
317: .getLocalMember(), ssoId, null);
318: msg.setAction(SingleSignOnMessage.REGISTER_SESSION);
319: msg.setAuthType(authType);
320: msg.setUsername(username);
321: msg.setPassword(password);
322:
323: cluster.sendClusterDomain(msg);
324: if (containerLog.isDebugEnabled())
325: containerLog
326: .debug("SingleSignOnMessage Send with action "
327: + msg.getAction());
328: }
329:
330: registerLocal(ssoId, principal, authType, username, password);
331:
332: }
333:
334: protected void registerLocal(String ssoId, Principal principal,
335: String authType, String username, String password) {
336:
337: super .register(ssoId, principal, authType, username, password);
338:
339: }
340:
341: /**
342: * Notifies the cluster of an update of the security credentials
343: * associated with an SSO session. Updates any <code>SingleSignOnEntry</code>
344: * found under key <code>ssoId</code> with the given authentication data.
345: * <p>
346: * The purpose of this method is to allow an SSO entry that was
347: * established without a username/password combination (i.e. established
348: * following DIGEST or CLIENT-CERT authentication) to be updated with
349: * a username and password if one becomes available through a subsequent
350: * BASIC or FORM authentication. The SSO entry will then be usable for
351: * reauthentication.
352: * <p>
353: * <b>NOTE:</b> Only updates the SSO entry if a call to
354: * <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
355: * <code>false</code>; otherwise, it is assumed that the SSO entry already
356: * has sufficient information to allow reauthentication and that no update
357: * is needed.
358: *
359: * @param ssoId identifier of Single sign to be updated
360: * @param principal the <code>Principal</code> returned by the latest
361: * call to <code>Realm.authenticate</code>.
362: * @param authType the type of authenticator used (BASIC, CLIENT-CERT,
363: * DIGEST or FORM)
364: * @param username the username (if any) used for the authentication
365: * @param password the password (if any) used for the authentication
366: */
367: protected void update(String ssoId, Principal principal,
368: String authType, String username, String password) {
369:
370: if (cluster != null) {
371: messageNumber++;
372: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
373: .getLocalMember(), ssoId, null);
374: msg.setAction(SingleSignOnMessage.UPDATE_SESSION);
375: msg.setAuthType(authType);
376: msg.setUsername(username);
377: msg.setPassword(password);
378:
379: cluster.sendClusterDomain(msg);
380: if (containerLog.isDebugEnabled())
381: containerLog
382: .debug("SingleSignOnMessage Send with action "
383: + msg.getAction());
384: }
385:
386: updateLocal(ssoId, principal, authType, username, password);
387:
388: }
389:
390: protected void updateLocal(String ssoId, Principal principal,
391: String authType, String username, String password) {
392:
393: super .update(ssoId, principal, authType, username, password);
394:
395: }
396:
397: /**
398: * Remove a single Session from a SingleSignOn and notify the cluster
399: * of the removal. Called when a session is timed out and no longer active.
400: *
401: * @param ssoId Single sign on identifier from which to remove the session.
402: * @param session the session to be removed.
403: */
404: protected void removeSession(String ssoId, Session session) {
405:
406: if (cluster != null) {
407: messageNumber++;
408: SingleSignOnMessage msg = new SingleSignOnMessage(cluster
409: .getLocalMember(), ssoId, session.getId());
410:
411: Manager mgr = session.getManager();
412: if ((mgr != null) && (mgr instanceof ClusterManager))
413: msg.setContextName(((ClusterManager) mgr).getName());
414:
415: msg.setAction(SingleSignOnMessage.REMOVE_SESSION);
416:
417: cluster.sendClusterDomain(msg);
418: if (containerLog.isDebugEnabled())
419: containerLog
420: .debug("SingleSignOnMessage Send with action "
421: + msg.getAction());
422: }
423:
424: removeSessionLocal(ssoId, session);
425: }
426:
427: protected void removeSessionLocal(String ssoId, Session session) {
428:
429: super.removeSession(ssoId, session);
430:
431: }
432:
433: }
|