Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]
    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>&lt;description&gt;/&lt;version&gt;</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   }

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » realm » [javadoc | source]