History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/com/sun/security/auth/module/LdapLoginModule.java (1 of 18352 files). Show all

Unified · Split

java/pkg/com/sun/security/auth/module/LdapLoginModule.java+1017−0
@@ -0,0 +1,1017 @@
1/*
2 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.security.auth.module;
27
28import java.io.IOException;
29import java.security.AccessController;
30import java.net.SocketPermission;
31import java.security.Principal;
32import java.security.PrivilegedAction;
33import java.util.Arrays;
34import java.util.Hashtable;
35import java.util.Iterator;
36import java.util.Map;
37import java.util.ResourceBundle;
38import java.util.regex.Matcher;
39import java.util.regex.Pattern;
40import java.util.Set;
41
42import javax.naming.*;
43import javax.naming.directory.*;
44import javax.naming.ldap.*;
45import javax.security.auth.*;
46import javax.security.auth.callback.*;
47import javax.security.auth.login.*;
48import javax.security.auth.spi.*;
49
50import com.sun.security.auth.LdapPrincipal;
51import com.sun.security.auth.UserPrincipal;
52
53import sun.security.util.AuthResources;
54
55/**
56 * This {@link LoginModule} performs LDAP-based authentication.
57 * A username and password is verified against the corresponding user
58 * credentials stored in an LDAP directory.
59 * This module requires the supplied {@link CallbackHandler} to support a
60 * {@link NameCallback} and a {@link PasswordCallback}.
61 * If authentication is successful then a new {@link LdapPrincipal} is created
62 * using the user's distinguished name and a new {@link UserPrincipal} is
63 * created using the user's username and both are associated
64 * with the current {@link Subject}.
65 *
66 * <p> This module operates in one of three modes: <i>search-first</i>,
67 * <i>authentication-first</i> or <i>authentication-only</i>.
68 * A mode is selected by specifying a particular set of options.
69 *
70 * <p> In search-first mode, the LDAP directory is searched to determine the
71 * user's distinguished name and then authentication is attempted.
72 * An (anonymous) search is performed using the supplied username in
73 * conjunction with a specified search filter.
74 * If successful then authentication is attempted using the user's
75 * distinguished name and the supplied password.
76 * To enable this mode, set the <code>userFilter</code> option and omit the
77 * <code>authIdentity</code> option.
78 * Use search-first mode when the user's distinguished name is not
79 * known in advance.
80 *
81 * <p> In authentication-first mode, authentication is attempted using the
82 * supplied username and password and then the LDAP directory is searched.
83 * If authentication is successful then a search is performed using the
84 * supplied username in conjunction with a specified search filter.
85 * To enable this mode, set the <code>authIdentity</code> and the
86 * <code>userFilter</code> options.
87 * Use authentication-first mode when accessing an LDAP directory
88 * that has been configured to disallow anonymous searches.
89 *
90 * <p> In authentication-only mode, authentication is attempted using the
91 * supplied username and password. The LDAP directory is not searched because
92 * the user's distinguished name is already known.
93 * To enable this mode, set the <code>authIdentity</code> option to a valid
94 * distinguished name and omit the <code>userFilter</code> option.
95 * Use authentication-only mode when the user's distinguished name is
96 * known in advance.
97 *
98 * <p> The following option is mandatory and must be specified in this
99 * module's login {@link Configuration}:
100 * <dl><dt></dt><dd>
101 * <dl>
102 * <dt> <code>userProvider=<b>ldap_urls</b></code>
103 * </dt>
104 * <dd> This option identifies the LDAP directory that stores user entries.
105 * <b>ldap_urls</b> is a list of space-separated LDAP URLs
106 * (<a href="http://www.ietf.org/rfc/rfc2255.txt">RFC 2255</a>)
107 * that identifies the LDAP server to use and the position in
108 * its directory tree where user entries are located.
109 * When several LDAP URLs are specified then each is attempted,
110 * in turn, until the first successful connection is established.
111 * Spaces in the distinguished name component of the URL must be escaped
112 * using the standard mechanism of percent character ('<code>%</code>')
113 * followed by two hexadecimal digits (see {@link java.net.URI}).
114 * Query components must also be omitted from the URL.
115 *
116 * <p>
117 * Automatic discovery of the LDAP server via DNS
118 * (<a href="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782</a>)
119 * is supported (once DNS has been configured to support such a service).
120 * It is enabled by omitting the hostname and port number components from
121 * the LDAP URL. </dd>
122 * </dl></dl>
123 *
124 * <p> This module also recognizes the following optional {@link Configuration}
125 * options:
126 * <dl><dt></dt><dd>
127 * <dl>
128 * <dt> <code>userFilter=<b>ldap_filter</b></code> </dt>
129 * <dd> This option specifies the search filter to use to locate a user's
130 * entry in the LDAP directory. It is used to determine a user's
131 * distinguished name.
132 * <code><b>ldap_filter</b></code> is an LDAP filter string
133 * (<a href="http://www.ietf.org/rfc/rfc2254.txt">RFC 2254</a>).
134 * If it contains the special token "<code><b>{USERNAME}</b></code>"
135 * then that token will be replaced with the supplied username value
136 * before the filter is used to search the directory. </dd>
137 *
138 * <dt> <code>authIdentity=<b>auth_id</b></code> </dt>
139 * <dd> This option specifies the identity to use when authenticating a user
140 * to the LDAP directory.
141 * <code><b>auth_id</b></code> may be an LDAP distinguished name string
142 * (<a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253</a>) or some
143 * other string name.
144 * It must contain the special token "<code><b>{USERNAME}</b></code>"
145 * which will be replaced with the supplied username value before the
146 * name is used for authentication.
147 * Note that if this option does not contain a distinguished name then
148 * the <code>userFilter</code> option must also be specified. </dd>
149 *
150 * <dt> <code>authzIdentity=<b>authz_id</b></code> </dt>
151 * <dd> This option specifies an authorization identity for the user.
152 * <code><b>authz_id</b></code> is any string name.
153 * If it comprises a single special token with curly braces then
154 * that token is treated as a attribute name and will be replaced with a
155 * single value of that attribute from the user's LDAP entry.
156 * If the attribute cannot be found then the option is ignored.
157 * When this option is supplied and the user has been successfully
158 * authenticated then an additional {@link UserPrincipal}
159 * is created using the authorization identity and it is assocated with
160 * the current {@link Subject}. </dd>
161 *
162 * <dt> <code>useSSL</code> </dt>
163 * <dd> if <code>false</code>, this module does not establish an SSL connection
164 * to the LDAP server before attempting authentication. SSL is used to
165 * protect the privacy of the user's password because it is transmitted
166 * in the clear over LDAP.
167 * By default, this module uses SSL. </dd>
168 *
169 * <dt> <code>useFirstPass</code> </dt>
170 * <dd> if <code>true</code>, this module retrieves the username and password
171 * from the module's shared state, using "javax.security.auth.login.name"
172 * and "javax.security.auth.login.password" as the respective keys. The
173 * retrieved values are used for authentication. If authentication fails,
174 * no attempt for a retry is made, and the failure is reported back to
175 * the calling application.</dd>
176 *
177 * <dt> <code>tryFirstPass</code> </dt>
178 * <dd> if <code>true</code>, this module retrieves the username and password
179 * from the module's shared state, using "javax.security.auth.login.name"
180 * and "javax.security.auth.login.password" as the respective keys. The
181 * retrieved values are used for authentication. If authentication fails,
182 * the module uses the {@link CallbackHandler} to retrieve a new username
183 * and password, and another attempt to authenticate is made. If the
184 * authentication fails, the failure is reported back to the calling
185 * application.</dd>
186 *
187 * <dt> <code>storePass</code> </dt>
188 * <dd> if <code>true</code>, this module stores the username and password
189 * obtained from the {@link CallbackHandler} in the module's shared state,
190 * using
191 * "javax.security.auth.login.name" and
192 * "javax.security.auth.login.password" as the respective keys. This is
193 * not performed if existing values already exist for the username and
194 * password in the shared state, or if authentication fails.</dd>
195 *
196 * <dt> <code>clearPass</code> </dt>
197 * <dd> if <code>true</code>, this module clears the username and password
198 * stored in the module's shared state after both phases of authentication
199 * (login and commit) have completed.</dd>
200 *
201 * <dt> <code>debug</code> </dt>
202 * <dd> if <code>true</code>, debug messages are displayed on the standard
203 * output stream.
204 * </dl>
205 * </dl>
206 *
207 * <p>
208 * Arbitrary
209 * <a href="{@docRoot}/../../../../../technotes/guides/jndi/jndi-ldap-gl.html#PROP">JNDI properties</a>
210 * may also be specified in the {@link Configuration}.
211 * They are added to the environment and passed to the LDAP provider.
212 * Note that the following four JNDI properties are set by this module directly
213 * and are ignored if also present in the configuration:
214 * <ul>
215 * <li> <code>java.naming.provider.url</code>
216 * <li> <code>java.naming.security.principal</code>
217 * <li> <code>java.naming.security.credentials</code>
218 * <li> <code>java.naming.security.protocol</code>
219 * </ul>
220 *
221 * <p>
222 * Three sample {@link Configuration}s are shown below.
223 * The first one activates search-first mode. It identifies the LDAP server
224 * and specifies that users' entries be located by their <code>uid</code> and
225 * <code>objectClass</code> attributes. It also specifies that an identity
226 * based on the user's <code>employeeNumber</code> attribute should be created.
227 * The second one activates authentication-first mode. It requests that the
228 * LDAP server be located dynamically, that authentication be performed using
229 * the supplied username directly but without the protection of SSL and that
230 * users' entries be located by one of three naming attributes and their
231 * <code>objectClass</code> attribute.
232 * The third one activates authentication-only mode. It identifies alternative
233 * LDAP servers, it specifies the distinguished name to use for
234 * authentication and a fixed identity to use for authorization. No directory
235 * search is performed.
236 *
237 * <pre>
238 *
239 * ExampleApplication {
240 * com.sun.security.auth.module.LdapLoginModule REQUIRED
241 * userProvider="ldap://ldap-svr/ou=people,dc=example,dc=com"
242 * userFilter="(&(uid={USERNAME})(objectClass=inetOrgPerson))"
243 * authzIdentity="{EMPLOYEENUMBER}"
244 * debug=true;
245 * };
246 *
247 * ExampleApplication {
248 * com.sun.security.auth.module.LdapLoginModule REQUIRED
249 * userProvider="ldap:///cn=users,dc=example,dc=com"
250 * authIdentity="{USERNAME}"
251 * userFilter="(&(|(samAccountName={USERNAME})(userPrincipalName={USERNAME})(cn={USERNAME}))(objectClass=user))"
252 * useSSL=false
253 * debug=true;
254 * };
255 *
256 * ExampleApplication {
257 * com.sun.security.auth.module.LdapLoginModule REQUIRED
258 * userProvider="ldap://ldap-svr1 ldap://ldap-svr2"
259 * authIdentity="cn={USERNAME},ou=people,dc=example,dc=com"
260 * authzIdentity="staff"
261 * debug=true;
262 * };
263 *
264 * </pre>
265 *
266 * <dl>
267 * <dt><b>Note:</b> </dt>
268 * <dd>When a {@link SecurityManager} is active then an application
269 * that creates a {@link LoginContext} and uses a {@link LoginModule}
270 * must be granted certain permissions.
271 * <p>
272 * If the application creates a login context using an <em>installed</em>
273 * {@link Configuration} then the application must be granted the
274 * {@link AuthPermission} to create login contexts.
275 * For example, the following security policy allows an application in
276 * the user's current directory to instantiate <em>any</em> login context:
277 * <pre>
278 *
279 * grant codebase "file:${user.dir}/" {
280 * permission javax.security.auth.AuthPermission "createLoginContext.*";
281 * };
282 * </pre>
283 *
284 * Alternatively, if the application creates a login context using a
285 * <em>caller-specified</em> {@link Configuration} then the application
286 * must be granted the permissions required by the {@link LoginModule}.
287 * <em>This</em> module requires the following two permissions:
288 * <p>
289 * <ul>
290 * <li> The {@link SocketPermission} to connect to an LDAP server.
291 * <li> The {@link AuthPermission} to modify the set of {@link Principal}s
292 * associated with a {@link Subject}.
293 * </ul>
294 * <p>
295 * For example, the following security policy grants an application in the
296 * user's current directory all the permissions required by this module:
297 * <pre>
298 *
299 * grant codebase "file:${user.dir}/" {
300 * permission java.net.SocketPermission "*:389", "connect";
301 * permission java.net.SocketPermission "*:636", "connect";
302 * permission javax.security.auth.AuthPermission "modifyPrincipals";
303 * };
304 * </pre>
305 * </dd>
306 * </dl>
307 *
308 * @since 1.6
309 */
310public class LdapLoginModule implements LoginModule {
311
312 // Use the default classloader for this class to load the prompt strings.
313 private static final ResourceBundle rb = AccessController.doPrivileged(
314 new PrivilegedAction<ResourceBundle>() {
315 public ResourceBundle run() {
316 return ResourceBundle.getBundle(
317 "sun.security.util.AuthResources");
318 }
319 }
320 );
321
322 // Keys to retrieve the stored username and password
323 private static final String USERNAME_KEY = "javax.security.auth.login.name";
324 private static final String PASSWORD_KEY =
325 "javax.security.auth.login.password";
326
327 // Option names
328 private static final String USER_PROVIDER = "userProvider";
329 private static final String USER_FILTER = "userFilter";
330 private static final String AUTHC_IDENTITY = "authIdentity";
331 private static final String AUTHZ_IDENTITY = "authzIdentity";
332
333 // Used for the username token replacement
334 private static final String USERNAME_TOKEN = "{USERNAME}";
335 private static final Pattern USERNAME_PATTERN =
336 Pattern.compile("\\{USERNAME\\}");
337
338 // Configurable options
339 private String userProvider;
340 private String userFilter;
341 private String authcIdentity;
342 private String authzIdentity;
343 private String authzIdentityAttr = null;
344 private boolean useSSL = true;
345 private boolean authFirst = false;
346 private boolean authOnly = false;
347 private boolean useFirstPass = false;
348 private boolean tryFirstPass = false;
349 private boolean storePass = false;
350 private boolean clearPass = false;
351 private boolean debug = false;
352
353 // Authentication status
354 private boolean succeeded = false;
355 private boolean commitSucceeded = false;
356
357 // Supplied username and password
358 private String username;
359 private char[] password;
360
361 // User's identities
362 private LdapPrincipal ldapPrincipal;
363 private UserPrincipal userPrincipal;
364 private UserPrincipal authzPrincipal;
365
366 // Initial state
367 private Subject subject;
368 private CallbackHandler callbackHandler;
369 private Map sharedState;
370 private Map<String, ?> options;
371 private LdapContext ctx;
372 private Matcher identityMatcher = null;
373 private Matcher filterMatcher = null;
374 private Hashtable ldapEnvironment;
375 private SearchControls constraints = null;
376
377 /**
378 * Initialize this <code>LoginModule</code>.
379 *
380 * @param subject the <code>Subject</code> to be authenticated.
381 * @param callbackHandler a <code>CallbackHandler</code> to acquire the
382 * username and password.
383 * @param sharedState shared <code>LoginModule</code> state.
384 * @param options options specified in the login
385 * <code>Configuration</code> for this particular
386 * <code>LoginModule</code>.
387 */
388 public void initialize(Subject subject, CallbackHandler callbackHandler,
389 Map<String, ?> sharedState, Map<String, ?> options) {
390
391 this.subject = subject;
392 this.callbackHandler = callbackHandler;
393 this.sharedState = sharedState;
394 this.options = options;
395
396 ldapEnvironment = new Hashtable(9);
397 ldapEnvironment.put(Context.INITIAL_CONTEXT_FACTORY,
398 "com.sun.jndi.ldap.LdapCtxFactory");
399
400 // Add any JNDI properties to the environment
401 for (String key : options.keySet()) {
402 if (key.indexOf(".") > -1) {
403 ldapEnvironment.put(key, options.get(key));
404 }
405 }
406
407 // initialize any configured options
408
409 userProvider = (String)options.get(USER_PROVIDER);
410 if (userProvider != null) {
411 ldapEnvironment.put(Context.PROVIDER_URL, userProvider);
412 }
413
414 authcIdentity = (String)options.get(AUTHC_IDENTITY);
415 if (authcIdentity != null &&
416 (authcIdentity.indexOf(USERNAME_TOKEN) != -1)) {
417 identityMatcher = USERNAME_PATTERN.matcher(authcIdentity);
418 }
419
420 userFilter = (String)options.get(USER_FILTER);
421 if (userFilter != null) {
422 if (userFilter.indexOf(USERNAME_TOKEN) != -1) {
423 filterMatcher = USERNAME_PATTERN.matcher(userFilter);
424 }
425 constraints = new SearchControls();
426 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
427 constraints.setReturningAttributes(new String[0]); //return no attrs
428 constraints.setReturningObjFlag(true); // to get the full DN
429 }
430
431 authzIdentity = (String)options.get(AUTHZ_IDENTITY);
432 if (authzIdentity != null &&
433 authzIdentity.startsWith("{") && authzIdentity.endsWith("}")) {
434 if (constraints != null) {
435 authzIdentityAttr =
436 authzIdentity.substring(1, authzIdentity.length() - 1);
437 constraints.setReturningAttributes(
438 new String[]{authzIdentityAttr});
439 }
440 authzIdentity = null; // set later, from the specified attribute
441 }
442
443 // determine mode
444 if (authcIdentity != null) {
445 if (userFilter != null) {
446 authFirst = true; // authentication-first mode
447 } else {
448 authOnly = true; // authentication-only mode
449 }
450 }
451
452 if ("false".equalsIgnoreCase((String)options.get("useSSL"))) {
453 useSSL = false;
454 ldapEnvironment.remove(Context.SECURITY_PROTOCOL);
455 } else {
456 ldapEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
457 }
458
459 tryFirstPass =
460 "true".equalsIgnoreCase((String)options.get("tryFirstPass"));
461
462 useFirstPass =
463 "true".equalsIgnoreCase((String)options.get("useFirstPass"));
464
465 storePass = "true".equalsIgnoreCase((String)options.get("storePass"));
466
467 clearPass = "true".equalsIgnoreCase((String)options.get("clearPass"));
468
469 debug = "true".equalsIgnoreCase((String)options.get("debug"));
470
471 if (debug) {
472 if (authFirst) {
473 System.out.println("\t\t[LdapLoginModule] " +
474 "authentication-first mode; " +
475 (useSSL ? "SSL enabled" : "SSL disabled"));
476 } else if (authOnly) {
477 System.out.println("\t\t[LdapLoginModule] " +
478 "authentication-only mode; " +
479 (useSSL ? "SSL enabled" : "SSL disabled"));
480 } else {
481 System.out.println("\t\t[LdapLoginModule] " +
482 "search-first mode; " +
483 (useSSL ? "SSL enabled" : "SSL disabled"));
484 }
485 }
486 }
487
488 /**
489 * Begin user authentication.
490 *
491 * <p> Acquire the user's credentials and verify them against the
492 * specified LDAP directory.
493 *
494 * @return true always, since this <code>LoginModule</code>
495 * should not be ignored.
496 * @exception FailedLoginException if the authentication fails.
497 * @exception LoginException if this <code>LoginModule</code>
498 * is unable to perform the authentication.
499 */
500 public boolean login() throws LoginException {
501
502 if (userProvider == null) {
503 throw new LoginException
504 ("Unable to locate the LDAP directory service");
505 }
506
507 if (debug) {
508 System.out.println("\t\t[LdapLoginModule] user provider: " +
509 userProvider);
510 }
511
512 // attempt the authentication
513 if (tryFirstPass) {
514
515 try {
516 // attempt the authentication by getting the
517 // username and password from shared state
518 attemptAuthentication(true);
519
520 // authentication succeeded
521 succeeded = true;
522 if (debug) {
523 System.out.println("\t\t[LdapLoginModule] " +
524 "tryFirstPass succeeded");
525 }
526 return true;
527
528 } catch (LoginException le) {
529 // authentication failed -- try again below by prompting
530 cleanState();
531 if (debug) {
532 System.out.println("\t\t[LdapLoginModule] " +
533 "tryFirstPass failed: " + le.toString());
534 }
535 }
536
537 } else if (useFirstPass) {
538
539 try {
540 // attempt the authentication by getting the
541 // username and password from shared state
542 attemptAuthentication(true);
543
544 // authentication succeeded
545 succeeded = true;
546 if (debug) {
547 System.out.println("\t\t[LdapLoginModule] " +
548 "useFirstPass succeeded");
549 }
550 return true;
551
552 } catch (LoginException le) {
553 // authentication failed
554 cleanState();
555 if (debug) {
556 System.out.println("\t\t[LdapLoginModule] " +
557 "useFirstPass failed");
558 }
559 throw le;
560 }
561 }
562
563 // attempt the authentication by prompting for the username and pwd
564 try {
565 attemptAuthentication(false);
566
567 // authentication succeeded
568 succeeded = true;
569 if (debug) {
570 System.out.println("\t\t[LdapLoginModule] " +
571 "authentication succeeded");
572 }
573 return true;
574
575 } catch (LoginException le) {
576 cleanState();
577 if (debug) {
578 System.out.println("\t\t[LdapLoginModule] " +
579 "authentication failed");
580 }
581 throw le;
582 }
583 }
584
585 /**
586 * Complete user authentication.
587 *
588 * <p> This method is called if the LoginContext's
589 * overall authentication succeeded
590 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
591 * succeeded).
592 *
593 * <p> If this LoginModule's own authentication attempt
594 * succeeded (checked by retrieving the private state saved by the
595 * <code>login</code> method), then this method associates an
596 * <code>LdapPrincipal</code> and one or more <code>UserPrincipal</code>s
597 * with the <code>Subject</code> located in the
598 * <code>LoginModule</code>. If this LoginModule's own
599 * authentication attempted failed, then this method removes
600 * any state that was originally saved.
601 *
602 * @exception LoginException if the commit fails
603 * @return true if this LoginModule's own login and commit
604 * attempts succeeded, or false otherwise.
605 */
606 public boolean commit() throws LoginException {
607
608 if (succeeded == false) {
609 return false;
610 } else {
611 if (subject.isReadOnly()) {
612 cleanState();
613 throw new LoginException ("Subject is read-only");
614 }
615 // add Principals to the Subject
616 Set<Principal> principals = subject.getPrincipals();
617 if (! principals.contains(ldapPrincipal)) {
618 principals.add(ldapPrincipal);
619 }
620 if (debug) {
621 System.out.println("\t\t[LdapLoginModule] " +
622 "added LdapPrincipal \"" +
623 ldapPrincipal +
624 "\" to Subject");
625 }
626
627 if (! principals.contains(userPrincipal)) {
628 principals.add(userPrincipal);
629 }
630 if (debug) {
631 System.out.println("\t\t[LdapLoginModule] " +
632 "added UserPrincipal \"" +
633 userPrincipal +
634 "\" to Subject");
635 }
636
637 if (authzPrincipal != null &&
638 (! principals.contains(authzPrincipal))) {
639 principals.add(authzPrincipal);
640
641 if (debug) {
642 System.out.println("\t\t[LdapLoginModule] " +
643 "added UserPrincipal \"" +
644 authzPrincipal +
645 "\" to Subject");
646 }
647 }
648 }
649 // in any case, clean out state
650 cleanState();
651 commitSucceeded = true;
652 return true;
653 }
654
655 /**
656 * Abort user authentication.
657 *
658 * <p> This method is called if the overall authentication failed.
659 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
660 * did not succeed).
661 *
662 * <p> If this LoginModule's own authentication attempt
663 * succeeded (checked by retrieving the private state saved by the
664 * <code>login</code> and <code>commit</code> methods),
665 * then this method cleans up any state that was originally saved.
666 *
667 * @exception LoginException if the abort fails.
668 * @return false if this LoginModule's own login and/or commit attempts
669 * failed, and true otherwise.
670 */
671 public boolean abort() throws LoginException {
672 if (debug)
673 System.out.println("\t\t[LdapLoginModule] " +
674 "aborted authentication");
675
676 if (succeeded == false) {
677 return false;
678 } else if (succeeded == true && commitSucceeded == false) {
679
680 // Clean out state
681 succeeded = false;
682 cleanState();
683
684 ldapPrincipal = null;
685 userPrincipal = null;
686 authzPrincipal = null;
687 } else {
688 // overall authentication succeeded and commit succeeded,
689 // but someone else's commit failed
690 logout();
691 }
692 return true;
693 }
694
695 /**
696 * Logout a user.
697 *
698 * <p> This method removes the Principals
699 * that were added by the <code>commit</code> method.
700 *
701 * @exception LoginException if the logout fails.
702 * @return true in all cases since this <code>LoginModule</code>
703 * should not be ignored.
704 */
705 public boolean logout() throws LoginException {
706 if (subject.isReadOnly()) {
707 cleanState();
708 throw new LoginException ("Subject is read-only");
709 }
710 Set<Principal> principals = subject.getPrincipals();
711 principals.remove(ldapPrincipal);
712 principals.remove(userPrincipal);
713 if (authzIdentity != null) {
714 principals.remove(authzPrincipal);
715 }
716
717 // clean out state
718 cleanState();
719 succeeded = false;
720 commitSucceeded = false;
721
722 ldapPrincipal = null;
723 userPrincipal = null;
724 authzPrincipal = null;
725
726 if (debug) {
727 System.out.println("\t\t[LdapLoginModule] logged out Subject");
728 }
729 return true;
730 }
731
732 /**
733 * Attempt authentication
734 *
735 * @param getPasswdFromSharedState boolean that tells this method whether
736 * to retrieve the password from the sharedState.
737 * @exception LoginException if the authentication attempt fails.
738 */
739 private void attemptAuthentication(boolean getPasswdFromSharedState)
740 throws LoginException {
741
742 // first get the username and password
743 getUsernamePassword(getPasswdFromSharedState);
744
745 if (password == null || password.length == 0) {
746 throw (LoginException)
747 new FailedLoginException("No password was supplied");
748 }
749
750 String dn = "";
751
752 if (authFirst || authOnly) {
753
754 String id = replaceUsernameToken(identityMatcher, authcIdentity);
755
756 // Prepare to bind using user's username and password
757 ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password);
758 ldapEnvironment.put(Context.SECURITY_PRINCIPAL, id);
759
760 if (debug) {
761 System.out.println("\t\t[LdapLoginModule] " +
762 "attempting to authenticate user: " + username);
763 }
764
765 try {
766 // Connect to the LDAP server (using simple bind)
767 ctx = new InitialLdapContext(ldapEnvironment, null);
768
769 } catch (NamingException e) {
770 throw (LoginException)
771 new FailedLoginException("Cannot bind to LDAP server")
772 .initCause(e);
773 }
774
775 // Authentication has succeeded
776
777 // Locate the user's distinguished name
778 if (userFilter != null) {
779 dn = findUserDN(ctx);
780 } else {
781 dn = id;
782 }
783
784 } else {
785
786 try {
787 // Connect to the LDAP server (using anonymous bind)
788 ctx = new InitialLdapContext(ldapEnvironment, null);
789
790 } catch (NamingException e) {
791 throw (LoginException)
792 new FailedLoginException("Cannot connect to LDAP server")
793 .initCause(e);
794 }
795
796 // Locate the user's distinguished name
797 dn = findUserDN(ctx);
798
799 try {
800
801 // Prepare to bind using user's distinguished name and password
802 ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
803 ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
804 ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
805
806 if (debug) {
807 System.out.println("\t\t[LdapLoginModule] " +
808 "attempting to authenticate user: " + username);
809 }
810 // Connect to the LDAP server (using simple bind)
811 ctx.reconnect(null);
812
813 // Authentication has succeeded
814
815 } catch (NamingException e) {
816 throw (LoginException)
817 new FailedLoginException("Cannot bind to LDAP server")
818 .initCause(e);
819 }
820 }
821
822 // Save input as shared state only if authentication succeeded
823 if (storePass &&
824 !sharedState.containsKey(USERNAME_KEY) &&
825 !sharedState.containsKey(PASSWORD_KEY)) {
826 sharedState.put(USERNAME_KEY, username);
827 sharedState.put(PASSWORD_KEY, password);
828 }
829
830 // Create the user principals
831 userPrincipal = new UserPrincipal(username);
832 if (authzIdentity != null) {
833 authzPrincipal = new UserPrincipal(authzIdentity);
834 }
835
836 try {
837
838 ldapPrincipal = new LdapPrincipal(dn);
839
840 } catch (InvalidNameException e) {
841 if (debug) {
842 System.out.println("\t\t[LdapLoginModule] " +
843 "cannot create LdapPrincipal: bad DN");
844 }
845 throw (LoginException)
846 new FailedLoginException("Cannot create LdapPrincipal")
847 .initCause(e);
848 }
849 }
850
851 /**
852 * Search for the user's entry.
853 * Determine the distinguished name of the user's entry and optionally
854 * an authorization identity for the user.
855 *
856 * @param ctx an LDAP context to use for the search
857 * @return the user's distinguished name or an empty string if none
858 * was found.
859 * @exception LoginException if the user's entry cannot be found.
860 */
861 private String findUserDN(LdapContext ctx) throws LoginException {
862
863 String userDN = "";
864
865 // Locate the user's LDAP entry
866 if (userFilter != null) {
867 if (debug) {
868 System.out.println("\t\t[LdapLoginModule] " +
869 "searching for entry belonging to user: " + username);
870 }
871 } else {
872 if (debug) {
873 System.out.println("\t\t[LdapLoginModule] " +
874 "cannot search for entry belonging to user: " + username);
875 }
876 throw (LoginException)
877 new FailedLoginException("Cannot find user's LDAP entry");
878 }
879
880 try {
881 NamingEnumeration<SearchResult> results = ctx.search("",
882 replaceUsernameToken(filterMatcher, userFilter), constraints);
883
884 // Extract the distinguished name of the user's entry
885 // (Use the first entry if more than one is returned)
886 if (results.hasMore()) {
887 SearchResult entry = results.next();
888
889 // %%% - use the SearchResult.getNameInNamespace method
890 // available in JDK 1.5 and later.
891 // (can remove call to constraints.setReturningObjFlag)
892 userDN = ((Context)entry.getObject()).getNameInNamespace();
893
894 if (debug) {
895 System.out.println("\t\t[LdapLoginModule] found entry: " +
896 userDN);
897 }
898
899 // Extract a value from user's authorization identity attribute
900 if (authzIdentityAttr != null) {
901 Attribute attr =
902 entry.getAttributes().get(authzIdentityAttr);
903 if (attr != null) {
904 Object val = attr.get();
905 if (val instanceof String) {
906 authzIdentity = (String) val;
907 }
908 }
909 }
910
911 results.close();
912
913 } else {
914 // Bad username
915 if (debug) {
916 System.out.println("\t\t[LdapLoginModule] user's entry " +
917 "not found");
918 }
919 }
920
921 } catch (NamingException e) {
922 // ignore
923 }
924
925 if (userDN.equals("")) {
926 throw (LoginException)
927 new FailedLoginException("Cannot find user's LDAP entry");
928 } else {
929 return userDN;
930 }
931 }
932
933 /**
934 * Replace the username token
935 *
936 * @param string the target string
937 * @return the modified string
938 */
939 private String replaceUsernameToken(Matcher matcher, String string) {
940 return matcher != null ? matcher.replaceAll(username) : string;
941 }
942
943 /**
944 * Get the username and password.
945 * This method does not return any value.
946 * Instead, it sets global name and password variables.
947 *
948 * <p> Also note that this method will set the username and password
949 * values in the shared state in case subsequent LoginModules
950 * want to use them via use/tryFirstPass.
951 *
952 * @param getPasswdFromSharedState boolean that tells this method whether
953 * to retrieve the password from the sharedState.
954 * @exception LoginException if the username/password cannot be acquired.
955 */
956 private void getUsernamePassword(boolean getPasswdFromSharedState)
957 throws LoginException {
958
959 if (getPasswdFromSharedState) {
960 // use the password saved by the first module in the stack
961 username = (String)sharedState.get(USERNAME_KEY);
962 password = (char[])sharedState.get(PASSWORD_KEY);
963 return;
964 }
965
966 // prompt for a username and password
967 if (callbackHandler == null)
968 throw new LoginException("No CallbackHandler available " +
969 "to acquire authentication information from the user");
970
971 Callback[] callbacks = new Callback[2];
972 callbacks[0] = new NameCallback(rb.getString("username."));
973 callbacks[1] = new PasswordCallback(rb.getString("password."), false);
974
975 try {
976 callbackHandler.handle(callbacks);
977 username = ((NameCallback)callbacks[0]).getName();
978 char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
979 password = new char[tmpPassword.length];
980 System.arraycopy(tmpPassword, 0,
981 password, 0, tmpPassword.length);
982 ((PasswordCallback)callbacks[1]).clearPassword();
983
984 } catch (java.io.IOException ioe) {
985 throw new LoginException(ioe.toString());
986
987 } catch (UnsupportedCallbackException uce) {
988 throw new LoginException("Error: " + uce.getCallback().toString() +
989 " not available to acquire authentication information" +
990 " from the user");
991 }
992 }
993
994 /**
995 * Clean out state because of a failed authentication attempt
996 */
997 private void cleanState() {
998 username = null;
999 if (password != null) {
1000 Arrays.fill(password, ' ');
1001 password = null;
1002 }
1003 try {
1004 if (ctx != null) {
1005 ctx.close();
1006 }
1007 } catch (NamingException e) {
1008 // ignore
1009 }
1010 ctx = null;
1011
1012 if (clearPass) {
1013 sharedState.remove(USERNAME_KEY);
1014 sharedState.remove(PASSWORD_KEY);
1015 }
1016 }
1017}