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 package org.apache.catalina.authenticator;
18
19 import java.io.IOException;
20 import java.security.Principal;
21 import java.security.cert.X509Certificate;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26
27 import javax.security.auth.Subject;
28 import javax.security.auth.callback.CallbackHandler;
29 import javax.security.auth.message.AuthException;
30 import javax.security.auth.message.AuthStatus;
31 import javax.security.auth.message.MessageInfo;
32 import javax.security.auth.message.config.AuthConfigFactory;
33 import javax.security.auth.message.config.AuthConfigProvider;
34 import javax.security.auth.message.config.RegistrationListener;
35 import javax.security.auth.message.config.ServerAuthConfig;
36 import javax.security.auth.message.config.ServerAuthContext;
37 import javax.servlet.DispatcherType;
38 import javax.servlet.ServletContext;
39 import javax.servlet.ServletException;
40 import javax.servlet.http.Cookie;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43
44 import org.apache.catalina.Authenticator;
45 import org.apache.catalina.Container;
46 import org.apache.catalina.Context;
47 import org.apache.catalina.Globals;
48 import org.apache.catalina.LifecycleException;
49 import org.apache.catalina.Realm;
50 import org.apache.catalina.Session;
51 import org.apache.catalina.TomcatPrincipal;
52 import org.apache.catalina.Valve;
53 import org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl;
54 import org.apache.catalina.authenticator.jaspic.MessageInfoImpl;
55 import org.apache.catalina.connector.Request;
56 import org.apache.catalina.connector.Response;
57 import org.apache.catalina.filters.CorsFilter;
58 import org.apache.catalina.filters.RemoteIpFilter;
59 import org.apache.catalina.realm.GenericPrincipal;
60 import org.apache.catalina.util.SessionIdGeneratorBase;
61 import org.apache.catalina.util.StandardSessionIdGenerator;
62 import org.apache.catalina.valves.RemoteIpValve;
63 import org.apache.catalina.valves.ValveBase;
64 import org.apache.coyote.ActionCode;
65 import org.apache.juli.logging.Log;
66 import org.apache.juli.logging.LogFactory;
67 import org.apache.tomcat.util.ExceptionUtils;
68 import org.apache.tomcat.util.descriptor.web.FilterDef;
69 import org.apache.tomcat.util.descriptor.web.FilterMap;
70 import org.apache.tomcat.util.descriptor.web.LoginConfig;
71 import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
72 import org.apache.tomcat.util.http.FastHttpDateFormat;
73 import org.apache.tomcat.util.http.RequestUtil;
74 import org.apache.tomcat.util.res.StringManager;
75
76 /**
77  * Basic implementation of the <b>Valve</b> interface that enforces the
78  * <code>&lt;security-constraint&gt;</code> elements in the web application
79  * deployment descriptor. This functionality is implemented as a Valve so that
80  * it can be omitted in environments that do not require these features.
81  * Individual implementations of each supported authentication method can
82  * subclass this base class as required.
83  * <p>
84  * <b>USAGE CONSTRAINT</b>: When this class is utilized, the Context to which it
85  * is attached (or a parent Container in a hierarchy) must have an associated
86  * Realm that can be used for authenticating users and enumerating the roles to
87  * which they have been assigned.
88  * <p>
89  * <b>USAGE CONSTRAINT</b>: This Valve is only useful when processing HTTP
90  * requests. Requests of any other type will simply be passed through.
91  *
92  * @author Craig R. McClanahan
93  */

94 public abstract class AuthenticatorBase extends ValveBase
95         implements Authenticator, RegistrationListener {
96
97     private final Log log = LogFactory.getLog(AuthenticatorBase.class); // must not be static
98
99     /**
100      * "Expires" header always set to Date(1), so generate once only
101      */

102     private static final String DATE_ONE = FastHttpDateFormat.formatDate(1);
103
104     /**
105      * The string manager for this package.
106      */

107     protected static final StringManager sm = StringManager.getManager(AuthenticatorBase.class);
108
109     /**
110      * Authentication header
111      */

112     protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
113
114     /**
115      * Default authentication realm name.
116      */

117     protected static final String REALM_NAME = "Authentication required";
118
119     protected static String getRealmName(Context context) {
120         if (context == null) {
121             // Very unlikely
122             return REALM_NAME;
123         }
124
125         LoginConfig config = context.getLoginConfig();
126         if (config == null) {
127             return REALM_NAME;
128         }
129
130         String result = config.getRealmName();
131         if (result == null) {
132             return REALM_NAME;
133         }
134
135         return result;
136     }
137
138     // ------------------------------------------------------ Constructor
139
140     public AuthenticatorBase() {
141         super(true);
142     }
143
144     // ----------------------------------------------------- Instance Variables
145
146     /**
147      * Should a session always be used once a user is authenticated? This may
148      * offer some performance benefits since the session can then be used to
149      * cache the authenticated Principal, hence removing the need to
150      * authenticate the user via the Realm on every request. This may be of help
151      * for combinations such as BASIC authentication used with the JNDIRealm or
152      * DataSourceRealms. However there will also be the performance cost of
153      * creating and GC'ing the session. By default, a session will not be
154      * created.
155      */

156     protected boolean alwaysUseSession = false;
157
158     /**
159      * Should we cache authenticated Principals if the request is part of an
160      * HTTP session?
161      */

162     protected boolean cache = true;
163
164     /**
165      * Should the session ID, if any, be changed upon a successful
166      * authentication to prevent a session fixation attack?
167      */

168     protected boolean changeSessionIdOnAuthentication = true;
169
170     /**
171      * The Context to which this Valve is attached.
172      */

173     protected Context context = null;
174
175     /**
176      * Flag to determine if we disable proxy caching, or leave the issue up to
177      * the webapp developer.
178      */

179     protected boolean disableProxyCaching = true;
180
181     /**
182      * Flag to determine if we disable proxy caching with headers incompatible
183      * with IE.
184      */

185     protected boolean securePagesWithPragma = false;
186
187     /**
188      * The Java class name of the secure random number generator class to be
189      * used when generating SSO session identifiers. The random number generator
190      * class must be self-seeding and have a zero-argument constructor. If not
191      * specified, an instance of {@link java.security.SecureRandom} will be
192      * generated.
193      */

194     protected String secureRandomClass = null;
195
196     /**
197      * The name of the algorithm to use to create instances of
198      * {@link java.security.SecureRandom} which are used to generate SSO session
199      * IDs. If no algorithm is specified, SHA1PRNG is used. To use the platform
200      * default (which may be SHA1PRNG), specify the empty string. If an invalid
201      * algorithm and/or provider is specified the SecureRandom instances will be
202      * created using the defaults. If that fails, the SecureRandom instances
203      * will be created using platform defaults.
204      */

205     protected String secureRandomAlgorithm = "SHA1PRNG";
206
207     /**
208      * The name of the provider to use to create instances of
209      * {@link java.security.SecureRandom} which are used to generate session SSO
210      * IDs. If no algorithm is specified the of SHA1PRNG default is used. If an
211      * invalid algorithm and/or provider is specified the SecureRandom instances
212      * will be created using the defaults. If that fails, the SecureRandom
213      * instances will be created using platform defaults.
214      */

215     protected String secureRandomProvider = null;
216
217     /**
218      * The name of the JASPIC callback handler class. If none is specified the
219      * default {@link org.apache.catalina.authenticator.jaspic.CallbackHandlerImpl}
220      * will be used.
221      */

222     protected String jaspicCallbackHandlerClass = null;
223
224     /**
225      * Should the auth information (remote user and auth type) be returned as response
226      * headers for a forwarded/proxied request? When the {@link RemoteIpValve} or
227      * {@link RemoteIpFilter} mark a forwarded request with the
228      * {@link Globals#REQUEST_FORWARDED_ATTRIBUTE} this authenticator can return the
229      * values of {@link HttpServletRequest#getRemoteUser()} and
230      * {@link HttpServletRequest#getAuthType()} as reponse headers {@code remote-user}
231      * and {@code auth-type} to a reverse proxy. This is useful, e.g., for access log
232      * consistency or other decisions to make.
233      */

234
235     protected boolean sendAuthInfoResponseHeaders = false;
236
237     protected SessionIdGeneratorBase sessionIdGenerator = null;
238
239     /**
240      * The SingleSignOn implementation in our request processing chain, if there
241      * is one.
242      */

243     protected SingleSignOn sso = null;
244
245     private AllowCorsPreflight allowCorsPreflight = AllowCorsPreflight.NEVER;
246
247     private volatile String jaspicAppContextID = null;
248     private volatile Optional<AuthConfigProvider> jaspicProvider = null;
249
250
251     // ------------------------------------------------------------- Properties
252
253     public String getAllowCorsPreflight() {
254         return allowCorsPreflight.name().toLowerCase(Locale.ENGLISH);
255     }
256
257     public void setAllowCorsPreflight(String allowCorsPreflight) {
258         this.allowCorsPreflight = AllowCorsPreflight.valueOf(allowCorsPreflight.trim().toUpperCase(Locale.ENGLISH));
259     }
260
261     public boolean getAlwaysUseSession() {
262         return alwaysUseSession;
263     }
264
265     public void setAlwaysUseSession(boolean alwaysUseSession) {
266         this.alwaysUseSession = alwaysUseSession;
267     }
268
269     /**
270      * Return the cache authenticated Principals flag.
271      *
272      * @return <code>true</code> if authenticated Principals will be cached,
273      *         otherwise <code>false</code>
274      */

275     public boolean getCache() {
276         return this.cache;
277     }
278
279     /**
280      * Set the cache authenticated Principals flag.
281      *
282      * @param cache
283      *            The new cache flag
284      */

285     public void setCache(boolean cache) {
286         this.cache = cache;
287     }
288
289     /**
290      * Return the Container to which this Valve is attached.
291      */

292     @Override
293     public Container getContainer() {
294         return this.context;
295     }
296
297     /**
298      * Set the Container to which this Valve is attached.
299      *
300      * @param container
301      *            The container to which we are attached
302      */

303     @Override
304     public void setContainer(Container container) {
305
306         if (container != null && !(container instanceof Context)) {
307             throw new IllegalArgumentException(sm.getString("authenticator.notContext"));
308         }
309
310         super.setContainer(container);
311         this.context = (Context) container;
312
313     }
314
315     /**
316      * Return the flag that states if we add headers to disable caching by
317      * proxies.
318      *
319      * @return <code>true</code> if the headers will be added, otherwise
320      *         <code>false</code>
321      */

322     public boolean getDisableProxyCaching() {
323         return disableProxyCaching;
324     }
325
326     /**
327      * Set the value of the flag that states if we add headers to disable
328      * caching by proxies.
329      *
330      * @param nocache
331      *            <code>true</code> if we add headers to disable proxy caching,
332      *            <code>false</code> if we leave the headers alone.
333      */

334     public void setDisableProxyCaching(boolean nocache) {
335         disableProxyCaching = nocache;
336     }
337
338     /**
339      * Return the flag that states, if proxy caching is disabled, what headers
340      * we add to disable the caching.
341      *
342      * @return <code>true</code> if a Pragma header should be used, otherwise
343      *         <code>false</code>
344      */

345     public boolean getSecurePagesWithPragma() {
346         return securePagesWithPragma;
347     }
348
349     /**
350      * Set the value of the flag that states what headers we add to disable
351      * proxy caching.
352      *
353      * @param securePagesWithPragma
354      *            <code>true</code> if we add headers which are incompatible
355      *            with downloading office documents in IE under SSL but which
356      *            fix a caching problem in Mozilla.
357      */

358     public void setSecurePagesWithPragma(boolean securePagesWithPragma) {
359         this.securePagesWithPragma = securePagesWithPragma;
360     }
361
362     /**
363      * Return the flag that states if we should change the session ID of an
364      * existing session upon successful authentication.
365      *
366      * @return <code>true</code> to change session ID upon successful
367      *         authentication, <code>false</code> to do not perform the change.
368      */

369     public boolean getChangeSessionIdOnAuthentication() {
370         return changeSessionIdOnAuthentication;
371     }
372
373     /**
374      * Set the value of the flag that states if we should change the session ID
375      * of an existing session upon successful authentication.
376      *
377      * @param changeSessionIdOnAuthentication <code>true</code> to change
378      *            session ID upon successful authentication, <code>false</code>
379      *            to do not perform the change.
380      */

381     public void setChangeSessionIdOnAuthentication(boolean changeSessionIdOnAuthentication) {
382         this.changeSessionIdOnAuthentication = changeSessionIdOnAuthentication;
383     }
384
385     /**
386      * Return the secure random number generator class name.
387      *
388      * @return The fully qualified name of the SecureRandom implementation to
389      *         use
390      */

391     public String getSecureRandomClass() {
392         return this.secureRandomClass;
393     }
394
395     /**
396      * Set the secure random number generator class name.
397      *
398      * @param secureRandomClass
399      *            The new secure random number generator class name
400      */

401     public void setSecureRandomClass(String secureRandomClass) {
402         this.secureRandomClass = secureRandomClass;
403     }
404
405     /**
406      * Return the secure random number generator algorithm name.
407      *
408      * @return The name of the SecureRandom algorithm used
409      */

410     public String getSecureRandomAlgorithm() {
411         return secureRandomAlgorithm;
412     }
413
414     /**
415      * Set the secure random number generator algorithm name.
416      *
417      * @param secureRandomAlgorithm
418      *            The new secure random number generator algorithm name
419      */

420     public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
421         this.secureRandomAlgorithm = secureRandomAlgorithm;
422     }
423
424     /**
425      * Return the secure random number generator provider name.
426      *
427      * @return The name of the SecureRandom provider
428      */

429     public String getSecureRandomProvider() {
430         return secureRandomProvider;
431     }
432
433     /**
434      * Set the secure random number generator provider name.
435      *
436      * @param secureRandomProvider
437      *            The new secure random number generator provider name
438      */

439     public void setSecureRandomProvider(String secureRandomProvider) {
440         this.secureRandomProvider = secureRandomProvider;
441     }
442
443     /**
444      * Return the JASPIC callback handler class name
445      *
446      * @return The name of the JASPIC callback handler
447      */

448     public String getJaspicCallbackHandlerClass() {
449         return jaspicCallbackHandlerClass;
450     }
451
452     /**
453      * Set the JASPIC callback handler class name
454      *
455      * @param jaspicCallbackHandlerClass
456      *            The new JASPIC callback handler class name
457      */

458     public void setJaspicCallbackHandlerClass(String jaspicCallbackHandlerClass) {
459         this.jaspicCallbackHandlerClass = jaspicCallbackHandlerClass;
460     }
461
462     /**
463      * Returns the flag whether authentication information will be sent to a reverse
464      * proxy on a forwarded request.
465      *
466      * @return {@code trueif response headers shall be sent,  {@code false} otherwise
467      */

468     public boolean isSendAuthInfoResponseHeaders() {
469         return sendAuthInfoResponseHeaders;
470     }
471
472     /**
473      * Sets the flag whether authentication information will be send to a reverse
474      * proxy on a forwarded request.
475      *
476      * @param sendAuthInfoResponseHeaders {@code trueif response headers shall be
477      *                                    sent, {@code false} otherwise
478      */

479     public void setSendAuthInfoResponseHeaders(boolean sendAuthInfoResponseHeaders) {
480         this.sendAuthInfoResponseHeaders = sendAuthInfoResponseHeaders;
481     }
482
483     // --------------------------------------------------------- Public Methods
484
485     /**
486      * Enforce the security restrictions in the web application deployment
487      * descriptor of our associated Context.
488      *
489      * @param request
490      *            Request to be processed
491      * @param response
492      *            Response to be processed
493      *
494      * @exception IOException
495      *                if an input/output error occurs
496      * @exception ServletException
497      *                if thrown by a processing element
498      */

499     @Override
500     public void invoke(Request request, Response response) throws IOException, ServletException {
501
502         if (log.isDebugEnabled()) {
503             log.debug("Security checking request " + request.getMethod() + " " +
504                     request.getRequestURI());
505         }
506
507         // Have we got a cached authenticated Principal to record?
508         if (cache) {
509             Principal principal = request.getUserPrincipal();
510             if (principal == null) {
511                 Session session = request.getSessionInternal(false);
512                 if (session != null) {
513                     principal = session.getPrincipal();
514                     if (principal != null) {
515                         if (log.isDebugEnabled()) {
516                             log.debug("We have cached auth type " + session.getAuthType() +
517                                     for principal " + principal);
518                         }
519                         request.setAuthType(session.getAuthType());
520                         request.setUserPrincipal(principal);
521                     }
522                 }
523             }
524         }
525
526         boolean authRequired = isContinuationRequired(request);
527
528         Realm realm = this.context.getRealm();
529         // Is this request URI subject to a security constraint?
530         SecurityConstraint[] constraints = realm.findSecurityConstraints(request, this.context);
531
532         AuthConfigProvider jaspicProvider = getJaspicProvider();
533         if (jaspicProvider != null) {
534             authRequired = true;
535         }
536
537         if (constraints == null && !context.getPreemptiveAuthentication() && !authRequired) {
538             if (log.isDebugEnabled()) {
539                 log.debug("Not subject to any constraint");
540             }
541             getNext().invoke(request, response);
542             return;
543         }
544
545         // Make sure that constrained resources are not cached by web proxies
546         // or browsers as caching can provide a security hole
547         if (constraints != null && disableProxyCaching &&
548                 !"POST".equalsIgnoreCase(request.getMethod())) {
549             if (securePagesWithPragma) {
550                 // Note: These can cause problems with downloading files with IE
551                 response.setHeader("Pragma""No-cache");
552                 response.setHeader("Cache-Control""no-cache");
553             } else {
554                 response.setHeader("Cache-Control""private");
555             }
556             response.setHeader("Expires", DATE_ONE);
557         }
558
559         if (constraints != null) {
560             // Enforce any user data constraint for this security constraint
561             if (log.isDebugEnabled()) {
562                 log.debug("Calling hasUserDataPermission()");
563             }
564             if (!realm.hasUserDataPermission(request, response, constraints)) {
565                 if (log.isDebugEnabled()) {
566                     log.debug("Failed hasUserDataPermission() test");
567                 }
568                 /*
569                  * ASSERT: Authenticator already set the appropriate HTTP status
570                  * code, so we do not have to do anything special
571                  */

572                 return;
573             }
574         }
575
576         // Since authenticate modifies the response on failure,
577         // we have to check for allow-from-all first.
578         boolean hasAuthConstraint = false;
579         if (constraints != null) {
580             hasAuthConstraint = true;
581             for (int i = 0; i < constraints.length && hasAuthConstraint; i++) {
582                 if (!constraints[i].getAuthConstraint()) {
583                     hasAuthConstraint = false;
584                 } else if (!constraints[i].getAllRoles() &&
585                         !constraints[i].getAuthenticatedUsers()) {
586                     String[] roles = constraints[i].findAuthRoles();
587                     if (roles == null || roles.length == 0) {
588                         hasAuthConstraint = false;
589                     }
590                 }
591             }
592         }
593
594         if (!authRequired && hasAuthConstraint) {
595             authRequired = true;
596         }
597
598         if (!authRequired && context.getPreemptiveAuthentication()) {
599             authRequired =
600                     request.getCoyoteRequest().getMimeHeaders().getValue("authorization") != null;
601         }
602
603         if (!authRequired && context.getPreemptiveAuthentication() &&
604                 HttpServletRequest.CLIENT_CERT_AUTH.equals(getAuthMethod())) {
605             X509Certificate[] certs = getRequestCertificates(request);
606             authRequired = certs != null && certs.length > 0;
607         }
608
609         JaspicState jaspicState = null;
610
611         if ((authRequired || constraints != null) && allowCorsPreflightBypass(request)) {
612             if (log.isDebugEnabled()) {
613                 log.debug("CORS Preflight request bypassing authentication");
614             }
615             getNext().invoke(request, response);
616             return;
617         }
618
619         if (authRequired) {
620             if (log.isDebugEnabled()) {
621                 log.debug("Calling authenticate()");
622             }
623
624             if (jaspicProvider != null) {
625                 jaspicState = getJaspicState(jaspicProvider, request, response, hasAuthConstraint);
626                 if (jaspicState == null) {
627                     return;
628                 }
629             }
630
631             if (jaspicProvider == null && !doAuthenticate(request, response) ||
632                     jaspicProvider != null &&
633                             !authenticateJaspic(request, response, jaspicState, false)) {
634                 if (log.isDebugEnabled()) {
635                     log.debug("Failed authenticate() test");
636                 }
637                 /*
638                  * ASSERT: Authenticator already set the appropriate HTTP status
639                  * code, so we do not have to do anything special
640                  */

641                 return;
642             }
643
644         }
645
646         if (constraints != null) {
647             if (log.isDebugEnabled()) {
648                 log.debug("Calling accessControl()");
649             }
650             if (!realm.hasResourcePermission(request, response, constraints, this.context)) {
651                 if (log.isDebugEnabled()) {
652                     log.debug("Failed accessControl() test");
653                 }
654                 /*
655                  * ASSERT: AccessControl method has already set the appropriate
656                  * HTTP status code, so we do not have to do anything special
657                  */

658                 return;
659             }
660         }
661
662         // Any and all specified constraints have been satisfied
663         if (log.isDebugEnabled()) {
664             log.debug("Successfully passed all security constraints");
665         }
666         getNext().invoke(request, response);
667
668         if (jaspicProvider != null) {
669             secureResponseJspic(request, response, jaspicState);
670         }
671     }
672
673
674     protected boolean allowCorsPreflightBypass(Request request) {
675         boolean allowBypass = false;
676
677         if (allowCorsPreflight != AllowCorsPreflight.NEVER) {
678             // First check to see if this is a CORS Preflight request
679             // This is a subset of the tests in CorsFilter.checkRequestType
680             if ("OPTIONS".equals(request.getMethod())) {
681                 String originHeader = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
682                 if (originHeader != null &&
683                         !originHeader.isEmpty() &&
684                         RequestUtil.isValidOrigin(originHeader) &&
685                         !RequestUtil.isSameOrigin(request, originHeader)) {
686                     String accessControlRequestMethodHeader =
687                             request.getHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
688                     if (accessControlRequestMethodHeader != null &&
689                             !accessControlRequestMethodHeader.isEmpty()) {
690                         // This appears to be a CORS Preflight request
691                         if (allowCorsPreflight == AllowCorsPreflight.ALWAYS) {
692                             allowBypass = true;
693                         } else if (allowCorsPreflight == AllowCorsPreflight.FILTER) {
694                             if (DispatcherType.REQUEST == request.getDispatcherType()) {
695                                 // Look at Filter configuration for the Context
696                                 // Can't cache this unless we add a listener to
697                                 // the Context to clear the cache on reload
698                                 for (FilterDef filterDef : request.getContext().findFilterDefs()) {
699                                     if (CorsFilter.class.getName().equals(filterDef.getFilterClass())) {
700                                         for (FilterMap filterMap : context.findFilterMaps()) {
701                                             if (filterMap.getFilterName().equals(filterDef.getFilterName())) {
702                                                 if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) {
703                                                     for (String urlPattern : filterMap.getURLPatterns()) {
704                                                         if ("/*".equals(urlPattern)) {
705                                                             allowBypass = true;
706                                                             // No need to check other patterns
707                                                             break;
708                                                         }
709                                                     }
710                                                 }
711                                                 // Found mappings for CORS filter.
712                                                 // No need to look further
713                                                 break;
714                                             }
715                                         }
716                                         // Found the CORS filter. No need to look further.
717                                         break;
718                                     }
719                                 }
720                             }
721                         } else {
722                             // Unexpected enum type
723                         }
724                     }
725                 }
726             }
727         }
728         return allowBypass;
729     }
730
731
732     @Override
733     public boolean authenticate(Request request, HttpServletResponse httpResponse)
734             throws IOException {
735
736         AuthConfigProvider jaspicProvider = getJaspicProvider();
737
738         if (jaspicProvider == null) {
739             return doAuthenticate(request, httpResponse);
740         } else {
741             Response response = request.getResponse();
742             JaspicState jaspicState = getJaspicState(jaspicProvider, request, response, true);
743             if (jaspicState == null) {
744                 return false;
745             }
746
747             boolean result = authenticateJaspic(request, response, jaspicState, true);
748
749             secureResponseJspic(request, response, jaspicState);
750
751             return result;
752         }
753     }
754
755
756     private void secureResponseJspic(Request request, Response response, JaspicState state) {
757         try {
758             state.serverAuthContext.secureResponse(state.messageInfo, null);
759             request.setRequest((HttpServletRequest) state.messageInfo.getRequestMessage());
760             response.setResponse((HttpServletResponse) state.messageInfo.getResponseMessage());
761         } catch (AuthException e) {
762             log.warn(sm.getString("authenticator.jaspicSecureResponseFail"), e);
763         }
764     }
765
766
767     private JaspicState getJaspicState(AuthConfigProvider jaspicProvider, Request request,
768             Response response, boolean authMandatory) throws IOException {
769         JaspicState jaspicState = new JaspicState();
770
771         jaspicState.messageInfo =
772                 new MessageInfoImpl(request.getRequest(), response.getResponse(), authMandatory);
773
774         try {
775             CallbackHandler callbackHandler = createCallbackHandler();
776             ServerAuthConfig serverAuthConfig = jaspicProvider.getServerAuthConfig(
777                     "HttpServlet", jaspicAppContextID, callbackHandler);
778             String authContextID = serverAuthConfig.getAuthContextID(jaspicState.messageInfo);
779             jaspicState.serverAuthContext = serverAuthConfig.getAuthContext(authContextID, nullnull);
780         } catch (AuthException e) {
781             log.warn(sm.getString("authenticator.jaspicServerAuthContextFail"), e);
782             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
783             return null;
784         }
785
786         return jaspicState;
787     }
788
789     private CallbackHandler createCallbackHandler() {
790         CallbackHandler callbackHandler = null;
791         if (jaspicCallbackHandlerClass == null) {
792             callbackHandler = CallbackHandlerImpl.getInstance();
793         } else {
794             Class<?> clazz = null;
795             try {
796                 clazz = Class.forName(jaspicCallbackHandlerClass, true,
797                         Thread.currentThread().getContextClassLoader());
798             } catch (ClassNotFoundException e) {
799                 // Proceed with the retry below
800             }
801
802             try {
803                 if (clazz == null) {
804                     clazz = Class.forName(jaspicCallbackHandlerClass);
805                 }
806                 callbackHandler = (CallbackHandler)clazz.getConstructor().newInstance();
807             } catch (ReflectiveOperationException e) {
808                 throw new SecurityException(e);
809             }
810         }
811
812         return callbackHandler;
813     }
814
815
816     // ------------------------------------------------------ Protected Methods
817
818     /**
819      * Provided for sub-classes to implement their specific authentication
820      * mechanism.
821      *
822      * @param request The request that triggered the authentication
823      * @param response The response associated with the request
824      *
825      * @return {@code trueif the the user was authenticated, otherwise {@code
826      *         false}, in which case an authentication challenge will have been
827      *         written to the response
828      *
829      * @throws IOException If an I/O problem occurred during the authentication
830      *                     process
831      */

832     protected abstract boolean doAuthenticate(Request request, HttpServletResponse response)
833             throws IOException;
834
835
836     /**
837      * Does this authenticator require that {@link #authenticate(Request,
838      * HttpServletResponse)} is called to continue an authentication process
839      * that started in a previous request?
840      *
841      * @param request The request currently being processed
842      *
843      * @return {@code trueif authenticate() must be called, otherwise
844      *         {@code false}
845      */

846     protected boolean isContinuationRequired(Request request) {
847         return false;
848     }
849
850
851     /**
852      * Look for the X509 certificate chain in the Request under the key
853      * <code>javax.servlet.request.X509Certificate</code>. If not found, trigger
854      * extracting the certificate chain from the Coyote request.
855      *
856      * @param request
857      *            Request to be processed
858      *
859      * @return The X509 certificate chain if found, <code>null</code> otherwise.
860      */

861     protected X509Certificate[] getRequestCertificates(final Request request)
862             throws IllegalStateException {
863
864         X509Certificate certs[] =
865                 (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
866
867         if ((certs == null) || (certs.length < 1)) {
868             try {
869                 request.getCoyoteRequest().action(ActionCode.REQ_SSL_CERTIFICATE, null);
870                 certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
871             } catch (IllegalStateException ise) {
872                 // Request body was too large for save buffer
873                 // Return null which will trigger an auth failure
874             }
875         }
876
877         return certs;
878     }
879
880     /**
881      * Associate the specified single sign on identifier with the specified
882      * Session.
883      *
884      * @param ssoId
885      *            Single sign on identifier
886      * @param session
887      *            Session to be associated
888      */

889     protected void associate(String ssoId, Session session) {
890
891         if (sso == null) {
892             return;
893         }
894         sso.associate(ssoId, session);
895
896     }
897
898
899     private boolean authenticateJaspic(Request request, Response response, JaspicState state,
900             boolean requirePrincipal) {
901
902         boolean cachedAuth = checkForCachedAuthentication(request, response, false);
903         Subject client = new Subject();
904         AuthStatus authStatus;
905         try {
906             authStatus = state.serverAuthContext.validateRequest(state.messageInfo, client, null);
907         } catch (AuthException e) {
908             log.debug(sm.getString("authenticator.loginFail"), e);
909             return false;
910         }
911
912         request.setRequest((HttpServletRequest) state.messageInfo.getRequestMessage());
913         response.setResponse((HttpServletResponse) state.messageInfo.getResponseMessage());
914
915         if (authStatus == AuthStatus.SUCCESS) {
916             GenericPrincipal principal = getPrincipal(client);
917             if (log.isDebugEnabled()) {
918                 log.debug("Authenticated user: " + principal);
919             }
920             if (principal == null) {
921                 request.setUserPrincipal(null);
922                 request.setAuthType(null);
923                 if (requirePrincipal) {
924                     return false;
925                 }
926             } else if (cachedAuth == false ||
927                     !principal.getUserPrincipal().equals(request.getUserPrincipal())) {
928                 // Skip registration if authentication credentials were
929                 // cached and the Principal did not change.
930                 @SuppressWarnings("rawtypes")// JASPIC API uses raw types
931                 Map map = state.messageInfo.getMap();
932                 if (map != null && map.containsKey("javax.servlet.http.registerSession")) {
933                     register(request, response, principal, "JASPIC"nullnulltruetrue);
934                 } else {
935                     register(request, response, principal, "JASPIC"nullnull);
936                 }
937             }
938             request.setNote(Constants.REQ_JASPIC_SUBJECT_NOTE, client);
939             return true;
940         }
941         return false;
942     }
943
944
945     private GenericPrincipal getPrincipal(Subject subject) {
946         if (subject == null) {
947             return null;
948         }
949
950         Set<GenericPrincipal> principals = subject.getPrivateCredentials(GenericPrincipal.class);
951         if (principals.isEmpty()) {
952             return null;
953         }
954
955         return principals.iterator().next();
956     }
957
958
959     /**
960      * Check to see if the user has already been authenticated earlier in the
961      * processing chain or if there is enough information available to
962      * authenticate the user without requiring further user interaction.
963      *
964      * @param request
965      *            The current request
966      * @param response
967      *            The current response
968      * @param useSSO
969      *            Should information available from SSO be used to attempt to
970      *            authenticate the current user?
971      *
972      * @return <code>true</code> if the user was authenticated via the cache,
973      *         otherwise <code>false</code>
974      */

975     protected boolean checkForCachedAuthentication(Request request, HttpServletResponse response, boolean useSSO) {
976
977         // Has the user already been authenticated?
978         Principal principal = request.getUserPrincipal();
979         String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
980         if (principal != null) {
981             if (log.isDebugEnabled()) {
982                 log.debug(sm.getString("authenticator.check.found", principal.getName()));
983             }
984             // Associate the session with any existing SSO session. Even if
985             // useSSO is falsethis will ensure coordinated session
986             // invalidation at log out.
987             if (ssoId != null) {
988                 associate(ssoId, request.getSessionInternal(true));
989             }
990             return true;
991         }
992
993         // Is there an SSO session against which we can try to reauthenticate?
994         if (useSSO && ssoId != null) {
995             if (log.isDebugEnabled()) {
996                 log.debug(sm.getString("authenticator.check.sso", ssoId));
997             }
998             /*
999              * Try to reauthenticate using data cached by SSO. If this fails,
1000              * either the original SSO logon was of DIGEST or SSL (which we
1001              * can't reauthenticate ourselves because there is no cached
1002              * username and password), or the realm denied the user's
1003              * reauthentication for some reason. In either case we have to
1004              * prompt the user for a logon
1005              */

1006             if (reauthenticateFromSSO(ssoId, request)) {
1007                 return true;
1008             }
1009         }
1010
1011         // Has the Connector provided a pre-authenticated Principal that now
1012         // needs to be authorized?
1013         if (request.getCoyoteRequest().getRemoteUserNeedsAuthorization()) {
1014             String username = request.getCoyoteRequest().getRemoteUser().toString();
1015             if (username != null) {
1016                 if (log.isDebugEnabled()) {
1017                     log.debug(sm.getString("authenticator.check.authorize", username));
1018                 }
1019                 Principal authorized = context.getRealm().authenticate(username);
1020                 if (authorized == null) {
1021                     // Realm doesn't recognise user. Create a user with no roles
1022                     // from the authenticated user name
1023                     if (log.isDebugEnabled()) {
1024                         log.debug(sm.getString("authenticator.check.authorizeFail", username));
1025                     }
1026                     authorized = new GenericPrincipal(username, nullnull);
1027                 }
1028                 String authType = request.getAuthType();
1029                 if (authType == null || authType.length() == 0) {
1030                     authType = getAuthMethod();
1031                 }
1032                 register(request, response, authorized, authType, username, null);
1033                 return true;
1034             }
1035         }
1036         return false;
1037     }
1038
1039     /**
1040      * Attempts reauthentication to the <code>Realm</code> using the credentials
1041      * included in argument <code>entry</code>.
1042      *
1043      * @param ssoId
1044      *            identifier of SingleSignOn session with which the caller is
1045      *            associated
1046      * @param request
1047      *            the request that needs to be authenticated
1048      * @return <code>true</code> if the reauthentication from SSL occurred
1049      */

1050     protected boolean reauthenticateFromSSO(String ssoId, Request request) {
1051
1052         if (sso == null || ssoId == null) {
1053             return false;
1054         }
1055
1056         boolean reauthenticated = false;
1057
1058         Container parent = getContainer();
1059         if (parent != null) {
1060             Realm realm = parent.getRealm();
1061             if (realm != null) {
1062                 reauthenticated = sso.reauthenticate(ssoId, realm, request);
1063             }
1064         }
1065
1066         if (reauthenticated) {
1067             associate(ssoId, request.getSessionInternal(true));
1068
1069             if (log.isDebugEnabled()) {
1070                 log.debug("Reauthenticated cached principal '" +
1071                         request.getUserPrincipal().getName() +
1072                         "' with auth type '" + request.getAuthType() + "'");
1073             }
1074         }
1075
1076         return reauthenticated;
1077     }
1078
1079     /**
1080      * Register an authenticated Principal and authentication type in our
1081      * request, in the current session (if there is one), and with our
1082      * SingleSignOn valve, if there is one. Set the appropriate cookie to be
1083      * returned.
1084      *
1085      * @param request
1086      *            The servlet request we are processing
1087      * @param response
1088      *            The servlet response we are generating
1089      * @param principal
1090      *            The authenticated Principal to be registered
1091      * @param authType
1092      *            The authentication type to be registered
1093      * @param username
1094      *            Username used to authenticate (if any)
1095      * @param password
1096      *            Password used to authenticate (if any)
1097      */

1098     public void register(Request request, HttpServletResponse response, Principal principal,
1099             String authType, String username, String password) {
1100         register(request, response, principal, authType, username, password, alwaysUseSession, cache);
1101     }
1102
1103
1104     /**
1105      * Register an authenticated Principal and authentication type in our
1106      * request, in the current session (if there is one), and with our
1107      * SingleSignOn valve, if there is one. Set the appropriate cookie to be
1108      * returned.
1109      *
1110      * @param request
1111      *            The servlet request we are processing
1112      * @param response
1113      *            The servlet response we are generating
1114      * @param principal
1115      *            The authenticated Principal to be registered
1116      * @param authType
1117      *            The authentication type to be registered
1118      * @param username
1119      *            Username used to authenticate (if any)
1120      * @param password
1121      *            Password used to authenticate (if any)
1122      * @param alwaysUseSession
1123      *            Should a session always be used once a user is authenticated?
1124      * @param cache
1125      *            Should we cache authenticated Principals if the request is part of an
1126      *            HTTP session?
1127      */

1128     protected void register(Request request, HttpServletResponse response, Principal principal,
1129             String authType, String username, String password, boolean alwaysUseSession,
1130             boolean cache) {
1131
1132         if (log.isDebugEnabled()) {
1133             String name = (principal == null) ? "none" : principal.getName();
1134             log.debug("Authenticated '" + name + "' with type '" + authType + "'");
1135         }
1136
1137         // Cache the authentication information in our request
1138         request.setAuthType(authType);
1139         request.setUserPrincipal(principal);
1140
1141         if (sendAuthInfoResponseHeaders
1142             && Boolean.TRUE.equals(request.getAttribute(Globals.REQUEST_FORWARDED_ATTRIBUTE))) {
1143             response.setHeader("remote-user", request.getRemoteUser());
1144             response.setHeader("auth-type", request.getAuthType());
1145         }
1146
1147         Session session = request.getSessionInternal(false);
1148
1149         if (session != null) {
1150             // If the principal is null then this is a logout. No need to change
1151             // the session ID. See BZ 59043.
1152             if (getChangeSessionIdOnAuthentication() && principal != null) {
1153                 String newSessionId = changeSessionID(request, session);
1154                 // If the current session ID is being tracked, update it.
1155                 if (session.getNote(Constants.SESSION_ID_NOTE) != null) {
1156                     session.setNote(Constants.SESSION_ID_NOTE, newSessionId);
1157                 }
1158             }
1159         } else if (alwaysUseSession) {
1160             session = request.getSessionInternal(true);
1161         }
1162
1163         // Cache the authentication information in our session, if any
1164         if (session != null && cache) {
1165             session.setAuthType(authType);
1166             session.setPrincipal(principal);
1167         }
1168
1169         // Construct a cookie to be returned to the client
1170         if (sso == null) {
1171             return;
1172         }
1173
1174         // Only create a new SSO entry if the SSO did not already set a note
1175         // for an existing entry (as it would do with subsequent requests
1176         // for DIGEST and SSL authenticated contexts)
1177         String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
1178         if (ssoId == null) {
1179             // Construct a cookie to be returned to the client
1180             ssoId = sessionIdGenerator.generateSessionId();
1181             Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
1182             cookie.setMaxAge(-1);
1183             cookie.setPath("/");
1184
1185             // Bugzilla 41217
1186             cookie.setSecure(request.isSecure());
1187
1188             // Bugzilla 34724
1189             String ssoDomain = sso.getCookieDomain();
1190             if (ssoDomain != null) {
1191                 cookie.setDomain(ssoDomain);
1192             }
1193
1194             // Configure httpOnly on SSO cookie using same rules as session
1195             // cookies
1196             if (request.getServletContext().getSessionCookieConfig().isHttpOnly() ||
1197                     request.getContext().getUseHttpOnly()) {
1198                 cookie.setHttpOnly(true);
1199             }
1200
1201             response.addCookie(cookie);
1202
1203             // Register this principal with our SSO valve
1204             sso.register(ssoId, principal, authType, username, password);
1205             request.setNote(Constants.REQ_SSOID_NOTE, ssoId);
1206
1207         } else {
1208             if (principal == null) {
1209                 // Registering a programmatic logout
1210                 sso.deregister(ssoId);
1211                 request.removeNote(Constants.REQ_SSOID_NOTE);
1212                 return;
1213             } else {
1214                 // Update the SSO session with the latest authentication data
1215                 sso.update(ssoId, principal, authType, username, password);
1216             }
1217         }
1218
1219         // Fix for Bug 10040
1220         // Always associate a session with a new SSO reqistration.
1221         // SSO entries are only removed from the SSO registry map when
1222         // associated sessions are destroyed; if a new SSO entry is created
1223         // above for this request and the user never revisits the context, the
1224         // SSO entry will never be cleared if we don't associate the session
1225         if (session == null) {
1226             session = request.getSessionInternal(true);
1227         }
1228         sso.associate(ssoId, session);
1229
1230     }
1231
1232
1233     protected String changeSessionID(Request request, Session session) {
1234         String oldId = null;
1235         if (log.isDebugEnabled()) {
1236             oldId = session.getId();
1237         }
1238         String newId = request.changeSessionId();
1239         if (log.isDebugEnabled()) {
1240             log.debug(sm.getString("authenticator.changeSessionId", oldId, newId));
1241         }
1242         return newId;
1243     }
1244
1245
1246     @Override
1247     public void login(String username, String password, Request request) throws ServletException {
1248         Principal principal = doLogin(request, username, password);
1249         register(request, request.getResponse(), principal, getAuthMethod(), username, password);
1250     }
1251
1252     protected abstract String getAuthMethod();
1253
1254     /**
1255      * Process the login request.
1256      *
1257      * @param request
1258      *            Associated request
1259      * @param username
1260      *            The user
1261      * @param password
1262      *            The password
1263      * @return The authenticated Principal
1264      * @throws ServletException
1265      *             No principal was authenticated with the specified credentials
1266      */

1267     protected Principal doLogin(Request request, String username, String password)
1268             throws ServletException {
1269         Principal p = context.getRealm().authenticate(username, password);
1270         if (p == null) {
1271             throw new ServletException(sm.getString("authenticator.loginFail"));
1272         }
1273         return p;
1274     }
1275
1276     @Override
1277     public void logout(Request request) {
1278         AuthConfigProvider provider = getJaspicProvider();
1279         if (provider != null) {
1280             MessageInfo messageInfo = new MessageInfoImpl(request, request.getResponse(), true);
1281             Subject client = (Subject) request.getNote(Constants.REQ_JASPIC_SUBJECT_NOTE);
1282             if (client != null) {
1283                 ServerAuthContext serverAuthContext;
1284                 try {
1285                     ServerAuthConfig serverAuthConfig = provider.getServerAuthConfig("HttpServlet",
1286                             jaspicAppContextID, CallbackHandlerImpl.getInstance());
1287                     String authContextID = serverAuthConfig.getAuthContextID(messageInfo);
1288                     serverAuthContext = serverAuthConfig.getAuthContext(authContextID, nullnull);
1289                     serverAuthContext.cleanSubject(messageInfo, client);
1290                 } catch (AuthException e) {
1291                     log.debug(sm.getString("authenticator.jaspicCleanSubjectFail"), e);
1292                 }
1293             }
1294         }
1295
1296         Principal p = request.getPrincipal();
1297         if (p instanceof TomcatPrincipal) {
1298             try {
1299                 ((TomcatPrincipal) p).logout();
1300             } catch (Throwable t) {
1301                 ExceptionUtils.handleThrowable(t);
1302                 log.debug(sm.getString("authenticator.tomcatPrincipalLogoutFail"), t);
1303             }
1304         }
1305
1306         register(request, request.getResponse(), nullnullnullnull);
1307     }
1308
1309
1310     /**
1311      * Start this component and implement the requirements of
1312      * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
1313      *
1314      * @exception LifecycleException
1315      *                if this component detects a fatal error that prevents this
1316      *                component from being used
1317      */

1318     @Override
1319     protected synchronized void startInternal() throws LifecycleException {
1320         ServletContext servletContext = context.getServletContext();
1321         jaspicAppContextID = servletContext.getVirtualServerName() + " " +
1322                 servletContext.getContextPath();
1323
1324         // Look up the SingleSignOn implementation in our request processing
1325         // path, if there is one
1326         Container parent = context.getParent();
1327         while ((sso == null) && (parent != null)) {
1328             Valve valves[] = parent.getPipeline().getValves();
1329             for (int i = 0; i < valves.length; i++) {
1330                 if (valves[i] instanceof SingleSignOn) {
1331                     sso = (SingleSignOn) valves[i];
1332                     break;
1333                 }
1334             }
1335             if (sso == null) {
1336                 parent = parent.getParent();
1337             }
1338         }
1339         if (log.isDebugEnabled()) {
1340             if (sso != null) {
1341                 log.debug("Found SingleSignOn Valve at " + sso);
1342             } else {
1343                 log.debug("No SingleSignOn Valve is present");
1344             }
1345         }
1346
1347         sessionIdGenerator = new StandardSessionIdGenerator();
1348         sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
1349         sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
1350         sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
1351
1352         super.startInternal();
1353     }
1354
1355     /**
1356      * Stop this component and implement the requirements of
1357      * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
1358      *
1359      * @exception LifecycleException
1360      *                if this component detects a fatal error that prevents this
1361      *                component from being used
1362      */

1363     @Override
1364     protected synchronized void stopInternal() throws LifecycleException {
1365
1366         super.stopInternal();
1367
1368         sso = null;
1369     }
1370
1371
1372     private AuthConfigProvider getJaspicProvider() {
1373         Optional<AuthConfigProvider> provider = jaspicProvider;
1374         if (provider == null) {
1375             provider = findJaspicProvider();
1376         }
1377         return provider.orElse(null);
1378     }
1379
1380
1381     private Optional<AuthConfigProvider> findJaspicProvider() {
1382         AuthConfigFactory factory = AuthConfigFactory.getFactory();
1383         Optional<AuthConfigProvider> provider;
1384         if (factory == null) {
1385             provider = Optional.empty();
1386         } else {
1387             provider = Optional.ofNullable(
1388                     factory.getConfigProvider("HttpServlet", jaspicAppContextID, this));
1389         }
1390         jaspicProvider = provider;
1391         return provider;
1392     }
1393
1394
1395     @Override
1396     public void notify(String layer, String appContext) {
1397         findJaspicProvider();
1398     }
1399
1400
1401     private static class JaspicState {
1402         public MessageInfo messageInfo = null;
1403         public ServerAuthContext serverAuthContext = null;
1404     }
1405
1406
1407     protected enum AllowCorsPreflight {
1408         NEVER,
1409         FILTER,
1410         ALWAYS
1411     }
1412 }
1413