Save This Page
Home » apache-tomcat-6.0.26-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: 781465 $ $Date: 2009-06-03 18:38:25 +0200 (Wed, 03 Jun 2009) $
   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           // Bug47080 - in rare cases this may be null
  474           // Mapper treats as '/' do the same to prevent NPE
  475           if (uri == null) {
  476               uri = "/";
  477           }
  478           
  479           String method = request.getMethod();
  480           int i;
  481           boolean found = false;
  482           for (i = 0; i < constraints.length; i++) {
  483               SecurityCollection [] collection = constraints[i].findCollections();
  484                        
  485               // If collection is null, continue to avoid an NPE
  486               // See Bugzilla 30624
  487               if ( collection == null) {
  488   		continue;
  489               }
  490   
  491               if (log.isDebugEnabled()) {
  492                   log.debug("  Checking constraint '" + constraints[i] +
  493                       "' against " + method + " " + uri + " --> " +
  494                       constraints[i].included(uri, method));
  495   	    }
  496   
  497               for(int j=0; j < collection.length; j++){
  498                   String [] patterns = collection[j].findPatterns();
  499    
  500                   // If patterns is null, continue to avoid an NPE
  501                   // See Bugzilla 30624
  502                   if ( patterns == null) {
  503   		    continue;
  504                   }
  505   
  506                   for(int k=0; k < patterns.length; k++) {
  507                       if(uri.equals(patterns[k])) {
  508                           found = true;
  509                           if(collection[j].findMethod(method)) {
  510                               if(results == null) {
  511                                   results = new ArrayList<SecurityConstraint>();
  512                               }
  513                               results.add(constraints[i]);
  514                           }
  515                       }
  516                   }
  517               }
  518           }
  519   
  520           if(found) {
  521               return resultsToArray(results);
  522           }
  523   
  524           int longest = -1;
  525   
  526           for (i = 0; i < constraints.length; i++) {
  527               SecurityCollection [] collection = constraints[i].findCollections();
  528               
  529               // If collection is null, continue to avoid an NPE
  530               // See Bugzilla 30624
  531               if ( collection == null) {
  532   		continue;
  533               }
  534   
  535               if (log.isDebugEnabled()) {
  536                   log.debug("  Checking constraint '" + constraints[i] +
  537                       "' against " + method + " " + uri + " --> " +
  538                       constraints[i].included(uri, method));
  539   	    }
  540   
  541               for(int j=0; j < collection.length; j++){
  542                   String [] patterns = collection[j].findPatterns();
  543   
  544                   // If patterns is null, continue to avoid an NPE
  545                   // See Bugzilla 30624
  546                   if ( patterns == null) {
  547   		    continue;
  548                   }
  549   
  550                   boolean matched = false;
  551                   int length = -1;
  552                   for(int k=0; k < patterns.length; k++) {
  553                       String pattern = patterns[k];
  554                       if(pattern.startsWith("/") && pattern.endsWith("/*") && 
  555                          pattern.length() >= longest) {
  556                               
  557                           if(pattern.length() == 2) {
  558                               matched = true;
  559                               length = pattern.length();
  560                           } else if(pattern.regionMatches(0,uri,0,
  561                                                           pattern.length()-1) ||
  562                                     (pattern.length()-2 == uri.length() &&
  563                                      pattern.regionMatches(0,uri,0,
  564                                                           pattern.length()-2))) {
  565                               matched = true;
  566                               length = pattern.length();
  567                           }
  568                       }
  569                   }
  570                   if(matched) {
  571                       found = true;
  572                       if(length > longest) {
  573                           if(results != null) {
  574                               results.clear();
  575                           }
  576                           longest = length;
  577                       }
  578                       if(collection[j].findMethod(method)) {
  579                           if(results == null) {
  580                               results = new ArrayList<SecurityConstraint>();
  581                           }
  582                           results.add(constraints[i]);
  583                       }
  584                   }
  585               }
  586           }
  587   
  588           if(found) {
  589               return  resultsToArray(results);
  590           }
  591   
  592           for (i = 0; i < constraints.length; i++) {
  593               SecurityCollection [] collection = constraints[i].findCollections();
  594   
  595               // If collection is null, continue to avoid an NPE
  596               // See Bugzilla 30624
  597               if ( collection == null) {
  598   		continue;
  599               }
  600               
  601               if (log.isDebugEnabled()) {
  602                   log.debug("  Checking constraint '" + constraints[i] +
  603                       "' against " + method + " " + uri + " --> " +
  604                       constraints[i].included(uri, method));
  605   	    }
  606   
  607               boolean matched = false;
  608               int pos = -1;
  609               for(int j=0; j < collection.length; j++){
  610                   String [] patterns = collection[j].findPatterns();
  611   
  612                   // If patterns is null, continue to avoid an NPE
  613                   // See Bugzilla 30624
  614                   if ( patterns == null) {
  615   		    continue;
  616                   }
  617   
  618                   for(int k=0; k < patterns.length && !matched; k++) {
  619                       String pattern = patterns[k];
  620                       if(pattern.startsWith("*.")){
  621                           int slash = uri.lastIndexOf("/");
  622                           int dot = uri.lastIndexOf(".");
  623                           if(slash >= 0 && dot > slash &&
  624                              dot != uri.length()-1 &&
  625                              uri.length()-dot == pattern.length()-1) {
  626                               if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
  627                                   matched = true;
  628                                   pos = j;
  629                               }
  630                           }
  631                       }
  632                   }
  633               }
  634               if(matched) {
  635                   found = true;
  636                   if(collection[pos].findMethod(method)) {
  637                       if(results == null) {
  638                           results = new ArrayList<SecurityConstraint>();
  639                       }
  640                       results.add(constraints[i]);
  641                   }
  642               }
  643           }
  644   
  645           if(found) {
  646               return resultsToArray(results);
  647           }
  648   
  649           for (i = 0; i < constraints.length; i++) {
  650               SecurityCollection [] collection = constraints[i].findCollections();
  651               
  652               // If collection is null, continue to avoid an NPE
  653               // See Bugzilla 30624
  654               if ( collection == null) {
  655   		continue;
  656               }
  657   
  658               if (log.isDebugEnabled()) {
  659                   log.debug("  Checking constraint '" + constraints[i] +
  660                       "' against " + method + " " + uri + " --> " +
  661                       constraints[i].included(uri, method));
  662   	    }
  663   
  664               for(int j=0; j < collection.length; j++){
  665                   String [] patterns = collection[j].findPatterns();
  666   
  667                   // If patterns is null, continue to avoid an NPE
  668                   // See Bugzilla 30624
  669                   if ( patterns == null) {
  670   		    continue;
  671                   }
  672   
  673                   boolean matched = false;
  674                   for(int k=0; k < patterns.length && !matched; k++) {
  675                       String pattern = patterns[k];
  676                       if(pattern.equals("/")){
  677                           matched = true;
  678                       }
  679                   }
  680                   if(matched) {
  681                       if(results == null) {
  682                           results = new ArrayList<SecurityConstraint>();
  683                       }                    
  684                       results.add(constraints[i]);
  685                   }
  686               }
  687           }
  688   
  689           if(results == null) {
  690               // No applicable security constraint was found
  691               if (log.isDebugEnabled())
  692                   log.debug("  No applicable constraint located");
  693           }
  694           return resultsToArray(results);
  695       }
  696    
  697       /**
  698        * Convert an ArrayList to a SecurityContraint [].
  699        */
  700       private SecurityConstraint [] resultsToArray(
  701               ArrayList<SecurityConstraint> results) {
  702           if(results == null) {
  703               return null;
  704           }
  705           SecurityConstraint [] array = new SecurityConstraint[results.size()];
  706           results.toArray(array);
  707           return array;
  708       }
  709   
  710       
  711       /**
  712        * Perform access control based on the specified authorization constraint.
  713        * Return <code>true</code> if this constraint is satisfied and processing
  714        * should continue, or <code>false</code> otherwise.
  715        *
  716        * @param request Request we are processing
  717        * @param response Response we are creating
  718        * @param constraints Security constraint we are enforcing
  719        * @param context The Context to which client of this class is attached.
  720        *
  721        * @exception IOException if an input/output error occurs
  722        */
  723       public boolean hasResourcePermission(Request request,
  724                                            Response response,
  725                                            SecurityConstraint []constraints,
  726                                            Context context)
  727           throws IOException {
  728   
  729           if (constraints == null || constraints.length == 0)
  730               return (true);
  731   
  732           // Specifically allow access to the form login and form error pages
  733           // and the "j_security_check" action
  734           LoginConfig config = context.getLoginConfig();
  735           if ((config != null) &&
  736               (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
  737               String requestURI = request.getRequestPathMB().toString();
  738               String loginPage = config.getLoginPage();
  739               if (loginPage.equals(requestURI)) {
  740                   if (log.isDebugEnabled())
  741                       log.debug(" Allow access to login page " + loginPage);
  742                   return (true);
  743               }
  744               String errorPage = config.getErrorPage();
  745               if (errorPage.equals(requestURI)) {
  746                   if (log.isDebugEnabled())
  747                       log.debug(" Allow access to error page " + errorPage);
  748                   return (true);
  749               }
  750               if (requestURI.endsWith(Constants.FORM_ACTION)) {
  751                   if (log.isDebugEnabled())
  752                       log.debug(" Allow access to username/password submission");
  753                   return (true);
  754               }
  755           }
  756   
  757           // Which user principal have we already authenticated?
  758           Principal principal = request.getPrincipal();
  759           boolean status = false;
  760           boolean denyfromall = false;
  761           for(int i=0; i < constraints.length; i++) {
  762               SecurityConstraint constraint = constraints[i];
  763   
  764               String roles[];
  765               if (constraint.getAllRoles()) {
  766                   // * means all roles defined in web.xml
  767                   roles = request.getContext().findSecurityRoles();
  768               } else {
  769                   roles = constraint.findAuthRoles();
  770               }
  771   
  772               if (roles == null)
  773                   roles = new String[0];
  774   
  775               if (log.isDebugEnabled())
  776                   log.debug("  Checking roles " + principal);
  777   
  778               if (roles.length == 0 && !constraint.getAllRoles()) {
  779                   if(constraint.getAuthConstraint()) {
  780                       if( log.isDebugEnabled() )
  781                           log.debug("No roles ");
  782                       status = false; // No listed roles means no access at all
  783                       denyfromall = true;
  784                       break;
  785                   } else {
  786                       if(log.isDebugEnabled())
  787                           log.debug("Passing all access");
  788                       status = true;
  789                   }
  790               } else if (principal == null) {
  791                   if (log.isDebugEnabled())
  792                       log.debug("  No user authenticated, cannot grant access");
  793               } else {
  794                   for (int j = 0; j < roles.length; j++) {
  795                       if (hasRole(principal, roles[j])) {
  796                           status = true;
  797                           if( log.isDebugEnabled() )
  798                               log.debug( "Role found:  " + roles[j]);
  799                       }
  800                       else if( log.isDebugEnabled() )
  801                           log.debug( "No role found:  " + roles[j]);
  802                   }
  803               }
  804           }
  805   
  806           if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
  807                   !status && principal != null) {
  808               if (log.isDebugEnabled()) {
  809                   log.debug("Checking for all roles mode: " + allRolesMode);
  810               }
  811               // Check for an all roles(role-name="*")
  812               for (int i = 0; i < constraints.length; i++) {
  813                   SecurityConstraint constraint = constraints[i];
  814                   String roles[];
  815                   // If the all roles mode exists, sets
  816                   if (constraint.getAllRoles()) {
  817                       if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
  818                           if (log.isDebugEnabled()) {
  819                               log.debug("Granting access for role-name=*, auth-only");
  820                           }
  821                           status = true;
  822                           break;
  823                       }
  824                       
  825                       // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
  826                       roles = request.getContext().findSecurityRoles();
  827                       if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
  828                           if (log.isDebugEnabled()) {
  829                               log.debug("Granting access for role-name=*, strict auth-only");
  830                           }
  831                           status = true;
  832                           break;
  833                       }
  834                   }
  835               }
  836           }
  837           
  838           // Return a "Forbidden" message denying access to this resource
  839           if(!status) {
  840               response.sendError
  841                   (HttpServletResponse.SC_FORBIDDEN,
  842                    sm.getString("realmBase.forbidden"));
  843           }
  844           return status;
  845   
  846       }
  847       
  848       
  849       /**
  850        * Return <code>true</code> if the specified Principal has the specified
  851        * security role, within the context of this Realm; otherwise return
  852        * <code>false</code>.  This method can be overridden by Realm
  853        * implementations, but the default is adequate when an instance of
  854        * <code>GenericPrincipal</code> is used to represent authenticated
  855        * Principals from this Realm.
  856        *
  857        * @param principal Principal for whom the role is to be checked
  858        * @param role Security role to be checked
  859        */
  860       public boolean hasRole(Principal principal, String role) {
  861   
  862           // Should be overriten in JAASRealm - to avoid pretty inefficient conversions
  863           if ((principal == null) || (role == null) ||
  864               !(principal instanceof GenericPrincipal))
  865               return (false);
  866   
  867           GenericPrincipal gp = (GenericPrincipal) principal;
  868           if (!(gp.getRealm() == this)) {
  869               if(log.isDebugEnabled())
  870                   log.debug("Different realm " + this + " " + gp.getRealm());//    return (false);
  871           }
  872           boolean result = gp.hasRole(role);
  873           if (log.isDebugEnabled()) {
  874               String name = principal.getName();
  875               if (result)
  876                   log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
  877               else
  878                   log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
  879           }
  880           return (result);
  881   
  882       }
  883   
  884       
  885       /**
  886        * Enforce any user data constraint required by the security constraint
  887        * guarding this request URI.  Return <code>true</code> if this constraint
  888        * was not violated and processing should continue, or <code>false</code>
  889        * if we have created a response already.
  890        *
  891        * @param request Request we are processing
  892        * @param response Response we are creating
  893        * @param constraints Security constraint being checked
  894        *
  895        * @exception IOException if an input/output error occurs
  896        */
  897       public boolean hasUserDataPermission(Request request,
  898                                            Response response,
  899                                            SecurityConstraint []constraints)
  900           throws IOException {
  901   
  902           // Is there a relevant user data constraint?
  903           if (constraints == null || constraints.length == 0) {
  904               if (log.isDebugEnabled())
  905                   log.debug("  No applicable security constraint defined");
  906               return (true);
  907           }
  908           for(int i=0; i < constraints.length; i++) {
  909               SecurityConstraint constraint = constraints[i];
  910               String userConstraint = constraint.getUserConstraint();
  911               if (userConstraint == null) {
  912                   if (log.isDebugEnabled())
  913                       log.debug("  No applicable user data constraint defined");
  914                   return (true);
  915               }
  916               if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
  917                   if (log.isDebugEnabled())
  918                       log.debug("  User data constraint has no restrictions");
  919                   return (true);
  920               }
  921   
  922           }
  923           // Validate the request against the user data constraint
  924           if (request.getRequest().isSecure()) {
  925               if (log.isDebugEnabled())
  926                   log.debug("  User data constraint already satisfied");
  927               return (true);
  928           }
  929           // Initialize variables we need to determine the appropriate action
  930           int redirectPort = request.getConnector().getRedirectPort();
  931   
  932           // Is redirecting disabled?
  933           if (redirectPort <= 0) {
  934               if (log.isDebugEnabled())
  935                   log.debug("  SSL redirect is disabled");
  936               response.sendError
  937                   (HttpServletResponse.SC_FORBIDDEN,
  938                    request.getRequestURI());
  939               return (false);
  940           }
  941   
  942           // Redirect to the corresponding SSL port
  943           StringBuffer file = new StringBuffer();
  944           String protocol = "https";
  945           String host = request.getServerName();
  946           // Protocol
  947           file.append(protocol).append("://").append(host);
  948           // Host with port
  949           if(redirectPort != 443) {
  950               file.append(":").append(redirectPort);
  951           }
  952           // URI
  953           file.append(request.getRequestURI());
  954           String requestedSessionId = request.getRequestedSessionId();
  955           if ((requestedSessionId != null) &&
  956               request.isRequestedSessionIdFromURL()) {
  957               file.append(";");
  958               file.append(Globals.SESSION_PARAMETER_NAME);
  959               file.append("=");
  960               file.append(requestedSessionId);
  961           }
  962           String queryString = request.getQueryString();
  963           if (queryString != null) {
  964               file.append('?');
  965               file.append(queryString);
  966           }
  967           if (log.isDebugEnabled())
  968               log.debug("  Redirecting to " + file.toString());
  969           response.sendRedirect(file.toString());
  970           return (false);
  971   
  972       }
  973       
  974       
  975       /**
  976        * Remove a property change listener from this component.
  977        *
  978        * @param listener The listener to remove
  979        */
  980       public void removePropertyChangeListener(PropertyChangeListener listener) {
  981   
  982           support.removePropertyChangeListener(listener);
  983   
  984       }
  985   
  986   
  987       // ------------------------------------------------------ Lifecycle Methods
  988   
  989   
  990       /**
  991        * Add a lifecycle event listener to this component.
  992        *
  993        * @param listener The listener to add
  994        */
  995       public void addLifecycleListener(LifecycleListener listener) {
  996   
  997           lifecycle.addLifecycleListener(listener);
  998   
  999       }
 1000   
 1001   
 1002       /**
 1003        * Get the lifecycle listeners associated with this lifecycle. If this 
 1004        * Lifecycle has no listeners registered, a zero-length array is returned.
 1005        */
 1006       public LifecycleListener[] findLifecycleListeners() {
 1007   
 1008           return lifecycle.findLifecycleListeners();
 1009   
 1010       }
 1011   
 1012   
 1013       /**
 1014        * Remove a lifecycle event listener from this component.
 1015        *
 1016        * @param listener The listener to remove
 1017        */
 1018       public void removeLifecycleListener(LifecycleListener listener) {
 1019   
 1020           lifecycle.removeLifecycleListener(listener);
 1021   
 1022       }
 1023   
 1024       /**
 1025        * Prepare for the beginning of active use of the public methods of this
 1026        * component.  This method should be called before any of the public
 1027        * methods of this component are utilized.  It should also send a
 1028        * LifecycleEvent of type START_EVENT to any registered listeners.
 1029        *
 1030        * @exception LifecycleException if this component detects a fatal error
 1031        *  that prevents this component from being used
 1032        */
 1033       public void start() throws LifecycleException {
 1034   
 1035           // Validate and update our current component state
 1036           if (started) {
 1037               if(log.isInfoEnabled())
 1038                   log.info(sm.getString("realmBase.alreadyStarted"));
 1039               return;
 1040           }
 1041           if( !initialized ) {
 1042               init();
 1043           }
 1044           lifecycle.fireLifecycleEvent(START_EVENT, null);
 1045           started = true;
 1046   
 1047           // Create a MessageDigest instance for credentials, if desired
 1048           if (digest != null) {
 1049               try {
 1050                   md = MessageDigest.getInstance(digest);
 1051               } catch (NoSuchAlgorithmException e) {
 1052                   throw new LifecycleException
 1053                       (sm.getString("realmBase.algorithm", digest), e);
 1054               }
 1055           }
 1056   
 1057       }
 1058   
 1059   
 1060       /**
 1061        * Gracefully terminate the active use of the public methods of this
 1062        * component.  This method should be the last one called on a given
 1063        * instance of this component.  It should also send a LifecycleEvent
 1064        * of type STOP_EVENT to any registered listeners.
 1065        *
 1066        * @exception LifecycleException if this component detects a fatal error
 1067        *  that needs to be reported
 1068        */
 1069       public void stop()
 1070           throws LifecycleException {
 1071   
 1072           // Validate and update our current component state
 1073           if (!started) {
 1074               if(log.isInfoEnabled())
 1075                   log.info(sm.getString("realmBase.notStarted"));
 1076               return;
 1077           }
 1078           lifecycle.fireLifecycleEvent(STOP_EVENT, null);
 1079           started = false;
 1080   
 1081           // Clean up allocated resources
 1082           md = null;
 1083           
 1084           destroy();
 1085       
 1086       }
 1087       
 1088       public void destroy() {
 1089       
 1090           // unregister this realm
 1091           if ( oname!=null ) {   
 1092               try {   
 1093                   Registry.getRegistry(null, null).unregisterComponent(oname); 
 1094                   if(log.isDebugEnabled())
 1095                       log.debug( "unregistering realm " + oname );   
 1096               } catch( Exception ex ) {   
 1097                   log.error( "Can't unregister realm " + oname, ex);   
 1098               }      
 1099           }
 1100             
 1101       }
 1102   
 1103       // ------------------------------------------------------ Protected Methods
 1104   
 1105   
 1106       /**
 1107        * Digest the password using the specified algorithm and
 1108        * convert the result to a corresponding hexadecimal string.
 1109        * If exception, the plain credentials string is returned.
 1110        *
 1111        * @param credentials Password or other credentials to use in
 1112        *  authenticating this username
 1113        */
 1114       protected String digest(String credentials)  {
 1115   
 1116           // If no MessageDigest instance is specified, return unchanged
 1117           if (hasMessageDigest() == false)
 1118               return (credentials);
 1119   
 1120           // Digest the user credentials and return as hexadecimal
 1121           synchronized (this) {
 1122               try {
 1123                   md.reset();
 1124       
 1125                   byte[] bytes = null;
 1126                   if(getDigestEncoding() == null) {
 1127                       bytes = credentials.getBytes();
 1128                   } else {
 1129                       try {
 1130                           bytes = credentials.getBytes(getDigestEncoding());
 1131                       } catch (UnsupportedEncodingException uee) {
 1132                           log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
 1133                           throw new IllegalArgumentException(uee.getMessage());
 1134                       }
 1135                   }
 1136                   md.update(bytes);
 1137   
 1138                   return (HexUtils.convert(md.digest()));
 1139               } catch (Exception e) {
 1140                   log.error(sm.getString("realmBase.digest"), e);
 1141                   return (credentials);
 1142               }
 1143           }
 1144   
 1145       }
 1146   
 1147       protected boolean hasMessageDigest() {
 1148           return !(md == null);
 1149       }
 1150   
 1151       /**
 1152        * Return the digest associated with given principal's user name.
 1153        */
 1154       protected String getDigest(String username, String realmName) {
 1155           if (md5Helper == null) {
 1156               try {
 1157                   md5Helper = MessageDigest.getInstance("MD5");
 1158               } catch (NoSuchAlgorithmException e) {
 1159                   log.error("Couldn't get MD5 digest: ", e);
 1160                   throw new IllegalStateException(e.getMessage());
 1161               }
 1162           }
 1163   
 1164       	if (hasMessageDigest()) {
 1165       		// Use pre-generated digest
 1166       		return getPassword(username);
 1167       	}
 1168       	
 1169           String digestValue = username + ":" + realmName + ":"
 1170               + getPassword(username);
 1171   
 1172           byte[] valueBytes = null;
 1173           if(getDigestEncoding() == null) {
 1174               valueBytes = digestValue.getBytes();
 1175           } else {
 1176               try {
 1177                   valueBytes = digestValue.getBytes(getDigestEncoding());
 1178               } catch (UnsupportedEncodingException uee) {
 1179                   log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
 1180                   throw new IllegalArgumentException(uee.getMessage());
 1181               }
 1182           }
 1183   
 1184           byte[] digest = null;
 1185           // Bugzilla 32137
 1186           synchronized(md5Helper) {
 1187               digest = md5Helper.digest(valueBytes);
 1188           }
 1189   
 1190           return md5Encoder.encode(digest);
 1191       }
 1192   
 1193   
 1194       /**
 1195        * Return a short name for this Realm implementation, for use in
 1196        * log messages.
 1197        */
 1198       protected abstract String getName();
 1199   
 1200   
 1201       /**
 1202        * Return the password associated with the given principal's user name.
 1203        */
 1204       protected abstract String getPassword(String username);
 1205   
 1206   
 1207       /**
 1208        * Return the Principal associated with the given certificate.
 1209        */
 1210       protected Principal getPrincipal(X509Certificate usercert) {
 1211           return(getPrincipal(usercert.getSubjectDN().getName()));
 1212       }
 1213       
 1214   
 1215       /**
 1216        * Return the Principal associated with the given user name.
 1217        */
 1218       protected abstract Principal getPrincipal(String username);
 1219   
 1220   
 1221       // --------------------------------------------------------- Static Methods
 1222   
 1223   
 1224       /**
 1225        * Digest password using the algorithm specified and
 1226        * convert the result to a corresponding hex string.
 1227        * If exception, the plain credentials string is returned
 1228        *
 1229        * @param credentials Password or other credentials to use in
 1230        *  authenticating this username
 1231        * @param algorithm Algorithm used to do the digest
 1232        * @param encoding Character encoding of the string to digest
 1233        */
 1234       public final static String Digest(String credentials, String algorithm,
 1235                                         String encoding) {
 1236   
 1237           try {
 1238               // Obtain a new message digest with "digest" encryption
 1239               MessageDigest md =
 1240                   (MessageDigest) MessageDigest.getInstance(algorithm).clone();
 1241   
 1242               // encode the credentials
 1243               // Should use the digestEncoding, but that's not a static field
 1244               if (encoding == null) {
 1245                   md.update(credentials.getBytes());
 1246               } else {
 1247                   md.update(credentials.getBytes(encoding));                
 1248               }
 1249   
 1250               // Digest the credentials and return as hexadecimal
 1251               return (HexUtils.convert(md.digest()));
 1252           } catch(Exception ex) {
 1253               log.error(ex);
 1254               return credentials;
 1255           }
 1256   
 1257       }
 1258   
 1259   
 1260       /**
 1261        * Digest password using the algorithm specified and
 1262        * convert the result to a corresponding hex string.
 1263        * If exception, the plain credentials string is returned
 1264        */
 1265       public static void main(String args[]) {
 1266   
 1267           String encoding = null;
 1268           int firstCredentialArg = 2;
 1269           
 1270           if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
 1271               encoding = args[3];
 1272               firstCredentialArg = 4;
 1273           }
 1274           
 1275           if(args.length > firstCredentialArg && args[0].equalsIgnoreCase("-a")) {
 1276               for(int i=firstCredentialArg; i < args.length ; i++){
 1277                   System.out.print(args[i]+":");
 1278                   System.out.println(Digest(args[i], args[1], encoding));
 1279               }
 1280           } else {
 1281               System.out.println
 1282                   ("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
 1283           }
 1284   
 1285       }
 1286   
 1287   
 1288       // -------------------- JMX and Registration  --------------------
 1289       protected String type;
 1290       protected String domain;
 1291       protected String host;
 1292       protected String path;
 1293       protected String realmPath = "/realm0";
 1294       protected ObjectName oname;
 1295       protected ObjectName controller;
 1296       protected MBeanServer mserver;
 1297   
 1298       public ObjectName getController() {
 1299           return controller;
 1300       }
 1301   
 1302       public void setController(ObjectName controller) {
 1303           this.controller = controller;
 1304       }
 1305   
 1306       public ObjectName getObjectName() {
 1307           return oname;
 1308       }
 1309   
 1310       public String getDomain() {
 1311           return domain;
 1312       }
 1313   
 1314       public String getType() {
 1315           return type;
 1316       }
 1317   
 1318       public String getRealmPath() {
 1319           return realmPath;
 1320       }
 1321       
 1322       public void setRealmPath(String theRealmPath) {
 1323           realmPath = theRealmPath;
 1324       }
 1325   
 1326       public ObjectName preRegister(MBeanServer server,
 1327                                     ObjectName name) throws Exception {
 1328           oname=name;
 1329           mserver=server;
 1330           domain=name.getDomain();
 1331   
 1332           type=name.getKeyProperty("type");
 1333           host=name.getKeyProperty("host");
 1334           path=name.getKeyProperty("path");
 1335   
 1336           return name;
 1337       }
 1338   
 1339       public void postRegister(Boolean registrationDone) {
 1340       }
 1341   
 1342       public void preDeregister() throws Exception {
 1343       }
 1344   
 1345       public void postDeregister() {
 1346       }
 1347   
 1348       protected boolean initialized=false;
 1349       
 1350       public void init() {
 1351           if( initialized && container != null ) return;
 1352   
 1353           // We want logger as soon as possible
 1354           if (container != null) {
 1355               this.containerLog = container.getLogger();
 1356           }
 1357           
 1358           initialized=true;
 1359           if( container== null ) {
 1360               ObjectName parent=null;
 1361               // Register with the parent
 1362               try {
 1363                   if( host == null ) {
 1364                       // global
 1365                       parent=new ObjectName(domain +":type=Engine");
 1366                   } else if( path==null ) {
 1367                       parent=new ObjectName(domain +
 1368                               ":type=Host,host=" + host);
 1369                   } else {
 1370                       parent=new ObjectName(domain +":j2eeType=WebModule,name=//" +
 1371                               host + path);
 1372                   }
 1373                   if( mserver.isRegistered(parent ))  {
 1374                       if(log.isDebugEnabled())
 1375                           log.debug("Register with " + parent);
 1376                       mserver.setAttribute(parent, new Attribute("realm", this));
 1377                   }
 1378               } catch (Exception e) {
 1379                   log.error("Parent not available yet: " + parent);  
 1380               }
 1381           }
 1382           
 1383           if( oname==null ) {
 1384               // register
 1385               try {
 1386                   ContainerBase cb=(ContainerBase)container;
 1387                   oname=new ObjectName(cb.getDomain()+":type=Realm" +
 1388                           getRealmSuffix() + cb.getContainerSuffix());
 1389                   Registry.getRegistry(null, null).registerComponent(this, oname, null );
 1390                   if(log.isDebugEnabled())
 1391                       log.debug("Register Realm "+oname);
 1392               } catch (Throwable e) {
 1393                   log.error( "Can't register " + oname, e);
 1394               }
 1395           }
 1396   
 1397       }
 1398   
 1399   
 1400       protected String getRealmSuffix() {
 1401           return ",realmPath=" + getRealmPath();
 1402       }
 1403   
 1404   
 1405       protected static class AllRolesMode {
 1406           
 1407           private String name;
 1408           /** Use the strict servlet spec interpretation which requires that the user
 1409            * have one of the web-app/security-role/role-name 
 1410            */
 1411           public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
 1412           /** Allow any authenticated user
 1413            */
 1414           public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
 1415           /** Allow any authenticated user only if there are no web-app/security-roles
 1416            */
 1417           public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
 1418           
 1419           static AllRolesMode toMode(String name)
 1420           {
 1421               AllRolesMode mode;
 1422               if( name.equalsIgnoreCase(STRICT_MODE.name) )
 1423                   mode = STRICT_MODE;
 1424               else if( name.equalsIgnoreCase(AUTH_ONLY_MODE.name) )
 1425                   mode = AUTH_ONLY_MODE;
 1426               else if( name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name) )
 1427                   mode = STRICT_AUTH_ONLY_MODE;
 1428               else
 1429                   throw new IllegalStateException("Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
 1430               return mode;
 1431           }
 1432           
 1433           private AllRolesMode(String name)
 1434           {
 1435               this.name = name;
 1436           }
 1437           
 1438           public boolean equals(Object o)
 1439           {
 1440               boolean equals = false;
 1441               if( o instanceof AllRolesMode )
 1442               {
 1443                   AllRolesMode mode = (AllRolesMode) o;
 1444                   equals = name.equals(mode.name);
 1445               }
 1446               return equals;
 1447           }
 1448           public int hashCode()
 1449           {
 1450               return name.hashCode();
 1451           }
 1452           public String toString()
 1453           {
 1454               return name;
 1455           }
 1456       }
 1457   
 1458   }

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