KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > security > auth > module > LdapLoginModule


1 /*
2  * @(#)LdapLoginModule.java 1.4 06/04/24
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.security.auth.module;
9
10 import java.io.IOException JavaDoc;
11 import java.security.AccessController JavaDoc;
12 import java.net.SocketPermission JavaDoc;
13 import java.security.Principal JavaDoc;
14 import java.security.PrivilegedAction JavaDoc;
15 import java.util.Arrays JavaDoc;
16 import java.util.Hashtable JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.Map JavaDoc;
19 import java.util.ResourceBundle JavaDoc;
20 import java.util.regex.Matcher JavaDoc;
21 import java.util.regex.Pattern JavaDoc;
22 import java.util.Set JavaDoc;
23
24 import javax.naming.*;
25 import javax.naming.directory.*;
26 import javax.naming.ldap.*;
27 import javax.security.auth.*;
28 import javax.security.auth.callback.*;
29 import javax.security.auth.login.*;
30 import javax.security.auth.spi.*;
31
32 import com.sun.security.auth.LdapPrincipal;
33 import com.sun.security.auth.UserPrincipal;
34
35 import sun.security.util.AuthResources;
36
37 /**
38  * This {@link LoginModule} performs LDAP-based authentication.
39  * A username and password is verified against the corresponding user
40  * credentials stored in an LDAP directory.
41  * This module requires the supplied {@link CallbackHandler} to support a
42  * {@link NameCallback} and a {@link PasswordCallback}.
43  * If authentication is successful then a new {@link LdapPrincipal} is created
44  * using the user's distinguished name and a new {@link UserPrincipal} is
45  * created using the user's username and both are associated
46  * with the current {@link Subject}.
47  *
48  * <p> This module operates in one of three modes: <i>search-first</i>,
49  * <i>authentication-first</i> or <i>authentication-only</i>.
50  * A mode is selected by specifying a particular set of options.
51  *
52  * <p> In search-first mode, the LDAP directory is searched to determine the
53  * user's distinguished name and then authentication is attempted.
54  * An (anonymous) search is performed using the supplied username in
55  * conjunction with a specified search filter.
56  * If successful then authentication is attempted using the user's
57  * distinguished name and the supplied password.
58  * To enable this mode, set the <code>userFilter</code> option and omit the
59  * <code>authIdentity</code> option.
60  * Use search-first mode when the user's distinguished name is not
61  * known in advance.
62  *
63  * <p> In authentication-first mode, authentication is attempted using the
64  * supplied username and password and then the LDAP directory is searched.
65  * If authentication is successful then a search is performed using the
66  * supplied username in conjunction with a specified search filter.
67  * To enable this mode, set the <code>authIdentity</code> and the
68  * <code>userFilter</code> options.
69  * Use authentication-first mode when accessing an LDAP directory
70  * that has been configured to disallow anonymous searches.
71  *
72  * <p> In authentication-only mode, authentication is attempted using the
73  * supplied username and password. The LDAP directory is not searched because
74  * the user's distinguished name is already known.
75  * To enable this mode, set the <code>authIdentity</code> option to a valid
76  * distinguished name and omit the <code>userFilter</code> option.
77  * Use authentication-only mode when the user's distinguished name is
78  * known in advance.
79  *
80  * <p> The following option is mandatory and must be specified in this
81  * module's login {@link Configuration}:
82  * <dl><dt></dt><dd>
83  * <dl>
84  * <dt> <code>userProvider=<b>ldap_urls</b></code>
85  * </dt>
86  * <dd> This option identifies the LDAP directory that stores user entries.
87  * <b>ldap_urls</b> is a list of space-separated LDAP URLs
88  * (<a HREF="http://www.ietf.org/rfc/rfc2255.txt">RFC 2255</a>)
89  * that identifies the LDAP server to use and the position in
90  * its directory tree where user entries are located.
91  * When several LDAP URLs are specified then each is attempted,
92  * in turn, until the first successful connection is established.
93  * Spaces in the distinguished name component of the URL must be escaped
94  * using the standard mechanism of percent character ('<code>%</code>')
95  * followed by two hexadecimal digits (see {@link java.net.URI}).
96  * Query components must also be omitted from the URL.
97  *
98  * <p>
99  * Automatic discovery of the LDAP server via DNS
100  * (<a HREF="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</a>)
101  * is supported (once DNS has been configured to support such a service).
102  * It is enabled by omitting the hostname and port number components from
103  * the LDAP URL. </dd>
104  * </dl></dl>
105  *
106  * <p> This module also recognizes the following optional {@link Configuration}
107  * options:
108  * <dl><dt></dt><dd>
109  * <dl>
110  * <dt> <code>userFilter=<b>ldap_filter</b></code> </dt>
111  * <dd> This option specifies the search filter to use to locate a user's
112  * entry in the LDAP directory. It is used to determine a user's
113  * distinguished name.
114  * <code><b>ldap_filter</b></code> is an LDAP filter string
115  * (<a HREF="http://www.ietf.org/rfc/rfc2254.txt">RFC 2254</a>).
116  * If it contains the special token "<code><b>{USERNAME}</b></code>"
117  * then that token will be replaced with the supplied username value
118  * before the filter is used to search the directory. </dd>
119  *
120  * <dt> <code>authIdentity=<b>auth_id</b></code> </dt>
121  * <dd> This option specifies the identity to use when authenticating a user
122  * to the LDAP directory.
123  * <code><b>auth_id</b></code> may be an LDAP distinguished name string
124  * (<a HREF="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>) or some
125  * other string name.
126  * It must contain the special token "<code><b>{USERNAME}</b></code>"
127  * which will be replaced with the supplied username value before the
128  * name is used for authentication.
129  * Note that if this option does not contain a distinguished name then
130  * the <code>userFilter</code> option must also be specified. </dd>
131  *
132  * <dt> <code>authzIdentity=<b>authz_id</b></code> </dt>
133  * <dd> This option specifies an authorization identity for the user.
134  * <code><b>authz_id</b></code> is any string name.
135  * If it comprises a single special token with curly braces then
136  * that token is treated as a attribute name and will be replaced with a
137  * single value of that attribute from the user's LDAP entry.
138  * If the attribute cannot be found then the option is ignored.
139  * When this option is supplied and the user has been successfully
140  * authenticated then an additional {@link UserPrincipal}
141  * is created using the authorization identity and it is assocated with
142  * the current {@link Subject}. </dd>
143  *
144  * <dt> <code>useSSL</code> </dt>
145  * <dd> if <code>false</code>, this module does not establish an SSL connection
146  * to the LDAP server before attempting authentication. SSL is used to
147  * protect the privacy of the user's password because it is transmitted
148  * in the clear over LDAP.
149  * By default, this module uses SSL. </dd>
150  *
151  * <dt> <code>useFirstPass</code> </dt>
152  * <dd> if <code>true</code>, this module retrieves the username and password
153  * from the module's shared state, using "javax.security.auth.login.name"
154  * and "javax.security.auth.login.password" as the respective keys. The
155  * retrieved values are used for authentication. If authentication fails,
156  * no attempt for a retry is made, and the failure is reported back to
157  * the calling application.</dd>
158  *
159  * <dt> <code>tryFirstPass</code> </dt>
160  * <dd> if <code>true</code>, this module retrieves the username and password
161  * from the module's shared state, using "javax.security.auth.login.name"
162  * and "javax.security.auth.login.password" as the respective keys. The
163  * retrieved values are used for authentication. If authentication fails,
164  * the module uses the {@link CallbackHandler} to retrieve a new username
165  * and password, and another attempt to authenticate is made. If the
166  * authentication fails, the failure is reported back to the calling
167  * application.</dd>
168  *
169  * <dt> <code>storePass</code> </dt>
170  * <dd> if <code>true</code>, this module stores the username and password
171  * obtained from the {@link CallbackHandler} in the module's shared state,
172  * using
173  * "javax.security.auth.login.name" and
174  * "javax.security.auth.login.password" as the respective keys. This is
175  * not performed if existing values already exist for the username and
176  * password in the shared state, or if authentication fails.</dd>
177  *
178  * <dt> <code>clearPass</code> </dt>
179  * <dd> if <code>true</code>, this module clears the username and password
180  * stored in the module's shared state after both phases of authentication
181  * (login and commit) have completed.</dd>
182  *
183  * <dt> <code>debug</code> </dt>
184  * <dd> if <code>true</code>, debug messages are displayed on the standard
185  * output stream.
186  * </dl>
187  * </dl>
188  *
189  * <p>
190  * Arbitrary
191  * <a HREF="{@docRoot}/../../../../../technotes/guides/jndi/jndi-ldap-gl.html#PROP">JNDI properties</a>
192  * may also be specified in the {@link Configuration}.
193  * They are added to the environment and passed to the LDAP provider.
194  * Note that the following four JNDI properties are set by this module directly
195  * and are ignored if also present in the configuration:
196  * <ul>
197  * <li> <code>java.naming.provider.url</code>
198  * <li> <code>java.naming.security.principal</code>
199  * <li> <code>java.naming.security.credentials</code>
200  * <li> <code>java.naming.security.protocol</code>
201  * </ul>
202  *
203  * <p>
204  * Three sample {@link Configuration}s are shown below.
205  * The first one activates search-first mode. It identifies the LDAP server
206  * and specifies that users' entries be located by their <code>uid</code> and
207  * <code>objectClass</code> attributes. It also specifies that an identity
208  * based on the user's <code>employeeNumber</code> attribute should be created.
209  * The second one activates authentication-first mode. It requests that the
210  * LDAP server be located dynamically, that authentication be performed using
211  * the supplied username directly but without the protection of SSL and that
212  * users' entries be located by one of three naming attributes and their
213  * <code>objectClass</code> attribute.
214  * The third one activates authentication-only mode. It identifies alternative
215  * LDAP servers, it specifies the distinguished name to use for
216  * authentication and a fixed identity to use for authorization. No directory
217  * search is performed.
218  *
219  * <pre>
220  *
221  * ExampleApplication {
222  * com.sun.security.auth.module.LdapLoginModule REQUIRED
223  * userProvider="ldap://ldap-svr/ou=people,dc=example,dc=com"
224  * userFilter="(&(uid={USERNAME})(objectClass=inetOrgPerson))"
225  * authzIdentity="{EMPLOYEENUMBER}"
226  * debug=true;
227  * };
228  *
229  * ExampleApplication {
230  * com.sun.security.auth.module.LdapLoginModule REQUIRED
231  * userProvider="ldap:///cn=users,dc=example,dc=com"
232  * authIdentity="{USERNAME}"
233  * userFilter="(&(|(samAccountName={USERNAME})(userPrincipalName={USERNAME})(cn={USERNAME}))(objectClass=user))"
234  * useSSL=false
235  * debug=true;
236  * };
237  *
238  * ExampleApplication {
239  * com.sun.security.auth.module.LdapLoginModule REQUIRED
240  * userProvider="ldap://ldap-svr1 ldap://ldap-svr2"
241  * authIdentity="cn={USERNAME},ou=people,dc=example,dc=com"
242  * authzIdentity="staff"
243  * debug=true;
244  * };
245  *
246  * </pre>
247  *
248  * <dl>
249  * <dt><b>Note:</b> </dt>
250  * <dd>When a {@link SecurityManager} is active then an application
251  * that creates a {@link LoginContext} and uses a {@link LoginModule}
252  * must be granted certain permissions.
253  * <p>
254  * If the application creates a login context using an <em>installed</em>
255  * {@link Configuration} then the application must be granted the
256  * {@link AuthPermission} to create login contexts.
257  * For example, the following security policy allows an application in
258  * the user's current directory to instantiate <em>any</em> login context:
259  * <pre>
260  *
261  * grant codebase "file:${user.dir}/" {
262  * permission javax.security.auth.AuthPermission "createLoginContext.*";
263  * };
264  * </pre>
265  *
266  * Alternatively, if the application creates a login context using a
267  * <em>caller-specified</em> {@link Configuration} then the application
268  * must be granted the permissions required by the {@link LoginModule}.
269  * <em>This</em> module requires the following two permissions:
270  * <p>
271  * <ul>
272  * <li> The {@link SocketPermission} to connect to an LDAP server.
273  * <li> The {@link AuthPermission} to modify the set of {@link Principal}s
274  * associated with a {@link Subject}.
275  * </ul>
276  * <p>
277  * For example, the following security policy grants an application in the
278  * user's current directory all the permissions required by this module:
279  * <pre>
280  *
281  * grant codebase "file:${user.dir}/" {
282  * permission java.net.SocketPermission "*:389", "connect";
283  * permission java.net.SocketPermission "*:636", "connect";
284  * permission javax.security.auth.AuthPermission "modifyPrincipals";
285  * };
286  * </pre>
287  * </dd>
288  * </dl>
289  *
290  * @since 1.6
291  */

292 public class LdapLoginModule implements LoginModule {
293
294     // Use the default classloader for this class to load the prompt strings.
295
private static final ResourceBundle JavaDoc rb =
296         (ResourceBundle JavaDoc) AccessController.doPrivileged(
297         new PrivilegedAction JavaDoc() {
298         public Object JavaDoc run() {
299             return ResourceBundle.getBundle(
300             "sun.security.util.AuthResources");
301         }
302         }
303     );
304
305     // Keys to retrieve the stored username and password
306
private static final String JavaDoc USERNAME_KEY = "javax.security.auth.login.name";
307     private static final String JavaDoc PASSWORD_KEY =
308     "javax.security.auth.login.password";
309
310     // Option names
311
private static final String JavaDoc USER_PROVIDER = "userProvider";
312     private static final String JavaDoc USER_FILTER = "userFilter";
313     private static final String JavaDoc AUTHC_IDENTITY = "authIdentity";
314     private static final String JavaDoc AUTHZ_IDENTITY = "authzIdentity";
315
316     // Used for the username token replacement
317
private static final String JavaDoc USERNAME_TOKEN = "{USERNAME}";
318     private static final Pattern JavaDoc USERNAME_PATTERN =
319     Pattern.compile("\\{USERNAME\\}");
320
321     // Configurable options
322
private String JavaDoc userProvider;
323     private String JavaDoc userFilter;
324     private String JavaDoc authcIdentity;
325     private String JavaDoc authzIdentity;
326     private String JavaDoc authzIdentityAttr = null;
327     private boolean useSSL = true;
328     private boolean authFirst = false;
329     private boolean authOnly = false;
330     private boolean useFirstPass = false;
331     private boolean tryFirstPass = false;
332     private boolean storePass = false;
333     private boolean clearPass = false;
334     private boolean debug = false;
335
336     // Authentication status
337
private boolean succeeded = false;
338     private boolean commitSucceeded = false;
339
340     // Supplied username and password
341
private String JavaDoc username;
342     private char[] password;
343
344     // User's identities
345
private LdapPrincipal ldapPrincipal;
346     private UserPrincipal userPrincipal;
347     private UserPrincipal authzPrincipal;
348
349     // Initial state
350
private Subject subject;
351     private CallbackHandler callbackHandler;
352     private Map JavaDoc sharedState;
353     private Map JavaDoc options;
354     private LdapContext ctx;
355     private Matcher JavaDoc identityMatcher = null;
356     private Matcher JavaDoc filterMatcher = null;
357     private Hashtable JavaDoc ldapEnvironment;
358     private SearchControls constraints = null;
359
360     /**
361      * Initialize this <code>LoginModule</code>.
362      *
363      * @param subject the <code>Subject</code> to be authenticated.
364      * @param callbackHandler a <code>CallbackHandler</code> to acquire the
365      * username and password.
366      * @param sharedState shared <code>LoginModule</code> state.
367      * @param options options specified in the login
368      * <code>Configuration</code> for this particular
369      * <code>LoginModule</code>.
370      */

371     public void initialize(Subject subject, CallbackHandler callbackHandler,
372             Map JavaDoc<String JavaDoc, ?> sharedState, Map JavaDoc<String JavaDoc, ?> options) {
373
374     this.subject = subject;
375     this.callbackHandler = callbackHandler;
376     this.sharedState = sharedState;
377     this.options = options;
378
379     ldapEnvironment = new Hashtable JavaDoc(9);
380     ldapEnvironment.put(Context.INITIAL_CONTEXT_FACTORY,
381         "com.sun.jndi.ldap.LdapCtxFactory");
382
383     // Add any JNDI properties to the environment
384
Set JavaDoc keys = options.keySet();
385     String JavaDoc key;
386     for (Iterator JavaDoc i = keys.iterator(); i.hasNext(); ) {
387         key = (String JavaDoc) i.next();
388         if (key.indexOf(".") > -1) {
389         ldapEnvironment.put(key, options.get(key));
390         }
391     }
392
393     // initialize any configured options
394

395     userProvider = (String JavaDoc)options.get(USER_PROVIDER);
396     if (userProvider != null) {
397         ldapEnvironment.put(Context.PROVIDER_URL, userProvider);
398     }
399
400     authcIdentity = (String JavaDoc)options.get(AUTHC_IDENTITY);
401     if (authcIdentity != null &&
402         (authcIdentity.indexOf(USERNAME_TOKEN) != -1)) {
403         identityMatcher = USERNAME_PATTERN.matcher(authcIdentity);
404     }
405
406     userFilter = (String JavaDoc)options.get(USER_FILTER);
407     if (userFilter != null) {
408         if (userFilter.indexOf(USERNAME_TOKEN) != -1) {
409         filterMatcher = USERNAME_PATTERN.matcher(userFilter);
410         }
411         constraints = new SearchControls();
412         constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
413         constraints.setReturningAttributes(new String JavaDoc[0]); //return no attrs
414
constraints.setReturningObjFlag(true); // to get the full DN
415
}
416
417     authzIdentity = (String JavaDoc)options.get(AUTHZ_IDENTITY);
418     if (authzIdentity != null &&
419         authzIdentity.startsWith("{") && authzIdentity.endsWith("}")) {
420         if (constraints != null) {
421         authzIdentityAttr =
422             authzIdentity.substring(1, authzIdentity.length() - 1);
423         constraints.setReturningAttributes(
424             new String JavaDoc[]{authzIdentityAttr});
425         }
426         authzIdentity = null; // set later, from the specified attribute
427
}
428
429     // determine mode
430
if (authcIdentity != null) {
431         if (userFilter != null) {
432         authFirst = true; // authentication-first mode
433
} else {
434         authOnly = true; // authentication-only mode
435
}
436     }
437
438     if ("false".equalsIgnoreCase((String JavaDoc)options.get("useSSL"))) {
439         useSSL = false;
440         ldapEnvironment.remove(Context.SECURITY_PROTOCOL);
441     } else {
442         ldapEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
443     }
444
445     tryFirstPass =
446         "true".equalsIgnoreCase((String JavaDoc)options.get("tryFirstPass"));
447
448     useFirstPass =
449         "true".equalsIgnoreCase((String JavaDoc)options.get("useFirstPass"));
450
451     storePass = "true".equalsIgnoreCase((String JavaDoc)options.get("storePass"));
452
453     clearPass = "true".equalsIgnoreCase((String JavaDoc)options.get("clearPass"));
454
455     debug = "true".equalsIgnoreCase((String JavaDoc)options.get("debug"));
456
457     if (debug) {
458         if (authFirst) {
459         System.out.println("\t\t[LdapLoginModule] " +
460             "authentication-first mode; " +
461             (useSSL ? "SSL enabled" : "SSL disabled"));
462         } else if (authOnly) {
463         System.out.println("\t\t[LdapLoginModule] " +
464             "authentication-only mode; " +
465             (useSSL ? "SSL enabled" : "SSL disabled"));
466         } else {
467         System.out.println("\t\t[LdapLoginModule] " +
468             "search-first mode; " +
469             (useSSL ? "SSL enabled" : "SSL disabled"));
470         }
471     }
472     }
473
474     /**
475      * Begin user authentication.
476      *
477      * <p> Acquire the user's credentials and verify them against the
478      * specified LDAP directory.
479      *
480      * @return true always, since this <code>LoginModule</code>
481      * should not be ignored.
482      * @exception FailedLoginException if the authentication fails.
483      * @exception LoginException if this <code>LoginModule</code>
484      * is unable to perform the authentication.
485      */

486     public boolean login() throws LoginException {
487
488     if (userProvider == null) {
489         throw new LoginException
490         ("Unable to locate the LDAP directory service");
491     }
492
493     if (debug) {
494         System.out.println("\t\t[LdapLoginModule] user provider: " +
495         userProvider);
496     }
497
498     // attempt the authentication
499
if (tryFirstPass) {
500
501         try {
502         // attempt the authentication by getting the
503
// username and password from shared state
504
attemptAuthentication(true);
505
506         // authentication succeeded
507
succeeded = true;
508         if (debug) {
509             System.out.println("\t\t[LdapLoginModule] " +
510                 "tryFirstPass succeeded");
511         }
512         return true;
513
514         } catch (LoginException le) {
515         // authentication failed -- try again below by prompting
516
cleanState();
517         if (debug) {
518             System.out.println("\t\t[LdapLoginModule] " +
519                 "tryFirstPass failed: " + le.toString());
520         }
521         }
522
523     } else if (useFirstPass) {
524
525         try {
526         // attempt the authentication by getting the
527
// username and password from shared state
528
attemptAuthentication(true);
529
530         // authentication succeeded
531
succeeded = true;
532         if (debug) {
533             System.out.println("\t\t[LdapLoginModule] " +
534                 "useFirstPass succeeded");
535         }
536         return true;
537
538         } catch (LoginException le) {
539         // authentication failed
540
cleanState();
541         if (debug) {
542             System.out.println("\t\t[LdapLoginModule] " +
543                 "useFirstPass failed");
544         }
545         throw le;
546         }
547     }
548
549     // attempt the authentication by prompting for the username and pwd
550
try {
551         attemptAuthentication(false);
552
553         // authentication succeeded
554
succeeded = true;
555         if (debug) {
556         System.out.println("\t\t[LdapLoginModule] " +
557                 "authentication succeeded");
558         }
559         return true;
560
561     } catch (LoginException le) {
562         cleanState();
563         if (debug) {
564         System.out.println("\t\t[LdapLoginModule] " +
565                 "authentication failed");
566         }
567         throw le;
568     }
569     }
570
571     /**
572      * Complete user authentication.
573      *
574      * <p> This method is called if the LoginContext's
575      * overall authentication succeeded
576      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
577      * succeeded).
578      *
579      * <p> If this LoginModule's own authentication attempt
580      * succeeded (checked by retrieving the private state saved by the
581      * <code>login</code> method), then this method associates an
582      * <code>LdapPrincipal</code> and one or more <code>UserPrincipal</code>s
583      * with the <code>Subject</code> located in the
584      * <code>LoginModule</code>. If this LoginModule's own
585      * authentication attempted failed, then this method removes
586      * any state that was originally saved.
587      *
588      * @exception LoginException if the commit fails
589      * @return true if this LoginModule's own login and commit
590      * attempts succeeded, or false otherwise.
591      */

592     public boolean commit() throws LoginException {
593
594     if (succeeded == false) {
595         return false;
596     } else {
597         if (subject.isReadOnly()) {
598         cleanState();
599         throw new LoginException ("Subject is read-only");
600         }
601         // add Principals to the Subject
602
Set JavaDoc principals = subject.getPrincipals();
603         if (! principals.contains(ldapPrincipal)) {
604         principals.add(ldapPrincipal);
605         }
606         if (debug) {
607         System.out.println("\t\t[LdapLoginModule] " +
608                    "added LdapPrincipal \"" +
609                    ldapPrincipal +
610                    "\" to Subject");
611         }
612
613         if (! principals.contains(userPrincipal)) {
614         principals.add(userPrincipal);
615         }
616         if (debug) {
617         System.out.println("\t\t[LdapLoginModule] " +
618                    "added UserPrincipal \"" +
619                    userPrincipal +
620                    "\" to Subject");
621         }
622
623         if (authzPrincipal != null &&
624         (! principals.contains(authzPrincipal))) {
625         principals.add(authzPrincipal);
626
627         if (debug) {
628             System.out.println("\t\t[LdapLoginModule] " +
629                    "added UserPrincipal \"" +
630                    authzPrincipal +
631                    "\" to Subject");
632         }
633         }
634     }
635     // in any case, clean out state
636
cleanState();
637     commitSucceeded = true;
638     return true;
639     }
640
641     /**
642      * Abort user authentication.
643      *
644      * <p> This method is called if the overall authentication failed.
645      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
646      * did not succeed).
647      *
648      * <p> If this LoginModule's own authentication attempt
649      * succeeded (checked by retrieving the private state saved by the
650      * <code>login</code> and <code>commit</code> methods),
651      * then this method cleans up any state that was originally saved.
652      *
653      * @exception LoginException if the abort fails.
654      * @return false if this LoginModule's own login and/or commit attempts
655      * failed, and true otherwise.
656      */

657     public boolean abort() throws LoginException {
658     if (debug)
659         System.out.println("\t\t[LdapLoginModule] " +
660         "aborted authentication");
661
662     if (succeeded == false) {
663         return false;
664     } else if (succeeded == true && commitSucceeded == false) {
665
666         // Clean out state
667
succeeded = false;
668         cleanState();
669
670         ldapPrincipal = null;
671         userPrincipal = null;
672         authzPrincipal = null;
673     } else {
674         // overall authentication succeeded and commit succeeded,
675
// but someone else's commit failed
676
logout();
677     }
678     return true;
679     }
680
681     /**
682      * Logout a user.
683      *
684      * <p> This method removes the Principals
685      * that were added by the <code>commit</code> method.
686      *
687      * @exception LoginException if the logout fails.
688      * @return true in all cases since this <code>LoginModule</code>
689      * should not be ignored.
690      */

691     public boolean logout() throws LoginException {
692     if (subject.isReadOnly()) {
693         cleanState();
694         throw new LoginException ("Subject is read-only");
695     }
696     Set JavaDoc principals = subject.getPrincipals();
697     principals.remove(ldapPrincipal);
698     principals.remove(userPrincipal);
699     if (authzIdentity != null) {
700         principals.remove(authzPrincipal);
701     }
702
703     // clean out state
704
cleanState();
705     succeeded = false;
706     commitSucceeded = false;
707
708     ldapPrincipal = null;
709     userPrincipal = null;
710     authzPrincipal = null;
711
712     if (debug) {
713         System.out.println("\t\t[LdapLoginModule] logged out Subject");
714     }
715     return true;
716     }
717
718     /**
719      * Attempt authentication
720      *
721      * @param getPasswdFromSharedState boolean that tells this method whether
722      * to retrieve the password from the sharedState.
723      * @exception LoginException if the authentication attempt fails.
724      */

725     private void attemptAuthentication(boolean getPasswdFromSharedState)
726     throws LoginException {
727
728     // first get the username and password
729
getUsernamePassword(getPasswdFromSharedState);
730
731     if (password == null || password.length == 0) {
732         throw (LoginException)
733         new FailedLoginException("No password was supplied");
734     }
735
736     String JavaDoc dn = "";
737
738     if (authFirst || authOnly) {
739
740         String JavaDoc id = replaceUsernameToken(identityMatcher, authcIdentity);
741
742         // Prepare to bind using user's username and password
743
ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password);
744         ldapEnvironment.put(Context.SECURITY_PRINCIPAL, id);
745
746         if (debug) {
747         System.out.println("\t\t[LdapLoginModule] " +
748             "attempting to authenticate user: " + username);
749         }
750
751         try {
752         // Connect to the LDAP server (using simple bind)
753
ctx = new InitialLdapContext(ldapEnvironment, null);
754
755         } catch (NamingException e) {
756         throw (LoginException)
757             new FailedLoginException("Cannot bind to LDAP server")
758             .initCause(e);
759         }
760
761         // Authentication has succeeded
762

763         // Locate the user's distinguished name
764
if (userFilter != null) {
765         dn = findUserDN(ctx);
766         } else {
767         dn = id;
768         }
769
770     } else {
771
772         try {
773         // Connect to the LDAP server (using anonymous bind)
774
ctx = new InitialLdapContext(ldapEnvironment, null);
775
776         } catch (NamingException e) {
777         throw (LoginException)
778             new FailedLoginException("Cannot connect to LDAP server")
779             .initCause(e);
780         }
781
782         // Locate the user's distinguished name
783
dn = findUserDN(ctx);
784
785         try {
786
787         // Prepare to bind using user's distinguished name and password
788
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
789         ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
790         ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
791
792         if (debug) {
793             System.out.println("\t\t[LdapLoginModule] " +
794             "attempting to authenticate user: " + username);
795         }
796         // Connect to the LDAP server (using simple bind)
797
ctx.reconnect(null);
798
799         // Authentication has succeeded
800

801         } catch (NamingException e) {
802         throw (LoginException)
803             new FailedLoginException("Cannot bind to LDAP server")
804             .initCause(e);
805         }
806     }
807
808     // Save input as shared state only if authentication succeeded
809
if (storePass &&
810         !sharedState.containsKey(USERNAME_KEY) &&
811         !sharedState.containsKey(PASSWORD_KEY)) {
812         sharedState.put(USERNAME_KEY, username);
813         sharedState.put(PASSWORD_KEY, password);
814     }
815
816     // Create the user principals
817
userPrincipal = new UserPrincipal(username);
818     if (authzIdentity != null) {
819         authzPrincipal = new UserPrincipal(authzIdentity);
820     }
821
822     try {
823
824         ldapPrincipal = new LdapPrincipal(dn);
825
826     } catch (InvalidNameException e) {
827         if (debug) {
828         System.out.println("\t\t[LdapLoginModule] " +
829                    "cannot create LdapPrincipal: bad DN");
830         }
831         throw (LoginException)
832         new FailedLoginException("Cannot create LdapPrincipal")
833             .initCause(e);
834     }
835     }
836
837     /**
838      * Search for the user's entry.
839      * Determine the distinguished name of the user's entry and optionally
840      * an authorization identity for the user.
841      *
842      * @param ctx an LDAP context to use for the search
843      * @return the user's distinguished name or an empty string if none
844      * was found.
845      * @exception LoginException if the user's entry cannot be found.
846      */

847     private String JavaDoc findUserDN(LdapContext ctx) throws LoginException {
848
849     String JavaDoc userDN = "";
850
851     // Locate the user's LDAP entry
852
if (userFilter != null) {
853         if (debug) {
854         System.out.println("\t\t[LdapLoginModule] " +
855             "searching for entry belonging to user: " + username);
856         }
857     } else {
858         if (debug) {
859         System.out.println("\t\t[LdapLoginModule] " +
860             "cannot search for entry belonging to user: " + username);
861         }
862         throw (LoginException)
863         new FailedLoginException("Cannot find user's LDAP entry");
864     }
865
866     try {
867         NamingEnumeration results = ctx.search("",
868         replaceUsernameToken(filterMatcher, userFilter), constraints);
869
870         // Extract the distinguished name of the user's entry
871
// (Use the first entry if more than one is returned)
872
if (results.hasMore()) {
873         SearchResult entry = (SearchResult) results.next();
874
875         // %%% - use the SearchResult.getNameInNamespace method
876
// available in JDK 1.5 and later.
877
// (can remove call to constraints.setReturningObjFlag)
878
userDN = ((Context)entry.getObject()).getNameInNamespace();
879
880         if (debug) {
881             System.out.println("\t\t[LdapLoginModule] found entry: " +
882             userDN);
883         }
884
885         // Extract a value from user's authorization identity attribute
886
if (authzIdentityAttr != null) {
887             Attribute attr =
888             entry.getAttributes().get(authzIdentityAttr);
889             if (attr != null) {
890             Object JavaDoc val = attr.get();
891             if (val instanceof String JavaDoc) {
892                 authzIdentity = (String JavaDoc) val;
893             }
894             }
895         }
896
897         results.close();
898
899         } else {
900         // Bad username
901
if (debug) {
902             System.out.println("\t\t[LdapLoginModule] user's entry " +
903             "not found");
904         }
905         }
906
907     } catch (NamingException e) {
908         // ignore
909
}
910
911     if (userDN.equals("")) {
912         throw (LoginException)
913         new FailedLoginException("Cannot find user's LDAP entry");
914     } else {
915         return userDN;
916     }
917     }
918
919     /**
920      * Replace the username token
921      *
922      * @param string the target string
923      * @return the modified string
924      */

925     private String JavaDoc replaceUsernameToken(Matcher JavaDoc matcher, String JavaDoc string) {
926     return matcher != null ? matcher.replaceAll(username) : string;
927     }
928
929     /**
930      * Get the username and password.
931      * This method does not return any value.
932      * Instead, it sets global name and password variables.
933      *
934      * <p> Also note that this method will set the username and password
935      * values in the shared state in case subsequent LoginModules
936      * want to use them via use/tryFirstPass.
937      *
938      * @param getPasswdFromSharedState boolean that tells this method whether
939      * to retrieve the password from the sharedState.
940      * @exception LoginException if the username/password cannot be acquired.
941      */

942     private void getUsernamePassword(boolean getPasswdFromSharedState)
943     throws LoginException {
944
945     if (getPasswdFromSharedState) {
946         // use the password saved by the first module in the stack
947
username = (String JavaDoc)sharedState.get(USERNAME_KEY);
948         password = (char[])sharedState.get(PASSWORD_KEY);
949         return;
950     }
951
952     // prompt for a username and password
953
if (callbackHandler == null)
954         throw new LoginException("No CallbackHandler available " +
955         "to acquire authentication information from the user");
956
957     Callback[] callbacks = new Callback[2];
958     callbacks[0] = new NameCallback(rb.getString("username: "));
959     callbacks[1] = new PasswordCallback(rb.getString("password: "), false);
960
961     try {
962         callbackHandler.handle(callbacks);
963         username = ((NameCallback)callbacks[0]).getName();
964         char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
965         password = new char[tmpPassword.length];
966         System.arraycopy(tmpPassword, 0,
967                 password, 0, tmpPassword.length);
968         ((PasswordCallback)callbacks[1]).clearPassword();
969
970     } catch (java.io.IOException JavaDoc ioe) {
971         throw new LoginException(ioe.toString());
972
973     } catch (UnsupportedCallbackException uce) {
974         throw new LoginException("Error: " + uce.getCallback().toString() +
975             " not available to acquire authentication information" +
976             " from the user");
977     }
978     }
979
980     /**
981      * Clean out state because of a failed authentication attempt
982      */

983     private void cleanState() {
984     username = null;
985     if (password != null) {
986         Arrays.fill(password, ' ');
987         password = null;
988     }
989     try {
990         if (ctx != null) {
991         ctx.close();
992         }
993     } catch (NamingException e) {
994         // ignore
995
}
996     ctx = null;
997
998     if (clearPass) {
999         sharedState.remove(USERNAME_KEY);
1000        sharedState.remove(PASSWORD_KEY);
1001    }
1002    }
1003}
1004
Popular Tags