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.realm;
18
19 import java.beans.PropertyChangeListener;
20 import java.beans.PropertyChangeSupport;
21 import java.io.IOException;
22 import java.io.UnsupportedEncodingException;
23 import java.nio.charset.Charset;
24 import java.nio.charset.StandardCharsets;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.Principal;
27 import java.security.cert.X509Certificate;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Locale;
31
32 import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
33 import javax.servlet.http.HttpServletResponse;
34
35 import org.apache.catalina.Container;
36 import org.apache.catalina.Context;
37 import org.apache.catalina.CredentialHandler;
38 import org.apache.catalina.Engine;
39 import org.apache.catalina.Host;
40 import org.apache.catalina.LifecycleException;
41 import org.apache.catalina.LifecycleState;
42 import org.apache.catalina.Realm;
43 import org.apache.catalina.Server;
44 import org.apache.catalina.Service;
45 import org.apache.catalina.Wrapper;
46 import org.apache.catalina.connector.Request;
47 import org.apache.catalina.connector.Response;
48 import org.apache.catalina.util.LifecycleMBeanBase;
49 import org.apache.catalina.util.SessionConfig;
50 import org.apache.catalina.util.ToStringUtil;
51 import org.apache.juli.logging.Log;
52 import org.apache.juli.logging.LogFactory;
53 import org.apache.tomcat.util.IntrospectionUtils;
54 import org.apache.tomcat.util.buf.B2CConverter;
55 import org.apache.tomcat.util.descriptor.web.SecurityCollection;
56 import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
57 import org.apache.tomcat.util.res.StringManager;
58 import org.apache.tomcat.util.security.ConcurrentMessageDigest;
59 import org.apache.tomcat.util.security.MD5Encoder;
60 import org.ietf.jgss.GSSContext;
61 import org.ietf.jgss.GSSCredential;
62 import org.ietf.jgss.GSSException;
63 import org.ietf.jgss.GSSName;
64
65 /**
66 * Simple implementation of <b>Realm</b> that reads an XML file to configure
67 * the valid users, passwords, and roles. The file format (and default file
68 * location) are identical to those currently supported by Tomcat 3.X.
69 *
70 * @author Craig R. McClanahan
71 */
72 public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
73
74 private static final Log log = LogFactory.getLog(RealmBase.class);
75
76 private static final List<Class<? extends DigestCredentialHandlerBase>> credentialHandlerClasses =
77 new ArrayList<>();
78
79 static {
80 // Order is important since it determines the search order for a
81 // matching handler if only an algorithm is specified when calling
82 // main()
83 credentialHandlerClasses.add(MessageDigestCredentialHandler.class);
84 credentialHandlerClasses.add(SecretKeyCredentialHandler.class);
85 }
86
87 // ----------------------------------------------------- Instance Variables
88
89
90 /**
91 * The Container with which this Realm is associated.
92 */
93 protected Container container = null;
94
95
96 /**
97 * Container log
98 */
99 protected Log containerLog = null;
100
101
102 private CredentialHandler credentialHandler;
103
104
105 /**
106 * The string manager for this package.
107 */
108 protected static final StringManager sm = StringManager.getManager(RealmBase.class);
109
110
111 /**
112 * The property change support for this component.
113 */
114 protected final PropertyChangeSupport support = new PropertyChangeSupport(this);
115
116
117 /**
118 * Should we validate client certificate chains when they are presented?
119 */
120 protected boolean validate = true;
121
122 /**
123 * The name of the class to use for retrieving user names from X509
124 * certificates.
125 */
126 protected String x509UsernameRetrieverClassName;
127
128 /**
129 * The object that will extract user names from X509 client certificates.
130 */
131 protected X509UsernameRetriever x509UsernameRetriever;
132
133 /**
134 * The all role mode.
135 */
136 protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
137
138
139 /**
140 * When processing users authenticated via the GSS-API, should any
141 * "@..." be stripped from the end of the user name?
142 */
143 protected boolean stripRealmForGss = true;
144
145
146 private int transportGuaranteeRedirectStatus = HttpServletResponse.SC_FOUND;
147
148
149 // ------------------------------------------------------------- Properties
150
151 /**
152 * @return The HTTP status code used when the container needs to issue an
153 * HTTP redirect to meet the requirements of a configured transport
154 * guarantee.
155 */
156 public int getTransportGuaranteeRedirectStatus() {
157 return transportGuaranteeRedirectStatus;
158 }
159
160
161 /**
162 * Set the HTTP status code used when the container needs to issue an HTTP
163 * redirect to meet the requirements of a configured transport guarantee.
164 *
165 * @param transportGuaranteeRedirectStatus The status to use. This value is
166 * not validated
167 */
168 public void setTransportGuaranteeRedirectStatus(int transportGuaranteeRedirectStatus) {
169 this.transportGuaranteeRedirectStatus = transportGuaranteeRedirectStatus;
170 }
171
172
173 @Override
174 public CredentialHandler getCredentialHandler() {
175 return credentialHandler;
176 }
177
178
179 @Override
180 public void setCredentialHandler(CredentialHandler credentialHandler) {
181 this.credentialHandler = credentialHandler;
182 }
183
184
185 /**
186 * Return the Container with which this Realm has been associated.
187 */
188 @Override
189 public Container getContainer() {
190 return container;
191 }
192
193
194 /**
195 * Set the Container with which this Realm has been associated.
196 *
197 * @param container The associated Container
198 */
199 @Override
200 public void setContainer(Container container) {
201
202 Container oldContainer = this.container;
203 this.container = container;
204 support.firePropertyChange("container", oldContainer, this.container);
205
206 }
207
208 /**
209 * Return the all roles mode.
210 * @return A string representation of the current all roles mode
211 */
212 public String getAllRolesMode() {
213 return allRolesMode.toString();
214 }
215
216
217 /**
218 * Set the all roles mode.
219 * @param allRolesMode A string representation of the new all roles mode
220 */
221 public void setAllRolesMode(String allRolesMode) {
222 this.allRolesMode = AllRolesMode.toMode(allRolesMode);
223 }
224
225
226 /**
227 * Return the "validate certificate chains" flag.
228 * @return The value of the validate certificate chains flag
229 */
230 public boolean getValidate() {
231 return validate;
232 }
233
234
235 /**
236 * Set the "validate certificate chains" flag.
237 *
238 * @param validate The new validate certificate chains flag
239 */
240 public void setValidate(boolean validate) {
241
242 this.validate = validate;
243
244 }
245
246 /**
247 * Gets the name of the class that will be used to extract user names
248 * from X509 client certificates.
249 * @return The name of the class that will be used to extract user names
250 * from X509 client certificates.
251 */
252 public String getX509UsernameRetrieverClassName() {
253 return x509UsernameRetrieverClassName;
254 }
255
256 /**
257 * Sets the name of the class that will be used to extract user names
258 * from X509 client certificates. The class must implement
259 * X509UsernameRetriever.
260 *
261 * @param className The name of the class that will be used to extract user names
262 * from X509 client certificates.
263 * @see X509UsernameRetriever
264 */
265 public void setX509UsernameRetrieverClassName(String className) {
266 this.x509UsernameRetrieverClassName = className;
267 }
268
269 public boolean isStripRealmForGss() {
270 return stripRealmForGss;
271 }
272
273
274 public void setStripRealmForGss(boolean stripRealmForGss) {
275 this.stripRealmForGss = stripRealmForGss;
276 }
277
278
279 // --------------------------------------------------------- Public Methods
280
281
282 /**
283 * Add a property change listener to this component.
284 *
285 * @param listener The listener to add
286 */
287 @Override
288 public void addPropertyChangeListener(PropertyChangeListener listener) {
289
290 support.addPropertyChangeListener(listener);
291
292 }
293
294
295 /**
296 * Return the Principal associated with the specified username, if there
297 * is one; otherwise return <code>null</code>.
298 *
299 * @param username Username of the Principal to look up
300 */
301 @Override
302 public Principal authenticate(String username) {
303
304 if (username == null) {
305 return null;
306 }
307
308 if (containerLog.isTraceEnabled()) {
309 containerLog.trace(sm.getString("realmBase.authenticateSuccess", username));
310 }
311
312 return getPrincipal(username);
313 }
314
315
316 /**
317 * Return the Principal associated with the specified username and
318 * credentials, if there is one; otherwise return <code>null</code>.
319 *
320 * @param username Username of the Principal to look up
321 * @param credentials Password or other credentials to use in
322 * authenticating this username
323 * @return the associated principal, or <code>null</code> if there is none.
324 */
325 @Override
326 public Principal authenticate(String username, String credentials) {
327 // No user or no credentials
328 // Can't possibly authenticate, don't bother doing anything.
329 if(username == null || credentials == null) {
330 if (containerLog.isTraceEnabled()) {
331 containerLog.trace(sm.getString("realmBase.authenticateFailure",
332 username));
333 }
334 return null;
335 }
336
337 // Look up the user's credentials
338 String serverCredentials = getPassword(username);
339
340 if (serverCredentials == null) {
341 // User was not found
342 // Waste a bit of time as not to reveal that the user does not exist.
343 getCredentialHandler().mutate(credentials);
344
345 if (containerLog.isTraceEnabled()) {
346 containerLog.trace(sm.getString("realmBase.authenticateFailure",
347 username));
348 }
349 return null;
350 }
351
352 boolean validated = getCredentialHandler().matches(credentials, serverCredentials);
353
354 if (validated) {
355 if (containerLog.isTraceEnabled()) {
356 containerLog.trace(sm.getString("realmBase.authenticateSuccess",
357 username));
358 }
359 return getPrincipal(username);
360 } else {
361 if (containerLog.isTraceEnabled()) {
362 containerLog.trace(sm.getString("realmBase.authenticateFailure",
363 username));
364 }
365 return null;
366 }
367 }
368
369
370 /**
371 * Try to authenticate with the specified username, which
372 * matches the digest calculated using the given parameters using the
373 * method described in RFC 2617 (which is a superset of RFC 2069).
374 *
375 * @param username Username of the Principal to look up
376 * @param clientDigest Digest which has been submitted by the client
377 * @param nonce Unique (or supposedly unique) token which has been used
378 * for this request
379 * @param nc the nonce counter
380 * @param cnonce the client chosen nonce
381 * @param qop the "quality of protection" (<code>nc</code> and <code>cnonce</code>
382 * will only be used, if <code>qop</code> is not <code>null</code>).
383 * @param realm Realm name
384 * @param md5a2 Second MD5 digest used to calculate the digest :
385 * MD5(Method + ":" + uri)
386 * @return the associated principal, or <code>null</code> if there is none.
387 */
388 @Override
389 public Principal authenticate(String username, String clientDigest,
390 String nonce, String nc, String cnonce,
391 String qop, String realm,
392 String md5a2) {
393
394 // In digest auth, digests are always lower case
395 String md5a1 = getDigest(username, realm);
396 if (md5a1 == null)
397 return null;
398 md5a1 = md5a1.toLowerCase(Locale.ENGLISH);
399 String serverDigestValue;
400 if (qop == null) {
401 serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
402 } else {
403 serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" +
404 cnonce + ":" + qop + ":" + md5a2;
405 }
406
407 byte[] valueBytes = null;
408 try {
409 valueBytes = serverDigestValue.getBytes(getDigestCharset());
410 } catch (UnsupportedEncodingException uee) {
411 throw new IllegalArgumentException(sm.getString("realmBase.invalidDigestEncoding", getDigestEncoding()), uee);
412 }
413
414 String serverDigest = MD5Encoder.encode(ConcurrentMessageDigest.digestMD5(valueBytes));
415
416 if (log.isDebugEnabled()) {
417 log.debug("Digest : " + clientDigest + " Username:" + username
418 + " ClientDigest:" + clientDigest + " nonce:" + nonce
419 + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
420 + " realm:" + realm + "md5a2:" + md5a2
421 + " Server digest:" + serverDigest);
422 }
423
424 if (serverDigest.equals(clientDigest)) {
425 return getPrincipal(username);
426 }
427
428 return null;
429 }
430
431
432 /**
433 * Return the Principal associated with the specified chain of X509
434 * client certificates. If there is none, return <code>null</code>.
435 *
436 * @param certs Array of client certificates, with the first one in
437 * the array being the certificate of the client itself.
438 */
439 @Override
440 public Principal authenticate(X509Certificate certs[]) {
441
442 if ((certs == null) || (certs.length < 1))
443 return null;
444
445 // Check the validity of each certificate in the chain
446 if (log.isDebugEnabled())
447 log.debug("Authenticating client certificate chain");
448 if (validate) {
449 for (int i = 0; i < certs.length; i++) {
450 if (log.isDebugEnabled())
451 log.debug(" Checking validity for '" +
452 certs[i].getSubjectDN().getName() + "'");
453 try {
454 certs[i].checkValidity();
455 } catch (Exception e) {
456 if (log.isDebugEnabled())
457 log.debug(" Validity exception", e);
458 return null;
459 }
460 }
461 }
462
463 // Check the existence of the client Principal in our database
464 return getPrincipal(certs[0]);
465 }
466
467
468 /**
469 * {@inheritDoc}
470 */
471 @Override
472 public Principal authenticate(GSSContext gssContext, boolean storeCred) {
473 if (gssContext.isEstablished()) {
474 GSSName gssName = null;
475 try {
476 gssName = gssContext.getSrcName();
477 } catch (GSSException e) {
478 log.warn(sm.getString("realmBase.gssNameFail"), e);
479 }
480
481 if (gssName!= null) {
482 GSSCredential gssCredential = null;
483 if (storeCred) {
484 if (gssContext.getCredDelegState()) {
485 try {
486 gssCredential = gssContext.getDelegCred();
487 } catch (GSSException e) {
488 log.warn(sm.getString(
489 "realmBase.delegatedCredentialFail", gssName), e);
490 }
491 } else {
492 if (log.isDebugEnabled()) {
493 log.debug(sm.getString(
494 "realmBase.credentialNotDelegated", gssName));
495 }
496 }
497 }
498
499 return getPrincipal(gssName, gssCredential);
500 }
501 } else {
502 log.error(sm.getString("realmBase.gssContextNotEstablished"));
503 }
504
505 // Fail in all other cases
506 return null;
507 }
508
509
510 /**
511 * {@inheritDoc}
512 */
513 @Override
514 public Principal authenticate(GSSName gssName, GSSCredential gssCredential) {
515 if (gssName == null) {
516 return null;
517 }
518
519 return getPrincipal(gssName, gssCredential);
520 }
521
522
523 /**
524 * Execute a periodic task, such as reloading, etc. This method will be
525 * invoked inside the classloading context of this container. Unexpected
526 * throwables will be caught and logged.
527 */
528 @Override
529 public void backgroundProcess() {
530 // NOOP in base class
531 }
532
533
534 /**
535 * Return the SecurityConstraints configured to guard the request URI for
536 * this request, or <code>null</code> if there is no such constraint.
537 *
538 * @param request Request we are processing
539 * @param context Context the Request is mapped to
540 */
541 @Override
542 public SecurityConstraint [] findSecurityConstraints(Request request,
543 Context context) {
544
545 ArrayList<SecurityConstraint> results = null;
546 // Are there any defined security constraints?
547 SecurityConstraint constraints[] = context.findConstraints();
548 if ((constraints == null) || (constraints.length == 0)) {
549 if (log.isDebugEnabled())
550 log.debug(" No applicable constraints defined");
551 return null;
552 }
553
554 // Check each defined security constraint
555 String uri = request.getRequestPathMB().toString();
556 // Bug47080 - in rare cases this may be null or ""
557 // Mapper treats as '/' do the same to prevent NPE
558 if (uri == null || uri.length() == 0) {
559 uri = "/";
560 }
561
562 String method = request.getMethod();
563 int i;
564 boolean found = false;
565 for (i = 0; i < constraints.length; i++) {
566 SecurityCollection [] collection = constraints[i].findCollections();
567
568 // If collection is null, continue to avoid an NPE
569 // See Bugzilla 30624
570 if ( collection == null) {
571 continue;
572 }
573
574 if (log.isDebugEnabled()) {
575 log.debug(" Checking constraint '" + constraints[i] +
576 "' against " + method + " " + uri + " --> " +
577 constraints[i].included(uri, method));
578 }
579
580 for(int j=0; j < collection.length; j++){
581 String [] patterns = collection[j].findPatterns();
582
583 // If patterns is null, continue to avoid an NPE
584 // See Bugzilla 30624
585 if ( patterns == null) {
586 continue;
587 }
588
589 for(int k=0; k < patterns.length; k++) {
590 // Exact match including special case for the context root.
591 if(uri.equals(patterns[k]) || patterns[k].length() == 0 && uri.equals("/")) {
592 found = true;
593 if(collection[j].findMethod(method)) {
594 if(results == null) {
595 results = new ArrayList<>();
596 }
597 results.add(constraints[i]);
598 }
599 }
600 }
601 }
602 }
603
604 if(found) {
605 return resultsToArray(results);
606 }
607
608 int longest = -1;
609
610 for (i = 0; i < constraints.length; i++) {
611 SecurityCollection [] collection = constraints[i].findCollections();
612
613 // If collection is null, continue to avoid an NPE
614 // See Bugzilla 30624
615 if ( collection == null) {
616 continue;
617 }
618
619 if (log.isDebugEnabled()) {
620 log.debug(" Checking constraint '" + constraints[i] +
621 "' against " + method + " " + uri + " --> " +
622 constraints[i].included(uri, method));
623 }
624
625 for(int j=0; j < collection.length; j++){
626 String [] patterns = collection[j].findPatterns();
627
628 // If patterns is null, continue to avoid an NPE
629 // See Bugzilla 30624
630 if ( patterns == null) {
631 continue;
632 }
633
634 boolean matched = false;
635 int length = -1;
636 for(int k=0; k < patterns.length; k++) {
637 String pattern = patterns[k];
638 if(pattern.startsWith("/") && pattern.endsWith("/*") &&
639 pattern.length() >= longest) {
640
641 if(pattern.length() == 2) {
642 matched = true;
643 length = pattern.length();
644 } else if(pattern.regionMatches(0,uri,0,
645 pattern.length()-1) ||
646 (pattern.length()-2 == uri.length() &&
647 pattern.regionMatches(0,uri,0,
648 pattern.length()-2))) {
649 matched = true;
650 length = pattern.length();
651 }
652 }
653 }
654 if(matched) {
655 if(length > longest) {
656 found = false;
657 if(results != null) {
658 results.clear();
659 }
660 longest = length;
661 }
662 if(collection[j].findMethod(method)) {
663 found = true;
664 if(results == null) {
665 results = new ArrayList<>();
666 }
667 results.add(constraints[i]);
668 }
669 }
670 }
671 }
672
673 if(found) {
674 return resultsToArray(results);
675 }
676
677 for (i = 0; i < constraints.length; i++) {
678 SecurityCollection [] collection = constraints[i].findCollections();
679
680 // If collection is null, continue to avoid an NPE
681 // See Bugzilla 30624
682 if ( collection == null) {
683 continue;
684 }
685
686 if (log.isDebugEnabled()) {
687 log.debug(" Checking constraint '" + constraints[i] +
688 "' against " + method + " " + uri + " --> " +
689 constraints[i].included(uri, method));
690 }
691
692 boolean matched = false;
693 int pos = -1;
694 for(int j=0; j < collection.length; j++){
695 String [] patterns = collection[j].findPatterns();
696
697 // If patterns is null, continue to avoid an NPE
698 // See Bugzilla 30624
699 if ( patterns == null) {
700 continue;
701 }
702
703 for(int k=0; k < patterns.length && !matched; k++) {
704 String pattern = patterns[k];
705 if(pattern.startsWith("*.")){
706 int slash = uri.lastIndexOf('/');
707 int dot = uri.lastIndexOf('.');
708 if(slash >= 0 && dot > slash &&
709 dot != uri.length()-1 &&
710 uri.length()-dot == pattern.length()-1) {
711 if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
712 matched = true;
713 pos = j;
714 }
715 }
716 }
717 }
718 }
719 if(matched) {
720 found = true;
721 if(collection[pos].findMethod(method)) {
722 if(results == null) {
723 results = new ArrayList<>();
724 }
725 results.add(constraints[i]);
726 }
727 }
728 }
729
730 if(found) {
731 return resultsToArray(results);
732 }
733
734 for (i = 0; i < constraints.length; i++) {
735 SecurityCollection [] collection = constraints[i].findCollections();
736
737 // If collection is null, continue to avoid an NPE
738 // See Bugzilla 30624
739 if ( collection == null) {
740 continue;
741 }
742
743 if (log.isDebugEnabled()) {
744 log.debug(" Checking constraint '" + constraints[i] +
745 "' against " + method + " " + uri + " --> " +
746 constraints[i].included(uri, method));
747 }
748
749 for(int j=0; j < collection.length; j++){
750 String [] patterns = collection[j].findPatterns();
751
752 // If patterns is null, continue to avoid an NPE
753 // See Bugzilla 30624
754 if ( patterns == null) {
755 continue;
756 }
757
758 boolean matched = false;
759 for(int k=0; k < patterns.length && !matched; k++) {
760 String pattern = patterns[k];
761 if(pattern.equals("/")){
762 matched = true;
763 }
764 }
765 if(matched) {
766 if(results == null) {
767 results = new ArrayList<>();
768 }
769 results.add(constraints[i]);
770 }
771 }
772 }
773
774 if(results == null) {
775 // No applicable security constraint was found
776 if (log.isDebugEnabled())
777 log.debug(" No applicable constraint located");
778 }
779 return resultsToArray(results);
780 }
781
782 /**
783 * Convert an ArrayList to a SecurityConstraint [].
784 */
785 private SecurityConstraint [] resultsToArray(
786 ArrayList<SecurityConstraint> results) {
787 if(results == null || results.size() == 0) {
788 return null;
789 }
790 SecurityConstraint [] array = new SecurityConstraint[results.size()];
791 results.toArray(array);
792 return array;
793 }
794
795
796 /**
797 * Perform access control based on the specified authorization constraint.
798 * Return <code>true</code> if this constraint is satisfied and processing
799 * should continue, or <code>false</code> otherwise.
800 *
801 * @param request Request we are processing
802 * @param response Response we are creating
803 * @param constraints Security constraint we are enforcing
804 * @param context The Context to which client of this class is attached.
805 *
806 * @exception IOException if an input/output error occurs
807 */
808 @Override
809 public boolean hasResourcePermission(Request request,
810 Response response,
811 SecurityConstraint []constraints,
812 Context context)
813 throws IOException {
814
815 if (constraints == null || constraints.length == 0)
816 return true;
817
818 // Which user principal have we already authenticated?
819 Principal principal = request.getPrincipal();
820 boolean status = false;
821 boolean denyfromall = false;
822 for(int i=0; i < constraints.length; i++) {
823 SecurityConstraint constraint = constraints[i];
824
825 String roles[];
826 if (constraint.getAllRoles()) {
827 // * means all roles defined in web.xml
828 roles = request.getContext().findSecurityRoles();
829 } else {
830 roles = constraint.findAuthRoles();
831 }
832
833 if (roles == null)
834 roles = new String[0];
835
836 if (log.isDebugEnabled())
837 log.debug(" Checking roles " + principal);
838
839 if (constraint.getAuthenticatedUsers() && principal != null) {
840 if (log.isDebugEnabled()) {
841 log.debug("Passing all authenticated users");
842 }
843 status = true;
844 } else if (roles.length == 0 && !constraint.getAllRoles() &&
845 !constraint.getAuthenticatedUsers()) {
846 if(constraint.getAuthConstraint()) {
847 if( log.isDebugEnabled() )
848 log.debug("No roles");
849 status = false; // No listed roles means no access at all
850 denyfromall = true;
851 break;
852 }
853
854 if(log.isDebugEnabled())
855 log.debug("Passing all access");
856 status = true;
857 } else if (principal == null) {
858 if (log.isDebugEnabled())
859 log.debug(" No user authenticated, cannot grant access");
860 } else {
861 for (int j = 0; j < roles.length; j++) {
862 if (hasRole(request.getWrapper(), principal, roles[j])) {
863 status = true;
864 if( log.isDebugEnabled() )
865 log.debug( "Role found: " + roles[j]);
866 }
867 else if( log.isDebugEnabled() )
868 log.debug( "No role found: " + roles[j]);
869 }
870 }
871 }
872
873 if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
874 !status && principal != null) {
875 if (log.isDebugEnabled()) {
876 log.debug("Checking for all roles mode: " + allRolesMode);
877 }
878 // Check for an all roles(role-name="*")
879 for (int i = 0; i < constraints.length; i++) {
880 SecurityConstraint constraint = constraints[i];
881 String roles[];
882 // If the all roles mode exists, sets
883 if (constraint.getAllRoles()) {
884 if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
885 if (log.isDebugEnabled()) {
886 log.debug("Granting access for role-name=*, auth-only");
887 }
888 status = true;
889 break;
890 }
891
892 // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
893 roles = request.getContext().findSecurityRoles();
894 if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
895 if (log.isDebugEnabled()) {
896 log.debug("Granting access for role-name=*, strict auth-only");
897 }
898 status = true;
899 break;
900 }
901 }
902 }
903 }
904
905 // Return a "Forbidden" message denying access to this resource
906 if(!status) {
907 response.sendError
908 (HttpServletResponse.SC_FORBIDDEN,
909 sm.getString("realmBase.forbidden"));
910 }
911 return status;
912
913 }
914
915
916 /**
917 * {@inheritDoc}
918 *
919 * This method or {@link #hasRoleInternal(Principal,
920 * String)} can be overridden by Realm implementations, but the default is
921 * adequate when an instance of <code>GenericPrincipal</code> is used to
922 * represent authenticated Principals from this Realm.
923 */
924 @Override
925 public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
926 // Check for a role alias
927 if (wrapper != null) {
928 String realRole = wrapper.findSecurityReference(role);
929 if (realRole != null) {
930 role = realRole;
931 }
932 }
933
934 // Should be overridden in JAASRealm - to avoid pretty inefficient conversions
935 if (principal == null || role == null) {
936 return false;
937 }
938
939 boolean result = hasRoleInternal(principal, role);
940
941 if (log.isDebugEnabled()) {
942 String name = principal.getName();
943 if (result)
944 log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
945 else
946 log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
947 }
948
949 return result;
950 }
951
952
953 /**
954 * Check if the specified Principal has the specified
955 * security role, within the context of this Realm.
956 *
957 * This method or {@link #hasRoleInternal(Principal,
958 * String)} can be overridden by Realm implementations, but the default is
959 * adequate when an instance of <code>GenericPrincipal</code> is used to
960 * represent authenticated Principals from this Realm.
961 *
962 * @param principal Principal for whom the role is to be checked
963 * @param role Security role to be checked
964 *
965 * @return <code>true</code> if the specified Principal has the specified
966 * security role, within the context of this Realm; otherwise return
967 * <code>false</code>.
968 */
969 protected boolean hasRoleInternal(Principal principal, String role) {
970 // Should be overridden in JAASRealm - to avoid pretty inefficient conversions
971 if (!(principal instanceof GenericPrincipal)) {
972 return false;
973 }
974
975 GenericPrincipal gp = (GenericPrincipal) principal;
976 return gp.hasRole(role);
977 }
978
979
980 /**
981 * Enforce any user data constraint required by the security constraint
982 * guarding this request URI. Return <code>true</code> if this constraint
983 * was not violated and processing should continue, or <code>false</code>
984 * if we have created a response already.
985 *
986 * @param request Request we are processing
987 * @param response Response we are creating
988 * @param constraints Security constraint being checked
989 *
990 * @exception IOException if an input/output error occurs
991 */
992 @Override
993 public boolean hasUserDataPermission(Request request,
994 Response response,
995 SecurityConstraint []constraints)
996 throws IOException {
997
998 // Is there a relevant user data constraint?
999 if (constraints == null || constraints.length == 0) {
1000 if (log.isDebugEnabled())
1001 log.debug(" No applicable security constraint defined");
1002 return true;
1003 }
1004 for(int i=0; i < constraints.length; i++) {
1005 SecurityConstraint constraint = constraints[i];
1006 String userConstraint = constraint.getUserConstraint();
1007 if (userConstraint == null) {
1008 if (log.isDebugEnabled())
1009 log.debug(" No applicable user data constraint defined");
1010 return true;
1011 }
1012 if (userConstraint.equals(TransportGuarantee.NONE.name())) {
1013 if (log.isDebugEnabled())
1014 log.debug(" User data constraint has no restrictions");
1015 return true;
1016 }
1017
1018 }
1019 // Validate the request against the user data constraint
1020 if (request.getRequest().isSecure()) {
1021 if (log.isDebugEnabled())
1022 log.debug(" User data constraint already satisfied");
1023 return true;
1024 }
1025 // Initialize variables we need to determine the appropriate action
1026 int redirectPort = request.getConnector().getRedirectPortWithOffset();
1027
1028 // Is redirecting disabled?
1029 if (redirectPort <= 0) {
1030 if (log.isDebugEnabled())
1031 log.debug(" SSL redirect is disabled");
1032 response.sendError
1033 (HttpServletResponse.SC_FORBIDDEN,
1034 request.getRequestURI());
1035 return false;
1036 }
1037
1038 // Redirect to the corresponding SSL port
1039 StringBuilder file = new StringBuilder();
1040 String protocol = "https";
1041 String host = request.getServerName();
1042 // Protocol
1043 file.append(protocol).append("://").append(host);
1044 // Host with port
1045 if(redirectPort != 443) {
1046 file.append(":").append(redirectPort);
1047 }
1048 // URI
1049 file.append(request.getRequestURI());
1050 String requestedSessionId = request.getRequestedSessionId();
1051 if ((requestedSessionId != null) &&
1052 request.isRequestedSessionIdFromURL()) {
1053 file.append(";");
1054 file.append(SessionConfig.getSessionUriParamName(
1055 request.getContext()));
1056 file.append("=");
1057 file.append(requestedSessionId);
1058 }
1059 String queryString = request.getQueryString();
1060 if (queryString != null) {
1061 file.append('?');
1062 file.append(queryString);
1063 }
1064 if (log.isDebugEnabled())
1065 log.debug(" Redirecting to " + file.toString());
1066 response.sendRedirect(file.toString(), transportGuaranteeRedirectStatus);
1067 return false;
1068
1069 }
1070
1071
1072 /**
1073 * Remove a property change listener from this component.
1074 *
1075 * @param listener The listener to remove
1076 */
1077 @Override
1078 public void removePropertyChangeListener(PropertyChangeListener listener) {
1079
1080 support.removePropertyChangeListener(listener);
1081
1082 }
1083
1084
1085 @Override
1086 protected void initInternal() throws LifecycleException {
1087
1088 super.initInternal();
1089
1090 // We want logger as soon as possible
1091 if (container != null) {
1092 this.containerLog = container.getLogger();
1093 }
1094
1095 x509UsernameRetriever = createUsernameRetriever(x509UsernameRetrieverClassName);
1096 }
1097
1098 /**
1099 * Prepare for the beginning of active use of the public methods of this
1100 * component and implement the requirements of
1101 * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
1102 *
1103 * @exception LifecycleException if this component detects a fatal error
1104 * that prevents this component from being used
1105 */
1106 @Override
1107 protected void startInternal() throws LifecycleException {
1108 if (credentialHandler == null) {
1109 credentialHandler = new MessageDigestCredentialHandler();
1110 }
1111
1112 setState(LifecycleState.STARTING);
1113 }
1114
1115
1116 /**
1117 * Gracefully terminate the active use of the public methods of this
1118 * component and implement the requirements of
1119 * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
1120 *
1121 * @exception LifecycleException if this component detects a fatal error
1122 * that needs to be reported
1123 */
1124 @Override
1125 protected void stopInternal() throws LifecycleException {
1126 setState(LifecycleState.STOPPING);
1127 }
1128
1129
1130 /**
1131 * Return a String representation of this component.
1132 */
1133 @Override
1134 public String toString() {
1135 return ToStringUtil.toString(this);
1136 }
1137
1138
1139 // ------------------------------------------------------ Protected Methods
1140
1141 protected boolean hasMessageDigest() {
1142 CredentialHandler ch = credentialHandler;
1143 if (ch instanceof MessageDigestCredentialHandler) {
1144 return ((MessageDigestCredentialHandler) ch).getAlgorithm() != null;
1145 }
1146 return false;
1147 }
1148
1149
1150 /**
1151 * Return the digest associated with given principal's user name.
1152 * @param username the user name
1153 * @param realmName the realm name
1154 * @return the digest for the specified user
1155 */
1156 protected String getDigest(String username, String realmName) {
1157 if (hasMessageDigest()) {
1158 // Use pre-generated digest
1159 return getPassword(username);
1160 }
1161
1162 String digestValue = username + ":" + realmName + ":"
1163 + getPassword(username);
1164
1165 byte[] valueBytes = null;
1166 try {
1167 valueBytes = digestValue.getBytes(getDigestCharset());
1168 } catch (UnsupportedEncodingException uee) {
1169 throw new IllegalArgumentException(sm.getString("realmBase.invalidDigestEncoding", getDigestEncoding()), uee);
1170 }
1171
1172 return MD5Encoder.encode(ConcurrentMessageDigest.digestMD5(valueBytes));
1173 }
1174
1175
1176 private String getDigestEncoding() {
1177 CredentialHandler ch = credentialHandler;
1178 if (ch instanceof MessageDigestCredentialHandler) {
1179 return ((MessageDigestCredentialHandler) ch).getEncoding();
1180 }
1181 return null;
1182 }
1183
1184
1185 private Charset getDigestCharset() throws UnsupportedEncodingException {
1186 String charset = getDigestEncoding();
1187 if (charset == null) {
1188 return StandardCharsets.ISO_8859_1;
1189 } else {
1190 return B2CConverter.getCharset(charset);
1191 }
1192 }
1193
1194
1195 /**
1196 * Get the password for the specified user.
1197 * @param username The user name
1198 * @return the password associated with the given principal's user name.
1199 */
1200 protected abstract String getPassword(String username);
1201
1202
1203 /**
1204 * Get the principal associated with the specified certificate.
1205 * @param usercert The user certificate
1206 * @return the Principal associated with the given certificate.
1207 */
1208 protected Principal getPrincipal(X509Certificate usercert) {
1209 String username = x509UsernameRetriever.getUsername(usercert);
1210
1211 if(log.isDebugEnabled())
1212 log.debug(sm.getString("realmBase.gotX509Username", username));
1213
1214 return getPrincipal(username);
1215 }
1216
1217
1218 /**
1219 * Get the principal associated with the specified user.
1220 * @param username The user name
1221 * @return the Principal associated with the given user name.
1222 */
1223 protected abstract Principal getPrincipal(String username);
1224
1225
1226 /**
1227 * Get the principal associated with the specified user name.
1228 *
1229 * @param username The user name
1230 * @param gssCredential the GSS credential of the principal
1231 * @return the principal associated with the given user name.
1232 * @deprecated This will be removed in Tomcat 10 onwards. Use
1233 * {@link #getPrincipal(GSSName, GSSCredential)} instead.
1234 */
1235 @Deprecated
1236 protected Principal getPrincipal(String username,
1237 GSSCredential gssCredential) {
1238 Principal p = getPrincipal(username);
1239
1240 if (p instanceof GenericPrincipal) {
1241 ((GenericPrincipal) p).setGssCredential(gssCredential);
1242 }
1243
1244 return p;
1245 }
1246
1247
1248 /**
1249 * Get the principal associated with the specified {@link GSSName}.
1250 *
1251 * @param gssName The GSS name
1252 * @param gssCredential the GSS credential of the principal
1253 * @return the principal associated with the given user name.
1254 */
1255 protected Principal getPrincipal(GSSName gssName,
1256 GSSCredential gssCredential) {
1257 String name = gssName.toString();
1258
1259 if (isStripRealmForGss()) {
1260 int i = name.indexOf('@');
1261 if (i > 0) {
1262 // Zero so we don't leave a zero length name
1263 name = name.substring(0, i);
1264 }
1265 }
1266
1267 Principal p = getPrincipal(name);
1268
1269 if (p instanceof GenericPrincipal) {
1270 ((GenericPrincipal) p).setGssCredential(gssCredential);
1271 }
1272
1273 return p;
1274 }
1275
1276
1277 /**
1278 * Return the Server object that is the ultimate parent for the container
1279 * with which this Realm is associated. If the server cannot be found (eg
1280 * because the container hierarchy is not complete), <code>null</code> is
1281 * returned.
1282 * @return the Server associated with the realm
1283 */
1284 protected Server getServer() {
1285 Container c = container;
1286 if (c instanceof Context) {
1287 c = c.getParent();
1288 }
1289 if (c instanceof Host) {
1290 c = c.getParent();
1291 }
1292 if (c instanceof Engine) {
1293 Service s = ((Engine)c).getService();
1294 if (s != null) {
1295 return s.getServer();
1296 }
1297 }
1298 return null;
1299 }
1300
1301
1302 // --------------------------------------------------------- Static Methods
1303
1304 /**
1305 * Generate a stored credential string for the given password and associated
1306 * parameters.
1307 * <p>The following parameters are supported:</p>
1308 * <ul>
1309 * <li><b>-a</b> - The algorithm to use to generate the stored
1310 * credential. If not specified a default of SHA-512 will be
1311 * used.</li>
1312 * <li><b>-e</b> - The encoding to use for any byte to/from character
1313 * conversion that may be necessary. If not specified, the
1314 * system encoding ({@link Charset#defaultCharset()}) will
1315 * be used.</li>
1316 * <li><b>-i</b> - The number of iterations to use when generating the
1317 * stored credential. If not specified, the default for the
1318 * CredentialHandler will be used.</li>
1319 * <li><b>-s</b> - The length (in bytes) of salt to generate and store as
1320 * part of the credential. If not specified, the default for
1321 * the CredentialHandler will be used.</li>
1322 * <li><b>-k</b> - The length (in bits) of the key(s), if any, created while
1323 * generating the credential. If not specified, the default
1324 * for the CredentialHandler will be used.</li>
1325 * <li><b>-h</b> - The fully qualified class name of the CredentialHandler
1326 * to use. If not specified, the built-in handlers will be
1327 * tested in turn and the first one to accept the specified
1328 * algorithm will be used.</li>
1329 * </ul>
1330 * <p>This generation process currently supports the following
1331 * CredentialHandlers, the correct one being selected based on the algorithm
1332 * specified:</p>
1333 * <ul>
1334 * <li>{@link MessageDigestCredentialHandler}</li>
1335 * <li>{@link SecretKeyCredentialHandler}</li>
1336 * </ul>
1337 * @param args The parameters passed on the command line
1338 */
1339 public static void main(String args[]) {
1340
1341 // Use negative values since null is not an option to indicate 'not set'
1342 int saltLength = -1;
1343 int iterations = -1;
1344 int keyLength = -1;
1345 // Default
1346 String encoding = Charset.defaultCharset().name();
1347 // Default values for these depend on whether either of them are set on
1348 // the command line
1349 String algorithm = null;
1350 String handlerClassName = null;
1351
1352 if (args.length == 0) {
1353 usage();
1354 return;
1355 }
1356
1357 int argIndex = 0;
1358
1359 while (args.length > argIndex + 2 && args[argIndex].length() == 2 &&
1360 args[argIndex].charAt(0) == '-' ) {
1361 switch (args[argIndex].charAt(1)) {
1362 case 'a': {
1363 algorithm = args[argIndex + 1];
1364 break;
1365 }
1366 case 'e': {
1367 encoding = args[argIndex + 1];
1368 break;
1369 }
1370 case 'i': {
1371 iterations = Integer.parseInt(args[argIndex + 1]);
1372 break;
1373 }
1374 case 's': {
1375 saltLength = Integer.parseInt(args[argIndex + 1]);
1376 break;
1377 }
1378 case 'k': {
1379 keyLength = Integer.parseInt(args[argIndex + 1]);
1380 break;
1381 }
1382 case 'h': {
1383 handlerClassName = args[argIndex + 1];
1384 break;
1385 }
1386 default: {
1387 usage();
1388 return;
1389 }
1390 }
1391 argIndex += 2;
1392 }
1393
1394 // Determine defaults for -a and -h. The rules are more complex to
1395 // express than the implementation:
1396 // - if neither -a nor -h is set, use SHA-512 and
1397 // MessageDigestCredentialHandler
1398 // - if only -a is set the built-in handlers will be searched in order
1399 // (MessageDigestCredentialHandler, SecretKeyCredentialHandler) and
1400 // the first handler that supports the algorithm will be used
1401 // - if only -h is set no default will be used for -a. The handler may
1402 // or may nor support -a and may or may not supply a sensible default
1403 if (algorithm == null && handlerClassName == null) {
1404 algorithm = "SHA-512";
1405 }
1406
1407 CredentialHandler handler = null;
1408
1409 if (handlerClassName == null) {
1410 for (Class<? extends DigestCredentialHandlerBase> clazz : credentialHandlerClasses) {
1411 try {
1412 handler = clazz.getConstructor().newInstance();
1413 if (IntrospectionUtils.setProperty(handler, "algorithm", algorithm)) {
1414 break;
1415 }
1416 } catch (ReflectiveOperationException e) {
1417 // This isn't good.
1418 throw new RuntimeException(e);
1419 }
1420 }
1421 } else {
1422 try {
1423 Class<?> clazz = Class.forName(handlerClassName);
1424 handler = (DigestCredentialHandlerBase) clazz.getConstructor().newInstance();
1425 IntrospectionUtils.setProperty(handler, "algorithm", algorithm);
1426 } catch (ReflectiveOperationException e) {
1427 throw new RuntimeException(e);
1428 }
1429 }
1430
1431 if (handler == null) {
1432 throw new RuntimeException(new NoSuchAlgorithmException(algorithm));
1433 }
1434
1435 IntrospectionUtils.setProperty(handler, "encoding", encoding);
1436 if (iterations > 0) {
1437 IntrospectionUtils.setProperty(handler, "iterations", Integer.toString(iterations));
1438 }
1439 if (saltLength > -1) {
1440 IntrospectionUtils.setProperty(handler, "saltLength", Integer.toString(saltLength));
1441 }
1442 if (keyLength > 0) {
1443 IntrospectionUtils.setProperty(handler, "keyLength", Integer.toString(keyLength));
1444 }
1445
1446 for (; argIndex < args.length; argIndex++) {
1447 String credential = args[argIndex];
1448 System.out.print(credential + ":");
1449 System.out.println(handler.mutate(credential));
1450 }
1451 }
1452
1453
1454 private static void usage() {
1455 System.out.println("Usage: RealmBase [-a <algorithm>] [-e <encoding>] " +
1456 "[-i <iterations>] [-s <salt-length>] [-k <key-length>] " +
1457 "[-h <handler-class-name>] <credentials>");
1458 }
1459
1460
1461 // -------------------- JMX and Registration --------------------
1462
1463 @Override
1464 public String getObjectNameKeyProperties() {
1465
1466 StringBuilder keyProperties = new StringBuilder("type=Realm");
1467 keyProperties.append(getRealmSuffix());
1468 keyProperties.append(container.getMBeanKeyProperties());
1469
1470 return keyProperties.toString();
1471 }
1472
1473 @Override
1474 public String getDomainInternal() {
1475 return container.getDomain();
1476 }
1477
1478 protected String realmPath = "/realm0";
1479
1480 public String getRealmPath() {
1481 return realmPath;
1482 }
1483
1484 public void setRealmPath(String theRealmPath) {
1485 realmPath = theRealmPath;
1486 }
1487
1488 protected String getRealmSuffix() {
1489 return ",realmPath=" + getRealmPath();
1490 }
1491
1492
1493 protected static class AllRolesMode {
1494
1495 private final String name;
1496 /** Use the strict servlet spec interpretation which requires that the user
1497 * have one of the web-app/security-role/role-name
1498 */
1499 public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
1500 /** Allow any authenticated user
1501 */
1502 public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
1503 /** Allow any authenticated user only if there are no web-app/security-roles
1504 */
1505 public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
1506
1507 static AllRolesMode toMode(String name) {
1508 AllRolesMode mode;
1509 if (name.equalsIgnoreCase(STRICT_MODE.name)) {
1510 mode = STRICT_MODE;
1511 } else if (name.equalsIgnoreCase(AUTH_ONLY_MODE.name)) {
1512 mode = AUTH_ONLY_MODE;
1513 } else if (name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name)) {
1514 mode = STRICT_AUTH_ONLY_MODE;
1515 } else {
1516 throw new IllegalStateException(
1517 sm.getString("realmBase.unknownAllRolesMode", name));
1518 }
1519 return mode;
1520 }
1521
1522 private AllRolesMode(String name) {
1523 this.name = name;
1524 }
1525
1526 @Override
1527 public boolean equals(Object o) {
1528 boolean equals = false;
1529 if (o instanceof AllRolesMode) {
1530 AllRolesMode mode = (AllRolesMode) o;
1531 equals = name.equals(mode.name);
1532 }
1533 return equals;
1534 }
1535
1536 @Override
1537 public int hashCode() {
1538 return name.hashCode();
1539 }
1540
1541 @Override
1542 public String toString() {
1543 return name;
1544 }
1545 }
1546
1547 private static X509UsernameRetriever createUsernameRetriever(String className)
1548 throws LifecycleException {
1549 if(null == className || "".equals(className.trim()))
1550 return new X509SubjectDnRetriever();
1551
1552 try {
1553 @SuppressWarnings("unchecked")
1554 Class<? extends X509UsernameRetriever> clazz = (Class<? extends X509UsernameRetriever>)Class.forName(className);
1555 return clazz.getConstructor().newInstance();
1556 } catch (ReflectiveOperationException e) {
1557 throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.newInstance", className), e);
1558 } catch (ClassCastException e) {
1559 throw new LifecycleException(sm.getString("realmBase.createUsernameRetriever.ClassCastException", className), e);
1560 }
1561 }
1562
1563
1564 @Override
1565 public String[] getRoles(Principal principal) {
1566 if (principal instanceof GenericPrincipal) {
1567 return ((GenericPrincipal) principal).getRoles();
1568 }
1569
1570 String className = principal.getClass().getSimpleName();
1571 throw new IllegalStateException(sm.getString("realmBase.cannotGetRoles", className));
1572 }
1573 }
1574