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   package org.apache.catalina.realm;
   19   
   20   import java.io.IOException;
   21   import java.security.Principal;
   22   import java.text.MessageFormat;
   23   import java.util.ArrayList;
   24   import java.util.Arrays;
   25   import java.util.Hashtable;
   26   import java.util.List;
   27   
   28   import javax.naming.Context;
   29   import javax.naming.CommunicationException;
   30   import javax.naming.CompositeName;
   31   import javax.naming.InvalidNameException;
   32   import javax.naming.NameNotFoundException;
   33   import javax.naming.NamingEnumeration;
   34   import javax.naming.NamingException;
   35   import javax.naming.NameParser;
   36   import javax.naming.Name;
   37   import javax.naming.AuthenticationException;
   38   import javax.naming.ServiceUnavailableException;
   39   import javax.naming.directory.Attribute;
   40   import javax.naming.directory.Attributes;
   41   import javax.naming.directory.DirContext;
   42   import javax.naming.directory.InitialDirContext;
   43   import javax.naming.directory.SearchControls;
   44   import javax.naming.directory.SearchResult;
   45   import org.apache.catalina.LifecycleException;
   46   import org.apache.catalina.util.Base64;
   47   import org.apache.tomcat.util.buf.ByteChunk;
   48   import org.apache.tomcat.util.buf.CharChunk;
   49   
   50   /**
   51    * <p>Implementation of <strong>Realm</strong> that works with a directory
   52    * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
   53    * The following constraints are imposed on the data structure in the
   54    * underlying directory server:</p>
   55    * <ul>
   56    *
   57    * <li>Each user that can be authenticated is represented by an individual
   58    *     element in the top level <code>DirContext</code> that is accessed
   59    *     via the <code>connectionURL</code> property.</li>
   60    *
   61    * <li>If a socket connection can not be made to the <code>connectURL</code>
   62    *     an attempt will be made to use the <code>alternateURL</code> if it
   63    *     exists.</li>
   64    *
   65    * <li>Each user element has a distinguished name that can be formed by
   66    *     substituting the presented username into a pattern configured by the
   67    *     <code>userPattern</code> property.</li>
   68    *
   69    * <li>Alternatively, if the <code>userPattern</code> property is not
   70    *     specified, a unique element can be located by searching the directory
   71    *     context. In this case:
   72    *     <ul>
   73    *     <li>The <code>userSearch</code> pattern specifies the search filter
   74    *         after substitution of the username.</li>
   75    *     <li>The <code>userBase</code> property can be set to the element that
   76    *         is the base of the subtree containing users.  If not specified,
   77    *         the search base is the top-level context.</li>
   78    *     <li>The <code>userSubtree</code> property can be set to
   79    *         <code>true</code> if you wish to search the entire subtree of the
   80    *         directory context.  The default value of <code>false</code>
   81    *         requests a search of only the current level.</li>
   82    *    </ul>
   83    * </li>
   84    *
   85    * <li>The user may be authenticated by binding to the directory with the
   86    *      username and password presented. This method is used when the
   87    *      <code>userPassword</code> property is not specified.</li>
   88    *
   89    * <li>The user may be authenticated by retrieving the value of an attribute
   90    *     from the directory and comparing it explicitly with the value presented
   91    *     by the user. This method is used when the <code>userPassword</code>
   92    *     property is specified, in which case:
   93    *     <ul>
   94    *     <li>The element for this user must contain an attribute named by the
   95    *         <code>userPassword</code> property.
   96    *     <li>The value of the user password attribute is either a cleartext
   97    *         String, or the result of passing a cleartext String through the
   98    *         <code>RealmBase.digest()</code> method (using the standard digest
   99    *         support included in <code>RealmBase</code>).
  100    *     <li>The user is considered to be authenticated if the presented
  101    *         credentials (after being passed through
  102    *         <code>RealmBase.digest()</code>) are equal to the retrieved value
  103    *         for the user password attribute.</li>
  104    *     </ul></li>
  105    *
  106    * <li>Each group of users that has been assigned a particular role may be
  107    *     represented by an individual element in the top level
  108    *     <code>DirContext</code> that is accessed via the
  109    *     <code>connectionURL</code> property.  This element has the following
  110    *     characteristics:
  111    *     <ul>
  112    *     <li>The set of all possible groups of interest can be selected by a
  113    *         search pattern configured by the <code>roleSearch</code>
  114    *         property.</li>
  115    *     <li>The <code>roleSearch</code> pattern optionally includes pattern
  116    *         replacements "{0}" for the distinguished name, and/or "{1}" for
  117    *         the username, of the authenticated user for which roles will be
  118    *         retrieved.</li>
  119    *     <li>The <code>roleBase</code> property can be set to the element that
  120    *         is the base of the search for matching roles.  If not specified,
  121    *         the entire context will be searched.</li>
  122    *     <li>The <code>roleSubtree</code> property can be set to
  123    *         <code>true</code> if you wish to search the entire subtree of the
  124    *         directory context.  The default value of <code>false</code>
  125    *         requests a search of only the current level.</li>
  126    *     <li>The element includes an attribute (whose name is configured by
  127    *         the <code>roleName</code> property) containing the name of the
  128    *         role represented by this element.</li>
  129    *     </ul></li>
  130    *
  131    * <li>In addition, roles may be represented by the values of an attribute
  132    * in the user's element whose name is configured by the
  133    * <code>userRoleName</code> property.</li>
  134    *
  135    * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
  136    *     the web application deployment descriptor allows applications to refer
  137    *     to roles programmatically by names other than those used in the
  138    *     directory server itself.</li>
  139    * </ul>
  140    *
  141    * <p><strong>TODO</strong> - Support connection pooling (including message
  142    * format objects) so that <code>authenticate()</code> does not have to be
  143    * synchronized.</p>
  144    *
  145    * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
  146    * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
  147    * successfully authenticated a non-existing user. The
  148    * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
  149    * With luck, Netscape has updated their provider code and this is not an
  150    * issue. </p>
  151    *
  152    * @author John Holman
  153    * @author Craig R. McClanahan
  154    * @version $Revision: 572859 $ $Date: 2007-09-05 04:14:45 +0200 (mer., 05 sept. 2007) $
  155    */
  156   
  157   public class JNDIRealm extends RealmBase {
  158   
  159   
  160       // ----------------------------------------------------- Instance Variables
  161   
  162       /**
  163        *  The type of authentication to use
  164        */
  165       protected String authentication = null;
  166   
  167       /**
  168        * The connection username for the server we will contact.
  169        */
  170       protected String connectionName = null;
  171   
  172   
  173       /**
  174        * The connection password for the server we will contact.
  175        */
  176       protected String connectionPassword = null;
  177   
  178   
  179       /**
  180        * The connection URL for the server we will contact.
  181        */
  182       protected String connectionURL = null;
  183   
  184   
  185       /**
  186        * The directory context linking us to our directory server.
  187        */
  188       protected DirContext context = null;
  189   
  190   
  191       /**
  192        * The JNDI context factory used to acquire our InitialContext.  By
  193        * default, assumes use of an LDAP server using the standard JNDI LDAP
  194        * provider.
  195        */
  196       protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
  197   
  198       
  199       /**
  200        * How aliases should be dereferenced during search operations.
  201        */
  202       protected String derefAliases = null;
  203   
  204       /**
  205        * Constant that holds the name of the environment property for specifying 
  206        * the manner in which aliases should be dereferenced.
  207        */
  208       public final static String DEREF_ALIASES = "java.naming.ldap.derefAliases";
  209   
  210       /**
  211        * Descriptive information about this Realm implementation.
  212        */
  213       protected static final String info =
  214           "org.apache.catalina.realm.JNDIRealm/1.0";
  215   
  216   
  217       /**
  218        * Descriptive information about this Realm implementation.
  219        */
  220       protected static final String name = "JNDIRealm";
  221   
  222   
  223       /**
  224        * The protocol that will be used in the communication with the
  225        * directory server.
  226        */
  227       protected String protocol = null;
  228   
  229   
  230       /**
  231        * How should we handle referrals?  Microsoft Active Directory can't handle
  232        * the default case, so an application authenticating against AD must
  233        * set referrals to "follow".
  234        */
  235       protected String referrals = null;
  236   
  237   
  238       /**
  239        * The base element for user searches.
  240        */
  241       protected String userBase = "";
  242   
  243   
  244       /**
  245        * The message format used to search for a user, with "{0}" marking
  246        * the spot where the username goes.
  247        */
  248       protected String userSearch = null;
  249   
  250   
  251       /**
  252        * The MessageFormat object associated with the current
  253        * <code>userSearch</code>.
  254        */
  255       protected MessageFormat userSearchFormat = null;
  256   
  257   
  258       /**
  259        * Should we search the entire subtree for matching users?
  260        */
  261       protected boolean userSubtree = false;
  262   
  263   
  264       /**
  265        * The attribute name used to retrieve the user password.
  266        */
  267       protected String userPassword = null;
  268   
  269   
  270       /**
  271        * A string of LDAP user patterns or paths, ":"-separated
  272        * These will be used to form the distinguished name of a
  273        * user, with "{0}" marking the spot where the specified username
  274        * goes.
  275        * This is similar to userPattern, but allows for multiple searches
  276        * for a user.
  277        */
  278       protected String[] userPatternArray = null;
  279   
  280   
  281       /**
  282        * The message format used to form the distinguished name of a
  283        * user, with "{0}" marking the spot where the specified username
  284        * goes.
  285        */
  286       protected String userPattern = null;
  287   
  288   
  289       /**
  290        * An array of MessageFormat objects associated with the current
  291        * <code>userPatternArray</code>.
  292        */
  293       protected MessageFormat[] userPatternFormatArray = null;
  294   
  295   
  296       /**
  297        * The base element for role searches.
  298        */
  299       protected String roleBase = "";
  300   
  301   
  302       /**
  303        * The MessageFormat object associated with the current
  304        * <code>roleSearch</code>.
  305        */
  306       protected MessageFormat roleFormat = null;
  307   
  308   
  309       /**
  310        * The name of an attribute in the user's entry containing
  311        * roles for that user
  312        */
  313       protected String userRoleName = null;
  314   
  315   
  316       /**
  317        * The name of the attribute containing roles held elsewhere
  318        */
  319       protected String roleName = null;
  320   
  321   
  322       /**
  323        * The message format used to select roles for a user, with "{0}" marking
  324        * the spot where the distinguished name of the user goes.
  325        */
  326       protected String roleSearch = null;
  327   
  328   
  329       /**
  330        * Should we search the entire subtree for matching memberships?
  331        */
  332       protected boolean roleSubtree = false;
  333   
  334       /**
  335        * An alternate URL, to which, we should connect if connectionURL fails.
  336        */
  337       protected String alternateURL;
  338   
  339       /**
  340        * The number of connection attempts.  If greater than zero we use the
  341        * alternate url.
  342        */
  343       protected int connectionAttempt = 0;
  344   
  345       /**
  346        * The current user pattern to be used for lookup and binding of a user.
  347        */
  348       protected int curUserPattern = 0;
  349   
  350       // ------------------------------------------------------------- Properties
  351   
  352       /**
  353        * Return the type of authentication to use.
  354        */
  355       public String getAuthentication() {
  356   
  357           return authentication;
  358   
  359       }
  360   
  361       /**
  362        * Set the type of authentication to use.
  363        *
  364        * @param authentication The authentication
  365        */
  366       public void setAuthentication(String authentication) {
  367   
  368           this.authentication = authentication;
  369   
  370       }
  371   
  372       /**
  373        * Return the connection username for this Realm.
  374        */
  375       public String getConnectionName() {
  376   
  377           return (this.connectionName);
  378   
  379       }
  380   
  381   
  382       /**
  383        * Set the connection username for this Realm.
  384        *
  385        * @param connectionName The new connection username
  386        */
  387       public void setConnectionName(String connectionName) {
  388   
  389           this.connectionName = connectionName;
  390   
  391       }
  392   
  393   
  394       /**
  395        * Return the connection password for this Realm.
  396        */
  397       public String getConnectionPassword() {
  398   
  399           return (this.connectionPassword);
  400   
  401       }
  402   
  403   
  404       /**
  405        * Set the connection password for this Realm.
  406        *
  407        * @param connectionPassword The new connection password
  408        */
  409       public void setConnectionPassword(String connectionPassword) {
  410   
  411           this.connectionPassword = connectionPassword;
  412   
  413       }
  414   
  415   
  416       /**
  417        * Return the connection URL for this Realm.
  418        */
  419       public String getConnectionURL() {
  420   
  421           return (this.connectionURL);
  422   
  423       }
  424   
  425   
  426       /**
  427        * Set the connection URL for this Realm.
  428        *
  429        * @param connectionURL The new connection URL
  430        */
  431       public void setConnectionURL(String connectionURL) {
  432   
  433           this.connectionURL = connectionURL;
  434   
  435       }
  436   
  437   
  438       /**
  439        * Return the JNDI context factory for this Realm.
  440        */
  441       public String getContextFactory() {
  442   
  443           return (this.contextFactory);
  444   
  445       }
  446   
  447   
  448       /**
  449        * Set the JNDI context factory for this Realm.
  450        *
  451        * @param contextFactory The new context factory
  452        */
  453       public void setContextFactory(String contextFactory) {
  454   
  455           this.contextFactory = contextFactory;
  456   
  457       }
  458   
  459       /**
  460        * Return the derefAliases setting to be used.
  461        */
  462       public java.lang.String getDerefAliases() {
  463           return derefAliases;
  464       }  
  465       
  466       /**
  467        * Set the value for derefAliases to be used when searching the directory.
  468        * 
  469        * @param derefAliases New value of property derefAliases.
  470        */
  471       public void setDerefAliases(java.lang.String derefAliases) {
  472         this.derefAliases = derefAliases;
  473       }
  474   
  475       /**
  476        * Return the protocol to be used.
  477        */
  478       public String getProtocol() {
  479   
  480           return protocol;
  481   
  482       }
  483   
  484       /**
  485        * Set the protocol for this Realm.
  486        *
  487        * @param protocol The new protocol.
  488        */
  489       public void setProtocol(String protocol) {
  490   
  491           this.protocol = protocol;
  492   
  493       }
  494   
  495   
  496       /**
  497        * Returns the current settings for handling JNDI referrals.
  498        */
  499       public String getReferrals () {
  500           return referrals;
  501       }
  502   
  503   
  504       /**
  505        * How do we handle JNDI referrals? ignore, follow, or throw
  506        * (see javax.naming.Context.REFERRAL for more information).
  507        */
  508       public void setReferrals (String referrals) {
  509           this.referrals = referrals;
  510       }
  511   
  512   
  513       /**
  514        * Return the base element for user searches.
  515        */
  516       public String getUserBase() {
  517   
  518           return (this.userBase);
  519   
  520       }
  521   
  522   
  523       /**
  524        * Set the base element for user searches.
  525        *
  526        * @param userBase The new base element
  527        */
  528       public void setUserBase(String userBase) {
  529   
  530           this.userBase = userBase;
  531   
  532       }
  533   
  534   
  535       /**
  536        * Return the message format pattern for selecting users in this Realm.
  537        */
  538       public String getUserSearch() {
  539   
  540           return (this.userSearch);
  541   
  542       }
  543   
  544   
  545       /**
  546        * Set the message format pattern for selecting users in this Realm.
  547        *
  548        * @param userSearch The new user search pattern
  549        */
  550       public void setUserSearch(String userSearch) {
  551   
  552           this.userSearch = userSearch;
  553           if (userSearch == null)
  554               userSearchFormat = null;
  555           else
  556               userSearchFormat = new MessageFormat(userSearch);
  557   
  558       }
  559   
  560   
  561       /**
  562        * Return the "search subtree for users" flag.
  563        */
  564       public boolean getUserSubtree() {
  565   
  566           return (this.userSubtree);
  567   
  568       }
  569   
  570   
  571       /**
  572        * Set the "search subtree for users" flag.
  573        *
  574        * @param userSubtree The new search flag
  575        */
  576       public void setUserSubtree(boolean userSubtree) {
  577   
  578           this.userSubtree = userSubtree;
  579   
  580       }
  581   
  582   
  583       /**
  584        * Return the user role name attribute name for this Realm.
  585        */
  586       public String getUserRoleName() {
  587   
  588           return userRoleName;
  589       }
  590   
  591   
  592       /**
  593        * Set the user role name attribute name for this Realm.
  594        *
  595        * @param userRoleName The new userRole name attribute name
  596        */
  597       public void setUserRoleName(String userRoleName) {
  598   
  599           this.userRoleName = userRoleName;
  600   
  601       }
  602   
  603   
  604       /**
  605        * Return the base element for role searches.
  606        */
  607       public String getRoleBase() {
  608   
  609           return (this.roleBase);
  610   
  611       }
  612   
  613   
  614       /**
  615        * Set the base element for role searches.
  616        *
  617        * @param roleBase The new base element
  618        */
  619       public void setRoleBase(String roleBase) {
  620   
  621           this.roleBase = roleBase;
  622   
  623       }
  624   
  625   
  626       /**
  627        * Return the role name attribute name for this Realm.
  628        */
  629       public String getRoleName() {
  630   
  631           return (this.roleName);
  632   
  633       }
  634   
  635   
  636       /**
  637        * Set the role name attribute name for this Realm.
  638        *
  639        * @param roleName The new role name attribute name
  640        */
  641       public void setRoleName(String roleName) {
  642   
  643           this.roleName = roleName;
  644   
  645       }
  646   
  647   
  648       /**
  649        * Return the message format pattern for selecting roles in this Realm.
  650        */
  651       public String getRoleSearch() {
  652   
  653           return (this.roleSearch);
  654   
  655       }
  656   
  657   
  658       /**
  659        * Set the message format pattern for selecting roles in this Realm.
  660        *
  661        * @param roleSearch The new role search pattern
  662        */
  663       public void setRoleSearch(String roleSearch) {
  664   
  665           this.roleSearch = roleSearch;
  666           if (roleSearch == null)
  667               roleFormat = null;
  668           else
  669               roleFormat = new MessageFormat(roleSearch);
  670   
  671       }
  672   
  673   
  674       /**
  675        * Return the "search subtree for roles" flag.
  676        */
  677       public boolean getRoleSubtree() {
  678   
  679           return (this.roleSubtree);
  680   
  681       }
  682   
  683   
  684       /**
  685        * Set the "search subtree for roles" flag.
  686        *
  687        * @param roleSubtree The new search flag
  688        */
  689       public void setRoleSubtree(boolean roleSubtree) {
  690   
  691           this.roleSubtree = roleSubtree;
  692   
  693       }
  694   
  695   
  696       /**
  697        * Return the password attribute used to retrieve the user password.
  698        */
  699       public String getUserPassword() {
  700   
  701           return (this.userPassword);
  702   
  703       }
  704   
  705   
  706       /**
  707        * Set the password attribute used to retrieve the user password.
  708        *
  709        * @param userPassword The new password attribute
  710        */
  711       public void setUserPassword(String userPassword) {
  712   
  713           this.userPassword = userPassword;
  714   
  715       }
  716   
  717   
  718       /**
  719        * Return the message format pattern for selecting users in this Realm.
  720        */
  721       public String getUserPattern() {
  722   
  723           return (this.userPattern);
  724   
  725       }
  726   
  727   
  728       /**
  729        * Set the message format pattern for selecting users in this Realm.
  730        * This may be one simple pattern, or multiple patterns to be tried,
  731        * separated by parentheses. (for example, either "cn={0}", or
  732        * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
  733        * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
  734        * also valid. Complex search strings with &, etc are NOT supported.
  735        *
  736        * @param userPattern The new user pattern
  737        */
  738       public void setUserPattern(String userPattern) {
  739   
  740           this.userPattern = userPattern;
  741           if (userPattern == null)
  742               userPatternArray = null;
  743           else {
  744               userPatternArray = parseUserPatternString(userPattern);
  745               int len = this.userPatternArray.length;
  746               userPatternFormatArray = new MessageFormat[len];
  747               for (int i=0; i < len; i++) {
  748                   userPatternFormatArray[i] =
  749                       new MessageFormat(userPatternArray[i]);
  750               }
  751           }
  752       }
  753   
  754   
  755       /**
  756        * Getter for property alternateURL.
  757        *
  758        * @return Value of property alternateURL.
  759        */
  760       public String getAlternateURL() {
  761   
  762           return this.alternateURL;
  763   
  764       }
  765   
  766   
  767       /**
  768        * Setter for property alternateURL.
  769        *
  770        * @param alternateURL New value of property alternateURL.
  771        */
  772       public void setAlternateURL(String alternateURL) {
  773   
  774           this.alternateURL = alternateURL;
  775   
  776       }
  777   
  778   
  779       // ---------------------------------------------------------- Realm Methods
  780   
  781   
  782       /**
  783        * Return the Principal associated with the specified username and
  784        * credentials, if there is one; otherwise return <code>null</code>.
  785        *
  786        * If there are any errors with the JDBC connection, executing
  787        * the query or anything we return null (don't authenticate). This
  788        * event is also logged, and the connection will be closed so that
  789        * a subsequent request will automatically re-open it.
  790        *
  791        * @param username Username of the Principal to look up
  792        * @param credentials Password or other credentials to use in
  793        *  authenticating this username
  794        */
  795       public Principal authenticate(String username, String credentials) {
  796   
  797           DirContext context = null;
  798           Principal principal = null;
  799   
  800           try {
  801   
  802               // Ensure that we have a directory context available
  803               context = open();
  804   
  805               // Occassionally the directory context will timeout.  Try one more
  806               // time before giving up.
  807               try {
  808   
  809                   // Authenticate the specified username if possible
  810                   principal = authenticate(context, username, credentials);
  811   
  812               } catch (NullPointerException e) {
  813                   /* BZ 42449 - Kludge Sun's LDAP provider
  814                      with broken SSL
  815                   */
  816                   // log the exception so we know it's there.
  817                   containerLog.warn(sm.getString("jndiRealm.exception"), e);
  818   
  819                   // close the connection so we know it will be reopened.
  820                   if (context != null)
  821                       close(context);
  822   
  823                   // open a new directory context.
  824                   context = open();
  825   
  826                   // Try the authentication again.
  827                   principal = authenticate(context, username, credentials);
  828   
  829               } catch (CommunicationException e) {
  830   
  831                   // log the exception so we know it's there.
  832                   containerLog.warn(sm.getString("jndiRealm.exception"), e);
  833   
  834                   // close the connection so we know it will be reopened.
  835                   if (context != null)
  836                       close(context);
  837   
  838                   // open a new directory context.
  839                   context = open();
  840   
  841                   // Try the authentication again.
  842                   principal = authenticate(context, username, credentials);
  843   
  844               } catch (ServiceUnavailableException e) {
  845   
  846                   // log the exception so we know it's there.
  847                   containerLog.warn(sm.getString("jndiRealm.exception"), e);
  848   
  849                   // close the connection so we know it will be reopened.
  850                   if (context != null)
  851                       close(context);
  852   
  853                   // open a new directory context.
  854                   context = open();
  855   
  856                   // Try the authentication again.
  857                   principal = authenticate(context, username, credentials);
  858   
  859               }
  860   
  861   
  862               // Release this context
  863               release(context);
  864   
  865               // Return the authenticated Principal (if any)
  866               return (principal);
  867   
  868           } catch (NamingException e) {
  869   
  870               // Log the problem for posterity
  871               containerLog.error(sm.getString("jndiRealm.exception"), e);
  872   
  873               // Close the connection so that it gets reopened next time
  874               if (context != null)
  875                   close(context);
  876   
  877               // Return "not authenticated" for this request
  878               return (null);
  879   
  880           }
  881   
  882       }
  883   
  884   
  885       // -------------------------------------------------------- Package Methods
  886   
  887   
  888       // ------------------------------------------------------ Protected Methods
  889   
  890   
  891       /**
  892        * Return the Principal associated with the specified username and
  893        * credentials, if there is one; otherwise return <code>null</code>.
  894        *
  895        * @param context The directory context
  896        * @param username Username of the Principal to look up
  897        * @param credentials Password or other credentials to use in
  898        *  authenticating this username
  899        *
  900        * @exception NamingException if a directory server error occurs
  901        */
  902       public synchronized Principal authenticate(DirContext context,
  903                                                  String username,
  904                                                  String credentials)
  905           throws NamingException {
  906   
  907           if (username == null || username.equals("")
  908               || credentials == null || credentials.equals(""))
  909               return (null);
  910   
  911           if (userPatternArray != null) {
  912               for (curUserPattern = 0;
  913                    curUserPattern < userPatternFormatArray.length;
  914                    curUserPattern++) {
  915                   // Retrieve user information
  916                   User user = getUser(context, username);
  917                   if (user != null) {
  918                       try {
  919                           // Check the user's credentials
  920                           if (checkCredentials(context, user, credentials)) {
  921                               // Search for additional roles
  922                               List<String> roles = getRoles(context, user);
  923                               return (new GenericPrincipal(this,
  924                                                            username,
  925                                                            credentials,
  926                                                            roles));
  927                           }
  928                       } catch (InvalidNameException ine) {
  929                           // Log the problem for posterity
  930                           containerLog.warn(sm.getString("jndiRealm.exception"), ine);
  931                           // ignore; this is probably due to a name not fitting
  932                           // the search path format exactly, as in a fully-
  933                           // qualified name being munged into a search path
  934                           // that already contains cn= or vice-versa
  935                       }
  936                   }
  937               }
  938               return null;
  939           } else {
  940               // Retrieve user information
  941               User user = getUser(context, username);
  942               if (user == null)
  943                   return (null);
  944   
  945               // Check the user's credentials
  946               if (!checkCredentials(context, user, credentials))
  947                   return (null);
  948   
  949               // Search for additional roles
  950               List<String> roles = getRoles(context, user);
  951   
  952               // Create and return a suitable Principal for this user
  953               return (new GenericPrincipal(this, username, credentials, roles));
  954           }
  955       }
  956   
  957   
  958       /**
  959        * Return a User object containing information about the user
  960        * with the specified username, if found in the directory;
  961        * otherwise return <code>null</code>.
  962        *
  963        * If the <code>userPassword</code> configuration attribute is
  964        * specified, the value of that attribute is retrieved from the
  965        * user's directory entry. If the <code>userRoleName</code>
  966        * configuration attribute is specified, all values of that
  967        * attribute are retrieved from the directory entry.
  968        *
  969        * @param context The directory context
  970        * @param username Username to be looked up
  971        *
  972        * @exception NamingException if a directory server error occurs
  973        */
  974       protected User getUser(DirContext context, String username)
  975           throws NamingException {
  976   
  977           User user = null;
  978   
  979           // Get attributes to retrieve from user entry
  980           ArrayList<String> list = new ArrayList<String>();
  981           if (userPassword != null)
  982               list.add(userPassword);
  983           if (userRoleName != null)
  984               list.add(userRoleName);
  985           String[] attrIds = new String[list.size()];
  986           list.toArray(attrIds);
  987   
  988           // Use pattern or search for user entry
  989           if (userPatternFormatArray != null) {
  990               user = getUserByPattern(context, username, attrIds);
  991           } else {
  992               user = getUserBySearch(context, username, attrIds);
  993           }
  994   
  995           return user;
  996       }
  997   
  998   
  999       /**
 1000        * Use the <code>UserPattern</code> configuration attribute to
 1001        * locate the directory entry for the user with the specified
 1002        * username and return a User object; otherwise return
 1003        * <code>null</code>.
 1004        *
 1005        * @param context The directory context
 1006        * @param username The username
 1007        * @param attrIds String[]containing names of attributes to
 1008        * retrieve.
 1009        *
 1010        * @exception NamingException if a directory server error occurs
 1011        */
 1012       protected User getUserByPattern(DirContext context,
 1013                                                 String username,
 1014                                                 String[] attrIds)
 1015           throws NamingException {
 1016   
 1017           if (username == null || userPatternFormatArray[curUserPattern] == null)
 1018               return (null);
 1019   
 1020           // Form the dn from the user pattern
 1021           String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
 1022   
 1023           // Get required attributes from user entry
 1024           Attributes attrs = null;
 1025           try {
 1026               attrs = context.getAttributes(dn, attrIds);
 1027           } catch (NameNotFoundException e) {
 1028               return (null);
 1029           }
 1030           if (attrs == null)
 1031               return (null);
 1032   
 1033           // Retrieve value of userPassword
 1034           String password = null;
 1035           if (userPassword != null)
 1036               password = getAttributeValue(userPassword, attrs);
 1037   
 1038           // Retrieve values of userRoleName attribute
 1039           ArrayList<String> roles = null;
 1040           if (userRoleName != null)
 1041               roles = addAttributeValues(userRoleName, attrs, roles);
 1042   
 1043           return new User(username, dn, password, roles);
 1044       }
 1045   
 1046   
 1047       /**
 1048        * Search the directory to return a User object containing
 1049        * information about the user with the specified username, if
 1050        * found in the directory; otherwise return <code>null</code>.
 1051        *
 1052        * @param context The directory context
 1053        * @param username The username
 1054        * @param attrIds String[]containing names of attributes to retrieve.
 1055        *
 1056        * @exception NamingException if a directory server error occurs
 1057        */
 1058       protected User getUserBySearch(DirContext context,
 1059                                              String username,
 1060                                              String[] attrIds)
 1061           throws NamingException {
 1062   
 1063           if (username == null || userSearchFormat == null)
 1064               return (null);
 1065   
 1066           // Form the search filter
 1067           String filter = userSearchFormat.format(new String[] { username });
 1068   
 1069           // Set up the search controls
 1070           SearchControls constraints = new SearchControls();
 1071   
 1072           if (userSubtree) {
 1073               constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
 1074           }
 1075           else {
 1076               constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
 1077           }
 1078   
 1079           // Specify the attributes to be retrieved
 1080           if (attrIds == null)
 1081               attrIds = new String[0];
 1082           constraints.setReturningAttributes(attrIds);
 1083   
 1084           NamingEnumeration results =
 1085               context.search(userBase, filter, constraints);
 1086   
 1087   
 1088           // Fail if no entries found
 1089           if (results == null || !results.hasMore()) {
 1090               return (null);
 1091           }
 1092   
 1093           // Get result for the first entry found
 1094           SearchResult result = (SearchResult)results.next();
 1095   
 1096           // Check no further entries were found
 1097           if (results.hasMore()) {
 1098               if(containerLog.isInfoEnabled())
 1099                   containerLog.info("username " + username + " has multiple entries");
 1100               return (null);
 1101           }
 1102   
 1103           // Get the entry's distinguished name
 1104           NameParser parser = context.getNameParser("");
 1105           Name contextName = parser.parse(context.getNameInNamespace());
 1106           Name baseName = parser.parse(userBase);
 1107   
 1108           // Bugzilla 32269
 1109           Name entryName = parser.parse(new CompositeName(result.getName()).get(0));
 1110   
 1111           Name name = contextName.addAll(baseName);
 1112           name = name.addAll(entryName);
 1113           String dn = name.toString();
 1114   
 1115           if (containerLog.isTraceEnabled())
 1116               containerLog.trace("  entry found for " + username + " with dn " + dn);
 1117   
 1118           // Get the entry's attributes
 1119           Attributes attrs = result.getAttributes();
 1120           if (attrs == null)
 1121               return null;
 1122   
 1123           // Retrieve value of userPassword
 1124           String password = null;
 1125           if (userPassword != null)
 1126               password = getAttributeValue(userPassword, attrs);
 1127   
 1128           // Retrieve values of userRoleName attribute
 1129           ArrayList<String> roles = null;
 1130           if (userRoleName != null)
 1131               roles = addAttributeValues(userRoleName, attrs, roles);
 1132   
 1133           return new User(username, dn, password, roles);
 1134       }
 1135   
 1136   
 1137       /**
 1138        * Check whether the given User can be authenticated with the
 1139        * given credentials. If the <code>userPassword</code>
 1140        * configuration attribute is specified, the credentials
 1141        * previously retrieved from the directory are compared explicitly
 1142        * with those presented by the user. Otherwise the presented
 1143        * credentials are checked by binding to the directory as the
 1144        * user.
 1145        *
 1146        * @param context The directory context
 1147        * @param user The User to be authenticated
 1148        * @param credentials The credentials presented by the user
 1149        *
 1150        * @exception NamingException if a directory server error occurs
 1151        */
 1152       protected boolean checkCredentials(DirContext context,
 1153                                        User user,
 1154                                        String credentials)
 1155            throws NamingException {
 1156   
 1157            boolean validated = false;
 1158   
 1159            if (userPassword == null) {
 1160                validated = bindAsUser(context, user, credentials);
 1161            } else {
 1162                validated = compareCredentials(context, user, credentials);
 1163            }
 1164   
 1165            if (containerLog.isTraceEnabled()) {
 1166                if (validated) {
 1167                    containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
 1168                                     user.username));
 1169                } else {
 1170                    containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
 1171                                     user.username));
 1172                }
 1173            }
 1174            return (validated);
 1175        }
 1176   
 1177   
 1178   
 1179       /**
 1180        * Check whether the credentials presented by the user match those
 1181        * retrieved from the directory.
 1182        *
 1183        * @param context The directory context
 1184        * @param info The User to be authenticated
 1185        * @param credentials Authentication credentials
 1186        *
 1187        * @exception NamingException if a directory server error occurs
 1188        */
 1189       protected boolean compareCredentials(DirContext context,
 1190                                            User info,
 1191                                            String credentials)
 1192           throws NamingException {
 1193   
 1194           if (info == null || credentials == null)
 1195               return (false);
 1196   
 1197           String password = info.password;
 1198           if (password == null)
 1199               return (false);
 1200   
 1201           // Validate the credentials specified by the user
 1202           if (containerLog.isTraceEnabled())
 1203               containerLog.trace("  validating credentials");
 1204   
 1205           boolean validated = false;
 1206           if (hasMessageDigest()) {
 1207               // iPlanet support if the values starts with {SHA1}
 1208               // The string is in a format compatible with Base64.encode not
 1209               // the Hex encoding of the parent class.
 1210               if (password.startsWith("{SHA}")) {
 1211                   /* sync since super.digest() does this same thing */
 1212                   synchronized (this) {
 1213                       password = password.substring(5);
 1214                       md.reset();
 1215                       md.update(credentials.getBytes());
 1216                       String digestedPassword =
 1217                           new String(Base64.encode(md.digest()));
 1218                       validated = password.equals(digestedPassword);
 1219                   }
 1220               } else if (password.startsWith("{SSHA}")) {
 1221                   // Bugzilla 32938
 1222                   /* sync since super.digest() does this same thing */
 1223                   synchronized (this) {
 1224                       password = password.substring(6);
 1225   
 1226                       md.reset();
 1227                       md.update(credentials.getBytes());
 1228   
 1229                       // Decode stored password.
 1230                       ByteChunk pwbc = new ByteChunk(password.length());
 1231                       try {
 1232                           pwbc.append(password.getBytes(), 0, password.length());
 1233                       } catch (IOException e) {
 1234                           // Should never happen
 1235                           containerLog.error("Could not append password bytes to chunk: ", e);
 1236                       }
 1237   
 1238                       CharChunk decoded = new CharChunk();
 1239                       Base64.decode(pwbc, decoded);
 1240                       char[] pwarray = decoded.getBuffer();
 1241   
 1242                       // Split decoded password into hash and salt.
 1243                       final int saltpos = 20;
 1244                       byte[] hash = new byte[saltpos];
 1245                       for (int i=0; i< hash.length; i++) {
 1246                           hash[i] = (byte) pwarray[i];
 1247                       }
 1248   
 1249                       byte[] salt = new byte[pwarray.length - saltpos];
 1250                       for (int i=0; i< salt.length; i++)
 1251                           salt[i] = (byte)pwarray[i+saltpos];
 1252   
 1253                       md.update(salt);
 1254                       byte[] dp = md.digest();
 1255   
 1256                       validated = Arrays.equals(dp, hash);
 1257                   } // End synchronized(this) block
 1258               } else {
 1259                   // Hex hashes should be compared case-insensitive
 1260                   validated = (digest(credentials).equalsIgnoreCase(password));
 1261               }
 1262           } else
 1263               validated = (digest(credentials).equals(password));
 1264           return (validated);
 1265   
 1266       }
 1267   
 1268   
 1269   
 1270       /**
 1271        * Check credentials by binding to the directory as the user
 1272        *
 1273        * @param context The directory context
 1274        * @param user The User to be authenticated
 1275        * @param credentials Authentication credentials
 1276        *
 1277        * @exception NamingException if a directory server error occurs
 1278        */
 1279        protected boolean bindAsUser(DirContext context,
 1280                                     User user,
 1281                                     String credentials)
 1282            throws NamingException {
 1283   
 1284            if (credentials == null || user == null)
 1285                return (false);
 1286   
 1287            String dn = user.dn;
 1288            if (dn == null)
 1289                return (false);
 1290   
 1291            // Validate the credentials specified by the user
 1292            if (containerLog.isTraceEnabled()) {
 1293                containerLog.trace("  validating credentials by binding as the user");
 1294           }
 1295   
 1296           // Set up security environment to bind as the user
 1297           context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
 1298           context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
 1299   
 1300           // Elicit an LDAP bind operation
 1301           boolean validated = false;
 1302           try {
 1303               if (containerLog.isTraceEnabled()) {
 1304                   containerLog.trace("  binding as "  + dn);
 1305               }
 1306               context.getAttributes("", null);
 1307               validated = true;
 1308           }
 1309           catch (AuthenticationException e) {
 1310               if (containerLog.isTraceEnabled()) {
 1311                   containerLog.trace("  bind attempt failed");
 1312               }
 1313           }
 1314   
 1315           // Restore the original security environment
 1316           if (connectionName != null) {
 1317               context.addToEnvironment(Context.SECURITY_PRINCIPAL,
 1318                                        connectionName);
 1319           } else {
 1320               context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
 1321           }
 1322   
 1323           if (connectionPassword != null) {
 1324               context.addToEnvironment(Context.SECURITY_CREDENTIALS,
 1325                                        connectionPassword);
 1326           }
 1327           else {
 1328               context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
 1329           }
 1330   
 1331           return (validated);
 1332        }
 1333   
 1334   
 1335       /**
 1336        * Return a List of roles associated with the given User.  Any
 1337        * roles present in the user's directory entry are supplemented by
 1338        * a directory search. If no roles are associated with this user,
 1339        * a zero-length List is returned.
 1340        *
 1341        * @param context The directory context we are searching
 1342        * @param user The User to be checked
 1343        *
 1344        * @exception NamingException if a directory server error occurs
 1345        */
 1346       protected List<String> getRoles(DirContext context, User user)
 1347           throws NamingException {
 1348   
 1349           if (user == null)
 1350               return (null);
 1351   
 1352           String dn = user.dn;
 1353           String username = user.username;
 1354   
 1355           if (dn == null || username == null)
 1356               return (null);
 1357   
 1358           if (containerLog.isTraceEnabled())
 1359               containerLog.trace("  getRoles(" + dn + ")");
 1360   
 1361           // Start with roles retrieved from the user entry
 1362           ArrayList<String> list = user.roles;
 1363           if (list == null) {
 1364               list = new ArrayList<String>();
 1365           }
 1366   
 1367           // Are we configured to do role searches?
 1368           if ((roleFormat == null) || (roleName == null))
 1369               return (list);
 1370   
 1371           // Set up parameters for an appropriate search
 1372           String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username });
 1373           SearchControls controls = new SearchControls();
 1374           if (roleSubtree)
 1375               controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 1376           else
 1377               controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
 1378           controls.setReturningAttributes(new String[] {roleName});
 1379   
 1380           // Perform the configured search and process the results
 1381           NamingEnumeration results =
 1382               context.search(roleBase, filter, controls);
 1383           if (results == null)
 1384               return (list);  // Should never happen, but just in case ...
 1385           while (results.hasMore()) {
 1386               SearchResult result = (SearchResult) results.next();
 1387               Attributes attrs = result.getAttributes();
 1388               if (attrs == null)
 1389                   continue;
 1390               list = addAttributeValues(roleName, attrs, list);
 1391           }
 1392   
 1393   
 1394           if (containerLog.isTraceEnabled()) {
 1395               if (list != null) {
 1396                   containerLog.trace("  Returning " + list.size() + " roles");
 1397                   for (int i=0; i<list.size(); i++)
 1398                       containerLog.trace(  "  Found role " + list.get(i));
 1399               } else {
 1400                   containerLog.trace("  getRoles about to return null ");
 1401               }
 1402           }
 1403   
 1404           return (list);
 1405       }
 1406   
 1407   
 1408       /**
 1409        * Return a String representing the value of the specified attribute.
 1410        *
 1411        * @param attrId Attribute name
 1412        * @param attrs Attributes containing the required value
 1413        *
 1414        * @exception NamingException if a directory server error occurs
 1415        */
 1416       private String getAttributeValue(String attrId, Attributes attrs)
 1417           throws NamingException {
 1418   
 1419           if (containerLog.isTraceEnabled())
 1420               containerLog.trace("  retrieving attribute " + attrId);
 1421   
 1422           if (attrId == null || attrs == null)
 1423               return null;
 1424   
 1425           Attribute attr = attrs.get(attrId);
 1426           if (attr == null)
 1427               return (null);
 1428           Object value = attr.get();
 1429           if (value == null)
 1430               return (null);
 1431           String valueString = null;
 1432           if (value instanceof byte[])
 1433               valueString = new String((byte[]) value);
 1434           else
 1435               valueString = value.toString();
 1436   
 1437           return valueString;
 1438       }
 1439   
 1440   
 1441   
 1442       /**
 1443        * Add values of a specified attribute to a list
 1444        *
 1445        * @param attrId Attribute name
 1446        * @param attrs Attributes containing the new values
 1447        * @param values ArrayList containing values found so far
 1448        *
 1449        * @exception NamingException if a directory server error occurs
 1450        */
 1451       private ArrayList<String> addAttributeValues(String attrId,
 1452                                            Attributes attrs,
 1453                                            ArrayList<String> values)
 1454           throws NamingException{
 1455   
 1456           if (containerLog.isTraceEnabled())
 1457               containerLog.trace("  retrieving values for attribute " + attrId);
 1458           if (attrId == null || attrs == null)
 1459               return values;
 1460           if (values == null)
 1461               values = new ArrayList<String>();
 1462           Attribute attr = attrs.get(attrId);
 1463           if (attr == null)
 1464               return (values);
 1465           NamingEnumeration e = attr.getAll();
 1466           while(e.hasMore()) {
 1467               String value = (String)e.next();
 1468               values.add(value);
 1469           }
 1470           return values;
 1471       }
 1472   
 1473   
 1474       /**
 1475        * Close any open connection to the directory server for this Realm.
 1476        *
 1477        * @param context The directory context to be closed
 1478        */
 1479       protected void close(DirContext context) {
 1480   
 1481           // Do nothing if there is no opened connection
 1482           if (context == null)
 1483               return;
 1484   
 1485           // Close our opened connection
 1486           try {
 1487               if (containerLog.isDebugEnabled())
 1488                   containerLog.debug("Closing directory context");
 1489               context.close();
 1490           } catch (NamingException e) {
 1491               containerLog.error(sm.getString("jndiRealm.close"), e);
 1492           }
 1493           this.context = null;
 1494   
 1495       }
 1496   
 1497   
 1498       /**
 1499        * Return a short name for this Realm implementation.
 1500        */
 1501       protected String getName() {
 1502   
 1503           return (name);
 1504   
 1505       }
 1506   
 1507   
 1508       /**
 1509        * Return the password associated with the given principal's user name.
 1510        */
 1511       protected String getPassword(String username) {
 1512   
 1513           return (null);
 1514   
 1515       }
 1516   
 1517       /**
 1518        * Return the Principal associated with the given user name.
 1519        */
 1520       protected Principal getPrincipal(String username) {
 1521   
 1522           DirContext context = null;
 1523           Principal principal = null;
 1524   
 1525           try {
 1526   
 1527               // Ensure that we have a directory context available
 1528               context = open();
 1529   
 1530               // Occassionally the directory context will timeout.  Try one more
 1531               // time before giving up.
 1532               try {
 1533   
 1534                   // Authenticate the specified username if possible
 1535                   principal = getPrincipal(context, username);
 1536   
 1537               } catch (CommunicationException e) {
 1538   
 1539                   // log the exception so we know it's there.
 1540                   containerLog.warn(sm.getString("jndiRealm.exception"), e);
 1541   
 1542                   // close the connection so we know it will be reopened.
 1543                   if (context != null)
 1544                       close(context);
 1545   
 1546                   // open a new directory context.
 1547                   context = open();
 1548   
 1549                   // Try the authentication again.
 1550                   principal = getPrincipal(context, username);
 1551   
 1552               } catch (ServiceUnavailableException e) {
 1553   
 1554                   // log the exception so we know it's there.
 1555                   containerLog.warn(sm.getString("jndiRealm.exception"), e);
 1556   
 1557                   // close the connection so we know it will be reopened.
 1558                   if (context != null)
 1559                       close(context);
 1560   
 1561                   // open a new directory context.
 1562                   context = open();
 1563   
 1564                   // Try the authentication again.
 1565                   principal = getPrincipal(context, username);
 1566   
 1567               }
 1568   
 1569   
 1570               // Release this context
 1571               release(context);
 1572   
 1573               // Return the authenticated Principal (if any)
 1574               return (principal);
 1575   
 1576           } catch (NamingException e) {
 1577   
 1578               // Log the problem for posterity
 1579               containerLog.error(sm.getString("jndiRealm.exception"), e);
 1580   
 1581               // Close the connection so that it gets reopened next time
 1582               if (context != null)
 1583                   close(context);
 1584   
 1585               // Return "not authenticated" for this request
 1586               return (null);
 1587   
 1588           }
 1589   
 1590   
 1591       }
 1592   
 1593   
 1594       /**
 1595        * Return the Principal associated with the given user name.
 1596        */
 1597       protected synchronized Principal getPrincipal(DirContext context,
 1598                                                     String username)
 1599           throws NamingException {
 1600           
 1601           User user = getUser(context, username);
 1602           
 1603           return new GenericPrincipal(this, user.username, user.password ,
 1604                   getRoles(context, user));
 1605       }
 1606   
 1607       /**
 1608        * Open (if necessary) and return a connection to the configured
 1609        * directory server for this Realm.
 1610        *
 1611        * @exception NamingException if a directory server error occurs
 1612        */
 1613       protected DirContext open() throws NamingException {
 1614   
 1615           // Do nothing if there is a directory server connection already open
 1616           if (context != null)
 1617               return (context);
 1618   
 1619           try {
 1620   
 1621               // Ensure that we have a directory context available
 1622               context = new InitialDirContext(getDirectoryContextEnvironment());
 1623   
 1624           } catch (Exception e) {
 1625   
 1626               connectionAttempt = 1;
 1627   
 1628               // log the first exception.
 1629               containerLog.warn(sm.getString("jndiRealm.exception"), e);
 1630   
 1631               // Try connecting to the alternate url.
 1632               context = new InitialDirContext(getDirectoryContextEnvironment());
 1633   
 1634           } finally {
 1635   
 1636               // reset it in case the connection times out.
 1637               // the primary may come back.
 1638               connectionAttempt = 0;
 1639   
 1640           }
 1641   
 1642           return (context);
 1643   
 1644       }
 1645   
 1646       /**
 1647        * Create our directory context configuration.
 1648        *
 1649        * @return java.util.Hashtable the configuration for the directory context.
 1650        */
 1651       protected Hashtable getDirectoryContextEnvironment() {
 1652   
 1653           Hashtable<String,String> env = new Hashtable<String,String>();
 1654   
 1655           // Configure our directory context environment.
 1656           if (containerLog.isDebugEnabled() && connectionAttempt == 0)
 1657               containerLog.debug("Connecting to URL " + connectionURL);
 1658           else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
 1659               containerLog.debug("Connecting to URL " + alternateURL);
 1660           env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
 1661           if (connectionName != null)
 1662               env.put(Context.SECURITY_PRINCIPAL, connectionName);
 1663           if (connectionPassword != null)
 1664               env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
 1665           if (connectionURL != null && connectionAttempt == 0)
 1666               env.put(Context.PROVIDER_URL, connectionURL);
 1667           else if (alternateURL != null && connectionAttempt > 0)
 1668               env.put(Context.PROVIDER_URL, alternateURL);
 1669           if (authentication != null)
 1670               env.put(Context.SECURITY_AUTHENTICATION, authentication);
 1671           if (protocol != null)
 1672               env.put(Context.SECURITY_PROTOCOL, protocol);
 1673           if (referrals != null)
 1674               env.put(Context.REFERRAL, referrals);
 1675           if (derefAliases != null)
 1676               env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
 1677   
 1678           return env;
 1679   
 1680       }
 1681   
 1682   
 1683       /**
 1684        * Release our use of this connection so that it can be recycled.
 1685        *
 1686        * @param context The directory context to release
 1687        */
 1688       protected void release(DirContext context) {
 1689   
 1690           ; // NO-OP since we are not pooling anything
 1691   
 1692       }
 1693   
 1694   
 1695       // ------------------------------------------------------ Lifecycle Methods
 1696   
 1697   
 1698       /**
 1699        * Prepare for active use of the public methods of this Component.
 1700        *
 1701        * @exception LifecycleException if this component detects a fatal error
 1702        *  that prevents it from being started
 1703        */
 1704       public void start() throws LifecycleException {
 1705   
 1706           // Perform normal superclass initialization
 1707           super.start();
 1708   
 1709           // Validate that we can open our connection
 1710           try {
 1711               open();
 1712           } catch (NamingException e) {
 1713               throw new LifecycleException(sm.getString("jndiRealm.open"), e);
 1714           }
 1715   
 1716       }
 1717   
 1718   
 1719       /**
 1720        * Gracefully shut down active use of the public methods of this Component.
 1721        *
 1722        * @exception LifecycleException if this component detects a fatal error
 1723        *  that needs to be reported
 1724        */
 1725       public void stop() throws LifecycleException {
 1726   
 1727           // Perform normal superclass finalization
 1728           super.stop();
 1729   
 1730           // Close any open directory server connection
 1731           close(this.context);
 1732   
 1733       }
 1734   
 1735       /**
 1736        * Given a string containing LDAP patterns for user locations (separated by
 1737        * parentheses in a pseudo-LDAP search string format -
 1738        * "(location1)(location2)", returns an array of those paths.  Real LDAP
 1739        * search strings are supported as well (though only the "|" "OR" type).
 1740        *
 1741        * @param userPatternString - a string LDAP search paths surrounded by
 1742        * parentheses
 1743        */
 1744       protected String[] parseUserPatternString(String userPatternString) {
 1745   
 1746           if (userPatternString != null) {
 1747               ArrayList<String> pathList = new ArrayList<String>();
 1748               int startParenLoc = userPatternString.indexOf('(');
 1749               if (startParenLoc == -1) {
 1750                   // no parens here; return whole thing
 1751                   return new String[] {userPatternString};
 1752               }
 1753               int startingPoint = 0;
 1754               while (startParenLoc > -1) {
 1755                   int endParenLoc = 0;
 1756                   // weed out escaped open parens and parens enclosing the
 1757                   // whole statement (in the case of valid LDAP search
 1758                   // strings: (|(something)(somethingelse))
 1759                   while ( (userPatternString.charAt(startParenLoc + 1) == '|') ||
 1760                           (startParenLoc != 0 && userPatternString.charAt(startParenLoc - 1) == '\\') ) {
 1761                       startParenLoc = userPatternString.indexOf("(", startParenLoc+1);
 1762                   }
 1763                   endParenLoc = userPatternString.indexOf(")", startParenLoc+1);
 1764                   // weed out escaped end-parens
 1765                   while (userPatternString.charAt(endParenLoc - 1) == '\\') {
 1766                       endParenLoc = userPatternString.indexOf(")", endParenLoc+1);
 1767                   }
 1768                   String nextPathPart = userPatternString.substring
 1769                       (startParenLoc+1, endParenLoc);
 1770                   pathList.add(nextPathPart);
 1771                   startingPoint = endParenLoc+1;
 1772                   startParenLoc = userPatternString.indexOf('(', startingPoint);
 1773               }
 1774               return (String[])pathList.toArray(new String[] {});
 1775           }
 1776           return null;
 1777   
 1778       }
 1779   
 1780   
 1781       /**
 1782        * Given an LDAP search string, returns the string with certain characters
 1783        * escaped according to RFC 2254 guidelines.
 1784        * The character mapping is as follows:
 1785        *     char ->  Replacement
 1786        *    ---------------------------
 1787        *     *  -> \2a
 1788        *     (  -> \28
 1789        *     )  -> \29
 1790        *     \  -> \5c
 1791        *     \0 -> \00
 1792        * @param inString string to escape according to RFC 2254 guidelines
 1793        * @return String the escaped/encoded result
 1794        */
 1795       protected String doRFC2254Encoding(String inString) {
 1796           StringBuffer buf = new StringBuffer(inString.length());
 1797           for (int i = 0; i < inString.length(); i++) {
 1798               char c = inString.charAt(i);
 1799               switch (c) {
 1800                   case '\\':
 1801                       buf.append("\\5c");
 1802                       break;
 1803                   case '*':
 1804                       buf.append("\\2a");
 1805                       break;
 1806                   case '(':
 1807                       buf.append("\\28");
 1808                       break;
 1809                   case ')':
 1810                       buf.append("\\29");
 1811                       break;
 1812                   case '\0':
 1813                       buf.append("\\00");
 1814                       break;
 1815                   default:
 1816                       buf.append(c);
 1817                       break;
 1818               }
 1819           }
 1820           return buf.toString();
 1821       }
 1822   
 1823   
 1824   }
 1825   
 1826   // ------------------------------------------------------ Private Classes
 1827   
 1828   /**
 1829    * A private class representing a User
 1830    */
 1831   class User {
 1832       String username = null;
 1833       String dn = null;
 1834       String password = null;
 1835       ArrayList<String> roles = null;
 1836   
 1837   
 1838       User(String username, String dn, String password,
 1839               ArrayList<String> roles) {
 1840           this.username = username;
 1841           this.dn = dn;
 1842           this.password = password;
 1843           this.roles = roles;
 1844       }
 1845   
 1846   }

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