Skip to content
LdapUserDAO.java 50.1 KiB
Newer Older
/*
 ************************************************************************
 *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
 **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
 *
 *  (c) 2014.                            (c) 2014.
 *  Government of Canada                 Gouvernement du Canada
 *  National Research Council            Conseil national de recherches
 *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
 *  All rights reserved                  Tous droits réservés
 *
 *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
 *  expressed, implied, or               énoncée, implicite ou légale,
 *  statutory, of any kind with          de quelque nature que ce
 *  respect to the software,             soit, concernant le logiciel,
 *  including without limitation         y compris sans restriction
 *  any warranty of merchantability      toute garantie de valeur
 *  or fitness for a particular          marchande ou de pertinence
 *  purpose. NRC shall not be            pour un usage particulier.
 *  liable in any event for any          Le CNRC ne pourra en aucun cas
 *  damages, whether direct or           être tenu responsable de tout
 *  indirect, special or general,        dommage, direct ou indirect,
 *  consequential or incidental,         particulier ou général,
 *  arising from the use of the          accessoire ou fortuit, résultant
 *  software.  Neither the name          de l'utilisation du logiciel. Ni
 *  of the National Research             le nom du Conseil National de
 *  Council of Canada nor the            Recherches du Canada ni les noms
 *  names of its contributors may        de ses  participants ne peuvent
 *  be used to endorse or promote        être utilisés pour approuver ou
 *  products derived from this           promouvoir les produits dérivés
 *  software without specific prior      de ce logiciel sans autorisation
 *  written permission.                  préalable et particulière
 *                                       par écrit.
 *
 *  This file is part of the             Ce fichier fait partie du projet
 *  OpenCADC project.                    OpenCADC.
 *
 *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
 *  you can redistribute it and/or       vous pouvez le redistribuer ou le
 *  modify it under the terms of         modifier suivant les termes de
 *  the GNU Affero General Public        la “GNU Affero General Public
 *  License as published by the          License” telle que publiée
 *  Free Software Foundation,            par la Free Software Foundation
 *  either version 3 of the              : soit la version 3 de cette
 *  License, or (at your option)         licence, soit (à votre gré)
 *  any later version.                   toute version ultérieure.
 *
 *  OpenCADC is distributed in the       OpenCADC est distribué
 *  hope that it will be useful,         dans l’espoir qu’il vous
 *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
 *  without even the implied             GARANTIE : sans même la garantie
 *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
 *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
 *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
 *  General Public License for           Générale Publique GNU Affero
 *  more details.                        pour plus de détails.
 *
 *  You should have received             Vous devriez avoir reçu une
 *  a copy of the GNU Affero             copie de la Licence Générale
 *  General Public License along         Publique GNU Affero avec
 *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
 *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
 *                                       <http://www.gnu.org/licenses/>.
 *
 *  $Revision: 4 $
 *
 ************************************************************************
 */
package ca.nrc.cadc.ac.server.ldap;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.AccessControlException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;

import javax.security.auth.x500.X500Principal;

import org.apache.log4j.Logger;

Jeff Burke's avatar
Jeff Burke committed
import ca.nrc.cadc.ac.AC;
import ca.nrc.cadc.ac.Group;
Jeff Burke's avatar
Jeff Burke committed
import ca.nrc.cadc.ac.InternalID;
Jeff Burke's avatar
Jeff Burke committed
import ca.nrc.cadc.ac.PersonalDetails;
Patrick Dowler's avatar
Patrick Dowler committed
import ca.nrc.cadc.ac.Role;
Jeff Burke's avatar
Jeff Burke committed
import ca.nrc.cadc.ac.User;
import ca.nrc.cadc.ac.UserAlreadyExistsException;
import ca.nrc.cadc.ac.UserNotFoundException;
import ca.nrc.cadc.ac.UserRequest;
Patrick Dowler's avatar
Patrick Dowler committed
import ca.nrc.cadc.ac.client.GroupMemberships;
import ca.nrc.cadc.auth.DNPrincipal;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.auth.NumericPrincipal;
import ca.nrc.cadc.net.TransientException;
import ca.nrc.cadc.profiler.Profiler;
import ca.nrc.cadc.util.ObjectUtil;
import ca.nrc.cadc.util.StringUtil;
import com.unboundid.ldap.sdk.AddRequest;
import com.unboundid.ldap.sdk.Attribute;
Jeff Burke's avatar
Jeff Burke committed
import com.unboundid.ldap.sdk.BindRequest;
import com.unboundid.ldap.sdk.BindResult;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.DeleteRequest;
import com.unboundid.ldap.sdk.Filter;
Brian Major's avatar
Brian Major committed
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.LDAPSearchException;
Jeff Burke's avatar
Jeff Burke committed
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModificationType;
import com.unboundid.ldap.sdk.ModifyDNRequest;
Jeff Burke's avatar
Jeff Burke committed
import com.unboundid.ldap.sdk.ModifyRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
Jeff Burke's avatar
Jeff Burke committed
import com.unboundid.ldap.sdk.SimpleBindRequest;
import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest;
import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult;

public class LdapUserDAO extends LdapDAO
    public static final String EMAIL_ADDRESS_CONFLICT_MESSAGE =
    private static final Logger logger = Logger.getLogger(LdapUserDAO.class);

Patrick Dowler's avatar
Patrick Dowler committed
    private final Profiler profiler = new Profiler(LdapUserDAO.class);
    private String internalIdUriPrefix = AC.USER_URI;

Jeff Burke's avatar
Jeff Burke committed
    // Map of identity type to LDAP attribute
    private final Map<Class<?>, String> userLdapAttrib = new HashMap<Class<?>, String>();

    // User cn and sn values for users without a HttpPrincipal
    protected static final String EXTERNAL_USER_CN = "$EXTERNAL-CN";
    protected static final String EXTERNAL_USER_SN = "$EXTERNAL-SN";

    // LDAP User attributes
    protected static final String LDAP_OBJECT_CLASS = "objectClass";
    protected static final String LDAP_INET_USER = "inetuser";
    protected static final String LDAP_INET_ORG_PERSON = "inetOrgPerson";
    protected static final String LDAP_CADC_ACCOUNT = "cadcaccount";
    protected static final String LDAP_NSACCOUNTLOCK = "nsaccountlock";
    protected static final String LDAP_MEMBEROF = "memberOf";
    protected static final String LDAP_ENTRYDN = "entrydn";
    protected static final String LDAP_USER_NAME = "cn";
    protected static final String LDAP_DISTINGUISHED_NAME = "distinguishedName";
    protected static final String LADP_USER_PASSWORD = "userPassword";
    protected static final String LDAP_FIRST_NAME = "givenName";
    protected static final String LDAP_LAST_NAME = "sn";
    protected static final String LDAP_ADDRESS = "address";
    protected static final String LDAP_CITY = "city";
    protected static final String LDAP_COUNTRY = "country";
    protected static final String LDAP_EMAIL = "email";
    protected static final String LDAP_INSTITUTE = "institute";
    protected static final String LDAP_UID = "uid";
    protected static final String USER_ID = "id";
    private String[] userAttribs = new String[]
    {
            LDAP_FIRST_NAME, LDAP_LAST_NAME, LDAP_ADDRESS, LDAP_CITY,
            LDAP_COUNTRY, LDAP_EMAIL, LDAP_INSTITUTE
    };
    private String[] firstLastAttribs = new String[]
    {
            LDAP_FIRST_NAME, LDAP_LAST_NAME
    };
    private String[] identityAttribs = new String[]
        LDAP_UID, LDAP_DISTINGUISHED_NAME, LDAP_ENTRYDN,
        LDAP_USER_NAME, LDAP_MEMBEROF // for group cache
Brian Major's avatar
Brian Major committed
    public LdapUserDAO(LdapConnections connections)
Brian Major's avatar
Brian Major committed
        super(connections);
        this.userLdapAttrib.put(HttpPrincipal.class, LDAP_USER_NAME);
        this.userLdapAttrib.put(X500Principal.class, LDAP_DISTINGUISHED_NAME);
        this.userLdapAttrib.put(NumericPrincipal.class, LDAP_UID);
        this.userLdapAttrib.put(DNPrincipal.class, LDAP_ENTRYDN);
        // add the id attributes to user and member attributes
Dustin Jenkins's avatar
Dustin Jenkins committed
        String[] princs = userLdapAttrib.values()
                .toArray(new String[userLdapAttrib.values().size()]);
        String[] tmp = new String[userAttribs.length + princs.length];
        System.arraycopy(princs, 0, tmp, 0, princs.length);
Dustin Jenkins's avatar
Dustin Jenkins committed
        System.arraycopy(userAttribs, 0, tmp, princs.length,
                         userAttribs.length);
        userAttribs = tmp;
        tmp = new String[firstLastAttribs.length + princs.length];
        System.arraycopy(princs, 0, tmp, 0, princs.length);
        System.arraycopy(firstLastAttribs, 0, tmp, princs.length,
                         firstLastAttribs.length);
        firstLastAttribs = tmp;
Jeff Burke's avatar
Jeff Burke committed
    /**
     * Verifies the username and password for an existing User.
     *
     * @param username username to verify.
     * @param password password to verify.
Jeff Burke's avatar
Jeff Burke committed
     * @throws TransientException
     * @throws UserNotFoundException
    public Boolean doLogin(final String username, final String password)
Jeff Burke's avatar
Jeff Burke committed
        throws TransientException, UserNotFoundException
    {
        try
        {
            HttpPrincipal httpPrincipal = new HttpPrincipal(username);
            User user = getUser(httpPrincipal);
            long uuid = uuid2long(user.getID().getUUID());
            BindRequest bindRequest = new SimpleBindRequest(
                getUserDN(uuid, config.getUsersDN()), new String(password));

            LDAPConnection conn = this.getUnboundReadConnection();
Brian Major's avatar
Brian Major committed
            BindResult bindResult = conn.bind(bindRequest);
Jeff Burke's avatar
Jeff Burke committed

            if (bindResult != null && bindResult.getResultCode() == ResultCode.SUCCESS)
            {
Jeff Burke's avatar
Jeff Burke committed
            }
            else
            {
                throw new AccessControlException("Invalid username or password");
            }
        }
        catch (UserNotFoundException e)
        {
            throw new AccessControlException("Invalid username");
        }
Jeff Burke's avatar
Jeff Burke committed
        catch (LDAPException e)
        {
            logger.debug("doLogin Exception: " + e, e);

            if (e.getResultCode() == ResultCode.INVALID_CREDENTIALS)
            {
                throw new AccessControlException("Invalid password");
            }
            else if (e.getResultCode() == ResultCode.NO_SUCH_OBJECT)
            {
                throw new AccessControlException("Invalid username");
            }
            else if (e.getResultCode() == ResultCode.UNWILLING_TO_PERFORM)
                throw new AccessControlException("Account inactivated");
            throw new RuntimeException("Unexpected LDAP exception", e);
     * Add the specified user to the active user tree.
     * @param user                 The user to add.
     * @throws TransientException         If an temporary, unexpected problem occurred.
     * @throws UserAlreadyExistsException If the user already exists.
    public void addUser(final User user)
        throws TransientException, UserAlreadyExistsException
        Set<Principal> principals = user.getIdentities();
        if (principals.isEmpty())
            throw new IllegalArgumentException("addUser: No user identities");
        if (user.posixDetails != null)
            throw new UnsupportedOperationException("addUser: Support for users PosixDetails not available");
        Set<X500Principal> x500Principals = user.getIdentities(X500Principal.class);
        if (x500Principals.isEmpty())
        {
            throw new IllegalArgumentException("addUser: No user X500Principals found");
        }
        X500Principal idForLogging = x500Principals.iterator().next();

        // check current users
        for (Principal p : principals)
        {
            checkUsers(p, null, config.getUsersDN());
        }

            long numericID = genNextNumericId();
            String password = UUID.randomUUID().toString();

            List<Attribute> attributes = new ArrayList<Attribute>();
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_INET_ORG_PERSON);
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_INET_USER);
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_CADC_ACCOUNT);
            addAttribute(attributes, LDAP_UID, String.valueOf(numericID));
            addAttribute(attributes, LDAP_USER_NAME,  EXTERNAL_USER_CN);
            addAttribute(attributes, LDAP_LAST_NAME, EXTERNAL_USER_SN);
            addAttribute(attributes, LADP_USER_PASSWORD, password);
            for (X500Principal p : x500Principals)
            {
                addAttribute(attributes, LDAP_DISTINGUISHED_NAME, p.getName());
            }

            DN userDN = getUserDN(numericID, config.getUsersDN());
            AddRequest addRequest = new AddRequest(userDN, attributes);
            logger.debug("addUser: adding " + idForLogging.getName() + " to " + config.getUsersDN());
            LDAPResult result = getReadWriteConnection().add(addRequest);
            LdapDAO.checkLdapResult(result.getResultCode());
        }
        catch (LDAPException e)
        {
            logger.error("addUser Exception: " + e, e);
            LdapUserDAO.checkUserLDAPResult(e.getResultCode());
            throw new RuntimeException("Unexpected LDAP exception", e);
        }
    private String getEmailAddress(final User user)
        if (user.personalDetails == null)
            String error = user.getHttpPrincipal().getName() + " missing required PersonalDetails";
        if (!StringUtil.hasText(user.personalDetails.email))
            String error = user.getHttpPrincipal().getName() + " missing required email address";
        return user.personalDetails.email;
    private void checkUsers(final Principal userID, final String email, final String usersDN)
        throws TransientException, UserAlreadyExistsException
            getUser(userID, usersDN);
            final String error = "user " + userID.getName() + " found in " + usersDN;
            throw new UserAlreadyExistsException(error);
        }
        catch (UserNotFoundException ok) { }
        // check if email address is already in use
        if (email != null)
                getUserByEmailAddress(email, usersDN);
                final String error = "user " + userID.getName() + " found in " + usersDN;
                throw new UserAlreadyExistsException(error);
            catch (UserNotFoundException ok) { }
    /**
     *Add the specified user to the pending user tree.
     *
     * @param userRequest                   The user to add.
     * @throws TransientException           If an temporary, unexpected problem occurred.
     * @throws UserAlreadyExistsException   If the user already exists.
     */
    public void addUserRequest(final UserRequest userRequest)
            throws TransientException, UserAlreadyExistsException
        final User user = userRequest.getUser();
        final HttpPrincipal userID = user.getHttpPrincipal();
        if (userID == null)
        {
            throw new IllegalArgumentException("User missing required HttpPrincipal type");
        if (userID.getName().startsWith("$"))
            final String error = "addUserRequest: username " + user.getHttpPrincipal().getName() +
                " cannot start with a $";
            throw new IllegalArgumentException(error);
        if (user.posixDetails != null)
        {
            throw new UnsupportedOperationException("Support for users PosixDetails not available");
        }

        // email is required
        String email = getEmailAddress(user);

        // check current users
        checkUsers(userID, email, config.getUsersDN());

        // check user requests
        checkUsers(userID, email, config.getUserRequestsDN());
            long numericID = genNextNumericId();
            List<Attribute> attributes = new ArrayList<Attribute>();
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_INET_ORG_PERSON);
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_INET_USER);
            addAttribute(attributes, LDAP_OBJECT_CLASS, LDAP_CADC_ACCOUNT);
            addAttribute(attributes, LDAP_UID, String.valueOf(numericID));
            addAttribute(attributes, LDAP_USER_NAME,  userID.getName());
            addAttribute(attributes, LDAP_LAST_NAME, user.personalDetails.getLastName());
            addAttribute(attributes, LADP_USER_PASSWORD, new String(userRequest.getPassword()));
            addAttribute(attributes, LDAP_FIRST_NAME, user.personalDetails.getFirstName());
            addAttribute(attributes, LDAP_ADDRESS, user.personalDetails.address);
            addAttribute(attributes, LDAP_CITY, user.personalDetails.city);
            addAttribute(attributes, LDAP_COUNTRY, user.personalDetails.country);
            addAttribute(attributes, LDAP_EMAIL, email);
            addAttribute(attributes, LDAP_INSTITUTE, user.personalDetails.institute);
            for (Principal princ : user.getIdentities())
            {
                if (princ instanceof X500Principal)
                {
                    addAttribute(attributes, LDAP_DISTINGUISHED_NAME, princ.getName());
                }
            }

            DN userDN = getUserDN(numericID, config.getUserRequestsDN());
            AddRequest addRequest = new AddRequest(userDN, attributes);
            logger.debug("addUserRequest: adding " + userID.getName() + " to " + config.getUserRequestsDN());
            LDAPResult result = getReadWriteConnection().add(addRequest);
            LdapDAO.checkLdapResult(result.getResultCode());
        }
        catch (LDAPException e)
        {
            logger.error("addUserRequest Exception: " + e, e);
            LdapUserDAO.checkUserLDAPResult(e.getResultCode());
            throw new RuntimeException("Unexpected LDAP exception", e);
        }
    }
Jeff Burke's avatar
Jeff Burke committed
    /**
     * Get the user specified by the userID.
Jeff Burke's avatar
Jeff Burke committed
     *
     * @param userID The userID.
Jeff Burke's avatar
Jeff Burke committed
     * @return User instance.
     * @throws UserNotFoundException  when the user is not found in the main tree.
Dustin Jenkins's avatar
Dustin Jenkins committed
     * @throws TransientException     If an temporary, unexpected problem occurred.
Jeff Burke's avatar
Jeff Burke committed
     * @throws AccessControlException If the operation is not permitted.
Jeff Burke's avatar
Jeff Burke committed
     */
    public User getUser(final Principal userID)
        throws UserNotFoundException, TransientException,
               AccessControlException
    {
        return getUser(userID, config.getUsersDN());
    }

    /**
     * Obtain a user who is awaiting approval.
     *
     * @param userID        The user ID of the pending user.
     * @return              A User instance awaiting approval.
     *
     * @throws UserNotFoundException  when the user is not found in the user request tree.
     * @throws TransientException     If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     */
    public User getUserRequest(final Principal userID)
        throws UserNotFoundException, TransientException,
               AccessControlException
    {
        return getUser(userID, config.getUserRequestsDN());
    }

    /**
     * Get the user specified by userID.
     *
     * @param userID  The userID.
     * @param usersDN The LDAP tree to search.
     * @return User instance.
     * @throws UserNotFoundException  when the user is not found.
     * @throws TransientException     If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     */
    private User getUser(final Principal userID, final String usersDN)
        throws UserNotFoundException, TransientException,
               AccessControlException
Dustin Jenkins's avatar
Dustin Jenkins committed
        String searchField = userLdapAttrib.get(userID.getClass());
        if (searchField == null)
        {
Jeff Burke's avatar
Jeff Burke committed
            throw new IllegalArgumentException(
                    "Unsupported principal type " + userID.getClass());
        }

        SearchResultEntry searchResult = null;
        try
        {
            String name;
            if (userID instanceof NumericPrincipal)
            {
                name = String.valueOf(uuid2long(UUID.fromString(userID.getName())));
            }
            else
            {
                name = userID.getName();
            }
            Filter filter = Filter.createEqualityFilter(searchField, name);
            logger.debug("getUser: search filter = " + filter);
            SearchRequest searchRequest = new SearchRequest(usersDN, SearchScope.ONE, filter, userAttribs);
Brian Major's avatar
Brian Major committed
            searchResult = getReadOnlyConnection().searchForEntry(searchRequest);
                String msg = "getUser: user " + userID.toString() + " not found in " + usersDN;
                logger.debug(msg);
                throw new UserNotFoundException(msg);
            }
        }
        catch (LDAPException e)
        {
            LdapDAO.checkLdapResult(e.getResultCode());
        User user = new User();
        String username = searchResult.getAttributeValue(userLdapAttrib.get(HttpPrincipal.class));
        logger.debug("getUser: username = " + username);
        if (username != null)
        {
            user.getIdentities().add(new HttpPrincipal(username));
        }
        String uid = searchResult.getAttributeValue(userLdapAttrib.get(NumericPrincipal.class));
        logger.debug("getUser: uid = " + uid);
        if (uid == null)
        {
            // If the numeric ID does not return it means the user
            // does not have permission
            throw new AccessControlException("Permission denied");
        }
        InternalID internalID = getInternalID(uid);
        ObjectUtil.setField(user, internalID, USER_ID);
        user.getIdentities().add(new NumericPrincipal(internalID.getUUID()));

        String x500str = searchResult.getAttributeValue(userLdapAttrib.get(X500Principal.class));
        logger.debug("getUser: x500principal = " + x500str);
        if (x500str != null)
            user.getIdentities().add(new X500Principal(x500str));
        String firstName = searchResult.getAttributeValue(LDAP_FIRST_NAME);
        String lastName = searchResult.getAttributeValue(LDAP_LAST_NAME);
        if (StringUtil.hasLength(firstName) && StringUtil.hasLength(lastName))
        {
            user.personalDetails = new PersonalDetails(firstName, lastName);
            user.personalDetails.address = searchResult.getAttributeValue(LDAP_ADDRESS);
            user.personalDetails.city = searchResult.getAttributeValue(LDAP_CITY);
            user.personalDetails.country = searchResult.getAttributeValue(LDAP_COUNTRY);
            user.personalDetails.email = searchResult.getAttributeValue(LDAP_EMAIL);
            user.personalDetails.institute = searchResult.getAttributeValue(LDAP_INSTITUTE);
        }
        logger.debug("getUser: found " + userID.getName() + " in " + usersDN);
        return user;
    }
    /**
     * Get the user specified by the email address exists.
     *
     * @param emailAddress The user's email address.
     *
     * @return User instance.
     *
     * @throws UserNotFoundException  when the user is not found in the main tree.
     * @throws TransientException If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     * @throws UserAlreadyExistsException A user with the same email address already exists
     */
    public User getUserByEmailAddress(final String emailAddress)
        throws UserNotFoundException, TransientException,
               AccessControlException, UserAlreadyExistsException
    {
        return getUserByEmailAddress(emailAddress, config.getUsersDN());
    }

    /**
     * Get the user specified by the email address exists.
     *
     * @param emailAddress  The user's email address.
     * @param usersDN The LDAP tree to search.
     * @return User ID
     * @throws UserNotFoundException  when the user is not found.
     * @throws TransientException     If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     * @throws UserAlreadyExistsException A user with the same email address already exists
    private User getUserByEmailAddress(final String emailAddress, final String usersDN)
        throws UserNotFoundException, TransientException,
               AccessControlException
    {
        SearchResultEntry searchResult = null;
        Filter filter = null;
        try
        {
            filter = Filter.createEqualityFilter("email", emailAddress);
            logger.debug("getUserByEmailAddress: search filter = " + filter);

            SearchRequest searchRequest =
                    new SearchRequest(usersDN, SearchScope.ONE, filter, userAttribs);

            searchResult = getReadOnlyConnection().searchForEntry(searchRequest);

            if (searchResult == null)
            {
                String msg = "getUserByEmailAddress: user with email address " +
                             emailAddress + " not found";
                logger.debug(msg);
                throw new UserNotFoundException(msg);
            }
        }
        catch (LDAPException e)
        {
            LdapDAO.checkLdapResult(e.getResultCode());
        String userIDString = searchResult.getAttributeValue(LDAP_USER_NAME);
        HttpPrincipal userID = new HttpPrincipal(userIDString);
        User user = new User();
        user.getIdentities().add(userID);
        // Set the User's private InternalID field
        String numericID = searchResult.getAttributeValue(userLdapAttrib.get(NumericPrincipal.class));
        InternalID internalID = getInternalID(numericID);
        ObjectUtil.setField(user, internalID, USER_ID);
        user.getIdentities().add(new NumericPrincipal(internalID.getUUID()));

        String x500str = searchResult.getAttributeValue(userLdapAttrib.get(X500Principal.class));
        logger.debug("getUserByEmailAddress: x500principal = " + x500str);

        if (x500str != null)
            user.getIdentities().add(new X500Principal(x500str));
        String firstName = searchResult.getAttributeValue(LDAP_FIRST_NAME);
        String lastName = searchResult.getAttributeValue(LDAP_LAST_NAME);
        if (StringUtil.hasLength(firstName) && StringUtil.hasLength(lastName))
        {
            user.personalDetails = new PersonalDetails(firstName, lastName);
            user.personalDetails.address = searchResult.getAttributeValue(LDAP_ADDRESS);
            user.personalDetails.city = searchResult.getAttributeValue(LDAP_CITY);
            user.personalDetails.country = searchResult.getAttributeValue(LDAP_COUNTRY);
            user.personalDetails.email = searchResult.getAttributeValue(LDAP_EMAIL);
            user.personalDetails.institute = searchResult.getAttributeValue(LDAP_INSTITUTE);
        }
        return user;
    public User getAugmentedUser(final Principal userID)
        throws UserNotFoundException, TransientException
    {
        String searchField = userLdapAttrib.get(userID.getClass());
Brian Major's avatar
Brian Major committed
        profiler.checkpoint("getAugmentedUser.getSearchField");
            throw new IllegalArgumentException("getAugmentedUser: unsupported principal type " +
                                                userID.getClass());
            String name;
            if (userID instanceof NumericPrincipal)
            {
                name = String.valueOf(uuid2long(UUID.fromString(userID.getName())));
            }
            else
            {
                name = userID.getName();
            }
            Filter filter = Filter.createEqualityFilter(searchField, name);
            profiler.checkpoint("getAugmentedUser.createFilter");
            logger.debug("getAugmentedUser: search filter = " + filter);
            SearchRequest searchRequest = new SearchRequest(
                config.getUsersDN(), SearchScope.ONE, filter, identityAttribs);
            profiler.checkpoint("getAugmentedUser.createSearchRequest");
Brian Major's avatar
Brian Major committed
            SearchResultEntry searchResult = getReadOnlyConnection().searchForEntry(searchRequest);
            profiler.checkpoint("getAugmentedUser.searchForEntry");
                String msg = "getAugmentedUser: user " + name + " not found";
                logger.debug(msg);
                throw new UserNotFoundException(msg);
            }

            User user = new User();
            String username = searchResult.getAttributeValue(LDAP_USER_NAME);
            logger.debug("getAugmentedUser: username = " + username);
            user.getIdentities().add(new HttpPrincipal(username));
            String numericID = searchResult.getAttributeValue(LDAP_UID);
            logger.debug("getAugmentedUser: numericID = " + numericID);
            InternalID internalID = getInternalID(numericID);
            ObjectUtil.setField(user, internalID, USER_ID);
            user.getIdentities().add(new NumericPrincipal(internalID.getUUID()));

            String dn = searchResult.getAttributeValue(LDAP_DISTINGUISHED_NAME);
            if (dn != null)
            {
                user.getIdentities().add(new X500Principal(dn));
            }
            user.getIdentities().add(new DNPrincipal(searchResult.getAttributeValue(LDAP_ENTRYDN)));
Patrick Dowler's avatar
Patrick Dowler committed
            // cache memberOf values in the user
            GroupMemberships gms = new GroupMemberships(userID);
Patrick Dowler's avatar
Patrick Dowler committed
            user.appData = gms; // add even if empty
            String[] mems = searchResult.getAttributeValues(LDAP_MEMBEROF);
            if (mems != null && mems.length > 0)
            {
                DN adminDN = new DN(config.getAdminGroupsDN());
                DN groupsDN = new DN(config.getGroupsDN());
                List<Group> memberOf = new ArrayList<Group>();
                List<Group> adminOf = new ArrayList<Group>();
                for (String m : mems)
                {
                    DN groupDN = new DN(m);
                    if (groupDN.isDescendantOf(groupsDN, false))
                        memberOf.add(createGroupFromDN(groupDN));
                    else if (groupDN.isDescendantOf(adminDN, false))
                        adminOf.add(createGroupFromDN(groupDN));
                }
                gms.add(adminOf, Role.ADMIN);
                gms.add(memberOf, Role.MEMBER);
            }
            profiler.checkpoint("getAugmentedUser.mapIdentities");
            logger.debug("getAugmentedUser: returning user " + userID.getName());
            logger.debug("getAugmentedUser Exception: " + e, e);
            LdapDAO.checkLdapResult(e.getResultCode());
            throw new RuntimeException("BUG: checkLdapResult didn't throw an exception");
        }
Brian Major's avatar
Brian Major committed
        finally
        {
            profiler.checkpoint("Done getAugmentedUser");
        }
Patrick Dowler's avatar
Patrick Dowler committed
    // some pretty horrible hacks to avoid querying LDAP for group details...
    private Group createGroupFromDN(DN groupDN)
    {
        String cn = groupDN.getRDNString();
        String[] parts = cn.split("=");
        if (parts.length == 2 && parts[0].equals("cn"))
        {
            return new Group(parts[1]);
        }
        throw new RuntimeException("BUG: failed to extract group name from " + groupDN
                .toString());
    }
Dustin Jenkins's avatar
Dustin Jenkins committed
    /**
     * @return A Collection of User's.
     * @throws TransientException If an temporary, unexpected problem occurred.
Dustin Jenkins's avatar
Dustin Jenkins committed
     */
    public Collection<User> getUsers()
Jeff Burke's avatar
Jeff Burke committed
        throws AccessControlException, TransientException
     * @throws TransientException If an temporary, unexpected problem occurred.
     */
    public Collection<User> getUserRequests()
Jeff Burke's avatar
Jeff Burke committed
        throws AccessControlException, TransientException
    private Collection<User> getUsers(final String usersDN)
Jeff Burke's avatar
Jeff Burke committed
        throws AccessControlException, TransientException
        final Collection<User> users = new ArrayList<User>();
        Filter filter =  Filter.createPresenceFilter(LDAP_UID);
        logger.debug("search filter: " + filter);
        final String[] attributes = new String[]
            { LDAP_UID, LDAP_FIRST_NAME, LDAP_LAST_NAME };
        final SearchRequest searchRequest =
            new SearchRequest(usersDN, SearchScope.ONE, filter, attributes);
        try
        {
            final SearchResult searchResult =
                getReadOnlyConnection().search(searchRequest);
            LdapDAO.checkLdapResult(searchResult.getResultCode());
            for (SearchResultEntry next : searchResult.getSearchEntries())
                final String firstName =
                    next.getAttributeValue(LDAP_FIRST_NAME);
                final String lastName =
                    next.getAttributeValue(LDAP_LAST_NAME).trim();
                final String uid = next.getAttributeValue(LDAP_UID);

                User user = new User();
                user.getIdentities().add(new HttpPrincipal(uid));

                // Only add Personal Details if it is relevant.
                if (StringUtil.hasLength(firstName) &&
                    StringUtil.hasLength(lastName))
                    user.personalDetails = new PersonalDetails(firstName.trim(), lastName.trim());
                users.add(user);
        catch (LDAPSearchException e)
            if (e.getResultCode() == ResultCode.NO_SUCH_OBJECT)
            {
                final String message = "Could not find users root";
                logger.debug(message, e);
                throw new IllegalStateException(message);
            }
        logger.debug("getUsers: found " + users.size() + " in " + usersDN);
        return users;
     * Move the pending user specified by userID from the
     * pending users tree to the active users tree.
     * @param userID
     * @return User instance.
     * @throws UserNotFoundException  when the user is not found.
     * @throws TransientException     If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     */
    public User approveUserRequest(final Principal userID)
        throws UserNotFoundException, TransientException, AccessControlException
    {
        User userRequest = getUserRequest(userID);
        if (userRequest.getHttpPrincipal() == null)
        {
            throw new RuntimeException("BUG: missing HttpPrincipal for " + userID.getName());
        }
        String uid = "uid=" + uuid2long(userRequest.getID().getUUID());
        String dn = uid + "," + config.getUserRequestsDN();

        try
        {
            ModifyDNRequest modifyDNRequest =
                new ModifyDNRequest(dn, uid, true, config.getUsersDN());
Brian Major's avatar
Brian Major committed
            LdapDAO.checkLdapResult(getReadWriteConnection().modifyDN(modifyDNRequest).getResultCode());
        }
        catch (LDAPException e)
        {
            logger.debug("Modify Exception", e);
            LdapDAO.checkLdapResult(e.getResultCode());
        }
        try
        {
            User user = getUser(userID);
            logger.debug("approvedUserRequest: " + userID.getName());
            return user;
        }
        catch (UserNotFoundException e)
        {
            throw new RuntimeException(
                "BUG: approved user not found (" + userID.getName() + ")");
        }
    }

    /**
     * Update the user specified by User.
     *
     * @return User instance.
     * @throws UserNotFoundException  when the user is not found.
     * @throws TransientException     If an temporary, unexpected problem occurred.
     * @throws AccessControlException If the operation is not permitted.
     */
    public User modifyUser(final User user)
Jeff Burke's avatar
Jeff Burke committed
            throws UserNotFoundException, TransientException, AccessControlException
        // Will we always have a HttpPrincipal?
        User existingUser = getUser(user.getHttpPrincipal());
Jeff Burke's avatar
Jeff Burke committed

        List<Modification> mods = new ArrayList<Modification>();

        if (user.personalDetails != null)
            addModification(mods, LDAP_FIRST_NAME, user.personalDetails.getFirstName());
            addModification(mods, LDAP_LAST_NAME, user.personalDetails.getLastName());
            addModification(mods, LDAP_ADDRESS, user.personalDetails.address);
            addModification(mods, LDAP_CITY, user.personalDetails.city);
            addModification(mods, LDAP_COUNTRY, user.personalDetails.country);
            addModification(mods, LDAP_EMAIL, user.personalDetails.email);
            addModification(mods, LDAP_INSTITUTE, user.personalDetails.institute);
        }

        if (user.posixDetails != null)
        {
            throw new UnsupportedOperationException(
                "Support for users PosixDetails not available");
        // set the x500 DNs if there
        Set<X500Principal> x500Principals = user.getIdentities(X500Principal.class);
        if (x500Principals != null && !x500Principals.isEmpty())
        {
            Iterator<X500Principal> i = x500Principals.iterator();
            X500Principal next = null;
            while (i.hasNext())
            {
                next = i.next();
                addModification(mods, LDAP_DISTINGUISHED_NAME, next.getName());
            }
        }

            ModifyRequest modifyRequest = new ModifyRequest(getUserDN(user), mods);
Patrick Dowler's avatar
Patrick Dowler committed
            //modifyRequest.addControl(
            //    new ProxiedAuthorizationV2RequestControl(
            //        "dn:" + getSubjectDN().toNormalizedString()));
Brian Major's avatar
Brian Major committed
            LdapDAO.checkLdapResult(getReadWriteConnection().modify(modifyRequest).getResultCode());
        catch (LDAPException e)
            logger.debug("Modify Exception", e);
            LdapDAO.checkLdapResult(e.getResultCode());
            User ret = getUser(user.getHttpPrincipal());
            logger.debug("ModifiedUser: " + user.getHttpPrincipal().getName());
            return ret;
Jeff Burke's avatar
Jeff Burke committed
        }
        catch (UserNotFoundException e)
        {
            throw new RuntimeException(
                "BUG: modified user not found (" + user.getHttpPrincipal().getName() + ")");
    protected void updatePassword(HttpPrincipal userID, String oldPassword, String newPassword)
            throws UserNotFoundException, TransientException, AccessControlException
            User user = new User();
            user.getIdentities().add(userID);
Jeff Burke's avatar
Jeff Burke committed
            DN userDN = getUserDN(user);
            //BindRequest bindRequest = new SimpleBindRequest(
            //        getUserDN(username, config.getUsersDN()), oldPassword);
            //LDAPConnection conn = this.getUnboundReadConnection();
            //conn.bind(bindRequest);
            LDAPConnection conn = this.getReadWriteConnection();
            PasswordModifyExtendedRequest passwordModifyRequest;
                        new PasswordModifyExtendedRequest(userDN.toNormalizedString(),