767 lines · 29.6 KB · Java
RawDownload
/* * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.security.auth.module; import javax.security.auth.*;import javax.security.auth.callback.*;import javax.security.auth.login.*;import javax.security.auth.spi.*;import javax.naming.*;import javax.naming.directory.*; import java.io.IOException;import java.util.Map;import java.util.LinkedList;import java.util.ResourceBundle; import com.sun.security.auth.UnixPrincipal;import com.sun.security.auth.UnixNumericUserPrincipal;import com.sun.security.auth.UnixNumericGroupPrincipal; import sun.security.util.AuthResources; /** * <p> The module prompts for a username and password * and then verifies the password against the password stored in * a directory service configured under JNDI. * * <p> This <code>LoginModule</code> interoperates with * any conformant JNDI service provider.  To direct this * <code>LoginModule</code> to use a specific JNDI service provider, * two options must be specified in the login <code>Configuration</code> * for this <code>LoginModule</code>. * <pre> *      user.provider.url=<b>name_service_url</b> *      group.provider.url=<b>name_service_url</b> * </pre> * * <b>name_service_url</b> specifies * the directory service and path where this <code>LoginModule</code> * can access the relevant user and group information.  Because this * <code>LoginModule</code> only performs one-level searches to * find the relevant user information, the <code>URL</code> * must point to a directory one level above where the user and group * information is stored in the directory service. * For example, to instruct this <code>LoginModule</code> * to contact a NIS server, the following URLs must be specified: * <pre> *    user.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/user" *    group.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/system/group" * </pre> * * <b>NISServerHostName</b> specifies the server host name of the * NIS server (for example, <i>nis.sun.com</i>, and <b>NISDomain</b> * specifies the domain for that NIS server (for example, <i>jaas.sun.com</i>. * To contact an LDAP server, the following URLs must be specified: * <pre> *    user.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>" *    group.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>" * </pre> * * <b>LDAPServerHostName</b> specifies the server host name of the * LDAP server, which may include a port number * (for example, <i>ldap.sun.com:389</i>), * and <b>LDAPName</b> specifies the entry name in the LDAP directory * (for example, <i>ou=People,o=Sun,c=US</i> and <i>ou=Groups,o=Sun,c=US</i> * for user and group information, respectively). * * <p> The format in which the user's information must be stored in * the directory service is specified in RFC 2307.  Specifically, * this <code>LoginModule</code> will search for the user's entry in the * directory service using the user's <i>uid</i> attribute, * where <i>uid=<b>username</b></i>.  If the search succeeds, * this <code>LoginModule</code> will then * obtain the user's encrypted password from the retrieved entry * using the <i>userPassword</i> attribute. * This <code>LoginModule</code> assumes that the password is stored * as a byte array, which when converted to a <code>String</code>, * has the following format: * <pre> *      "{crypt}<b>encrypted_password</b>" * </pre> * * The LDAP directory server must be configured * to permit read access to the userPassword attribute. * If the user entered a valid username and password, * this <code>LoginModule</code> associates a * <code>UnixPrincipal</code>, <code>UnixNumericUserPrincipal</code>, * and the relevant UnixNumericGroupPrincipals with the * <code>Subject</code>. * * <p> This LoginModule also recognizes the following <code>Configuration</code> * options: * <pre> *    debug          if, true, debug messages are output to System.out. * *    useFirstPass   if, true, this LoginModule retrieves the *                   username and password from the module's shared state, *                   using "javax.security.auth.login.name" and *                   "javax.security.auth.login.password" as the respective *                   keys.  The retrieved values are used for authentication. *                   If authentication fails, no attempt for a retry is made, *                   and the failure is reported back to the calling *                   application. * *    tryFirstPass   if, true, this LoginModule retrieves the *                   the username and password from the module's shared state, *                   using "javax.security.auth.login.name" and *                   "javax.security.auth.login.password" as the respective *                   keys.  The retrieved values are used for authentication. *                   If authentication fails, the module uses the *                   CallbackHandler to retrieve a new username and password, *                   and another attempt to authenticate is made. *                   If the authentication fails, the failure is reported *                   back to the calling application. * *    storePass      if, true, this LoginModule stores the username and password *                   obtained from the CallbackHandler in the module's *                   shared state, using "javax.security.auth.login.name" and *                   "javax.security.auth.login.password" as the respective *                   keys.  This is not performed if existing values already *                   exist for the username and password in the shared state, *                   or if authentication fails. * *    clearPass     if, true, this <code>LoginModule</code> clears the *                  username and password stored in the module's shared state *                  after both phases of authentication (login and commit) *                  have completed. * </pre> * */public class JndiLoginModule implements LoginModule {     static final java.util.ResourceBundle rb =        java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");     /** JNDI Provider */    public final String USER_PROVIDER = "user.provider.url";    public final String GROUP_PROVIDER = "group.provider.url";     // configurable options    private boolean debug = false;    private boolean strongDebug = false;    private String userProvider;    private String groupProvider;    private boolean useFirstPass = false;    private boolean tryFirstPass = false;    private boolean storePass = false;    private boolean clearPass = false;     // the authentication status    private boolean succeeded = false;    private boolean commitSucceeded = false;     // username, password, and JNDI context    private String username;    private char[] password;    DirContext ctx;     // the user (assume it is a UnixPrincipal)    private UnixPrincipal userPrincipal;    private UnixNumericUserPrincipal UIDPrincipal;    private UnixNumericGroupPrincipal GIDPrincipal;    private LinkedList<UnixNumericGroupPrincipal> supplementaryGroups =                                new LinkedList<>();     // initial state    private Subject subject;    private CallbackHandler callbackHandler;    private Map sharedState;    private Map<String, ?> options;     private static final String CRYPT = "{crypt}";    private static final String USER_PWD = "userPassword";    private static final String USER_UID = "uidNumber";    private static final String USER_GID = "gidNumber";    private static final String GROUP_ID = "gidNumber";    private static final String NAME = "javax.security.auth.login.name";    private static final String PWD = "javax.security.auth.login.password";     /**     * Initialize this <code>LoginModule</code>.     *     * <p>     *     * @param subject the <code>Subject</code> to be authenticated. <p>     *     * @param callbackHandler a <code>CallbackHandler</code> for communicating     *                  with the end user (prompting for usernames and     *                  passwords, for example). <p>     *     * @param sharedState shared <code>LoginModule</code> state. <p>     *     * @param options options specified in the login     *                  <code>Configuration</code> for this particular     *                  <code>LoginModule</code>.     */    public void initialize(Subject subject, CallbackHandler callbackHandler,                           Map<String,?> sharedState,                           Map<String,?> options) {         this.subject = subject;        this.callbackHandler = callbackHandler;        this.sharedState = sharedState;        this.options = options;         // initialize any configured options        debug = "true".equalsIgnoreCase((String)options.get("debug"));        strongDebug =                "true".equalsIgnoreCase((String)options.get("strongDebug"));        userProvider = (String)options.get(USER_PROVIDER);        groupProvider = (String)options.get(GROUP_PROVIDER);        tryFirstPass =                "true".equalsIgnoreCase((String)options.get("tryFirstPass"));        useFirstPass =                "true".equalsIgnoreCase((String)options.get("useFirstPass"));        storePass =                "true".equalsIgnoreCase((String)options.get("storePass"));        clearPass =                "true".equalsIgnoreCase((String)options.get("clearPass"));    }     /**     * <p> Prompt for username and password.     * Verify the password against the relevant name service.     *     * <p>     *     * @return true always, since this <code>LoginModule</code>     *          should not be ignored.     *     * @exception FailedLoginException if the authentication fails. <p>     *     * @exception LoginException if this <code>LoginModule</code>     *          is unable to perform the authentication.     */    public boolean login() throws LoginException {         if (userProvider == null) {            throw new LoginException                ("Error: Unable to locate JNDI user provider");        }        if (groupProvider == null) {            throw new LoginException                ("Error: Unable to locate JNDI group provider");        }         if (debug) {            System.out.println("\t\t[JndiLoginModule] user provider: " +                                userProvider);            System.out.println("\t\t[JndiLoginModule] group provider: " +                                groupProvider);        }         // attempt the authentication        if (tryFirstPass) {             try {                // attempt the authentication by getting the                // username and password from shared state                attemptAuthentication(true);                 // authentication succeeded                succeeded = true;                if (debug) {                    System.out.println("\t\t[JndiLoginModule] " +                                "tryFirstPass succeeded");                }                return true;            } catch (LoginException le) {                // authentication failed -- try again below by prompting                cleanState();                if (debug) {                    System.out.println("\t\t[JndiLoginModule] " +                                "tryFirstPass failed with:" +                                le.toString());                }            }         } else if (useFirstPass) {             try {                // attempt the authentication by getting the                // username and password from shared state                attemptAuthentication(true);                 // authentication succeeded                succeeded = true;                if (debug) {                    System.out.println("\t\t[JndiLoginModule] " +                                "useFirstPass succeeded");                }                return true;            } catch (LoginException le) {                // authentication failed                cleanState();                if (debug) {                    System.out.println("\t\t[JndiLoginModule] " +                                "useFirstPass failed");                }                throw le;            }        }         // attempt the authentication by prompting for the username and pwd        try {            attemptAuthentication(false);             // authentication succeeded           succeeded = true;            if (debug) {                System.out.println("\t\t[JndiLoginModule] " +                                "regular authentication succeeded");            }            return true;        } catch (LoginException le) {            cleanState();            if (debug) {                System.out.println("\t\t[JndiLoginModule] " +                                "regular authentication failed");            }            throw le;        }    }     /**     * Abstract method to commit the authentication process (phase 2).     *     * <p> This method is called if the LoginContext's     * overall authentication succeeded     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules     * succeeded).     *     * <p> If this LoginModule's own authentication attempt     * succeeded (checked by retrieving the private state saved by the     * <code>login</code> method), then this method associates a     * <code>UnixPrincipal</code>     * with the <code>Subject</code> located in the     * <code>LoginModule</code>.  If this LoginModule's own     * authentication attempted failed, then this method removes     * any state that was originally saved.     *     * <p>     *     * @exception LoginException if the commit fails     *     * @return true if this LoginModule's own login and commit     *          attempts succeeded, or false otherwise.     */    public boolean commit() throws LoginException {         if (succeeded == false) {            return false;        } else {            if (subject.isReadOnly()) {                cleanState();                throw new LoginException ("Subject is Readonly");            }            // add Principals to the Subject            if (!subject.getPrincipals().contains(userPrincipal))                subject.getPrincipals().add(userPrincipal);            if (!subject.getPrincipals().contains(UIDPrincipal))                subject.getPrincipals().add(UIDPrincipal);            if (!subject.getPrincipals().contains(GIDPrincipal))                subject.getPrincipals().add(GIDPrincipal);            for (int i = 0; i < supplementaryGroups.size(); i++) {                if (!subject.getPrincipals().contains                        (supplementaryGroups.get(i)))                    subject.getPrincipals().add(supplementaryGroups.get(i));            }             if (debug) {                System.out.println("\t\t[JndiLoginModule]: " +                                   "added UnixPrincipal,");                System.out.println("\t\t\t\tUnixNumericUserPrincipal,");                System.out.println("\t\t\t\tUnixNumericGroupPrincipal(s),");                System.out.println("\t\t\t to Subject");            }        }        // in any case, clean out state        cleanState();        commitSucceeded = true;        return true;    }     /**     * <p> This method is called if the LoginContext's     * overall authentication failed.     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules     * did not succeed).     *     * <p> If this LoginModule's own authentication attempt     * succeeded (checked by retrieving the private state saved by the     * <code>login</code> and <code>commit</code> methods),     * then this method cleans up any state that was originally saved.     *     * <p>     *     * @exception LoginException if the abort fails.     *     * @return false if this LoginModule's own login and/or commit attempts     *          failed, and true otherwise.     */    public boolean abort() throws LoginException {        if (debug)            System.out.println("\t\t[JndiLoginModule]: " +                "aborted authentication failed");         if (succeeded == false) {            return false;        } else if (succeeded == true && commitSucceeded == false) {             // Clean out state            succeeded = false;            cleanState();             userPrincipal = null;            UIDPrincipal = null;            GIDPrincipal = null;            supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();        } else {            // overall authentication succeeded and commit succeeded,            // but someone else's commit failed            logout();        }        return true;    }     /**     * Logout a user.     *     * <p> This method removes the Principals     * that were added by the <code>commit</code> method.     *     * <p>     *     * @exception LoginException if the logout fails.     *     * @return true in all cases since this <code>LoginModule</code>     *          should not be ignored.     */    public boolean logout() throws LoginException {        if (subject.isReadOnly()) {            cleanState();            throw new LoginException ("Subject is Readonly");        }        subject.getPrincipals().remove(userPrincipal);        subject.getPrincipals().remove(UIDPrincipal);        subject.getPrincipals().remove(GIDPrincipal);        for (int i = 0; i < supplementaryGroups.size(); i++) {            subject.getPrincipals().remove(supplementaryGroups.get(i));        }          // clean out state        cleanState();        succeeded = false;        commitSucceeded = false;         userPrincipal = null;        UIDPrincipal = null;        GIDPrincipal = null;        supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();         if (debug) {            System.out.println("\t\t[JndiLoginModule]: " +                "logged out Subject");        }        return true;    }     /**     * Attempt authentication     *     * <p>     *     * @param getPasswdFromSharedState boolean that tells this method whether     *          to retrieve the password from the sharedState.

Lines 1–500 of 767.