1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.realm;
20
21
22 import java.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.Principal;
29 import java.security.cert.X509Certificate;
30 import java.util.ArrayList;
31
32 import javax.management.Attribute;
33 import javax.management.MBeanRegistration;
34 import javax.management.MBeanServer;
35 import javax.management.ObjectName;
36 import javax.servlet.http.HttpServletResponse;
37
38 import org.apache.catalina.Container;
39 import org.apache.catalina.Context;
40 import org.apache.catalina.Globals;
41 import org.apache.catalina.Lifecycle;
42 import org.apache.catalina.LifecycleException;
43 import org.apache.catalina.LifecycleListener;
44 import org.apache.catalina.Realm;
45 import org.apache.catalina.connector.Request;
46 import org.apache.catalina.connector.Response;
47 import org.apache.catalina.core.ContainerBase;
48 import org.apache.catalina.deploy.LoginConfig;
49 import org.apache.catalina.deploy.SecurityConstraint;
50 import org.apache.catalina.deploy.SecurityCollection;
51 import org.apache.catalina.util.HexUtils;
52 import org.apache.catalina.util.LifecycleSupport;
53 import org.apache.catalina.util.MD5Encoder;
54 import org.apache.catalina.util.StringManager;
55 import org.apache.juli.logging.Log;
56 import org.apache.juli.logging.LogFactory;
57 import org.apache.tomcat.util.modeler.Registry;
58
59 /**
60 * Simple implementation of <b>Realm</b> that reads an XML file to configure
61 * the valid users, passwords, and roles. The file format (and default file
62 * location) are identical to those currently supported by Tomcat 3.X.
63 *
64 * @author Craig R. McClanahan
65 * @version $Revision: 555304 $ $Date: 2007-07-11 17:28:52 +0200 (mer., 11 juil. 2007) $
66 */
67
68 public abstract class RealmBase
69 implements Lifecycle, Realm, MBeanRegistration {
70
71 private static Log log = LogFactory.getLog(RealmBase.class);
72
73 // ----------------------------------------------------- Instance Variables
74
75
76 /**
77 * The Container with which this Realm is associated.
78 */
79 protected Container container = null;
80
81
82 /**
83 * Container log
84 */
85 protected Log containerLog = null;
86
87
88 /**
89 * Digest algorithm used in storing passwords in a non-plaintext format.
90 * Valid values are those accepted for the algorithm name by the
91 * MessageDigest class, or <code>null</code> if no digesting should
92 * be performed.
93 */
94 protected String digest = null;
95
96 /**
97 * The encoding charset for the digest.
98 */
99 protected String digestEncoding = null;
100
101
102 /**
103 * Descriptive information about this Realm implementation.
104 */
105 protected static final String info =
106 "org.apache.catalina.realm.RealmBase/1.0";
107
108
109 /**
110 * The lifecycle event support for this component.
111 */
112 protected LifecycleSupport lifecycle = new LifecycleSupport(this);
113
114
115 /**
116 * The MessageDigest object for digesting user credentials (passwords).
117 */
118 protected MessageDigest md = null;
119
120
121 /**
122 * The MD5 helper object for this class.
123 */
124 protected static final MD5Encoder md5Encoder = new MD5Encoder();
125
126
127 /**
128 * MD5 message digest provider.
129 */
130 protected static MessageDigest md5Helper;
131
132
133 /**
134 * The string manager for this package.
135 */
136 protected static StringManager sm =
137 StringManager.getManager(Constants.Package);
138
139
140 /**
141 * Has this component been started?
142 */
143 protected boolean started = false;
144
145
146 /**
147 * The property change support for this component.
148 */
149 protected PropertyChangeSupport support = new PropertyChangeSupport(this);
150
151
152 /**
153 * Should we validate client certificate chains when they are presented?
154 */
155 protected boolean validate = true;
156
157
158 /**
159 * The all role mode.
160 */
161 protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
162
163
164 // ------------------------------------------------------------- Properties
165
166
167 /**
168 * Return the Container with which this Realm has been associated.
169 */
170 public Container getContainer() {
171
172 return (container);
173
174 }
175
176
177 /**
178 * Set the Container with which this Realm has been associated.
179 *
180 * @param container The associated Container
181 */
182 public void setContainer(Container container) {
183
184 Container oldContainer = this.container;
185 this.container = container;
186 support.firePropertyChange("container", oldContainer, this.container);
187
188 }
189
190 /**
191 * Return the all roles mode.
192 */
193 public String getAllRolesMode() {
194
195 return allRolesMode.toString();
196
197 }
198
199
200 /**
201 * Set the all roles mode.
202 */
203 public void setAllRolesMode(String allRolesMode) {
204
205 this.allRolesMode = AllRolesMode.toMode(allRolesMode);
206
207 }
208
209 /**
210 * Return the digest algorithm used for storing credentials.
211 */
212 public String getDigest() {
213
214 return digest;
215
216 }
217
218
219 /**
220 * Set the digest algorithm used for storing credentials.
221 *
222 * @param digest The new digest algorithm
223 */
224 public void setDigest(String digest) {
225
226 this.digest = digest;
227
228 }
229
230 /**
231 * Returns the digest encoding charset.
232 *
233 * @return The charset (may be null) for platform default
234 */
235 public String getDigestEncoding() {
236 return digestEncoding;
237 }
238
239 /**
240 * Sets the digest encoding charset.
241 *
242 * @param charset The charset (null for platform default)
243 */
244 public void setDigestEncoding(String charset) {
245 digestEncoding = charset;
246 }
247
248 /**
249 * Return descriptive information about this Realm implementation and
250 * the corresponding version number, in the format
251 * <code><description>/<version></code>.
252 */
253 public String getInfo() {
254
255 return info;
256
257 }
258
259
260 /**
261 * Return the "validate certificate chains" flag.
262 */
263 public boolean getValidate() {
264
265 return (this.validate);
266
267 }
268
269
270 /**
271 * Set the "validate certificate chains" flag.
272 *
273 * @param validate The new validate certificate chains flag
274 */
275 public void setValidate(boolean validate) {
276
277 this.validate = validate;
278
279 }
280
281
282 // --------------------------------------------------------- Public Methods
283
284
285
286 /**
287 * Add a property change listener to this component.
288 *
289 * @param listener The listener to add
290 */
291 public void addPropertyChangeListener(PropertyChangeListener listener) {
292
293 support.addPropertyChangeListener(listener);
294
295 }
296
297
298 /**
299 * Return the Principal associated with the specified username and
300 * credentials, if there is one; otherwise return <code>null</code>.
301 *
302 * @param username Username of the Principal to look up
303 * @param credentials Password or other credentials to use in
304 * authenticating this username
305 */
306 public Principal authenticate(String username, String credentials) {
307
308 String serverCredentials = getPassword(username);
309
310 boolean validated ;
311 if ( serverCredentials == null ) {
312 validated = false;
313 } else if(hasMessageDigest()) {
314 validated = serverCredentials.equalsIgnoreCase(digest(credentials));
315 } else {
316 validated = serverCredentials.equals(credentials);
317 }
318 if(! validated ) {
319 if (containerLog.isTraceEnabled()) {
320 containerLog.trace(sm.getString("realmBase.authenticateFailure",
321 username));
322 }
323 return null;
324 }
325 if (containerLog.isTraceEnabled()) {
326 containerLog.trace(sm.getString("realmBase.authenticateSuccess",
327 username));
328 }
329
330 return getPrincipal(username);
331 }
332
333
334 /**
335 * Return the Principal associated with the specified username and
336 * credentials, if there is one; otherwise return <code>null</code>.
337 *
338 * @param username Username of the Principal to look up
339 * @param credentials Password or other credentials to use in
340 * authenticating this username
341 */
342 public Principal authenticate(String username, byte[] credentials) {
343
344 return (authenticate(username, credentials.toString()));
345
346 }
347
348
349 /**
350 * Return the Principal associated with the specified username, which
351 * matches the digest calculated using the given parameters using the
352 * method described in RFC 2069; otherwise return <code>null</code>.
353 *
354 * @param username Username of the Principal to look up
355 * @param clientDigest Digest which has been submitted by the client
356 * @param nOnce Unique (or supposedly unique) token which has been used
357 * for this request
358 * @param realm Realm name
359 * @param md5a2 Second MD5 digest used to calculate the digest :
360 * MD5(Method + ":" + uri)
361 */
362 public Principal authenticate(String username, String clientDigest,
363 String nOnce, String nc, String cnonce,
364 String qop, String realm,
365 String md5a2) {
366
367 String md5a1 = getDigest(username, realm);
368 if (md5a1 == null)
369 return null;
370 String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
371 + cnonce + ":" + qop + ":" + md5a2;
372
373 byte[] valueBytes = null;
374 if(getDigestEncoding() == null) {
375 valueBytes = serverDigestValue.getBytes();
376 } else {
377 try {
378 valueBytes = serverDigestValue.getBytes(getDigestEncoding());
379 } catch (UnsupportedEncodingException uee) {
380 log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
381 throw new IllegalArgumentException(uee.getMessage());
382 }
383 }
384
385 String serverDigest = null;
386 // Bugzilla 32137
387 synchronized(md5Helper) {
388 serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
389 }
390
391 if (log.isDebugEnabled()) {
392 log.debug("Digest : " + clientDigest + " Username:" + username
393 + " ClientSigest:" + clientDigest + " nOnce:" + nOnce
394 + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
395 + " realm:" + realm + "md5a2:" + md5a2
396 + " Server digest:" + serverDigest);
397 }
398
399 if (serverDigest.equals(clientDigest))
400 return getPrincipal(username);
401 else
402 return null;
403 }
404
405
406
407 /**
408 * Return the Principal associated with the specified chain of X509
409 * client certificates. If there is none, return <code>null</code>.
410 *
411 * @param certs Array of client certificates, with the first one in
412 * the array being the certificate of the client itself.
413 */
414 public Principal authenticate(X509Certificate certs[]) {
415
416 if ((certs == null) || (certs.length < 1))
417 return (null);
418
419 // Check the validity of each certificate in the chain
420 if (log.isDebugEnabled())
421 log.debug("Authenticating client certificate chain");
422 if (validate) {
423 for (int i = 0; i < certs.length; i++) {
424 if (log.isDebugEnabled())
425 log.debug(" Checking validity for '" +
426 certs[i].getSubjectDN().getName() + "'");
427 try {
428 certs[i].checkValidity();
429 } catch (Exception e) {
430 if (log.isDebugEnabled())
431 log.debug(" Validity exception", e);
432 return (null);
433 }
434 }
435 }
436
437 // Check the existence of the client Principal in our database
438 return (getPrincipal(certs[0]));
439
440 }
441
442
443 /**
444 * Execute a periodic task, such as reloading, etc. This method will be
445 * invoked inside the classloading context of this container. Unexpected
446 * throwables will be caught and logged.
447 */
448 public void backgroundProcess() {
449 }
450
451
452 /**
453 * Return the SecurityConstraints configured to guard the request URI for
454 * this request, or <code>null</code> if there is no such constraint.
455 *
456 * @param request Request we are processing
457 * @param context Context the Request is mapped to
458 */
459 public SecurityConstraint [] findSecurityConstraints(Request request,
460 Context context) {
461
462 ArrayList<SecurityConstraint> results = null;
463 // Are there any defined security constraints?
464 SecurityConstraint constraints[] = context.findConstraints();
465 if ((constraints == null) || (constraints.length == 0)) {
466 if (log.isDebugEnabled())
467 log.debug(" No applicable constraints defined");
468 return (null);
469 }
470
471 // Check each defined security constraint
472 String uri = request.getRequestPathMB().toString();
473
474 String method = request.getMethod();
475 int i;
476 boolean found = false;
477 for (i = 0; i < constraints.length; i++) {
478 SecurityCollection [] collection = constraints[i].findCollections();
479
480 // If collection is null, continue to avoid an NPE
481 // See Bugzilla 30624
482 if ( collection == null) {
483 continue;
484 }
485
486 if (log.isDebugEnabled()) {
487 log.debug(" Checking constraint '" + constraints[i] +
488 "' against " + method + " " + uri + " --> " +
489 constraints[i].included(uri, method));
490 }
491
492 for(int j=0; j < collection.length; j++){
493 String [] patterns = collection[j].findPatterns();
494
495 // If patterns is null, continue to avoid an NPE
496 // See Bugzilla 30624
497 if ( patterns == null) {
498 continue;
499 }
500
501 for(int k=0; k < patterns.length; k++) {
502 if(uri.equals(patterns[k])) {
503 found = true;
504 if(collection[j].findMethod(method)) {
505 if(results == null) {
506 results = new ArrayList<SecurityConstraint>();
507 }
508 results.add(constraints[i]);
509 }
510 }
511 }
512 }
513 }
514
515 if(found) {
516 return resultsToArray(results);
517 }
518
519 int longest = -1;
520
521 for (i = 0; i < constraints.length; i++) {
522 SecurityCollection [] collection = constraints[i].findCollections();
523
524 // If collection is null, continue to avoid an NPE
525 // See Bugzilla 30624
526 if ( collection == null) {
527 continue;
528 }
529
530 if (log.isDebugEnabled()) {
531 log.debug(" Checking constraint '" + constraints[i] +
532 "' against " + method + " " + uri + " --> " +
533 constraints[i].included(uri, method));
534 }
535
536 for(int j=0; j < collection.length; j++){
537 String [] patterns = collection[j].findPatterns();
538
539 // If patterns is null, continue to avoid an NPE
540 // See Bugzilla 30624
541 if ( patterns == null) {
542 continue;
543 }
544
545 boolean matched = false;
546 int length = -1;
547 for(int k=0; k < patterns.length; k++) {
548 String pattern = patterns[k];
549 if(pattern.startsWith("/") && pattern.endsWith("/*") &&
550 pattern.length() >= longest) {
551
552 if(pattern.length() == 2) {
553 matched = true;
554 length = pattern.length();
555 } else if(pattern.regionMatches(0,uri,0,
556 pattern.length()-1) ||
557 (pattern.length()-2 == uri.length() &&
558 pattern.regionMatches(0,uri,0,
559 pattern.length()-2))) {
560 matched = true;
561 length = pattern.length();
562 }
563 }
564 }
565 if(matched) {
566 found = true;
567 if(length > longest) {
568 if(results != null) {
569 results.clear();
570 }
571 longest = length;
572 }
573 if(collection[j].findMethod(method)) {
574 if(results == null) {
575 results = new ArrayList<SecurityConstraint>();
576 }
577 results.add(constraints[i]);
578 }
579 }
580 }
581 }
582
583 if(found) {
584 return resultsToArray(results);
585 }
586
587 for (i = 0; i < constraints.length; i++) {
588 SecurityCollection [] collection = constraints[i].findCollections();
589
590 // If collection is null, continue to avoid an NPE
591 // See Bugzilla 30624
592 if ( collection == null) {
593 continue;
594 }
595
596 if (log.isDebugEnabled()) {
597 log.debug(" Checking constraint '" + constraints[i] +
598 "' against " + method + " " + uri + " --> " +
599 constraints[i].included(uri, method));
600 }
601
602 boolean matched = false;
603 int pos = -1;
604 for(int j=0; j < collection.length; j++){
605 String [] patterns = collection[j].findPatterns();
606
607 // If patterns is null, continue to avoid an NPE
608 // See Bugzilla 30624
609 if ( patterns == null) {
610 continue;
611 }
612
613 for(int k=0; k < patterns.length && !matched; k++) {
614 String pattern = patterns[k];
615 if(pattern.startsWith("*.")){
616 int slash = uri.lastIndexOf("/");
617 int dot = uri.lastIndexOf(".");
618 if(slash >= 0 && dot > slash &&
619 dot != uri.length()-1 &&
620 uri.length()-dot == pattern.length()-1) {
621 if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
622 matched = true;
623 pos = j;
624 }
625 }
626 }
627 }
628 }
629 if(matched) {
630 found = true;
631 if(collection[pos].findMethod(method)) {
632 if(results == null) {
633 results = new ArrayList<SecurityConstraint>();
634 }
635 results.add(constraints[i]);
636 }
637 }
638 }
639
640 if(found) {
641 return resultsToArray(results);
642 }
643
644 for (i = 0; i < constraints.length; i++) {
645 SecurityCollection [] collection = constraints[i].findCollections();
646
647 // If collection is null, continue to avoid an NPE
648 // See Bugzilla 30624
649 if ( collection == null) {
650 continue;
651 }
652
653 if (log.isDebugEnabled()) {
654 log.debug(" Checking constraint '" + constraints[i] +
655 "' against " + method + " " + uri + " --> " +
656 constraints[i].included(uri, method));
657 }
658
659 for(int j=0; j < collection.length; j++){
660 String [] patterns = collection[j].findPatterns();
661
662 // If patterns is null, continue to avoid an NPE
663 // See Bugzilla 30624
664 if ( patterns == null) {
665 continue;
666 }
667
668 boolean matched = false;
669 for(int k=0; k < patterns.length && !matched; k++) {
670 String pattern = patterns[k];
671 if(pattern.equals("/")){
672 matched = true;
673 }
674 }
675 if(matched) {
676 if(results == null) {
677 results = new ArrayList<SecurityConstraint>();
678 }
679 results.add(constraints[i]);
680 }
681 }
682 }
683
684 if(results == null) {
685 // No applicable security constraint was found
686 if (log.isDebugEnabled())
687 log.debug(" No applicable constraint located");
688 }
689 return resultsToArray(results);
690 }
691
692 /**
693 * Convert an ArrayList to a SecurityContraint [].
694 */
695 private SecurityConstraint [] resultsToArray(
696 ArrayList<SecurityConstraint> results) {
697 if(results == null) {
698 return null;
699 }
700 SecurityConstraint [] array = new SecurityConstraint[results.size()];
701 results.toArray(array);
702 return array;
703 }
704
705
706 /**
707 * Perform access control based on the specified authorization constraint.
708 * Return <code>true</code> if this constraint is satisfied and processing
709 * should continue, or <code>false</code> otherwise.
710 *
711 * @param request Request we are processing
712 * @param response Response we are creating
713 * @param constraints Security constraint we are enforcing
714 * @param context The Context to which client of this class is attached.
715 *
716 * @exception IOException if an input/output error occurs
717 */
718 public boolean hasResourcePermission(Request request,
719 Response response,
720 SecurityConstraint []constraints,
721 Context context)
722 throws IOException {
723
724 if (constraints == null || constraints.length == 0)
725 return (true);
726
727 // Specifically allow access to the form login and form error pages
728 // and the "j_security_check" action
729 LoginConfig config = context.getLoginConfig();
730 if ((config != null) &&
731 (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
732 String requestURI = request.getRequestPathMB().toString();
733 String loginPage = config.getLoginPage();
734 if (loginPage.equals(requestURI)) {
735 if (log.isDebugEnabled())
736 log.debug(" Allow access to login page " + loginPage);
737 return (true);
738 }
739 String errorPage = config.getErrorPage();
740 if (errorPage.equals(requestURI)) {
741 if (log.isDebugEnabled())
742 log.debug(" Allow access to error page " + errorPage);
743 return (true);
744 }
745 if (requestURI.endsWith(Constants.FORM_ACTION)) {
746 if (log.isDebugEnabled())
747 log.debug(" Allow access to username/password submission");
748 return (true);
749 }
750 }
751
752 // Which user principal have we already authenticated?
753 Principal principal = request.getPrincipal();
754 boolean status = false;
755 boolean denyfromall = false;
756 for(int i=0; i < constraints.length; i++) {
757 SecurityConstraint constraint = constraints[i];
758
759 String roles[];
760 if (constraint.getAllRoles()) {
761 // * means all roles defined in web.xml
762 roles = request.getContext().findSecurityRoles();
763 } else {
764 roles = constraint.findAuthRoles();
765 }
766
767 if (roles == null)
768 roles = new String[0];
769
770 if (log.isDebugEnabled())
771 log.debug(" Checking roles " + principal);
772
773 if (roles.length == 0 && !constraint.getAllRoles()) {
774 if(constraint.getAuthConstraint()) {
775 if( log.isDebugEnabled() )
776 log.debug("No roles ");
777 status = false; // No listed roles means no access at all
778 denyfromall = true;
779 } else {
780 if(log.isDebugEnabled())
781 log.debug("Passing all access");
782 return (true);
783 }
784 } else if (principal == null) {
785 if (log.isDebugEnabled())
786 log.debug(" No user authenticated, cannot grant access");
787 status = false;
788 } else if(!denyfromall) {
789
790 for (int j = 0; j < roles.length; j++) {
791 if (hasRole(principal, roles[j]))
792 status = true;
793 if( log.isDebugEnabled() )
794 log.debug( "No role found: " + roles[j]);
795 }
796 }
797 }
798
799 if (allRolesMode != AllRolesMode.STRICT_MODE && !status && principal != null) {
800 if (log.isDebugEnabled()) {
801 log.debug("Checking for all roles mode: " + allRolesMode);
802 }
803 // Check for an all roles(role-name="*")
804 for (int i = 0; i < constraints.length; i++) {
805 SecurityConstraint constraint = constraints[i];
806 String roles[];
807 // If the all roles mode exists, sets
808 if (constraint.getAllRoles()) {
809 if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
810 if (log.isDebugEnabled()) {
811 log.debug("Granting access for role-name=*, auth-only");
812 }
813 status = true;
814 break;
815 }
816
817 // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
818 roles = request.getContext().findSecurityRoles();
819 if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
820 if (log.isDebugEnabled()) {
821 log.debug("Granting access for role-name=*, strict auth-only");
822 }
823 status = true;
824 break;
825 }
826 }
827 }
828 }
829
830 // Return a "Forbidden" message denying access to this resource
831 if(!status) {
832 response.sendError
833 (HttpServletResponse.SC_FORBIDDEN,
834 sm.getString("realmBase.forbidden"));
835 }
836 return status;
837
838 }
839
840
841 /**
842 * Return <code>true</code> if the specified Principal has the specified
843 * security role, within the context of this Realm; otherwise return
844 * <code>false</code>. This method can be overridden by Realm
845 * implementations, but the default is adequate when an instance of
846 * <code>GenericPrincipal</code> is used to represent authenticated
847 * Principals from this Realm.
848 *
849 * @param principal Principal for whom the role is to be checked
850 * @param role Security role to be checked
851 */
852 public boolean hasRole(Principal principal, String role) {
853
854 // Should be overriten in JAASRealm - to avoid pretty inefficient conversions
855 if ((principal == null) || (role == null) ||
856 !(principal instanceof GenericPrincipal))
857 return (false);
858
859 GenericPrincipal gp = (GenericPrincipal) principal;
860 if (!(gp.getRealm() == this)) {
861 if(log.isDebugEnabled())
862 log.debug("Different realm " + this + " " + gp.getRealm());// return (false);
863 }
864 boolean result = gp.hasRole(role);
865 if (log.isDebugEnabled()) {
866 String name = principal.getName();
867 if (result)
868 log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
869 else
870 log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
871 }
872 return (result);
873
874 }
875
876
877 /**
878 * Enforce any user data constraint required by the security constraint
879 * guarding this request URI. Return <code>true</code> if this constraint
880 * was not violated and processing should continue, or <code>false</code>
881 * if we have created a response already.
882 *
883 * @param request Request we are processing
884 * @param response Response we are creating
885 * @param constraints Security constraint being checked
886 *
887 * @exception IOException if an input/output error occurs
888 */
889 public boolean hasUserDataPermission(Request request,
890 Response response,
891 SecurityConstraint []constraints)
892 throws IOException {
893
894 // Is there a relevant user data constraint?
895 if (constraints == null || constraints.length == 0) {
896 if (log.isDebugEnabled())
897 log.debug(" No applicable security constraint defined");
898 return (true);
899 }
900 for(int i=0; i < constraints.length; i++) {
901 SecurityConstraint constraint = constraints[i];
902 String userConstraint = constraint.getUserConstraint();
903 if (userConstraint == null) {
904 if (log.isDebugEnabled())
905 log.debug(" No applicable user data constraint defined");
906 return (true);
907 }
908 if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
909 if (log.isDebugEnabled())
910 log.debug(" User data constraint has no restrictions");
911 return (true);
912 }
913
914 }
915 // Validate the request against the user data constraint
916 if (request.getRequest().isSecure()) {
917 if (log.isDebugEnabled())
918 log.debug(" User data constraint already satisfied");
919 return (true);
920 }
921 // Initialize variables we need to determine the appropriate action
922 int redirectPort = request.getConnector().getRedirectPort();
923
924 // Is redirecting disabled?
925 if (redirectPort <= 0) {
926 if (log.isDebugEnabled())
927 log.debug(" SSL redirect is disabled");
928 response.sendError
929 (HttpServletResponse.SC_FORBIDDEN,
930 request.getRequestURI());
931 return (false);
932 }
933
934 // Redirect to the corresponding SSL port
935 StringBuffer file = new StringBuffer();
936 String protocol = "https";
937 String host = request.getServerName();
938 // Protocol
939 file.append(protocol).append("://").append(host);
940 // Host with port
941 if(redirectPort != 443) {
942 file.append(":").append(redirectPort);
943 }
944 // URI
945 file.append(request.getRequestURI());
946 String requestedSessionId = request.getRequestedSessionId();
947 if ((requestedSessionId != null) &&
948 request.isRequestedSessionIdFromURL()) {
949 file.append(";");
950 file.append(Globals.SESSION_PARAMETER_NAME);
951 file.append("=");
952 file.append(requestedSessionId);
953 }
954 String queryString = request.getQueryString();
955 if (queryString != null) {
956 file.append('?');
957 file.append(queryString);
958 }
959 if (log.isDebugEnabled())
960 log.debug(" Redirecting to " + file.toString());
961 response.sendRedirect(file.toString());
962 return (false);
963
964 }
965
966
967 /**
968 * Remove a property change listener from this component.
969 *
970 * @param listener The listener to remove
971 */
972 public void removePropertyChangeListener(PropertyChangeListener listener) {
973
974 support.removePropertyChangeListener(listener);
975
976 }
977
978
979 // ------------------------------------------------------ Lifecycle Methods
980
981
982 /**
983 * Add a lifecycle event listener to this component.
984 *
985 * @param listener The listener to add
986 */
987 public void addLifecycleListener(LifecycleListener listener) {
988
989 lifecycle.addLifecycleListener(listener);
990
991 }
992
993
994 /**
995 * Get the lifecycle listeners associated with this lifecycle. If this
996 * Lifecycle has no listeners registered, a zero-length array is returned.
997 */
998 public LifecycleListener[] findLifecycleListeners() {
999
1000 return lifecycle.findLifecycleListeners();
1001
1002 }
1003
1004
1005 /**
1006 * Remove a lifecycle event listener from this component.
1007 *
1008 * @param listener The listener to remove
1009 */
1010 public void removeLifecycleListener(LifecycleListener listener) {
1011
1012 lifecycle.removeLifecycleListener(listener);
1013
1014 }
1015
1016 /**
1017 * Prepare for the beginning of active use of the public methods of this
1018 * component. This method should be called before any of the public
1019 * methods of this component are utilized. It should also send a
1020 * LifecycleEvent of type START_EVENT to any registered listeners.
1021 *
1022 * @exception LifecycleException if this component detects a fatal error
1023 * that prevents this component from being used
1024 */
1025 public void start() throws LifecycleException {
1026
1027 // Validate and update our current component state
1028 if (started) {
1029 if(log.isInfoEnabled())
1030 log.info(sm.getString("realmBase.alreadyStarted"));
1031 return;
1032 }
1033 if( !initialized ) {
1034 init();
1035 }
1036 lifecycle.fireLifecycleEvent(START_EVENT, null);
1037 started = true;
1038
1039 // Create a MessageDigest instance for credentials, if desired
1040 if (digest != null) {
1041 try {
1042 md = MessageDigest.getInstance(digest);
1043 } catch (NoSuchAlgorithmException e) {
1044 throw new LifecycleException
1045 (sm.getString("realmBase.algorithm", digest), e);
1046 }
1047 }
1048
1049 }
1050
1051
1052 /**
1053 * Gracefully terminate the active use of the public methods of this
1054 * component. This method should be the last one called on a given
1055 * instance of this component. It should also send a LifecycleEvent
1056 * of type STOP_EVENT to any registered listeners.
1057 *
1058 * @exception LifecycleException if this component detects a fatal error
1059 * that needs to be reported
1060 */
1061 public void stop()
1062 throws LifecycleException {
1063
1064 // Validate and update our current component state
1065 if (!started) {
1066 if(log.isInfoEnabled())
1067 log.info(sm.getString("realmBase.notStarted"));
1068 return;
1069 }
1070 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1071 started = false;
1072
1073 // Clean up allocated resources
1074 md = null;
1075
1076 destroy();
1077
1078 }
1079
1080 public void destroy() {
1081
1082 // unregister this realm
1083 if ( oname!=null ) {
1084 try {
1085 Registry.getRegistry(null, null).unregisterComponent(oname);
1086 if(log.isDebugEnabled())
1087 log.debug( "unregistering realm " + oname );
1088 } catch( Exception ex ) {
1089 log.error( "Can't unregister realm " + oname, ex);
1090 }
1091 }
1092
1093 }
1094
1095 // ------------------------------------------------------ Protected Methods
1096
1097
1098 /**
1099 * Digest the password using the specified algorithm and
1100 * convert the result to a corresponding hexadecimal string.
1101 * If exception, the plain credentials string is returned.
1102 *
1103 * @param credentials Password or other credentials to use in
1104 * authenticating this username
1105 */
1106 protected String digest(String credentials) {
1107
1108 // If no MessageDigest instance is specified, return unchanged
1109 if (hasMessageDigest() == false)
1110 return (credentials);
1111
1112 // Digest the user credentials and return as hexadecimal
1113 synchronized (this) {
1114 try {
1115 md.reset();
1116
1117 byte[] bytes = null;
1118 if(getDigestEncoding() == null) {
1119 bytes = credentials.getBytes();
1120 } else {
1121 try {
1122 bytes = credentials.getBytes(getDigestEncoding());
1123 } catch (UnsupportedEncodingException uee) {
1124 log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1125 throw new IllegalArgumentException(uee.getMessage());
1126 }
1127 }
1128 md.update(bytes);
1129
1130 return (HexUtils.convert(md.digest()));
1131 } catch (Exception e) {
1132 log.error(sm.getString("realmBase.digest"), e);
1133 return (credentials);
1134 }
1135 }
1136
1137 }
1138
1139 protected boolean hasMessageDigest() {
1140 return !(md == null);
1141 }
1142
1143 /**
1144 * Return the digest associated with given principal's user name.
1145 */
1146 protected String getDigest(String username, String realmName) {
1147 if (md5Helper == null) {
1148 try {
1149 md5Helper = MessageDigest.getInstance("MD5");
1150 } catch (NoSuchAlgorithmException e) {
1151 log.error("Couldn't get MD5 digest: ", e);
1152 throw new IllegalStateException(e.getMessage());
1153 }
1154 }
1155
1156 if (hasMessageDigest()) {
1157 // Use pre-generated digest
1158 return getPassword(username);
1159 }
1160
1161 String digestValue = username + ":" + realmName + ":"
1162 + getPassword(username);
1163
1164 byte[] valueBytes = null;
1165 if(getDigestEncoding() == null) {
1166 valueBytes = digestValue.getBytes();
1167 } else {
1168 try {
1169 valueBytes = digestValue.getBytes(getDigestEncoding());
1170 } catch (UnsupportedEncodingException uee) {
1171 log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
1172 throw new IllegalArgumentException(uee.getMessage());
1173 }
1174 }
1175
1176 byte[] digest = null;
1177 // Bugzilla 32137
1178 synchronized(md5Helper) {
1179 digest = md5Helper.digest(valueBytes);
1180 }
1181
1182 return md5Encoder.encode(digest);
1183 }
1184
1185
1186 /**
1187 * Return a short name for this Realm implementation, for use in
1188 * log messages.
1189 */
1190 protected abstract String getName();
1191
1192
1193 /**
1194 * Return the password associated with the given principal's user name.
1195 */
1196 protected abstract String getPassword(String username);
1197
1198
1199 /**
1200 * Return the Principal associated with the given certificate.
1201 */
1202 protected Principal getPrincipal(X509Certificate usercert) {
1203 return(getPrincipal(usercert.getSubjectDN().getName()));
1204 }
1205
1206
1207 /**
1208 * Return the Principal associated with the given user name.
1209 */
1210 protected abstract Principal getPrincipal(String username);
1211
1212
1213 // --------------------------------------------------------- Static Methods
1214
1215
1216 /**
1217 * Digest password using the algorithm especificied and
1218 * convert the result to a corresponding hex string.
1219 * If exception, the plain credentials string is returned
1220 *
1221 * @param credentials Password or other credentials to use in
1222 * authenticating this username
1223 * @param algorithm Algorithm used to do the digest
1224 * @param encoding Character encoding of the string to digest
1225 */
1226 public final static String Digest(String credentials, String algorithm,
1227 String encoding) {
1228
1229 try {
1230 // Obtain a new message digest with "digest" encryption
1231 MessageDigest md =
1232 (MessageDigest) MessageDigest.getInstance(algorithm).clone();
1233
1234 // encode the credentials
1235 // Should use the digestEncoding, but that's not a static field
1236 if (encoding == null) {
1237 md.update(credentials.getBytes());
1238 } else {
1239 md.update(credentials.getBytes(encoding));
1240 }
1241
1242 // Digest the credentials and return as hexadecimal
1243 return (HexUtils.convert(md.digest()));
1244 } catch(Exception ex) {
1245 log.error(ex);
1246 return credentials;
1247 }
1248
1249 }
1250
1251
1252 /**
1253 * Digest password using the algorithm especificied and
1254 * convert the result to a corresponding hex string.
1255 * If exception, the plain credentials string is returned
1256 */
1257 public static void main(String args[]) {
1258
1259 String encoding = null;
1260 int firstCredentialArg = 2;
1261
1262 if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
1263 encoding = args[3];
1264 firstCredentialArg = 4;
1265 }
1266
1267 if(args.length > firstCredentialArg && args[0].equalsIgnoreCase("-a")) {
1268 for(int i=firstCredentialArg; i < args.length ; i++){
1269 System.out.print(args[i]+":");
1270 System.out.println(Digest(args[i], args[1], encoding));
1271 }
1272 } else {
1273 System.out.println
1274 ("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
1275 }
1276
1277 }
1278
1279
1280 // -------------------- JMX and Registration --------------------
1281 protected String type;
1282 protected String domain;
1283 protected String host;
1284 protected String path;
1285 protected ObjectName oname;
1286 protected ObjectName controller;
1287 protected MBeanServer mserver;
1288
1289 public ObjectName getController() {
1290 return controller;
1291 }
1292
1293 public void setController(ObjectName controller) {
1294 this.controller = controller;
1295 }
1296
1297 public ObjectName getObjectName() {
1298 return oname;
1299 }
1300
1301 public String getDomain() {
1302 return domain;
1303 }
1304
1305 public String getType() {
1306 return type;
1307 }
1308
1309 public ObjectName preRegister(MBeanServer server,
1310 ObjectName name) throws Exception {
1311 oname=name;
1312 mserver=server;
1313 domain=name.getDomain();
1314
1315 type=name.getKeyProperty("type");
1316 host=name.getKeyProperty("host");
1317 path=name.getKeyProperty("path");
1318
1319 return name;
1320 }
1321
1322 public void postRegister(Boolean registrationDone) {
1323 }
1324
1325 public void preDeregister() throws Exception {
1326 }
1327
1328 public void postDeregister() {
1329 }
1330
1331 protected boolean initialized=false;
1332
1333 public void init() {
1334 if( initialized && container != null ) return;
1335
1336 // We want logger as soon as possible
1337 if (container != null) {
1338 this.containerLog = container.getLogger();
1339 }
1340
1341 initialized=true;
1342 if( container== null ) {
1343 ObjectName parent=null;
1344 // Register with the parent
1345 try {
1346 if( host == null ) {
1347 // global
1348 parent=new ObjectName(domain +":type=Engine");
1349 } else if( path==null ) {
1350 parent=new ObjectName(domain +
1351 ":type=Host,host=" + host);
1352 } else {
1353 parent=new ObjectName(domain +":j2eeType=WebModule,name=//" +
1354 host + path);
1355 }
1356 if( mserver.isRegistered(parent )) {
1357 if(log.isDebugEnabled())
1358 log.debug("Register with " + parent);
1359 mserver.setAttribute(parent, new Attribute("realm", this));
1360 }
1361 } catch (Exception e) {
1362 log.error("Parent not available yet: " + parent);
1363 }
1364 }
1365
1366 if( oname==null ) {
1367 // register
1368 try {
1369 ContainerBase cb=(ContainerBase)container;
1370 oname=new ObjectName(cb.getDomain()+":type=Realm" + cb.getContainerSuffix());
1371 Registry.getRegistry(null, null).registerComponent(this, oname, null );
1372 if(log.isDebugEnabled())
1373 log.debug("Register Realm "+oname);
1374 } catch (Throwable e) {
1375 log.error( "Can't register " + oname, e);
1376 }
1377 }
1378
1379 }
1380
1381
1382 protected static class AllRolesMode {
1383
1384 private String name;
1385 /** Use the strict servlet spec interpretation which requires that the user
1386 * have one of the web-app/security-role/role-name
1387 */
1388 public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
1389 /** Allow any authenticated user
1390 */
1391 public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
1392 /** Allow any authenticated user only if there are no web-app/security-roles
1393 */
1394 public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
1395
1396 static AllRolesMode toMode(String name)
1397 {
1398 AllRolesMode mode;
1399 if( name.equalsIgnoreCase(STRICT_MODE.name) )
1400 mode = STRICT_MODE;
1401 else if( name.equalsIgnoreCase(AUTH_ONLY_MODE.name) )
1402 mode = AUTH_ONLY_MODE;
1403 else if( name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name) )
1404 mode = STRICT_AUTH_ONLY_MODE;
1405 else
1406 throw new IllegalStateException("Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
1407 return mode;
1408 }
1409
1410 private AllRolesMode(String name)
1411 {
1412 this.name = name;
1413 }
1414
1415 public boolean equals(Object o)
1416 {
1417 boolean equals = false;
1418 if( o instanceof AllRolesMode )
1419 {
1420 AllRolesMode mode = (AllRolesMode) o;
1421 equals = name.equals(mode.name);
1422 }
1423 return equals;
1424 }
1425 public int hashCode()
1426 {
1427 return name.hashCode();
1428 }
1429 public String toString()
1430 {
1431 return name;
1432 }
1433 }
1434
1435 }