KickJava   Java API By Example, From Geeks To Geeks.

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


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 JavaDoc;
21 import java.security.Principal JavaDoc;
22 import java.text.MessageFormat JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Arrays JavaDoc;
25 import java.util.Hashtable JavaDoc;
26 import java.util.List JavaDoc;
27
28 import javax.naming.Context JavaDoc;
29 import javax.naming.CommunicationException JavaDoc;
30 import javax.naming.CompositeName JavaDoc;
31 import javax.naming.InvalidNameException JavaDoc;
32 import javax.naming.NameNotFoundException JavaDoc;
33 import javax.naming.NamingEnumeration JavaDoc;
34 import javax.naming.NamingException JavaDoc;
35 import javax.naming.NameParser JavaDoc;
36 import javax.naming.Name JavaDoc;
37 import javax.naming.AuthenticationException JavaDoc;
38 import javax.naming.directory.Attribute JavaDoc;
39 import javax.naming.directory.Attributes JavaDoc;
40 import javax.naming.directory.DirContext JavaDoc;
41 import javax.naming.directory.InitialDirContext JavaDoc;
42 import javax.naming.directory.SearchControls JavaDoc;
43 import javax.naming.directory.SearchResult JavaDoc;
44 import org.apache.catalina.LifecycleException;
45 import org.apache.catalina.util.Base64;
46 import org.apache.tomcat.util.buf.ByteChunk;
47 import org.apache.tomcat.util.buf.CharChunk;
48
49 /**
50  * <p>Implementation of <strong>Realm</strong> that works with a directory
51  * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
52  * The following constraints are imposed on the data structure in the
53  * underlying directory server:</p>
54  * <ul>
55  *
56  * <li>Each user that can be authenticated is represented by an individual
57  * element in the top level <code>DirContext</code> that is accessed
58  * via the <code>connectionURL</code> property.</li>
59  *
60  * <li>If a socket connection can not be made to the <code>connectURL</code>
61  * an attempt will be made to use the <code>alternateURL</code> if it
62  * exists.</li>
63  *
64  * <li>Each user element has a distinguished name that can be formed by
65  * substituting the presented username into a pattern configured by the
66  * <code>userPattern</code> property.</li>
67  *
68  * <li>Alternatively, if the <code>userPattern</code> property is not
69  * specified, a unique element can be located by searching the directory
70  * context. In this case:
71  * <ul>
72  * <li>The <code>userSearch</code> pattern specifies the search filter
73  * after substitution of the username.</li>
74  * <li>The <code>userBase</code> property can be set to the element that
75  * is the base of the subtree containing users. If not specified,
76  * the search base is the top-level context.</li>
77  * <li>The <code>userSubtree</code> property can be set to
78  * <code>true</code> if you wish to search the entire subtree of the
79  * directory context. The default value of <code>false</code>
80  * requests a search of only the current level.</li>
81  * </ul>
82  * </li>
83  *
84  * <li>The user may be authenticated by binding to the directory with the
85  * username and password presented. This method is used when the
86  * <code>userPassword</code> property is not specified.</li>
87  *
88  * <li>The user may be authenticated by retrieving the value of an attribute
89  * from the directory and comparing it explicitly with the value presented
90  * by the user. This method is used when the <code>userPassword</code>
91  * property is specified, in which case:
92  * <ul>
93  * <li>The element for this user must contain an attribute named by the
94  * <code>userPassword</code> property.
95  * <li>The value of the user password attribute is either a cleartext
96  * String, or the result of passing a cleartext String through the
97  * <code>RealmBase.digest()</code> method (using the standard digest
98  * support included in <code>RealmBase</code>).
99  * <li>The user is considered to be authenticated if the presented
100  * credentials (after being passed through
101  * <code>RealmBase.digest()</code>) are equal to the retrieved value
102  * for the user password attribute.</li>
103  * </ul></li>
104  *
105  * <li>Each group of users that has been assigned a particular role may be
106  * represented by an individual element in the top level
107  * <code>DirContext</code> that is accessed via the
108  * <code>connectionURL</code> property. This element has the following
109  * characteristics:
110  * <ul>
111  * <li>The set of all possible groups of interest can be selected by a
112  * search pattern configured by the <code>roleSearch</code>
113  * property.</li>
114  * <li>The <code>roleSearch</code> pattern optionally includes pattern
115  * replacements "{0}" for the distinguished name, and/or "{1}" for
116  * the username, of the authenticated user for which roles will be
117  * retrieved.</li>
118  * <li>The <code>roleBase</code> property can be set to the element that
119  * is the base of the search for matching roles. If not specified,
120  * the entire context will be searched.</li>
121  * <li>The <code>roleSubtree</code> property can be set to
122  * <code>true</code> if you wish to search the entire subtree of the
123  * directory context. The default value of <code>false</code>
124  * requests a search of only the current level.</li>
125  * <li>The element includes an attribute (whose name is configured by
126  * the <code>roleName</code> property) containing the name of the
127  * role represented by this element.</li>
128  * </ul></li>
129  *
130  * <li>In addition, roles may be represented by the values of an attribute
131  * in the user's element whose name is configured by the
132  * <code>userRoleName</code> property.</li>
133  *
134  * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
135  * the web application deployment descriptor allows applications to refer
136  * to roles programmatically by names other than those used in the
137  * directory server itself.</li>
138  * </ul>
139  *
140  * <p><strong>TODO</strong> - Support connection pooling (including message
141  * format objects) so that <code>authenticate()</code> does not have to be
142  * synchronized.</p>
143  *
144  * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
145  * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
146  * successfully authenticated a non-existing user. The
147  * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
148  * With luck, Netscape has updated their provider code and this is not an
149  * issue. </p>
150  *
151  * @author John Holman
152  * @author Craig R. McClanahan
153  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
154  */

155
156 public class JNDIRealm extends RealmBase {
157
158
159     // ----------------------------------------------------- Instance Variables
160

161     /**
162      * The type of authentication to use
163      */

164     protected String JavaDoc authentication = null;
165
166     /**
167      * The connection username for the server we will contact.
168      */

169     protected String JavaDoc connectionName = null;
170
171
172     /**
173      * The connection password for the server we will contact.
174      */

175     protected String JavaDoc connectionPassword = null;
176
177
178     /**
179      * The connection URL for the server we will contact.
180      */

181     protected String JavaDoc connectionURL = null;
182
183
184     /**
185      * The directory context linking us to our directory server.
186      */

187     protected DirContext JavaDoc context = null;
188
189
190     /**
191      * The JNDI context factory used to acquire our InitialContext. By
192      * default, assumes use of an LDAP server using the standard JNDI LDAP
193      * provider.
194      */

195     protected String JavaDoc contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
196
197     
198     /**
199      * How aliases should be dereferenced during search operations.
200      */

201     protected String JavaDoc derefAliases = null;
202
203     /**
204      * Constant that holds the name of the environment property for specifying
205      * the manner in which aliases should be dereferenced.
206      */

207     public final static String JavaDoc DEREF_ALIASES = "java.naming.ldap.derefAliases";
208
209     /**
210      * Descriptive information about this Realm implementation.
211      */

212     protected static final String JavaDoc info =
213         "org.apache.catalina.realm.JNDIRealm/1.0";
214
215
216     /**
217      * Descriptive information about this Realm implementation.
218      */

219     protected static final String JavaDoc name = "JNDIRealm";
220
221
222     /**
223      * The protocol that will be used in the communication with the
224      * directory server.
225      */

226     protected String JavaDoc protocol = null;
227
228
229     /**
230      * How should we handle referrals? Microsoft Active Directory can't handle
231      * the default case, so an application authenticating against AD must
232      * set referrals to "follow".
233      */

234     protected String JavaDoc referrals = null;
235
236
237     /**
238      * The base element for user searches.
239      */

240     protected String JavaDoc userBase = "";
241
242
243     /**
244      * The message format used to search for a user, with "{0}" marking
245      * the spot where the username goes.
246      */

247     protected String JavaDoc userSearch = null;
248
249
250     /**
251      * The MessageFormat object associated with the current
252      * <code>userSearch</code>.
253      */

254     protected MessageFormat JavaDoc userSearchFormat = null;
255
256
257     /**
258      * Should we search the entire subtree for matching users?
259      */

260     protected boolean userSubtree = false;
261
262
263     /**
264      * The attribute name used to retrieve the user password.
265      */

266     protected String JavaDoc userPassword = null;
267
268
269     /**
270      * A string of LDAP user patterns or paths, ":"-separated
271      * These will be used to form the distinguished name of a
272      * user, with "{0}" marking the spot where the specified username
273      * goes.
274      * This is similar to userPattern, but allows for multiple searches
275      * for a user.
276      */

277     protected String JavaDoc[] userPatternArray = null;
278
279
280     /**
281      * The message format used to form the distinguished name of a
282      * user, with "{0}" marking the spot where the specified username
283      * goes.
284      */

285     protected String JavaDoc userPattern = null;
286
287
288     /**
289      * An array of MessageFormat objects associated with the current
290      * <code>userPatternArray</code>.
291      */

292     protected MessageFormat JavaDoc[] userPatternFormatArray = null;
293
294
295     /**
296      * The base element for role searches.
297      */

298     protected String JavaDoc roleBase = "";
299
300
301     /**
302      * The MessageFormat object associated with the current
303      * <code>roleSearch</code>.
304      */

305     protected MessageFormat JavaDoc roleFormat = null;
306
307
308     /**
309      * The name of an attribute in the user's entry containing
310      * roles for that user
311      */

312     protected String JavaDoc userRoleName = null;
313
314
315     /**
316      * The name of the attribute containing roles held elsewhere
317      */

318     protected String JavaDoc roleName = null;
319
320
321     /**
322      * The message format used to select roles for a user, with "{0}" marking
323      * the spot where the distinguished name of the user goes.
324      */

325     protected String JavaDoc roleSearch = null;
326
327
328     /**
329      * Should we search the entire subtree for matching memberships?
330      */

331     protected boolean roleSubtree = false;
332
333     /**
334      * An alternate URL, to which, we should connect if connectionURL fails.
335      */

336     protected String JavaDoc alternateURL;
337
338     /**
339      * The number of connection attempts. If greater than zero we use the
340      * alternate url.
341      */

342     protected int connectionAttempt = 0;
343
344     /**
345      * The current user pattern to be used for lookup and binding of a user.
346      */

347     protected int curUserPattern = 0;
348
349     // ------------------------------------------------------------- Properties
350

351     /**
352      * Return the type of authentication to use.
353      */

354     public String JavaDoc getAuthentication() {
355
356         return authentication;
357
358     }
359
360     /**
361      * Set the type of authentication to use.
362      *
363      * @param authentication The authentication
364      */

365     public void setAuthentication(String JavaDoc authentication) {
366
367         this.authentication = authentication;
368
369     }
370
371     /**
372      * Return the connection username for this Realm.
373      */

374     public String JavaDoc getConnectionName() {
375
376         return (this.connectionName);
377
378     }
379
380
381     /**
382      * Set the connection username for this Realm.
383      *
384      * @param connectionName The new connection username
385      */

386     public void setConnectionName(String JavaDoc connectionName) {
387
388         this.connectionName = connectionName;
389
390     }
391
392
393     /**
394      * Return the connection password for this Realm.
395      */

396     public String JavaDoc getConnectionPassword() {
397
398         return (this.connectionPassword);
399
400     }
401
402
403     /**
404      * Set the connection password for this Realm.
405      *
406      * @param connectionPassword The new connection password
407      */

408     public void setConnectionPassword(String JavaDoc connectionPassword) {
409
410         this.connectionPassword = connectionPassword;
411
412     }
413
414
415     /**
416      * Return the connection URL for this Realm.
417      */

418     public String JavaDoc getConnectionURL() {
419
420         return (this.connectionURL);
421
422     }
423
424
425     /**
426      * Set the connection URL for this Realm.
427      *
428      * @param connectionURL The new connection URL
429      */

430     public void setConnectionURL(String JavaDoc connectionURL) {
431
432         this.connectionURL = connectionURL;
433
434     }
435
436
437     /**
438      * Return the JNDI context factory for this Realm.
439      */

440     public String JavaDoc getContextFactory() {
441
442         return (this.contextFactory);
443
444     }
445
446
447     /**
448      * Set the JNDI context factory for this Realm.
449      *
450      * @param contextFactory The new context factory
451      */

452     public void setContextFactory(String JavaDoc contextFactory) {
453
454         this.contextFactory = contextFactory;
455
456     }
457
458     /**
459      * Return the derefAliases setting to be used.
460      */

461     public java.lang.String JavaDoc getDerefAliases() {
462         return derefAliases;
463     }
464     
465     /**
466      * Set the value for derefAliases to be used when searching the directory.
467      *
468      * @param derefAliases New value of property derefAliases.
469      */

470     public void setDerefAliases(java.lang.String JavaDoc derefAliases) {
471       this.derefAliases = derefAliases;
472     }
473
474     /**
475      * Return the protocol to be used.
476      */

477     public String JavaDoc getProtocol() {
478
479         return protocol;
480
481     }
482
483     /**
484      * Set the protocol for this Realm.
485      *
486      * @param protocol The new protocol.
487      */

488     public void setProtocol(String JavaDoc protocol) {
489
490         this.protocol = protocol;
491
492     }
493
494
495     /**
496      * Returns the current settings for handling JNDI referrals.
497      */

498     public String JavaDoc getReferrals () {
499         return referrals;
500     }
501
502
503     /**
504      * How do we handle JNDI referrals? ignore, follow, or throw
505      * (see javax.naming.Context.REFERRAL for more information).
506      */

507     public void setReferrals (String JavaDoc referrals) {
508         this.referrals = referrals;
509     }
510
511
512     /**
513      * Return the base element for user searches.
514      */

515     public String JavaDoc getUserBase() {
516
517         return (this.userBase);
518
519     }
520
521
522     /**
523      * Set the base element for user searches.
524      *
525      * @param userBase The new base element
526      */

527     public void setUserBase(String JavaDoc userBase) {
528
529         this.userBase = userBase;
530
531     }
532
533
534     /**
535      * Return the message format pattern for selecting users in this Realm.
536      */

537     public String JavaDoc getUserSearch() {
538
539         return (this.userSearch);
540
541     }
542
543
544     /**
545      * Set the message format pattern for selecting users in this Realm.
546      *
547      * @param userSearch The new user search pattern
548      */

549     public void setUserSearch(String JavaDoc userSearch) {
550
551         this.userSearch = userSearch;
552         if (userSearch == null)
553             userSearchFormat = null;
554         else
555             userSearchFormat = new MessageFormat JavaDoc(userSearch);
556
557     }
558
559
560     /**
561      * Return the "search subtree for users" flag.
562      */

563     public boolean getUserSubtree() {
564
565         return (this.userSubtree);
566
567     }
568
569
570     /**
571      * Set the "search subtree for users" flag.
572      *
573      * @param userSubtree The new search flag
574      */

575     public void setUserSubtree(boolean userSubtree) {
576
577         this.userSubtree = userSubtree;
578
579     }
580
581
582     /**
583      * Return the user role name attribute name for this Realm.
584      */

585     public String JavaDoc getUserRoleName() {
586
587         return userRoleName;
588     }
589
590
591     /**
592      * Set the user role name attribute name for this Realm.
593      *
594      * @param userRoleName The new userRole name attribute name
595      */

596     public void setUserRoleName(String JavaDoc userRoleName) {
597
598         this.userRoleName = userRoleName;
599
600     }
601
602
603     /**
604      * Return the base element for role searches.
605      */

606     public String JavaDoc getRoleBase() {
607
608         return (this.roleBase);
609
610     }
611
612
613     /**
614      * Set the base element for role searches.
615      *
616      * @param roleBase The new base element
617      */

618     public void setRoleBase(String JavaDoc roleBase) {
619
620         this.roleBase = roleBase;
621
622     }
623
624
625     /**
626      * Return the role name attribute name for this Realm.
627      */

628     public String JavaDoc getRoleName() {
629
630         return (this.roleName);
631
632     }
633
634
635     /**
636      * Set the role name attribute name for this Realm.
637      *
638      * @param roleName The new role name attribute name
639      */

640     public void setRoleName(String JavaDoc roleName) {
641
642         this.roleName = roleName;
643
644     }
645
646
647     /**
648      * Return the message format pattern for selecting roles in this Realm.
649      */

650     public String JavaDoc getRoleSearch() {
651
652         return (this.roleSearch);
653
654     }
655
656
657     /**
658      * Set the message format pattern for selecting roles in this Realm.
659      *
660      * @param roleSearch The new role search pattern
661      */

662     public void setRoleSearch(String JavaDoc roleSearch) {
663
664         this.roleSearch = roleSearch;
665         if (roleSearch == null)
666             roleFormat = null;
667         else
668             roleFormat = new MessageFormat JavaDoc(roleSearch);
669
670     }
671
672
673     /**
674      * Return the "search subtree for roles" flag.
675      */

676     public boolean getRoleSubtree() {
677
678         return (this.roleSubtree);
679
680     }
681
682
683     /**
684      * Set the "search subtree for roles" flag.
685      *
686      * @param roleSubtree The new search flag
687      */

688     public void setRoleSubtree(boolean roleSubtree) {
689
690         this.roleSubtree = roleSubtree;
691
692     }
693
694
695     /**
696      * Return the password attribute used to retrieve the user password.
697      */

698     public String JavaDoc getUserPassword() {
699
700         return (this.userPassword);
701
702     }
703
704
705     /**
706      * Set the password attribute used to retrieve the user password.
707      *
708      * @param userPassword The new password attribute
709      */

710     public void setUserPassword(String JavaDoc userPassword) {
711
712         this.userPassword = userPassword;
713
714     }
715
716
717     /**
718      * Return the message format pattern for selecting users in this Realm.
719      */

720     public String JavaDoc getUserPattern() {
721
722         return (this.userPattern);
723
724     }
725
726
727     /**
728      * Set the message format pattern for selecting users in this Realm.
729      * This may be one simple pattern, or multiple patterns to be tried,
730      * separated by parentheses. (for example, either "cn={0}", or
731      * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
732      * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
733      * also valid. Complex search strings with &, etc are NOT supported.
734      *
735      * @param userPattern The new user pattern
736      */

737     public void setUserPattern(String JavaDoc userPattern) {
738
739         this.userPattern = userPattern;
740         if (userPattern == null)
741             userPatternArray = null;
742         else {
743             userPatternArray = parseUserPatternString(userPattern);
744             int len = this.userPatternArray.length;
745             userPatternFormatArray = new MessageFormat JavaDoc[len];
746             for (int i=0; i < len; i++) {
747                 userPatternFormatArray[i] =
748                     new MessageFormat JavaDoc(userPatternArray[i]);
749             }
750         }
751     }
752
753
754     /**
755      * Getter for property alternateURL.
756      *
757      * @return Value of property alternateURL.
758      */

759     public String JavaDoc getAlternateURL() {
760
761         return this.alternateURL;
762
763     }
764
765
766     /**
767      * Setter for property alternateURL.
768      *
769      * @param alternateURL New value of property alternateURL.
770      */

771     public void setAlternateURL(String JavaDoc alternateURL) {
772
773         this.alternateURL = alternateURL;
774
775     }
776
777
778     // ---------------------------------------------------------- Realm Methods
779

780
781     /**
782      * Return the Principal associated with the specified username and
783      * credentials, if there is one; otherwise return <code>null</code>.
784      *
785      * If there are any errors with the JDBC connection, executing
786      * the query or anything we return null (don't authenticate). This
787      * event is also logged, and the connection will be closed so that
788      * a subsequent request will automatically re-open it.
789      *
790      * @param username Username of the Principal to look up
791      * @param credentials Password or other credentials to use in
792      * authenticating this username
793      */

794     public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc credentials) {
795
796         DirContext JavaDoc context = null;
797         Principal JavaDoc principal = null;
798
799         try {
800
801             // Ensure that we have a directory context available
802
context = open();
803
804             // Occassionally the directory context will timeout. Try one more
805
// time before giving up.
806
try {
807
808                 // Authenticate the specified username if possible
809
principal = authenticate(context, username, credentials);
810
811             } catch (CommunicationException JavaDoc e) {
812
813                 // log the exception so we know it's there.
814
containerLog.warn(sm.getString("jndiRealm.exception"), e);
815
816                 // close the connection so we know it will be reopened.
817
if (context != null)
818                     close(context);
819
820                 // open a new directory context.
821
context = open();
822
823                 // Try the authentication again.
824
principal = authenticate(context, username, credentials);
825
826             }
827
828
829             // Release this context
830
release(context);
831
832             // Return the authenticated Principal (if any)
833
return (principal);
834
835         } catch (NamingException JavaDoc e) {
836
837             // Log the problem for posterity
838
containerLog.error(sm.getString("jndiRealm.exception"), e);
839
840             // Close the connection so that it gets reopened next time
841
if (context != null)
842                 close(context);
843
844             // Return "not authenticated" for this request
845
return (null);
846
847         }
848
849     }
850
851
852     // -------------------------------------------------------- Package Methods
853

854
855     // ------------------------------------------------------ Protected Methods
856

857
858     /**
859      * Return the Principal associated with the specified username and
860      * credentials, if there is one; otherwise return <code>null</code>.
861      *
862      * @param context The directory context
863      * @param username Username of the Principal to look up
864      * @param credentials Password or other credentials to use in
865      * authenticating this username
866      *
867      * @exception NamingException if a directory server error occurs
868      */

869     public synchronized Principal JavaDoc authenticate(DirContext JavaDoc context,
870                                                String JavaDoc username,
871                                                String JavaDoc credentials)
872         throws NamingException JavaDoc {
873
874         if (username == null || username.equals("")
875             || credentials == null || credentials.equals(""))
876             return (null);
877
878         if (userPatternArray != null) {
879             for (curUserPattern = 0;
880                  curUserPattern < userPatternFormatArray.length;
881                  curUserPattern++) {
882                 // Retrieve user information
883
User user = getUser(context, username);
884                 if (user != null) {
885                     try {
886                         // Check the user's credentials
887
if (checkCredentials(context, user, credentials)) {
888                             // Search for additional roles
889
List JavaDoc roles = getRoles(context, user);
890                             return (new GenericPrincipal(this,
891                                                          username,
892                                                          credentials,
893                                                          roles));
894                         }
895                     } catch (InvalidNameException JavaDoc ine) {
896                         // Log the problem for posterity
897
containerLog.warn(sm.getString("jndiRealm.exception"), ine);
898                         // ignore; this is probably due to a name not fitting
899
// the search path format exactly, as in a fully-
900
// qualified name being munged into a search path
901
// that already contains cn= or vice-versa
902
}
903                 }
904             }
905             return null;
906         } else {
907             // Retrieve user information
908
User user = getUser(context, username);
909             if (user == null)
910                 return (null);
911
912             // Check the user's credentials
913
if (!checkCredentials(context, user, credentials))
914                 return (null);
915
916             // Search for additional roles
917
List JavaDoc roles = getRoles(context, user);
918
919             // Create and return a suitable Principal for this user
920
return (new GenericPrincipal(this, username, credentials, roles));
921         }
922     }
923
924
925     /**
926      * Return a User object containing information about the user
927      * with the specified username, if found in the directory;
928      * otherwise return <code>null</code>.
929      *
930      * If the <code>userPassword</code> configuration attribute is
931      * specified, the value of that attribute is retrieved from the
932      * user's directory entry. If the <code>userRoleName</code>
933      * configuration attribute is specified, all values of that
934      * attribute are retrieved from the directory entry.
935      *
936      * @param context The directory context
937      * @param username Username to be looked up
938      *
939      * @exception NamingException if a directory server error occurs
940      */

941     protected User getUser(DirContext JavaDoc context, String JavaDoc username)
942         throws NamingException JavaDoc {
943
944         User user = null;
945
946         // Get attributes to retrieve from user entry
947
ArrayList JavaDoc list = new ArrayList JavaDoc();
948         if (userPassword != null)
949             list.add(userPassword);
950         if (userRoleName != null)
951             list.add(userRoleName);
952         String JavaDoc[] attrIds = new String JavaDoc[list.size()];
953         list.toArray(attrIds);
954
955         // Use pattern or search for user entry
956
if (userPatternFormatArray != null) {
957             user = getUserByPattern(context, username, attrIds);
958         } else {
959             user = getUserBySearch(context, username, attrIds);
960         }
961
962         return user;
963     }
964
965
966     /**
967      * Use the <code>UserPattern</code> configuration attribute to
968      * locate the directory entry for the user with the specified
969      * username and return a User object; otherwise return
970      * <code>null</code>.
971      *
972      * @param context The directory context
973      * @param username The username
974      * @param attrIds String[]containing names of attributes to
975      * retrieve.
976      *
977      * @exception NamingException if a directory server error occurs
978      */

979     protected User getUserByPattern(DirContext JavaDoc context,
980                                               String JavaDoc username,
981                                               String JavaDoc[] attrIds)
982         throws NamingException JavaDoc {
983
984         if (username == null || userPatternFormatArray[curUserPattern] == null)
985             return (null);
986
987         // Form the dn from the user pattern
988
String JavaDoc dn = userPatternFormatArray[curUserPattern].format(new String JavaDoc[] { username });
989
990         // Get required attributes from user entry
991
Attributes JavaDoc attrs = null;
992         try {
993             attrs = context.getAttributes(dn, attrIds);
994         } catch (NameNotFoundException JavaDoc e) {
995             return (null);
996         }
997         if (attrs == null)
998             return (null);
999
1000        // Retrieve value of userPassword
1001
String JavaDoc password = null;
1002        if (userPassword != null)
1003            password = getAttributeValue(userPassword, attrs);
1004
1005        // Retrieve values of userRoleName attribute
1006
ArrayList JavaDoc roles = null;
1007        if (userRoleName != null)
1008            roles = addAttributeValues(userRoleName, attrs, roles);
1009
1010        return new User(username, dn, password, roles);
1011    }
1012
1013
1014    /**
1015     * Search the directory to return a User object containing
1016     * information about the user with the specified username, if
1017     * found in the directory; otherwise return <code>null</code>.
1018     *
1019     * @param context The directory context
1020     * @param username The username
1021     * @param attrIds String[]containing names of attributes to retrieve.
1022     *
1023     * @exception NamingException if a directory server error occurs
1024     */

1025    protected User getUserBySearch(DirContext JavaDoc context,
1026                                           String JavaDoc username,
1027                                           String JavaDoc[] attrIds)
1028        throws NamingException JavaDoc {
1029
1030        if (username == null || userSearchFormat == null)
1031            return (null);
1032
1033        // Form the search filter
1034
String JavaDoc filter = userSearchFormat.format(new String JavaDoc[] { username });
1035
1036        // Set up the search controls
1037
SearchControls JavaDoc constraints = new SearchControls JavaDoc();
1038
1039        if (userSubtree) {
1040            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
1041        }
1042        else {
1043            constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1044        }
1045
1046        // Specify the attributes to be retrieved
1047
if (attrIds == null)
1048            attrIds = new String JavaDoc[0];
1049        constraints.setReturningAttributes(attrIds);
1050
1051        NamingEnumeration JavaDoc results =
1052            context.search(userBase, filter, constraints);
1053
1054
1055        // Fail if no entries found
1056
if (results == null || !results.hasMore()) {
1057            return (null);
1058        }
1059
1060        // Get result for the first entry found
1061
SearchResult JavaDoc result = (SearchResult JavaDoc)results.next();
1062
1063        // Check no further entries were found
1064
if (results.hasMore()) {
1065            if(containerLog.isInfoEnabled())
1066                containerLog.info("username " + username + " has multiple entries");
1067            return (null);
1068        }
1069
1070        // Get the entry's distinguished name
1071
NameParser JavaDoc parser = context.getNameParser("");
1072        Name JavaDoc contextName = parser.parse(context.getNameInNamespace());
1073        Name JavaDoc baseName = parser.parse(userBase);
1074
1075        // Bugzilla 32269
1076
Name JavaDoc entryName = parser.parse(new CompositeName JavaDoc(result.getName()).get(0));
1077
1078        Name JavaDoc name = contextName.addAll(baseName);
1079        name = name.addAll(entryName);
1080        String JavaDoc dn = name.toString();
1081
1082        if (containerLog.isTraceEnabled())
1083            containerLog.trace(" entry found for " + username + " with dn " + dn);
1084
1085        // Get the entry's attributes
1086
Attributes JavaDoc attrs = result.getAttributes();
1087        if (attrs == null)
1088            return null;
1089
1090        // Retrieve value of userPassword
1091
String JavaDoc password = null;
1092        if (userPassword != null)
1093            password = getAttributeValue(userPassword, attrs);
1094
1095        // Retrieve values of userRoleName attribute
1096
ArrayList JavaDoc roles = null;
1097        if (userRoleName != null)
1098            roles = addAttributeValues(userRoleName, attrs, roles);
1099
1100        return new User(username, dn, password, roles);
1101    }
1102
1103
1104    /**
1105     * Check whether the given User can be authenticated with the
1106     * given credentials. If the <code>userPassword</code>
1107     * configuration attribute is specified, the credentials
1108     * previously retrieved from the directory are compared explicitly
1109     * with those presented by the user. Otherwise the presented
1110     * credentials are checked by binding to the directory as the
1111     * user.
1112     *
1113     * @param context The directory context
1114     * @param user The User to be authenticated
1115     * @param credentials The credentials presented by the user
1116     *
1117     * @exception NamingException if a directory server error occurs
1118     */

1119    protected boolean checkCredentials(DirContext JavaDoc context,
1120                                     User user,
1121                                     String JavaDoc credentials)
1122         throws NamingException JavaDoc {
1123
1124         boolean validated = false;
1125
1126         if (userPassword == null) {
1127             validated = bindAsUser(context, user, credentials);
1128         } else {
1129             validated = compareCredentials(context, user, credentials);
1130         }
1131
1132         if (containerLog.isTraceEnabled()) {
1133             if (validated) {
1134                 containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
1135                                  user.username));
1136             } else {
1137                 containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
1138                                  user.username));
1139             }
1140         }
1141         return (validated);
1142     }
1143
1144
1145
1146    /**
1147     * Check whether the credentials presented by the user match those
1148     * retrieved from the directory.
1149     *
1150     * @param context The directory context
1151     * @param info The User to be authenticated
1152     * @param credentials Authentication credentials
1153     *
1154     * @exception NamingException if a directory server error occurs
1155     */

1156    protected boolean compareCredentials(DirContext JavaDoc context,
1157                                         User info,
1158                                         String JavaDoc credentials)
1159        throws NamingException JavaDoc {
1160
1161        if (info == null || credentials == null)
1162            return (false);
1163
1164        String JavaDoc password = info.password;
1165        if (password == null)
1166            return (false);
1167
1168        // Validate the credentials specified by the user
1169
if (containerLog.isTraceEnabled())
1170            containerLog.trace(" validating credentials");
1171
1172        boolean validated = false;
1173        if (hasMessageDigest()) {
1174            // iPlanet support if the values starts with {SHA1}
1175
// The string is in a format compatible with Base64.encode not
1176
// the Hex encoding of the parent class.
1177
if (password.startsWith("{SHA}")) {
1178                /* sync since super.digest() does this same thing */
1179                synchronized (this) {
1180                    password = password.substring(5);
1181                    md.reset();
1182                    md.update(credentials.getBytes());
1183                    String JavaDoc digestedPassword =
1184                        new String JavaDoc(Base64.encode(md.digest()));
1185                    validated = password.equals(digestedPassword);
1186                }
1187            } else if (password.startsWith("{SSHA}")) {
1188                // Bugzilla 32938
1189
/* sync since super.digest() does this same thing */
1190                synchronized (this) {
1191                    password = password.substring(6);
1192
1193                    md.reset();
1194                    md.update(credentials.getBytes());
1195
1196                    // Decode stored password.
1197
ByteChunk pwbc = new ByteChunk(password.length());
1198                    try {
1199                        pwbc.append(password.getBytes(), 0, password.length());
1200                    } catch (IOException JavaDoc e) {
1201                        // Should never happen
1202
containerLog.error("Could not append password bytes to chunk: ", e);
1203                    }
1204
1205                    CharChunk decoded = new CharChunk();
1206                    Base64.decode(pwbc, decoded);
1207                    char[] pwarray = decoded.getBuffer();
1208
1209                    // Split decoded password into hash and salt.
1210
final int saltpos = 20;
1211                    byte[] hash = new byte[saltpos];
1212                    for (int i=0; i< hash.length; i++) {
1213                        hash[i] = (byte) pwarray[i];
1214                    }
1215
1216                    byte[] salt = new byte[pwarray.length - saltpos];
1217                    for (int i=0; i< salt.length; i++)
1218                        salt[i] = (byte)pwarray[i+saltpos];
1219
1220                    md.update(salt);
1221                    byte[] dp = md.digest();
1222
1223                    validated = Arrays.equals(dp, hash);
1224                } // End synchronized(this) block
1225
} else {
1226                // Hex hashes should be compared case-insensitive
1227
validated = (digest(credentials).equalsIgnoreCase(password));
1228            }
1229        } else
1230            validated = (digest(credentials).equals(password));
1231        return (validated);
1232
1233    }
1234
1235
1236
1237    /**
1238     * Check credentials by binding to the directory as the user
1239     *
1240     * @param context The directory context
1241     * @param user The User to be authenticated
1242     * @param credentials Authentication credentials
1243     *
1244     * @exception NamingException if a directory server error occurs
1245     */

1246     protected boolean bindAsUser(DirContext JavaDoc context,
1247                                  User user,
1248                                  String JavaDoc credentials)
1249         throws NamingException JavaDoc {
1250         Attributes JavaDoc attr;
1251
1252         if (credentials == null || user == null)
1253             return (false);
1254
1255         String JavaDoc dn = user.dn;
1256         if (dn == null)
1257             return (false);
1258
1259         // Validate the credentials specified by the user
1260
if (containerLog.isTraceEnabled()) {
1261             containerLog.trace(" validating credentials by binding as the user");
1262        }
1263
1264        // Set up security environment to bind as the user
1265
context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
1266        context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
1267
1268        // Elicit an LDAP bind operation
1269
boolean validated = false;
1270        try {
1271            if (containerLog.isTraceEnabled()) {
1272                containerLog.trace(" binding as " + dn);
1273            }
1274            attr = context.getAttributes("", null);
1275            validated = true;
1276        }
1277        catch (AuthenticationException JavaDoc e) {
1278            if (containerLog.isTraceEnabled()) {
1279                containerLog.trace(" bind attempt failed");
1280            }
1281        }
1282
1283        // Restore the original security environment
1284
if (connectionName != null) {
1285            context.addToEnvironment(Context.SECURITY_PRINCIPAL,
1286                                     connectionName);
1287        } else {
1288            context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
1289        }
1290
1291        if (connectionPassword != null) {
1292            context.addToEnvironment(Context.SECURITY_CREDENTIALS,
1293                                     connectionPassword);
1294        }
1295        else {
1296            context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
1297        }
1298
1299        return (validated);
1300     }
1301
1302
1303    /**
1304     * Return a List of roles associated with the given User. Any
1305     * roles present in the user's directory entry are supplemented by
1306     * a directory search. If no roles are associated with this user,
1307     * a zero-length List is returned.
1308     *
1309     * @param context The directory context we are searching
1310     * @param user The User to be checked
1311     *
1312     * @exception NamingException if a directory server error occurs
1313     */

1314    protected List JavaDoc getRoles(DirContext JavaDoc context, User user)
1315        throws NamingException JavaDoc {
1316
1317        if (user == null)
1318            return (null);
1319
1320        String JavaDoc dn = user.dn;
1321        String JavaDoc username = user.username;
1322
1323        if (dn == null || username == null)
1324            return (null);
1325
1326        if (containerLog.isTraceEnabled())
1327            containerLog.trace(" getRoles(" + dn + ")");
1328
1329        // Start with roles retrieved from the user entry
1330
ArrayList JavaDoc list = user.roles;
1331        if (list == null) {
1332            list = new ArrayList JavaDoc();
1333        }
1334
1335        // Are we configured to do role searches?
1336
if ((roleFormat == null) || (roleName == null))
1337            return (list);
1338
1339        // Set up parameters for an appropriate search
1340
String JavaDoc filter = roleFormat.format(new String JavaDoc[] { doRFC2254Encoding(dn), username });
1341        SearchControls JavaDoc controls = new SearchControls JavaDoc();
1342        if (roleSubtree)
1343            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1344        else
1345            controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1346        controls.setReturningAttributes(new String JavaDoc[] {roleName});
1347
1348        // Perform the configured search and process the results
1349
NamingEnumeration JavaDoc results =
1350            context.search(roleBase, filter, controls);
1351        if (results == null)
1352            return (list); // Should never happen, but just in case ...
1353
while (results.hasMore()) {
1354            SearchResult JavaDoc result = (SearchResult JavaDoc) results.next();
1355            Attributes JavaDoc attrs = result.getAttributes();
1356            if (attrs == null)
1357                continue;
1358            list = addAttributeValues(roleName, attrs, list);
1359        }
1360
1361
1362        if (containerLog.isTraceEnabled()) {
1363            if (list != null) {
1364                containerLog.trace(" Returning " + list.size() + " roles");
1365                for (int i=0; i<list.size(); i++)
1366                    containerLog.trace( " Found role " + list.get(i));
1367            } else {
1368                containerLog.trace(" getRoles about to return null ");
1369            }
1370        }
1371
1372        return (list);
1373    }
1374
1375
1376    /**
1377     * Return a String representing the value of the specified attribute.
1378     *
1379     * @param attrId Attribute name
1380     * @param attrs Attributes containing the required value
1381     *
1382     * @exception NamingException if a directory server error occurs
1383     */

1384    private String JavaDoc getAttributeValue(String JavaDoc attrId, Attributes JavaDoc attrs)
1385        throws NamingException JavaDoc {
1386
1387        if (containerLog.isTraceEnabled())
1388            containerLog.trace(" retrieving attribute " + attrId);
1389
1390        if (attrId == null || attrs == null)
1391            return null;
1392
1393        Attribute JavaDoc attr = attrs.get(attrId);
1394        if (attr == null)
1395            return (null);
1396        Object JavaDoc value = attr.get();
1397        if (value == null)
1398            return (null);
1399        String JavaDoc valueString = null;
1400        if (value instanceof byte[])
1401            valueString = new String JavaDoc((byte[]) value);
1402        else
1403            valueString = value.toString();
1404
1405        return valueString;
1406    }
1407
1408
1409
1410    /**
1411     * Add values of a specified attribute to a list
1412     *
1413     * @param attrId Attribute name
1414     * @param attrs Attributes containing the new values
1415     * @param values ArrayList containing values found so far
1416     *
1417     * @exception NamingException if a directory server error occurs
1418     */

1419    private ArrayList JavaDoc addAttributeValues(String JavaDoc attrId,
1420                                         Attributes JavaDoc attrs,
1421                                         ArrayList JavaDoc values)
1422        throws NamingException JavaDoc{
1423
1424        if (containerLog.isTraceEnabled())
1425            containerLog.trace(" retrieving values for attribute " + attrId);
1426        if (attrId == null || attrs == null)
1427            return values;
1428        if (values == null)
1429            values = new ArrayList JavaDoc();
1430        Attribute JavaDoc attr = attrs.get(attrId);
1431        if (attr == null)
1432            return (values);
1433        NamingEnumeration JavaDoc e = attr.getAll();
1434        while(e.hasMore()) {
1435            String JavaDoc value = (String JavaDoc)e.next();
1436            values.add(value);
1437        }
1438        return values;
1439    }
1440
1441
1442    /**
1443     * Close any open connection to the directory server for this Realm.
1444     *
1445     * @param context The directory context to be closed
1446     */

1447    protected void close(DirContext JavaDoc context) {
1448
1449        // Do nothing if there is no opened connection
1450
if (context == null)
1451            return;
1452
1453        // Close our opened connection
1454
try {
1455            if (containerLog.isDebugEnabled())
1456                containerLog.debug("Closing directory context");
1457            context.close();
1458        } catch (NamingException JavaDoc e) {
1459            containerLog.error(sm.getString("jndiRealm.close"), e);
1460        }
1461        this.context = null;
1462
1463    }
1464
1465
1466    /**
1467     * Return a short name for this Realm implementation.
1468     */

1469    protected String JavaDoc getName() {
1470
1471        return (name);
1472
1473    }
1474
1475
1476    /**
1477     * Return the password associated with the given principal's user name.
1478     */

1479    protected String JavaDoc getPassword(String JavaDoc username) {
1480
1481        return (null);
1482
1483    }
1484
1485    /**
1486     * Return the Principal associated with the given user name.
1487     */

1488    protected Principal JavaDoc getPrincipal(String JavaDoc username) {
1489
1490        DirContext JavaDoc context = null;
1491        Principal JavaDoc principal = null;
1492
1493        try {
1494
1495            // Ensure that we have a directory context available
1496
context = open();
1497
1498            // Occassionally the directory context will timeout. Try one more
1499
// time before giving up.
1500
try {
1501
1502                // Authenticate the specified username if possible
1503
principal = getPrincipal(context, username);
1504
1505            } catch (CommunicationException JavaDoc e) {
1506
1507                // log the exception so we know it's there.
1508
containerLog.warn(sm.getString("jndiRealm.exception"), e);
1509
1510                // close the connection so we know it will be reopened.
1511
if (context != null)
1512                    close(context);
1513
1514                // open a new directory context.
1515
context = open();
1516
1517                // Try the authentication again.
1518
principal = getPrincipal(context, username);
1519
1520            }
1521
1522
1523            // Release this context
1524
release(context);
1525
1526            // Return the authenticated Principal (if any)
1527
return (principal);
1528
1529        } catch (NamingException JavaDoc e) {
1530
1531            // Log the problem for posterity
1532
containerLog.error(sm.getString("jndiRealm.exception"), e);
1533
1534            // Close the connection so that it gets reopened next time
1535
if (context != null)
1536                close(context);
1537
1538            // Return "not authenticated" for this request
1539
return (null);
1540
1541        }
1542
1543
1544    }
1545
1546
1547    /**
1548     * Return the Principal associated with the given user name.
1549     */

1550    protected synchronized Principal JavaDoc getPrincipal(DirContext JavaDoc context,
1551                                                  String JavaDoc username)
1552        throws NamingException JavaDoc {
1553        
1554        User user = getUser(context, username);
1555        
1556        return new GenericPrincipal(this, user.username, user.password ,
1557                getRoles(context, user));
1558    }
1559
1560    /**
1561     * Open (if necessary) and return a connection to the configured
1562     * directory server for this Realm.
1563     *
1564     * @exception NamingException if a directory server error occurs
1565     */

1566    protected DirContext JavaDoc open() throws NamingException JavaDoc {
1567
1568        // Do nothing if there is a directory server connection already open
1569
if (context != null)
1570            return (context);
1571
1572        try {
1573
1574            // Ensure that we have a directory context available
1575
context = new InitialDirContext JavaDoc(getDirectoryContextEnvironment());
1576
1577        } catch (Exception JavaDoc e) {
1578
1579            connectionAttempt = 1;
1580
1581            // log the first exception.
1582
containerLog.warn(sm.getString("jndiRealm.exception"), e);
1583
1584            // Try connecting to the alternate url.
1585
context = new InitialDirContext JavaDoc(getDirectoryContextEnvironment());
1586
1587        } finally {
1588
1589            // reset it in case the connection times out.
1590
// the primary may come back.
1591
connectionAttempt = 0;
1592
1593        }
1594
1595        return (context);
1596
1597    }
1598
1599    /**
1600     * Create our directory context configuration.
1601     *
1602     * @return java.util.Hashtable the configuration for the directory context.
1603     */

1604    protected Hashtable JavaDoc getDirectoryContextEnvironment() {
1605
1606        Hashtable JavaDoc env = new Hashtable JavaDoc();
1607
1608        // Configure our directory context environment.
1609
if (containerLog.isDebugEnabled() && connectionAttempt == 0)
1610            containerLog.debug("Connecting to URL " + connectionURL);
1611        else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
1612            containerLog.debug("Connecting to URL " + alternateURL);
1613        env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
1614        if (connectionName != null)
1615            env.put(Context.SECURITY_PRINCIPAL, connectionName);
1616        if (connectionPassword != null)
1617            env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
1618        if (connectionURL != null && connectionAttempt == 0)
1619            env.put(Context.PROVIDER_URL, connectionURL);
1620        else if (alternateURL != null && connectionAttempt > 0)
1621            env.put(Context.PROVIDER_URL, alternateURL);
1622        if (authentication != null)
1623            env.put(Context.SECURITY_AUTHENTICATION, authentication);
1624        if (protocol != null)
1625            env.put(Context.SECURITY_PROTOCOL, protocol);
1626        if (referrals != null)
1627            env.put(Context.REFERRAL, referrals);
1628        if (derefAliases != null)
1629            env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
1630
1631        return env;
1632
1633    }
1634
1635
1636    /**
1637     * Release our use of this connection so that it can be recycled.
1638     *
1639     * @param context The directory context to release
1640     */

1641    protected void release(DirContext JavaDoc context) {
1642
1643        ; // NO-OP since we are not pooling anything
1644

1645    }
1646
1647
1648    // ------------------------------------------------------ Lifecycle Methods
1649

1650
1651    /**
1652     * Prepare for active use of the public methods of this Component.
1653     *
1654     * @exception LifecycleException if this component detects a fatal error
1655     * that prevents it from being started
1656     */

1657    public void start() throws LifecycleException {
1658
1659        // Perform normal superclass initialization
1660
super.start();
1661
1662        // Validate that we can open our connection
1663
try {
1664            open();
1665        } catch (NamingException JavaDoc e) {
1666            throw new LifecycleException(sm.getString("jndiRealm.open"), e);
1667        }
1668
1669    }
1670
1671
1672    /**
1673     * Gracefully shut down active use of the public methods of this Component.
1674     *
1675     * @exception LifecycleException if this component detects a fatal error
1676     * that needs to be reported
1677     */

1678    public void stop() throws LifecycleException {
1679
1680        // Perform normal superclass finalization
1681
super.stop();
1682
1683        // Close any open directory server connection
1684
close(this.context);
1685
1686    }
1687
1688    /**
1689     * Given a string containing LDAP patterns for user locations (separated by
1690     * parentheses in a pseudo-LDAP search string format -
1691     * "(location1)(location2)", returns an array of those paths. Real LDAP
1692     * search strings are supported as well (though only the "|" "OR" type).
1693     *
1694     * @param userPatternString - a string LDAP search paths surrounded by
1695     * parentheses
1696     */

1697    protected String JavaDoc[] parseUserPatternString(String JavaDoc userPatternString) {
1698
1699        if (userPatternString != null) {
1700            ArrayList JavaDoc pathList = new ArrayList JavaDoc();
1701            int startParenLoc = userPatternString.indexOf('(');
1702            if (startParenLoc == -1) {
1703                // no parens here; return whole thing
1704
return new String JavaDoc[] {userPatternString};
1705            }
1706            int startingPoint = 0;
1707            while (startParenLoc > -1) {
1708                int endParenLoc = 0;
1709                // weed out escaped open parens and parens enclosing the
1710
// whole statement (in the case of valid LDAP search
1711
// strings: (|(something)(somethingelse))
1712
while ( (userPatternString.charAt(startParenLoc + 1) == '|') ||
1713                        (startParenLoc != 0 && userPatternString.charAt(startParenLoc - 1) == '\\') ) {
1714                    startParenLoc = userPatternString.indexOf("(", startParenLoc+1);
1715                }
1716                endParenLoc = userPatternString.indexOf(")", startParenLoc+1);
1717                // weed out escaped end-parens
1718
while (userPatternString.charAt(endParenLoc - 1) == '\\') {
1719                    endParenLoc = userPatternString.indexOf(")", endParenLoc+1);
1720                }
1721                String JavaDoc nextPathPart = userPatternString.substring
1722                    (startParenLoc+1, endParenLoc);
1723                pathList.add(nextPathPart);
1724                startingPoint = endParenLoc+1;
1725                startParenLoc = userPatternString.indexOf('(', startingPoint);
1726            }
1727            return (String JavaDoc[])pathList.toArray(new String JavaDoc[] {});
1728        }
1729        return null;
1730
1731    }
1732
1733
1734    /**
1735     * Given an LDAP search string, returns the string with certain characters
1736     * escaped according to RFC 2254 guidelines.
1737     * The character mapping is as follows:
1738     * char -> Replacement
1739     * ---------------------------
1740     * * -> \2a
1741     * ( -> \28
1742     * ) -> \29
1743     * \ -> \5c
1744     * \0 -> \00
1745     * @param inString string to escape according to RFC 2254 guidelines
1746     * @return String the escaped/encoded result
1747     */

1748    protected String JavaDoc doRFC2254Encoding(String JavaDoc inString) {
1749        StringBuffer JavaDoc buf = new StringBuffer JavaDoc(inString.length());
1750        for (int i = 0; i < inString.length(); i++) {
1751            char c = inString.charAt(i);
1752            switch (c) {
1753                case '\\':
1754                    buf.append("\\5c");
1755                    break;
1756                case '*':
1757                    buf.append("\\2a");
1758                    break;
1759                case '(':
1760                    buf.append("\\28");
1761                    break;
1762                case ')':
1763                    buf.append("\\29");
1764                    break;
1765                case '\0':
1766                    buf.append("\\00");
1767                    break;
1768                default:
1769                    buf.append(c);
1770                    break;
1771            }
1772        }
1773        return buf.toString();
1774    }
1775
1776
1777}
1778
1779// ------------------------------------------------------ Private Classes
1780

1781/**
1782 * A private class representing a User
1783 */

1784class User {
1785    String JavaDoc username = null;
1786    String JavaDoc dn = null;
1787    String JavaDoc password = null;
1788    ArrayList JavaDoc roles = null;
1789
1790
1791    User(String JavaDoc username, String JavaDoc dn, String JavaDoc password, ArrayList JavaDoc roles) {
1792        this.username = username;
1793        this.dn = dn;
1794        this.password = password;
1795        this.roles = roles;
1796    }
1797
1798}
1799
Popular Tags