    001    package org.apache.turbine.services.security.ldap;
    002
    003    /*
    004     * Licensed to the Apache Software Foundation (ASF) under one
    005     * or more contributor license agreements.  See the NOTICE file
    006     * distributed with this work for additional information
    007     * regarding copyright ownership.  The ASF licenses this file
    008     * to you under the Apache License, Version 2.0 (the
    009     * "License"); you may not use this file except in compliance
    010     * with the License.  You may obtain a copy of the License at
    011     *
    012     *   http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing,
    015     * software distributed under the License is distributed on an
    016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    017     * KIND, either express or implied.  See the License for the
    018     * specific language governing permissions and limitations
    019     * under the License.
    020     */
    021
    022    import java.util.List;
    023    import java.util.Hashtable;
    024    import java.util.Vector;
    025
    026    import javax.naming.AuthenticationException;
    027    import javax.naming.Context;
    028    import javax.naming.NamingEnumeration;
    029    import javax.naming.NamingException;
    030    import javax.naming.directory.Attributes;
    031    import javax.naming.directory.DirContext;
    032    import javax.naming.directory.SearchControls;
    033    import javax.naming.directory.SearchResult;
    034
    035    import org.apache.commons.configuration.Configuration;
    036
    037    import org.apache.turbine.om.security.User;
    038    import org.apache.turbine.services.security.TurbineSecurity;
    039    import org.apache.turbine.services.security.UserManager;
    040    import org.apache.turbine.util.security.DataBackendException;
    041    import org.apache.turbine.util.security.EntityExistsException;
    042    import org.apache.turbine.util.security.PasswordMismatchException;
    043    import org.apache.turbine.util.security.UnknownEntityException;
    044
    045    /**
    046     * A UserManager performs {@link org.apache.turbine.om.security.User}
    047     * object related tasks on behalf of the
    048     * {@link org.apache.turbine.services.security.SecurityService}.
    049     *
    050     * This implementation uses ldap for retrieving user data. It
    051     * expects that the User interface implementation will be castable to
    052     * {@link org.apache.turbine.om.BaseObject}.
    053     *
    054     * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
    055     * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
    056     * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
    057     * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
    058     * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
    059     * @author <a href="mailto:tadewunmi@gluecode.com">Tracy M. Adewunmi</a>
    060     * @author <a href="mailto:lflournoy@gluecode.com">Leonard J. Flournoy</a>
    061     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    062     * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    063     * @author <a href="mailto:hhernandez@itweb.com.mx">Humberto Hernandez</a>
    064     * @version $Id: LDAPUserManager.java 1096130 2011-04-23 10:37:19Z ludwig $
    065     */
    066    public class LDAPUserManager implements UserManager
    067    {
    068        /**
    069         * Initializes the UserManager
    070         *
    071         * @param conf A Configuration object to init this Manager
    072         */
    073        public void init(Configuration conf)
    074        {
    075            // GNDN
    076        }
    077
    078        /**
    079         * Check wether a specified user's account exists.
    080         *
    081         * The login name is used for looking up the account.
    082         *
    083         * @param user The user to be checked.
    084         * @return true if the specified account exists
    085         * @throws DataBackendException Error accessing the data backend.
    086         */
    087        public boolean accountExists(User user) throws DataBackendException
    088        {
    089            return accountExists(user.getName());
    090        }
    091
    092        /**
    093         *
    094         * Check wether a specified user's account exists.
    095         * The login name is used for looking up the account.
    096         *
    097         * @param username The name of the user to be checked.
    098         * @return true if the specified account exists
    099         * @throws DataBackendException Error accessing the data backend.
    100         */
    101        public boolean accountExists(String username)
    102                throws DataBackendException
    103        {
    104            try
    105            {
    106                User ldapUser = retrieve(username);
    107            }
    108            catch (UnknownEntityException ex)
    109            {
    110                return false;
    111            }
    112
    113            return true;
    114        }
    115
    116        /**
    117         * Retrieve a user from persistent storage using username as the
    118         * key.
    119         *
    120         * @param username the name of the user.
    121         * @return an User object.
    122         * @exception UnknownEntityException if the user's account does not
    123         *            exist in the database.
    124         * @exception DataBackendException Error accessing the data backend.
    125         */
    126        public User retrieve(String username)
    127                throws UnknownEntityException, DataBackendException
    128        {
    129            try
    130            {
    131                DirContext ctx = bindAsAdmin();
    132
    133                /*
    134                 * Define the search.
    135                 */
    136                String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
    137                String filter = LDAPSecurityConstants.getNameAttribute();
    138
    139                filter = "(" + filter + "=" + username + ")";
    140
    141                /*
    142                 * Create the default search controls.
    143                 */
    144                SearchControls ctls = new SearchControls();
    145
    146                NamingEnumeration answer =
    147                        ctx.search(userBaseSearch, filter, ctls);
    148
    149                if (answer.hasMore())
    150                {
    151                    SearchResult sr = (SearchResult) answer.next();
    152                    Attributes attribs = sr.getAttributes();
    153                    LDAPUser ldapUser = createLDAPUser();
    154
    155                    ldapUser.setLDAPAttributes(attribs);
    156                    ldapUser.setTemp("turbine.user", ldapUser);
    157
    158                    return ldapUser;
    159                }
    160                else
    161                {
    162                    throw new UnknownEntityException("The given user: "
    163                            + username + "\n does not exist.");
    164                }
    165            }
    166            catch (NamingException ex)
    167            {
    168                throw new DataBackendException(
    169                        "The LDAP server specified is unavailable", ex);
    170            }
    171        }
    172
    173        /**
    174         * This is currently not implemented to behave as expected.  It
    175         * ignores the Criteria argument and returns all the users.
    176         *
    177         * Retrieve a set of users that meet the specified criteria.
    178         *
    179         * As the keys for the criteria, you should use the constants that
    180         * are defined in {@link User} interface, plus the the names
    181         * of the custom attributes you added to your user representation
    182         * in the data storage. Use verbatim names of the attributes -
    183         * without table name prefix in case of DB implementation.
    184         *
    185         * @param criteria The criteria of selection.
    186         * @return a List of users meeting the criteria.
    187         * @throws DataBackendException Error accessing the data backend.
    188         * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
    189         */
    190        public User[] retrieve(Object criteria)
    191                throws DataBackendException
    192        {
    193            return (User []) retrieveList(criteria).toArray(new User[0]);
    194        }
    195
    196        /**
    197         * Retrieve a list of users that meet the specified criteria.
    198         *
    199         * As the keys for the criteria, you should use the constants that
    200         * are defined in {@link User} interface, plus the names
    201         * of the custom attributes you added to your user representation
    202         * in the data storage. Use verbatim names of the attributes -
    203         * without table name prefix in case of Torque implementation.
    204         *
    205         * @param criteria The criteria of selection.
    206         * @return a List of users meeting the criteria.
    207         * @throws DataBackendException if there is a problem accessing the
    208         *         storage.
    209         */
    210        public List retrieveList(Object criteria)
    211                throws DataBackendException
    212        {
    213            List users = new Vector(0);
    214
    215            try
    216            {
    217                DirContext ctx = bindAsAdmin();
    218
    219                String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
    220                String filter = LDAPSecurityConstants.getNameAttribute();
    221
    222                filter = "(" + filter + "=*)";
    223
    224                /*
    225                 * Create the default search controls.
    226                 */
    227                SearchControls ctls = new SearchControls();
    228
    229                NamingEnumeration answer =
    230                        ctx.search(userBaseSearch, filter, ctls);
    231
    232                while (answer.hasMore())
    233                {
    234                    SearchResult sr = (SearchResult) answer.next();
    235                    Attributes attribs = sr.getAttributes();
    236                    LDAPUser ldapUser = createLDAPUser();
    237
    238                    ldapUser.setLDAPAttributes(attribs);
    239                    ldapUser.setTemp("turbine.user", ldapUser);
    240                    users.add(ldapUser);
    241                }
    242            }
    243            catch (NamingException ex)
    244            {
    245                throw new DataBackendException(
    246                        "The LDAP server specified is unavailable", ex);
    247            }
    248            return users;
    249        }
    250
    251        /**
    252         * Retrieve a user from persistent storage using username as the
    253         * key, and authenticate the user. The implementation may chose
    254         * to authenticate to the server as the user whose data is being
    255         * retrieved.
    256         *
    257         * @param username the name of the user.
    258         * @param password the user supplied password.
    259         * @return an User object.
    260         * @exception PasswordMismatchException if the supplied password was
    261         *            incorrect.
    262         * @exception UnknownEntityException if the user's account does not
    263         *            exist in the database.
    264         * @exception DataBackendException Error accessing the data backend.
    265         */
    266        public User retrieve(String username, String password)
    267                throws PasswordMismatchException,
    268                UnknownEntityException, DataBackendException
    269        {
    270            User user = retrieve(username);
    271
    272            authenticate(user, password);
    273            return user;
    274        }
    275
    276        /**
    277         * Save a User object to persistent storage. User's account is
    278         * required to exist in the storage.
    279         *
    280         * @param user an User object to store.
    281         * @throws UnknownEntityException if the user's account does not
    282         *            exist in the database.
    283         * @throws DataBackendException if there is an LDAP error
    284         *
    285         */
    286        public void store(User user)
    287                throws UnknownEntityException, DataBackendException
    288        {
    289            if (!accountExists(user))
    290            {
    291                throw new UnknownEntityException("The account '"
    292                        + user.getName() + "' does not exist");
    293            }
    294
    295            try
    296            {
    297                LDAPUser ldapUser = (LDAPUser) user;
    298                Attributes attrs = ldapUser.getLDAPAttributes();
    299                String name = ldapUser.getDN();
    300
    301                DirContext ctx = bindAsAdmin();
    302
    303                ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, attrs);
    304            }
    305            catch (NamingException ex)
    306            {
    307                throw new DataBackendException("NamingException caught", ex);
    308            }
    309        }
    310
    311        /**
    312         * This method is not yet implemented.
    313         * Saves User data when the session is unbound. The user account is required
    314         * to exist in the storage.
    315         *
    316         * LastLogin, AccessCounter, persistent pull tools, and any data stored
    317         * in the permData hashtable that is not mapped to a column will be saved.
    318         *
    319         * @exception UnknownEntityException if the user's account does not
    320         *            exist in the database.
    321         * @exception DataBackendException if there is a problem accessing the
    322         *            storage.
    323         */
    324        public void saveOnSessionUnbind(User user)
    325                throws UnknownEntityException, DataBackendException
    326        {
    327            if (!accountExists(user))
    328            {
    329                throw new UnknownEntityException("The account '" +
    330                        user.getName() + "' does not exist");
    331            }
    332        }
    333
    334        /**
    335         * Authenticate a User with the specified password. If authentication
    336         * is successful the method returns nothing. If there are any problems,
    337         * exception was thrown.
    338         *
    339         * @param user a User object to authenticate.
    340         * @param password the user supplied password.
    341         * @exception PasswordMismatchException if the supplied password was
    342         *            incorrect.
    343         * @exception UnknownEntityException if the user's account does not
    344         *            exist in the database.
    345         * @exception DataBackendException Error accessing the data backend.
    346         */
    347        public void authenticate(User user, String password)
    348                throws PasswordMismatchException,
    349                UnknownEntityException,
    350                DataBackendException
    351        {
    352            LDAPUser ldapUser = (LDAPUser) user;
    353
    354            try
    355            {
    356                bind(ldapUser.getDN(), password);
    357            }
    358            catch (AuthenticationException ex)
    359            {
    360                throw new PasswordMismatchException(
    361                        "The given password for: "
    362                        + ldapUser.getDN() + " is invalid\n");
    363            }
    364            catch (NamingException ex)
    365            {
    366                throw new DataBackendException(
    367                        "NamingException caught:", ex);
    368            }
    369        }
    370
    371        /**
    372         * This method is not yet implemented
    373         * Change the password for an User.
    374         *
    375         * @param user an User to change password for.
    376         * @param newPass the new password.
    377         * @param oldPass the old password.
    378         * @exception PasswordMismatchException if the supplied password was
    379         *            incorrect.
    380         * @exception UnknownEntityException if the user's account does not
    381         *            exist in the database.
    382         * @exception DataBackendException Error accessing the data backend.
    383         */
    384        public void changePassword(User user, String oldPass, String newPass)
    385                throws PasswordMismatchException,
    386                UnknownEntityException, DataBackendException
    387        {
    388            throw new DataBackendException(
    389                    "The method changePassword has no implementation.");
    390        }
    391
    392        /**
    393         * This method is not yet implemented
    394         * Forcibly sets new password for an User.
    395         *
    396         * This is supposed to be used by the administrator to change the forgotten
    397         * or compromised passwords. Certain implementatations of this feature
    398         * would require adminstrative level access to the authenticating
    399         * server / program.
    400         *
    401         * @param user an User to change password for.
    402         * @param password the new password.
    403         * @exception UnknownEntityException if the user's record does not
    404         *            exist in the database.
    405         * @exception DataBackendException Error accessing the data backend.
    406         */
    407        public void forcePassword(User user, String password)
    408                throws UnknownEntityException, DataBackendException
    409        {
    410            throw new DataBackendException(
    411                    "The method forcePassword has no implementation.");
    412        }
    413
    414        /**
    415         * Creates new user account with specified attributes.
    416         *
    417         * @param user the object describing account to be created.
    418         * @param initialPassword Not used yet.
    419         * @throws DataBackendException Error accessing the data backend.
    420         * @throws EntityExistsException if the user account already exists.
    421         */
    422        public void createAccount(User user, String initialPassword)
    423                throws EntityExistsException, DataBackendException
    424        {
    425            if (accountExists(user))
    426            {
    427                throw new EntityExistsException("The account '"
    428                        + user.getName() + "' already exist");
    429            }
    430
    431            try
    432            {
    433                LDAPUser ldapUser = (LDAPUser) user;
    434                Attributes attrs = ldapUser.getLDAPAttributes();
    435                String name = ldapUser.getDN();
    436
    437                DirContext ctx = bindAsAdmin();
    438
    439                ctx.bind(name, null, attrs);
    440            }
    441            catch (NamingException ex)
    442            {
    443                throw new DataBackendException("NamingException caught", ex);
    444            }
    445        }
    446
    447        /**
    448         * Removes an user account from the system.
    449         *
    450         * @param user the object describing the account to be removed.
    451         * @throws DataBackendException Error accessing the data backend.
    452         * @throws UnknownEntityException if the user account is not present.
    453         */
    454        public void removeAccount(User user)
    455                throws UnknownEntityException, DataBackendException
    456        {
    457            if (!accountExists(user))
    458            {
    459                throw new UnknownEntityException("The account '"
    460                        + user.getName() + "' does not exist");
    461            }
    462
    463            try
    464            {
    465                LDAPUser ldapUser = (LDAPUser) user;
    466                String name = ldapUser.getDN();
    467
    468                DirContext ctx = bindAsAdmin();
    469
    470                ctx.unbind(name);
    471            }
    472            catch (NamingException ex)
    473            {
    474                throw new DataBackendException("NamingException caught", ex);
    475            }
    476        }
    477
    478        /**
    479         * Bind as the admin user.
    480         *
    481         * @throws NamingException when an error occurs with the named server.
    482         * @return a new DirContext.
    483         */
    484        public static DirContext bindAsAdmin()
    485                throws NamingException
    486        {
    487            String adminUser = LDAPSecurityConstants.getAdminUsername();
    488            String adminPassword = LDAPSecurityConstants.getAdminPassword();
    489
    490            return bind(adminUser, adminPassword);
    491        }
    492
    493        /**
    494         * Creates an initial context.
    495         *
    496         * @param username admin username supplied in TRP.
    497         * @param password admin password supplied in TRP
    498         * @throws NamingException when an error occurs with the named server.
    499         * @return a new DirContext.
    500         */
    501        public static DirContext bind(String username, String password)
    502                throws NamingException
    503        {
    504            String host = LDAPSecurityConstants.getLDAPHost();
    505            String port = LDAPSecurityConstants.getLDAPPort();
    506            String providerURL = "ldap://" + host + ":" + port;
    507            String ldapProvider = LDAPSecurityConstants.getLDAPProvider();
    508            String authentication = LDAPSecurityConstants.getLDAPAuthentication();
    509
    510            /*
    511             * creating an initial context using Sun's client
    512             * LDAP Provider.
    513             */
    514            Hashtable env = new Hashtable();
    515
    516            env.put(Context.INITIAL_CONTEXT_FACTORY, ldapProvider);
    517            env.put(Context.PROVIDER_URL, providerURL);
    518            env.put(Context.SECURITY_AUTHENTICATION, authentication);
    519            env.put(Context.SECURITY_PRINCIPAL, username);
    520            env.put(Context.SECURITY_CREDENTIALS, password);
    521
    522            DirContext ctx = new javax.naming.directory.InitialDirContext(env);
    523
    524            return ctx;
    525        }
    526
    527        /**
    528         * Create a new instance of the LDAP User according to the value
    529         * configured in TurbineResources.properties.
    530         * @return a new instance of the LDAP User.
    531         * @throws DataBackendException if there is an error creating the
    532         */
    533        private LDAPUser createLDAPUser()
    534                throws DataBackendException
    535        {
    536            try
    537            {
    538                return (LDAPUser) TurbineSecurity.getUserInstance();
    539            }
    540            catch (ClassCastException ex)
    541            {
    542                throw new DataBackendException("ClassCastException:", ex);
    543            }
    544            catch (UnknownEntityException ex)
    545            {
    546                throw new DataBackendException("UnknownEntityException:", ex);
    547            }
    548        }
    549
    550    }