View Javadoc

    1   package org.apache.turbine.services.security.ldap;
    2
    3   /*
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *   http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    20   */
    21
    22  import java.util.List;
    23  import java.util.Hashtable;
    24  import java.util.Vector;
    25
    26  import javax.naming.AuthenticationException;
    27  import javax.naming.Context;
    28  import javax.naming.NamingEnumeration;
    29  import javax.naming.NamingException;
    30  import javax.naming.directory.Attributes;
    31  import javax.naming.directory.DirContext;
    32  import javax.naming.directory.SearchControls;
    33  import javax.naming.directory.SearchResult;
    34
    35  import org.apache.commons.configuration.Configuration;
    36
    37  import org.apache.turbine.om.security.User;
    38  import org.apache.turbine.services.security.TurbineSecurity;
    39  import org.apache.turbine.services.security.UserManager;
    40  import org.apache.turbine.util.security.DataBackendException;
    41  import org.apache.turbine.util.security.EntityExistsException;
    42  import org.apache.turbine.util.security.PasswordMismatchException;
    43  import org.apache.turbine.util.security.UnknownEntityException;
    44
    45  /**
    46   * A UserManager performs {@link org.apache.turbine.om.security.User}
    47   * object related tasks on behalf of the
    48   * {@link org.apache.turbine.services.security.SecurityService}.
    49   *
    50   * This implementation uses ldap for retrieving user data. It
    51   * expects that the User interface implementation will be castable to
    52   * {@link org.apache.turbine.om.BaseObject}.
    53   *
    54   * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
    55   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
    56   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
    57   * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
    58   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
    59   * @author <a href="mailto:tadewunmi@gluecode.com">Tracy M. Adewunmi</a>
    60   * @author <a href="mailto:lflournoy@gluecode.com">Leonard J. Flournoy</a>
    61   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    62   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    63   * @author <a href="mailto:hhernandez@itweb.com.mx">Humberto Hernandez</a>
    64   * @version $Id: LDAPUserManager.java 1096130 2011-04-23 10:37:19Z ludwig $
    65   */
    66  public class LDAPUserManager implements UserManager
    67  {
    68      /**
    69       * Initializes the UserManager
    70       *
    71       * @param conf A Configuration object to init this Manager
    72       */
    73      public void init(Configuration conf)
    74      {
    75          // GNDN
    76      }
    77
    78      /**
    79       * Check wether a specified user's account exists.
    80       *
    81       * The login name is used for looking up the account.
    82       *
    83       * @param user The user to be checked.
    84       * @return true if the specified account exists
    85       * @throws DataBackendException Error accessing the data backend.
    86       */
    87      public boolean accountExists(User user) throws DataBackendException
    88      {
    89          return accountExists(user.getName());
    90      }
    91
    92      /**
    93       *
    94       * Check wether a specified user's account exists.
    95       * The login name is used for looking up the account.
    96       *
    97       * @param username The name of the user to be checked.
    98       * @return true if the specified account exists
    99       * @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 }