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+
26+package com.sun.security.auth.module;
27+
28+import java.io.IOException;
29+import java.security.AccessController;
30+import java.net.SocketPermission;
31+import java.security.Principal;
32+import java.security.PrivilegedAction;
33+import java.util.Arrays;
34+import java.util.Hashtable;
35+import java.util.Iterator;
36+import java.util.Map;
37+import java.util.ResourceBundle;
38+import java.util.regex.Matcher;
39+import java.util.regex.Pattern;
40+import java.util.Set;
41+
42+import javax.naming.*;
43+import javax.naming.directory.*;
44+import javax.naming.ldap.*;
45+import javax.security.auth.*;
46+import javax.security.auth.callback.*;
47+import javax.security.auth.login.*;
48+import javax.security.auth.spi.*;
49+
50+import com.sun.security.auth.LdapPrincipal;
51+import com.sun.security.auth.UserPrincipal;
52+
53+import 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+ */
310+public 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+}