1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.authenticator;
20
21
22 import java.io.IOException;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.Principal;
26 import java.text.SimpleDateFormat;
27 import java.util.Date;
28 import java.util.Locale;
29 import java.util.Random;
30
31 import javax.servlet.ServletException;
32 import javax.servlet.http.Cookie;
33
34 import org.apache.catalina.Authenticator;
35 import org.apache.catalina.Container;
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Lifecycle;
38 import org.apache.catalina.LifecycleException;
39 import org.apache.catalina.LifecycleListener;
40 import org.apache.catalina.Manager;
41 import org.apache.catalina.Pipeline;
42 import org.apache.catalina.Realm;
43 import org.apache.catalina.Session;
44 import org.apache.catalina.Valve;
45 import org.apache.catalina.connector.Request;
46 import org.apache.catalina.connector.Response;
47 import org.apache.catalina.deploy.LoginConfig;
48 import org.apache.catalina.deploy.SecurityConstraint;
49 import org.apache.catalina.util.DateTool;
50 import org.apache.catalina.util.LifecycleSupport;
51 import org.apache.catalina.util.StringManager;
52 import org.apache.catalina.valves.ValveBase;
53 import org.apache.juli.logging.Log;
54 import org.apache.juli.logging.LogFactory;
55
56
57 /**
58 * Basic implementation of the <b>Valve</b> interface that enforces the
59 * <code><security-constraint></code> elements in the web application
60 * deployment descriptor. This functionality is implemented as a Valve
61 * so that it can be ommitted in environments that do not require these
62 * features. Individual implementations of each supported authentication
63 * method can subclass this base class as required.
64 * <p>
65 * <b>USAGE CONSTRAINT</b>: When this class is utilized, the Context to
66 * which it is attached (or a parent Container in a hierarchy) must have an
67 * associated Realm that can be used for authenticating users and enumerating
68 * the roles to which they have been assigned.
69 * <p>
70 * <b>USAGE CONSTRAINT</b>: This Valve is only useful when processing HTTP
71 * requests. Requests of any other type will simply be passed through.
72 *
73 * @author Craig R. McClanahan
74 * @version $Revision: 892545 $ $Date: 2009-12-20 02:04:17 +0100 (Sun, 20 Dec 2009) $
75 */
76
77
78 public abstract class AuthenticatorBase
79 extends ValveBase
80 implements Authenticator, Lifecycle {
81 private static Log log = LogFactory.getLog(AuthenticatorBase.class);
82
83
84 // ----------------------------------------------------- Instance Variables
85
86
87 /**
88 * The default message digest algorithm to use if we cannot use
89 * the requested one.
90 */
91 protected static final String DEFAULT_ALGORITHM = "MD5";
92
93
94 /**
95 * The number of random bytes to include when generating a
96 * session identifier.
97 */
98 protected static final int SESSION_ID_BYTES = 16;
99
100
101 /**
102 * The message digest algorithm to be used when generating session
103 * identifiers. This must be an algorithm supported by the
104 * <code>java.security.MessageDigest</code> class on your platform.
105 */
106 protected String algorithm = DEFAULT_ALGORITHM;
107
108
109 /**
110 * Should we cache authenticated Principals if the request is part of
111 * an HTTP session?
112 */
113 protected boolean cache = true;
114
115
116 /**
117 * Should the session ID, if any, be changed upon a successful
118 * authentication to prevent a session fixation attack?
119 */
120 protected boolean changeSessionIdOnAuthentication = true;
121
122 /**
123 * The Context to which this Valve is attached.
124 */
125 protected Context context = null;
126
127
128 /**
129 * Return the MessageDigest implementation to be used when
130 * creating session identifiers.
131 */
132 protected MessageDigest digest = null;
133
134
135 /**
136 * A String initialization parameter used to increase the entropy of
137 * the initialization of our random number generator.
138 */
139 protected String entropy = null;
140
141
142 /**
143 * Descriptive information about this implementation.
144 */
145 protected static final String info =
146 "org.apache.catalina.authenticator.AuthenticatorBase/1.0";
147
148 /**
149 * Flag to determine if we disable proxy caching, or leave the issue
150 * up to the webapp developer.
151 */
152 protected boolean disableProxyCaching = true;
153
154 /**
155 * Flag to determine if we disable proxy caching with headers incompatible
156 * with IE
157 */
158 protected boolean securePagesWithPragma = true;
159
160 /**
161 * The lifecycle event support for this component.
162 */
163 protected LifecycleSupport lifecycle = new LifecycleSupport(this);
164
165
166 /**
167 * A random number generator to use when generating session identifiers.
168 */
169 protected Random random = null;
170
171
172 /**
173 * The Java class name of the random number generator class to be used
174 * when generating session identifiers.
175 */
176 protected String randomClass = "java.security.SecureRandom";
177
178
179 /**
180 * The string manager for this package.
181 */
182 protected static final StringManager sm =
183 StringManager.getManager(Constants.Package);
184
185
186 /**
187 * The SingleSignOn implementation in our request processing chain,
188 * if there is one.
189 */
190 protected SingleSignOn sso = null;
191
192
193 /**
194 * Has this component been started?
195 */
196 protected boolean started = false;
197
198
199 /**
200 * "Expires" header always set to Date(1), so generate once only
201 */
202 private static final String DATE_ONE =
203 (new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
204 Locale.US)).format(new Date(1));
205
206
207 // ------------------------------------------------------------- Properties
208
209
210 /**
211 * Return the message digest algorithm for this Manager.
212 */
213 public String getAlgorithm() {
214
215 return (this.algorithm);
216
217 }
218
219
220 /**
221 * Set the message digest algorithm for this Manager.
222 *
223 * @param algorithm The new message digest algorithm
224 */
225 public void setAlgorithm(String algorithm) {
226
227 this.algorithm = algorithm;
228
229 }
230
231
232 /**
233 * Return the cache authenticated Principals flag.
234 */
235 public boolean getCache() {
236
237 return (this.cache);
238
239 }
240
241
242 /**
243 * Set the cache authenticated Principals flag.
244 *
245 * @param cache The new cache flag
246 */
247 public void setCache(boolean cache) {
248
249 this.cache = cache;
250
251 }
252
253
254 /**
255 * Return the Container to which this Valve is attached.
256 */
257 public Container getContainer() {
258
259 return (this.context);
260
261 }
262
263
264 /**
265 * Set the Container to which this Valve is attached.
266 *
267 * @param container The container to which we are attached
268 */
269 public void setContainer(Container container) {
270
271 if (!(container instanceof Context))
272 throw new IllegalArgumentException
273 (sm.getString("authenticator.notContext"));
274
275 super.setContainer(container);
276 this.context = (Context) container;
277
278 }
279
280
281 /**
282 * Return the entropy increaser value, or compute a semi-useful value
283 * if this String has not yet been set.
284 */
285 public String getEntropy() {
286
287 // Calculate a semi-useful value if this has not been set
288 if (this.entropy == null)
289 setEntropy(this.toString());
290
291 return (this.entropy);
292
293 }
294
295
296 /**
297 * Set the entropy increaser value.
298 *
299 * @param entropy The new entropy increaser value
300 */
301 public void setEntropy(String entropy) {
302
303 this.entropy = entropy;
304
305 }
306
307
308 /**
309 * Return descriptive information about this Valve implementation.
310 */
311 public String getInfo() {
312
313 return (info);
314
315 }
316
317
318 /**
319 * Return the random number generator class name.
320 */
321 public String getRandomClass() {
322
323 return (this.randomClass);
324
325 }
326
327
328 /**
329 * Set the random number generator class name.
330 *
331 * @param randomClass The new random number generator class name
332 */
333 public void setRandomClass(String randomClass) {
334
335 this.randomClass = randomClass;
336
337 }
338
339 /**
340 * Return the flag that states if we add headers to disable caching by
341 * proxies.
342 */
343 public boolean getDisableProxyCaching() {
344 return disableProxyCaching;
345 }
346
347 /**
348 * Set the value of the flag that states if we add headers to disable
349 * caching by proxies.
350 * @param nocache <code>true</code> if we add headers to disable proxy
351 * caching, <code>false</code> if we leave the headers alone.
352 */
353 public void setDisableProxyCaching(boolean nocache) {
354 disableProxyCaching = nocache;
355 }
356
357 /**
358 * Return the flag that states, if proxy caching is disabled, what headers
359 * we add to disable the caching.
360 */
361 public boolean getSecurePagesWithPragma() {
362 return securePagesWithPragma;
363 }
364
365 /**
366 * Set the value of the flag that states what headers we add to disable
367 * proxy caching.
368 * @param securePagesWithPragma <code>true</code> if we add headers which
369 * are incompatible with downloading office documents in IE under SSL but
370 * which fix a caching problem in Mozilla.
371 */
372 public void setSecurePagesWithPragma(boolean securePagesWithPragma) {
373 this.securePagesWithPragma = securePagesWithPragma;
374 }
375
376 /**
377 * Return the flag that states if we should change the session ID of an
378 * existing session upon successful authentication.
379 *
380 * @return <code>true</code> to change session ID upon successful
381 * authentication, <code>false</code> to do not perform the change.
382 */
383 public boolean getChangeSessionIdOnAuthentication() {
384 return changeSessionIdOnAuthentication;
385 }
386
387 /**
388 * Set the value of the flag that states if we should change the session ID
389 * of an existing session upon successful authentication.
390 *
391 * @param changeSessionIdOnAuthentication
392 * <code>true</code> to change session ID upon successful
393 * authentication, <code>false</code> to do not perform the
394 * change.
395 */
396 public void setChangeSessionIdOnAuthentication(
397 boolean changeSessionIdOnAuthentication) {
398 this.changeSessionIdOnAuthentication = changeSessionIdOnAuthentication;
399 }
400
401 // --------------------------------------------------------- Public Methods
402
403
404 /**
405 * Enforce the security restrictions in the web application deployment
406 * descriptor of our associated Context.
407 *
408 * @param request Request to be processed
409 * @param response Response to be processed
410 *
411 * @exception IOException if an input/output error occurs
412 * @exception ServletException if thrown by a processing element
413 */
414 public void invoke(Request request, Response response)
415 throws IOException, ServletException {
416
417 if (log.isDebugEnabled())
418 log.debug("Security checking request " +
419 request.getMethod() + " " + request.getRequestURI());
420 LoginConfig config = this.context.getLoginConfig();
421
422 // Have we got a cached authenticated Principal to record?
423 if (cache) {
424 Principal principal = request.getUserPrincipal();
425 if (principal == null) {
426 Session session = request.getSessionInternal(false);
427 if (session != null) {
428 principal = session.getPrincipal();
429 if (principal != null) {
430 if (log.isDebugEnabled())
431 log.debug("We have cached auth type " +
432 session.getAuthType() +
433 " for principal " +
434 session.getPrincipal());
435 request.setAuthType(session.getAuthType());
436 request.setUserPrincipal(principal);
437 }
438 }
439 }
440 }
441
442 // Special handling for form-based logins to deal with the case
443 // where the login form (and therefore the "j_security_check" URI
444 // to which it submits) might be outside the secured area
445 String contextPath = this.context.getPath();
446 String requestURI = request.getDecodedRequestURI();
447 if (requestURI.startsWith(contextPath) &&
448 requestURI.endsWith(Constants.FORM_ACTION)) {
449 if (!authenticate(request, response, config)) {
450 if (log.isDebugEnabled())
451 log.debug(" Failed authenticate() test ??" + requestURI );
452 return;
453 }
454 }
455
456 Realm realm = this.context.getRealm();
457 // Is this request URI subject to a security constraint?
458 SecurityConstraint [] constraints
459 = realm.findSecurityConstraints(request, this.context);
460
461 if ((constraints == null) /* &&
462 (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
463 if (log.isDebugEnabled())
464 log.debug(" Not subject to any constraint");
465 getNext().invoke(request, response);
466 return;
467 }
468
469 // Make sure that constrained resources are not cached by web proxies
470 // or browsers as caching can provide a security hole
471 if (disableProxyCaching &&
472 // FIXME: Disabled for Mozilla FORM support over SSL
473 // (improper caching issue)
474 //!request.isSecure() &&
475 !"POST".equalsIgnoreCase(request.getMethod())) {
476 if (securePagesWithPragma) {
477 // FIXME: These cause problems with downloading office docs
478 // from IE under SSL and may not be needed for newer Mozilla
479 // clients.
480 response.setHeader("Pragma", "No-cache");
481 response.setHeader("Cache-Control", "no-cache");
482 } else {
483 response.setHeader("Cache-Control", "private");
484 }
485 response.setHeader("Expires", DATE_ONE);
486 }
487
488 int i;
489 // Enforce any user data constraint for this security constraint
490 if (log.isDebugEnabled()) {
491 log.debug(" Calling hasUserDataPermission()");
492 }
493 if (!realm.hasUserDataPermission(request, response,
494 constraints)) {
495 if (log.isDebugEnabled()) {
496 log.debug(" Failed hasUserDataPermission() test");
497 }
498 /*
499 * ASSERT: Authenticator already set the appropriate
500 * HTTP status code, so we do not have to do anything special
501 */
502 return;
503 }
504
505 // Since authenticate modifies the response on failure,
506 // we have to check for allow-from-all first.
507 boolean authRequired = true;
508 for(i=0; i < constraints.length && authRequired; i++) {
509 if(!constraints[i].getAuthConstraint()) {
510 authRequired = false;
511 } else if(!constraints[i].getAllRoles()) {
512 String [] roles = constraints[i].findAuthRoles();
513 if(roles == null || roles.length == 0) {
514 authRequired = false;
515 }
516 }
517 }
518
519 if(authRequired) {
520 if (log.isDebugEnabled()) {
521 log.debug(" Calling authenticate()");
522 }
523 if (!authenticate(request, response, config)) {
524 if (log.isDebugEnabled()) {
525 log.debug(" Failed authenticate() test");
526 }
527 /*
528 * ASSERT: Authenticator already set the appropriate
529 * HTTP status code, so we do not have to do anything
530 * special
531 */
532 return;
533 }
534
535 }
536
537 if (log.isDebugEnabled()) {
538 log.debug(" Calling accessControl()");
539 }
540 if (!realm.hasResourcePermission(request, response,
541 constraints,
542 this.context)) {
543 if (log.isDebugEnabled()) {
544 log.debug(" Failed accessControl() test");
545 }
546 /*
547 * ASSERT: AccessControl method has already set the
548 * appropriate HTTP status code, so we do not have to do
549 * anything special
550 */
551 return;
552 }
553
554 // Any and all specified constraints have been satisfied
555 if (log.isDebugEnabled()) {
556 log.debug(" Successfully passed all security constraints");
557 }
558 getNext().invoke(request, response);
559
560 }
561
562
563 // ------------------------------------------------------ Protected Methods
564
565
566
567
568 /**
569 * Associate the specified single sign on identifier with the
570 * specified Session.
571 *
572 * @param ssoId Single sign on identifier
573 * @param session Session to be associated
574 */
575 protected void associate(String ssoId, Session session) {
576
577 if (sso == null)
578 return;
579 sso.associate(ssoId, session);
580
581 }
582
583
584 /**
585 * Authenticate the user making this request, based on the specified
586 * login configuration. Return <code>true</code> if any specified
587 * constraint has been satisfied, or <code>false</code> if we have
588 * created a response challenge already.
589 *
590 * @param request Request we are processing
591 * @param response Response we are creating
592 * @param config Login configuration describing how authentication
593 * should be performed
594 *
595 * @exception IOException if an input/output error occurs
596 */
597 protected abstract boolean authenticate(Request request,
598 Response response,
599 LoginConfig config)
600 throws IOException;
601
602
603 /**
604 * Generate and return a new session identifier for the cookie that
605 * identifies an SSO principal.
606 */
607 protected synchronized String generateSessionId() {
608
609 // Generate a byte array containing a session identifier
610 byte bytes[] = new byte[SESSION_ID_BYTES];
611 getRandom().nextBytes(bytes);
612 bytes = getDigest().digest(bytes);
613
614 // Render the result as a String of hexadecimal digits
615 StringBuffer result = new StringBuffer();
616 for (int i = 0; i < bytes.length; i++) {
617 byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
618 byte b2 = (byte) (bytes[i] & 0x0f);
619 if (b1 < 10)
620 result.append((char) ('0' + b1));
621 else
622 result.append((char) ('A' + (b1 - 10)));
623 if (b2 < 10)
624 result.append((char) ('0' + b2));
625 else
626 result.append((char) ('A' + (b2 - 10)));
627 }
628 return (result.toString());
629
630 }
631
632
633 /**
634 * Return the MessageDigest object to be used for calculating
635 * session identifiers. If none has been created yet, initialize
636 * one the first time this method is called.
637 */
638 protected synchronized MessageDigest getDigest() {
639
640 if (this.digest == null) {
641 try {
642 this.digest = MessageDigest.getInstance(algorithm);
643 } catch (NoSuchAlgorithmException e) {
644 try {
645 this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
646 } catch (NoSuchAlgorithmException f) {
647 this.digest = null;
648 }
649 }
650 }
651
652 return (this.digest);
653
654 }
655
656
657 /**
658 * Return the random number generator instance we should use for
659 * generating session identifiers. If there is no such generator
660 * currently defined, construct and seed a new one.
661 */
662 protected synchronized Random getRandom() {
663
664 if (this.random == null) {
665 try {
666 Class clazz = Class.forName(randomClass);
667 this.random = (Random) clazz.newInstance();
668 long seed = System.currentTimeMillis();
669 char entropy[] = getEntropy().toCharArray();
670 for (int i = 0; i < entropy.length; i++) {
671 long update = ((byte) entropy[i]) << ((i % 8) * 8);
672 seed ^= update;
673 }
674 this.random.setSeed(seed);
675 } catch (Exception e) {
676 this.random = new java.util.Random();
677 }
678 }
679
680 return (this.random);
681
682 }
683
684
685 /**
686 * Attempts reauthentication to the <code>Realm</code> using
687 * the credentials included in argument <code>entry</code>.
688 *
689 * @param ssoId identifier of SingleSignOn session with which the
690 * caller is associated
691 * @param request the request that needs to be authenticated
692 */
693 protected boolean reauthenticateFromSSO(String ssoId, Request request) {
694
695 if (sso == null || ssoId == null)
696 return false;
697
698 boolean reauthenticated = false;
699
700 Container parent = getContainer();
701 if (parent != null) {
702 Realm realm = parent.getRealm();
703 if (realm != null) {
704 reauthenticated = sso.reauthenticate(ssoId, realm, request);
705 }
706 }
707
708 if (reauthenticated) {
709 associate(ssoId, request.getSessionInternal(true));
710
711 if (log.isDebugEnabled()) {
712 log.debug(" Reauthenticated cached principal '" +
713 request.getUserPrincipal().getName() +
714 "' with auth type '" + request.getAuthType() + "'");
715 }
716 }
717
718 return reauthenticated;
719 }
720
721
722 /**
723 * Register an authenticated Principal and authentication type in our
724 * request, in the current session (if there is one), and with our
725 * SingleSignOn valve, if there is one. Set the appropriate cookie
726 * to be returned.
727 *
728 * @param request The servlet request we are processing
729 * @param response The servlet response we are generating
730 * @param principal The authenticated Principal to be registered
731 * @param authType The authentication type to be registered
732 * @param username Username used to authenticate (if any)
733 * @param password Password used to authenticate (if any)
734 */
735 protected void register(Request request, Response response,
736 Principal principal, String authType,
737 String username, String password) {
738
739 if (log.isDebugEnabled())
740 log.debug("Authenticated '" + principal.getName() + "' with type '"
741 + authType + "'");
742
743 // Cache the authentication information in our request
744 request.setAuthType(authType);
745 request.setUserPrincipal(principal);
746
747 Session session = request.getSessionInternal(false);
748
749 if (session != null && changeSessionIdOnAuthentication) {
750 Manager manager = request.getContext().getManager();
751 manager.changeSessionId(session);
752 request.changeSessionId(session.getId());
753 }
754
755 // Cache the authentication information in our session, if any
756 if (cache) {
757 if (session != null) {
758 session.setAuthType(authType);
759 session.setPrincipal(principal);
760 if (username != null)
761 session.setNote(Constants.SESS_USERNAME_NOTE, username);
762 else
763 session.removeNote(Constants.SESS_USERNAME_NOTE);
764 if (password != null)
765 session.setNote(Constants.SESS_PASSWORD_NOTE, password);
766 else
767 session.removeNote(Constants.SESS_PASSWORD_NOTE);
768 }
769 }
770
771 // Construct a cookie to be returned to the client
772 if (sso == null)
773 return;
774
775 // Only create a new SSO entry if the SSO did not already set a note
776 // for an existing entry (as it would do with subsequent requests
777 // for DIGEST and SSL authenticated contexts)
778 String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
779 if (ssoId == null) {
780 // Construct a cookie to be returned to the client
781 ssoId = generateSessionId();
782 Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
783 cookie.setMaxAge(-1);
784 cookie.setPath("/");
785
786 // Bugzilla 41217
787 cookie.setSecure(request.isSecure());
788
789 // Bugzilla 34724
790 String ssoDomain = sso.getCookieDomain();
791 if(ssoDomain != null) {
792 cookie.setDomain(ssoDomain);
793 }
794
795 response.addCookie(cookie);
796
797 // Register this principal with our SSO valve
798 sso.register(ssoId, principal, authType, username, password);
799 request.setNote(Constants.REQ_SSOID_NOTE, ssoId);
800
801 } else {
802 // Update the SSO session with the latest authentication data
803 sso.update(ssoId, principal, authType, username, password);
804 }
805
806 // Fix for Bug 10040
807 // Always associate a session with a new SSO reqistration.
808 // SSO entries are only removed from the SSO registry map when
809 // associated sessions are destroyed; if a new SSO entry is created
810 // above for this request and the user never revisits the context, the
811 // SSO entry will never be cleared if we don't associate the session
812 if (session == null)
813 session = request.getSessionInternal(true);
814 sso.associate(ssoId, session);
815
816 }
817
818
819 // ------------------------------------------------------ Lifecycle Methods
820
821
822 /**
823 * Add a lifecycle event listener to this component.
824 *
825 * @param listener The listener to add
826 */
827 public void addLifecycleListener(LifecycleListener listener) {
828
829 lifecycle.addLifecycleListener(listener);
830
831 }
832
833
834 /**
835 * Get the lifecycle listeners associated with this lifecycle. If this
836 * Lifecycle has no listeners registered, a zero-length array is returned.
837 */
838 public LifecycleListener[] findLifecycleListeners() {
839
840 return lifecycle.findLifecycleListeners();
841
842 }
843
844
845 /**
846 * Remove a lifecycle event listener from this component.
847 *
848 * @param listener The listener to remove
849 */
850 public void removeLifecycleListener(LifecycleListener listener) {
851
852 lifecycle.removeLifecycleListener(listener);
853
854 }
855
856
857 /**
858 * Prepare for the beginning of active use of the public methods of this
859 * component. This method should be called after <code>configure()</code>,
860 * and before any of the public methods of the component are utilized.
861 *
862 * @exception LifecycleException if this component detects a fatal error
863 * that prevents this component from being used
864 */
865 public void start() throws LifecycleException {
866
867 // Validate and update our current component state
868 if (started)
869 throw new LifecycleException
870 (sm.getString("authenticator.alreadyStarted"));
871 lifecycle.fireLifecycleEvent(START_EVENT, null);
872 started = true;
873
874 // Look up the SingleSignOn implementation in our request processing
875 // path, if there is one
876 Container parent = context.getParent();
877 while ((sso == null) && (parent != null)) {
878 if (!(parent instanceof Pipeline)) {
879 parent = parent.getParent();
880 continue;
881 }
882 Valve valves[] = ((Pipeline) parent).getValves();
883 for (int i = 0; i < valves.length; i++) {
884 if (valves[i] instanceof SingleSignOn) {
885 sso = (SingleSignOn) valves[i];
886 break;
887 }
888 }
889 if (sso == null)
890 parent = parent.getParent();
891 }
892 if (log.isDebugEnabled()) {
893 if (sso != null)
894 log.debug("Found SingleSignOn Valve at " + sso);
895 else
896 log.debug("No SingleSignOn Valve is present");
897 }
898
899 }
900
901
902 /**
903 * Gracefully terminate the active use of the public methods of this
904 * component. This method should be the last one called on a given
905 * instance of this component.
906 *
907 * @exception LifecycleException if this component detects a fatal error
908 * that needs to be reported
909 */
910 public void stop() throws LifecycleException {
911
912 // Validate and update our current component state
913 if (!started)
914 throw new LifecycleException
915 (sm.getString("authenticator.notStarted"));
916 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
917 started = false;
918
919 sso = null;
920
921 }
922
923
924 }