KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > realm > RealmBase


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 JavaDoc;
23 import java.beans.PropertyChangeSupport JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.UnsupportedEncodingException JavaDoc;
26 import java.security.MessageDigest JavaDoc;
27 import java.security.NoSuchAlgorithmException JavaDoc;
28 import java.security.Principal JavaDoc;
29 import java.security.cert.X509Certificate JavaDoc;
30 import java.util.ArrayList JavaDoc;
31
32 import javax.management.Attribute JavaDoc;
33 import javax.management.MBeanRegistration JavaDoc;
34 import javax.management.MBeanServer JavaDoc;
35 import javax.management.ObjectName JavaDoc;
36 import javax.servlet.http.HttpServletResponse JavaDoc;
37
38 import org.apache.catalina.Container;
39 import org.apache.catalina.Context;
40 import org.apache.catalina.Lifecycle;
41 import org.apache.catalina.LifecycleException;
42 import org.apache.catalina.LifecycleListener;
43 import org.apache.catalina.Realm;
44 import org.apache.catalina.connector.Request;
45 import org.apache.catalina.connector.Response;
46 import org.apache.catalina.core.ContainerBase;
47 import org.apache.catalina.deploy.LoginConfig;
48 import org.apache.catalina.deploy.SecurityConstraint;
49 import org.apache.catalina.deploy.SecurityCollection;
50 import org.apache.catalina.util.HexUtils;
51 import org.apache.catalina.util.LifecycleSupport;
52 import org.apache.catalina.util.MD5Encoder;
53 import org.apache.catalina.util.StringManager;
54 import org.apache.commons.logging.Log;
55 import org.apache.commons.logging.LogFactory;
56 import org.apache.tomcat.util.modeler.Registry;
57
58 /**
59  * Simple implementation of <b>Realm</b> that reads an XML file to configure
60  * the valid users, passwords, and roles. The file format (and default file
61  * location) are identical to those currently supported by Tomcat 3.X.
62  *
63  * @author Craig R. McClanahan
64  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
65  */

66
67 public abstract class RealmBase
68     implements Lifecycle, Realm, MBeanRegistration JavaDoc {
69
70     private static Log log = LogFactory.getLog(RealmBase.class);
71
72     // ----------------------------------------------------- Instance Variables
73

74
75     /**
76      * The Container with which this Realm is associated.
77      */

78     protected Container container = null;
79
80
81     /**
82      * Container log
83      */

84     protected Log containerLog = null;
85
86
87     /**
88      * Digest algorithm used in storing passwords in a non-plaintext format.
89      * Valid values are those accepted for the algorithm name by the
90      * MessageDigest class, or <code>null</code> if no digesting should
91      * be performed.
92      */

93     protected String JavaDoc digest = null;
94
95     /**
96      * The encoding charset for the digest.
97      */

98     protected String JavaDoc digestEncoding = null;
99
100
101     /**
102      * Descriptive information about this Realm implementation.
103      */

104     protected static final String JavaDoc info =
105         "org.apache.catalina.realm.RealmBase/1.0";
106
107
108     /**
109      * The lifecycle event support for this component.
110      */

111     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
112
113
114     /**
115      * The MessageDigest object for digesting user credentials (passwords).
116      */

117     protected MessageDigest JavaDoc md = null;
118
119
120     /**
121      * The MD5 helper object for this class.
122      */

123     protected static final MD5Encoder md5Encoder = new MD5Encoder();
124
125
126     /**
127      * MD5 message digest provider.
128      */

129     protected static MessageDigest JavaDoc md5Helper;
130
131
132     /**
133      * The string manager for this package.
134      */

135     protected static StringManager sm =
136         StringManager.getManager(Constants.Package);
137
138
139     /**
140      * Has this component been started?
141      */

142     protected boolean started = false;
143
144
145     /**
146      * The property change support for this component.
147      */

148     protected PropertyChangeSupport JavaDoc support = new PropertyChangeSupport JavaDoc(this);
149
150
151     /**
152      * Should we validate client certificate chains when they are presented?
153      */

154     protected boolean validate = true;
155
156     
157     /**
158      * The all role mode.
159      */

160     protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
161     
162
163     // ------------------------------------------------------------- Properties
164

165
166     /**
167      * Return the Container with which this Realm has been associated.
168      */

169     public Container getContainer() {
170
171         return (container);
172
173     }
174
175
176     /**
177      * Set the Container with which this Realm has been associated.
178      *
179      * @param container The associated Container
180      */

181     public void setContainer(Container container) {
182
183         Container oldContainer = this.container;
184         this.container = container;
185         support.firePropertyChange("container", oldContainer, this.container);
186
187     }
188
189     /**
190      * Return the all roles mode.
191      */

192     public String JavaDoc getAllRolesMode() {
193
194         return allRolesMode.toString();
195
196     }
197
198
199     /**
200      * Set the all roles mode.
201      */

202     public void setAllRolesMode(String JavaDoc allRolesMode) {
203
204         this.allRolesMode = AllRolesMode.toMode(allRolesMode);
205
206     }
207
208     /**
209      * Return the digest algorithm used for storing credentials.
210      */

211     public String JavaDoc getDigest() {
212
213         return digest;
214
215     }
216
217
218     /**
219      * Set the digest algorithm used for storing credentials.
220      *
221      * @param digest The new digest algorithm
222      */

223     public void setDigest(String JavaDoc digest) {
224
225         this.digest = digest;
226
227     }
228
229     /**
230      * Returns the digest encoding charset.
231      *
232      * @return The charset (may be null) for platform default
233      */

234     public String JavaDoc getDigestEncoding() {
235         return digestEncoding;
236     }
237
238     /**
239      * Sets the digest encoding charset.
240      *
241      * @param charset The charset (null for platform default)
242      */

243     public void setDigestEncoding(String JavaDoc charset) {
244         digestEncoding = charset;
245     }
246
247     /**
248      * Return descriptive information about this Realm implementation and
249      * the corresponding version number, in the format
250      * <code>&lt;description&gt;/&lt;version&gt;</code>.
251      */

252     public String JavaDoc getInfo() {
253
254         return info;
255
256     }
257
258
259     /**
260      * Return the "validate certificate chains" flag.
261      */

262     public boolean getValidate() {
263
264         return (this.validate);
265
266     }
267
268
269     /**
270      * Set the "validate certificate chains" flag.
271      *
272      * @param validate The new validate certificate chains flag
273      */

274     public void setValidate(boolean validate) {
275
276         this.validate = validate;
277
278     }
279
280
281     // --------------------------------------------------------- Public Methods
282

283
284     
285     /**
286      * Add a property change listener to this component.
287      *
288      * @param listener The listener to add
289      */

290     public void addPropertyChangeListener(PropertyChangeListener JavaDoc listener) {
291
292         support.addPropertyChangeListener(listener);
293
294     }
295
296
297     /**
298      * Return the Principal associated with the specified username and
299      * credentials, if there is one; otherwise return <code>null</code>.
300      *
301      * @param username Username of the Principal to look up
302      * @param credentials Password or other credentials to use in
303      * authenticating this username
304      */

305     public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc credentials) {
306
307         String JavaDoc serverCredentials = getPassword(username);
308
309         boolean validated ;
310         if ( serverCredentials == null ) {
311             validated = false;
312         } else if(hasMessageDigest()) {
313             validated = serverCredentials.equalsIgnoreCase(digest(credentials));
314         } else {
315             validated = serverCredentials.equals(credentials);
316         }
317         if(! validated ) {
318             if (containerLog.isTraceEnabled()) {
319                 containerLog.trace(sm.getString("realmBase.authenticateFailure",
320                                                 username));
321             }
322             return null;
323         }
324         if (containerLog.isTraceEnabled()) {
325             containerLog.trace(sm.getString("realmBase.authenticateSuccess",
326                                             username));
327         }
328
329         return getPrincipal(username);
330     }
331
332
333     /**
334      * Return the Principal associated with the specified username and
335      * credentials, if there is one; otherwise return <code>null</code>.
336      *
337      * @param username Username of the Principal to look up
338      * @param credentials Password or other credentials to use in
339      * authenticating this username
340      */

341     public Principal JavaDoc authenticate(String JavaDoc username, byte[] credentials) {
342
343         return (authenticate(username, credentials.toString()));
344
345     }
346
347
348     /**
349      * Return the Principal associated with the specified username, which
350      * matches the digest calculated using the given parameters using the
351      * method described in RFC 2069; otherwise return <code>null</code>.
352      *
353      * @param username Username of the Principal to look up
354      * @param clientDigest Digest which has been submitted by the client
355      * @param nOnce Unique (or supposedly unique) token which has been used
356      * for this request
357      * @param realm Realm name
358      * @param md5a2 Second MD5 digest used to calculate the digest :
359      * MD5(Method + ":" + uri)
360      */

361     public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc clientDigest,
362                                   String JavaDoc nOnce, String JavaDoc nc, String JavaDoc cnonce,
363                                   String JavaDoc qop, String JavaDoc realm,
364                                   String JavaDoc md5a2) {
365
366         String JavaDoc md5a1 = getDigest(username, realm);
367         if (md5a1 == null)
368             return null;
369         String JavaDoc serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":"
370             + cnonce + ":" + qop + ":" + md5a2;
371
372         byte[] valueBytes = null;
373         if(getDigestEncoding() == null) {
374             valueBytes = serverDigestValue.getBytes();
375         } else {
376             try {
377                 valueBytes = serverDigestValue.getBytes(getDigestEncoding());
378             } catch (UnsupportedEncodingException JavaDoc uee) {
379                 log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
380                 throw new IllegalArgumentException JavaDoc(uee.getMessage());
381             }
382         }
383
384         String JavaDoc serverDigest = null;
385         // Bugzilla 32137
386
synchronized(md5Helper) {
387             serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
388         }
389
390         if (log.isDebugEnabled()) {
391             log.debug("Digest : " + clientDigest + " Username:" + username
392                     + " ClientSigest:" + clientDigest + " nOnce:" + nOnce
393                     + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop
394                     + " realm:" + realm + "md5a2:" + md5a2
395                     + " Server digest:" + serverDigest);
396         }
397         
398         if (serverDigest.equals(clientDigest))
399             return getPrincipal(username);
400         else
401             return null;
402     }
403
404
405
406     /**
407      * Return the Principal associated with the specified chain of X509
408      * client certificates. If there is none, return <code>null</code>.
409      *
410      * @param certs Array of client certificates, with the first one in
411      * the array being the certificate of the client itself.
412      */

413     public Principal JavaDoc authenticate(X509Certificate JavaDoc certs[]) {
414
415         if ((certs == null) || (certs.length < 1))
416             return (null);
417
418         // Check the validity of each certificate in the chain
419
if (log.isDebugEnabled())
420             log.debug("Authenticating client certificate chain");
421         if (validate) {
422             for (int i = 0; i < certs.length; i++) {
423                 if (log.isDebugEnabled())
424                     log.debug(" Checking validity for '" +
425                         certs[i].getSubjectDN().getName() + "'");
426                 try {
427                     certs[i].checkValidity();
428                 } catch (Exception JavaDoc e) {
429                     if (log.isDebugEnabled())
430                         log.debug(" Validity exception", e);
431                     return (null);
432                 }
433             }
434         }
435
436         // Check the existence of the client Principal in our database
437
return (getPrincipal(certs[0]));
438
439     }
440
441     
442     /**
443      * Execute a periodic task, such as reloading, etc. This method will be
444      * invoked inside the classloading context of this container. Unexpected
445      * throwables will be caught and logged.
446      */

447     public void backgroundProcess() {
448     }
449
450
451     /**
452      * Return the SecurityConstraints configured to guard the request URI for
453      * this request, or <code>null</code> if there is no such constraint.
454      *
455      * @param request Request we are processing
456      * @param context Context the Request is mapped to
457      */

458     public SecurityConstraint [] findSecurityConstraints(Request request,
459                                                          Context context) {
460
461         ArrayList JavaDoc results = null;
462         // Are there any defined security constraints?
463
SecurityConstraint constraints[] = context.findConstraints();
464         if ((constraints == null) || (constraints.length == 0)) {
465             if (log.isDebugEnabled())
466                 log.debug(" No applicable constraints defined");
467             return (null);
468         }
469
470         // Check each defined security constraint
471
String JavaDoc uri = request.getRequestPathMB().toString();
472         
473         String JavaDoc method = request.getMethod();
474         int i;
475         boolean found = false;
476         for (i = 0; i < constraints.length; i++) {
477             SecurityCollection [] collection = constraints[i].findCollections();
478                      
479             // If collection is null, continue to avoid an NPE
480
// See Bugzilla 30624
481
if ( collection == null) {
482         continue;
483             }
484
485             if (log.isDebugEnabled()) {
486                 log.debug(" Checking constraint '" + constraints[i] +
487                     "' against " + method + " " + uri + " --> " +
488                     constraints[i].included(uri, method));
489         }
490
491             for(int j=0; j < collection.length; j++){
492                 String JavaDoc [] patterns = collection[j].findPatterns();
493  
494                 // If patterns is null, continue to avoid an NPE
495
// See Bugzilla 30624
496
if ( patterns == null) {
497             continue;
498                 }
499
500                 for(int k=0; k < patterns.length; k++) {
501                     if(uri.equals(patterns[k])) {
502                         found = true;
503                         if(collection[j].findMethod(method)) {
504                             if(results == null) {
505                                 results = new ArrayList JavaDoc();
506                             }
507                             results.add(constraints[i]);
508                         }
509                     }
510                 }
511             }
512         }
513
514         if(found) {
515             return resultsToArray(results);
516         }
517
518         int longest = -1;
519
520         for (i = 0; i < constraints.length; i++) {
521             SecurityCollection [] collection = constraints[i].findCollections();
522             
523             // If collection is null, continue to avoid an NPE
524
// See Bugzilla 30624
525
if ( collection == null) {
526         continue;
527             }
528
529             if (log.isDebugEnabled()) {
530                 log.debug(" Checking constraint '" + constraints[i] +
531                     "' against " + method + " " + uri + " --> " +
532                     constraints[i].included(uri, method));
533         }
534
535             for(int j=0; j < collection.length; j++){
536                 String JavaDoc [] patterns = collection[j].findPatterns();
537
538                 // If patterns is null, continue to avoid an NPE
539
// See Bugzilla 30624
540
if ( patterns == null) {
541             continue;
542                 }
543
544                 boolean matched = false;
545                 int length = -1;
546                 for(int k=0; k < patterns.length; k++) {
547                     String JavaDoc pattern = patterns[k];
548                     if(pattern.startsWith("/") && pattern.endsWith("/*") &&
549                        pattern.length() >= longest) {
550                             
551                         if(pattern.length() == 2) {
552                             matched = true;
553                             length = pattern.length();
554                         } else if(pattern.regionMatches(0,uri,0,
555                                                         pattern.length()-1) ||
556                                   (pattern.length()-2 == uri.length() &&
557                                    pattern.regionMatches(0,uri,0,
558                                                         pattern.length()-2))) {
559                             matched = true;
560                             length = pattern.length();
561                         }
562                     }
563                 }
564                 if(matched) {
565                     found = true;
566                     if(length > longest) {
567                         if(results != null) {
568                             results.clear();
569                         }
570                         longest = length;
571                     }
572                     if(collection[j].findMethod(method)) {
573                         if(results == null) {
574                             results = new ArrayList JavaDoc();
575                         }
576                         results.add(constraints[i]);
577                     }
578                 }
579             }
580         }
581
582         if(found) {
583             return resultsToArray(results);
584         }
585
586         for (i = 0; i < constraints.length; i++) {
587             SecurityCollection [] collection = constraints[i].findCollections();
588
589             // If collection is null, continue to avoid an NPE
590
// See Bugzilla 30624
591
if ( collection == null) {
592         continue;
593             }
594             
595             if (log.isDebugEnabled()) {
596                 log.debug(" Checking constraint '" + constraints[i] +
597                     "' against " + method + " " + uri + " --> " +
598                     constraints[i].included(uri, method));
599         }
600
601             boolean matched = false;
602             int pos = -1;
603             for(int j=0; j < collection.length; j++){
604                 String JavaDoc [] patterns = collection[j].findPatterns();
605
606                 // If patterns is null, continue to avoid an NPE
607
// See Bugzilla 30624
608
if ( patterns == null) {
609             continue;
610                 }
611
612                 for(int k=0; k < patterns.length && !matched; k++) {
613                     String JavaDoc pattern = patterns[k];
614                     if(pattern.startsWith("*.")){
615                         int slash = uri.lastIndexOf("/");
616                         int dot = uri.lastIndexOf(".");
617                         if(slash >= 0 && dot > slash &&
618                            dot != uri.length()-1 &&
619                            uri.length()-dot == pattern.length()-1) {
620                             if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
621                                 matched = true;
622                                 pos = j;
623                             }
624                         }
625                     }
626                 }
627             }
628             if(matched) {
629                 found = true;
630                 if(collection[pos].findMethod(method)) {
631                     if(results == null) {
632                         results = new ArrayList JavaDoc();
633                     }
634                     results.add(constraints[i]);
635                 }
636             }
637         }
638
639         if(found) {
640             return resultsToArray(results);
641         }
642
643         for (i = 0; i < constraints.length; i++) {
644             SecurityCollection [] collection = constraints[i].findCollections();
645             
646             // If collection is null, continue to avoid an NPE
647
// See Bugzilla 30624
648
if ( collection == null) {
649         continue;
650             }
651
652             if (log.isDebugEnabled()) {
653                 log.debug(" Checking constraint '" + constraints[i] +
654                     "' against " + method + " " + uri + " --> " +
655                     constraints[i].included(uri, method));
656         }
657
658             for(int j=0; j < collection.length; j++){
659                 String JavaDoc [] patterns = collection[j].findPatterns();
660
661                 // If patterns is null, continue to avoid an NPE
662
// See Bugzilla 30624
663
if ( patterns == null) {
664             continue;
665                 }
666
667                 boolean matched = false;
668                 for(int k=0; k < patterns.length && !matched; k++) {
669                     String JavaDoc pattern = patterns[k];
670                     if(pattern.equals("/")){
671                         matched = true;
672                     }
673                 }
674                 if(matched) {
675                     if(results == null) {
676                         results = new ArrayList JavaDoc();
677                     }
678                     results.add(constraints[i]);
679                 }
680             }
681         }
682
683         if(results == null) {
684             // No applicable security constraint was found
685
if (log.isDebugEnabled())
686                 log.debug(" No applicable constraint located");
687         }
688         return resultsToArray(results);
689     }
690  
691     /**
692      * Convert an ArrayList to a SecurityContraint [].
693      */

694     private SecurityConstraint [] resultsToArray(ArrayList JavaDoc results) {
695         if(results == null) {
696             return null;
697         }
698         SecurityConstraint [] array = new SecurityConstraint[results.size()];
699         results.toArray(array);
700         return array;
701     }
702
703     
704     /**
705      * Perform access control based on the specified authorization constraint.
706      * Return <code>true</code> if this constraint is satisfied and processing
707      * should continue, or <code>false</code> otherwise.
708      *
709      * @param request Request we are processing
710      * @param response Response we are creating
711      * @param constraints Security constraint we are enforcing
712      * @param context The Context to which client of this class is attached.
713      *
714      * @exception IOException if an input/output error occurs
715      */

716     public boolean hasResourcePermission(Request request,
717                                          Response JavaDoc response,
718                                          SecurityConstraint []constraints,
719                                          Context context)
720         throws IOException JavaDoc {
721
722         if (constraints == null || constraints.length == 0)
723             return (true);
724
725         // Specifically allow access to the form login and form error pages
726
// and the "j_security_check" action
727
LoginConfig config = context.getLoginConfig();
728         if ((config != null) &&
729             (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
730             String JavaDoc requestURI = request.getRequestPathMB().toString();
731             String JavaDoc loginPage = config.getLoginPage();
732             if (loginPage.equals(requestURI)) {
733                 if (log.isDebugEnabled())
734                     log.debug(" Allow access to login page " + loginPage);
735                 return (true);
736             }
737             String JavaDoc errorPage = config.getErrorPage();
738             if (errorPage.equals(requestURI)) {
739                 if (log.isDebugEnabled())
740                     log.debug(" Allow access to error page " + errorPage);
741                 return (true);
742             }
743             if (requestURI.endsWith(Constants.FORM_ACTION)) {
744                 if (log.isDebugEnabled())
745                     log.debug(" Allow access to username/password submission");
746                 return (true);
747             }
748         }
749
750         // Which user principal have we already authenticated?
751
Principal JavaDoc principal = request.getPrincipal();
752         boolean status = false;
753         boolean denyfromall = false;
754         for(int i=0; i < constraints.length; i++) {
755             SecurityConstraint constraint = constraints[i];
756
757             String JavaDoc roles[];
758             if (constraint.getAllRoles()) {
759                 // * means all roles defined in web.xml
760
roles = request.getContext().findSecurityRoles();
761             } else {
762                 roles = constraint.findAuthRoles();
763             }
764
765             if (roles == null)
766                 roles = new String JavaDoc[0];
767
768             if (log.isDebugEnabled())
769                 log.debug(" Checking roles " + principal);
770
771             if (roles.length == 0 && !constraint.getAllRoles()) {
772                 if(constraint.getAuthConstraint()) {
773                     if( log.isDebugEnabled() )
774                         log.debug("No roles ");
775                     status = false; // No listed roles means no access at all
776
denyfromall = true;
777                 } else {
778                     if(log.isDebugEnabled())
779                         log.debug("Passing all access");
780                     return (true);
781                 }
782             } else if (principal == null) {
783                 if (log.isDebugEnabled())
784                     log.debug(" No user authenticated, cannot grant access");
785                 status = false;
786             } else if(!denyfromall) {
787
788                 for (int j = 0; j < roles.length; j++) {
789                     if (hasRole(principal, roles[j]))
790                         status = true;
791                     if( log.isDebugEnabled() )
792                         log.debug( "No role found: " + roles[j]);
793                 }
794             }
795         }
796
797         if (allRolesMode != AllRolesMode.STRICT_MODE && !status && principal != null) {
798             if (log.isDebugEnabled()) {
799                 log.debug("Checking for all roles mode: " + allRolesMode);
800             }
801             // Check for an all roles(role-name="*")
802
for (int i = 0; i < constraints.length; i++) {
803                 SecurityConstraint constraint = constraints[i];
804                 String JavaDoc roles[];
805                 // If the all roles mode exists, sets
806
if (constraint.getAllRoles()) {
807                     if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
808                         if (log.isDebugEnabled()) {
809                             log.debug("Granting access for role-name=*, auth-only");
810                         }
811