View Javadoc

1   package org.apache.turbine.services.security.ldap;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.apache.org/>.
55   */
56  
57  import java.util.List;
58  import java.util.Hashtable;
59  import java.util.Vector;
60  
61  import javax.naming.AuthenticationException;
62  import javax.naming.Context;
63  import javax.naming.NamingEnumeration;
64  import javax.naming.NamingException;
65  import javax.naming.directory.Attributes;
66  import javax.naming.directory.DirContext;
67  import javax.naming.directory.SearchControls;
68  import javax.naming.directory.SearchResult;
69  
70  import org.apache.commons.configuration.Configuration;
71  
72  import org.apache.torque.util.Criteria;
73  
74  import org.apache.turbine.om.security.User;
75  import org.apache.turbine.services.security.TurbineSecurity;
76  import org.apache.turbine.services.security.UserManager;
77  import org.apache.turbine.util.security.DataBackendException;
78  import org.apache.turbine.util.security.EntityExistsException;
79  import org.apache.turbine.util.security.PasswordMismatchException;
80  import org.apache.turbine.util.security.UnknownEntityException;
81  
82  /***
83   * A UserManager performs {@link org.apache.turbine.om.security.User}
84   * object related tasks on behalf of the
85   * {@link org.apache.turbine.services.security.SecurityService}.
86   *
87   * This implementation uses ldap for retrieving user data. It
88   * expects that the User interface implementation will be castable to
89   * {@link org.apache.turbine.om.BaseObject}.
90   *
91   * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
92   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
93   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
94   * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
95   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
96   * @author <a href="mailto:tadewunmi@gluecode.com">Tracy M. Adewunmi</a>
97   * @author <a href="mailto:lflournoy@gluecode.com">Leonard J. Flournoy</a>
98   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
99   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
100  * @author <a href="mailto:hhernandez@itweb.com.mx">Humberto Hernandez</a>
101  * @version $Id: LDAPUserManager.java,v 1.14 2003/04/11 11:59:59 henning Exp $
102  */
103 public class LDAPUserManager implements UserManager
104 {
105     /***
106      * Initializes the UserManager
107      *
108      * @param conf A Configuration object to init this Manager
109      */
110     public void init(Configuration conf)
111     {
112         // GNDN
113     }
114 
115     /***
116      * Check wether a specified user's account exists.
117      *
118      * The login name is used for looking up the account.
119      *
120      * @param user The user to be checked.
121      * @return true if the specified account exists
122      * @throws DataBackendException Error accessing the data backend.
123      */
124     public boolean accountExists(User user) throws DataBackendException
125     {
126         return accountExists(user.getName());
127     }
128 
129     /***
130      *
131      * Check wether a specified user's account exists.
132      * The login name is used for looking up the account.
133      *
134      * @param username The name of the user to be checked.
135      * @return true if the specified account exists
136      * @throws DataBackendException Error accessing the data backend.
137      */
138     public boolean accountExists(String username)
139             throws DataBackendException
140     {
141         try
142         {
143             User ldapUser = retrieve(username);
144         }
145         catch (UnknownEntityException ex)
146         {
147             return false;
148         }
149 
150         return true;
151     }
152 
153     /***
154      * Retrieve a user from persistent storage using username as the
155      * key.
156      *
157      * @param username the name of the user.
158      * @return an User object.
159      * @exception UnknownEntityException if the user's account does not
160      *            exist in the database.
161      * @exception DataBackendException Error accessing the data backend.
162      */
163     public User retrieve(String username)
164             throws UnknownEntityException, DataBackendException
165     {
166         try
167         {
168             DirContext ctx = bindAsAdmin();
169 
170             /*
171              * Define the search.
172              */
173             String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
174             String filter = LDAPSecurityConstants.getNameAttribute();
175 
176             filter = "(" + filter + "=" + username + ")";
177 
178             /*
179              * Create the default search controls.
180              */
181             SearchControls ctls = new SearchControls();
182 
183             NamingEnumeration answer =
184                     ctx.search(userBaseSearch, filter, ctls);
185 
186             if (answer.hasMore())
187             {
188                 SearchResult sr = (SearchResult) answer.next();
189                 Attributes attribs = sr.getAttributes();
190                 LDAPUser ldapUser = createLDAPUser();
191 
192                 ldapUser.setLDAPAttributes(attribs);
193                 ldapUser.setTemp("turbine.user", ldapUser);
194 
195                 return ldapUser;
196             }
197             else
198             {
199                 throw new UnknownEntityException("The given user: "
200                         + username + "\n does not exist.");
201             }
202         }
203         catch (NamingException ex)
204         {
205             throw new DataBackendException(
206                     "The LDAP server specified is unavailable", ex);
207         }
208     }
209 
210     /***
211      * This is currently not implemented to behave as expected.  It
212      * ignores the Criteria argument and returns all the users.
213      *
214      * Retrieve a set of users that meet the specified criteria.
215      *
216      * As the keys for the criteria, you should use the constants that
217      * are defined in {@link User} interface, plus the the names
218      * of the custom attributes you added to your user representation
219      * in the data storage. Use verbatim names of the attributes -
220      * without table name prefix in case of DB implementation.
221      *
222      * @param criteria The criteria of selection.
223      * @return a List of users meeting the criteria.
224      * @throws DataBackendException Error accessing the data backend.
225      * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
226      */
227     public User[] retrieve(Criteria criteria)
228             throws DataBackendException
229     {
230         return (User []) retrieveList(criteria).toArray(new User[0]);
231     }
232 
233     /***
234      * Retrieve a list of users that meet the specified criteria.
235      *
236      * As the keys for the criteria, you should use the constants that
237      * are defined in {@link User} interface, plus the names
238      * of the custom attributes you added to your user representation
239      * in the data storage. Use verbatim names of the attributes -
240      * without table name prefix in case of Torque implementation.
241      *
242      * @param criteria The criteria of selection.
243      * @return a List of users meeting the criteria.
244      * @throws DataBackendException if there is a problem accessing the
245      *         storage.
246      */
247     public List retrieveList(Criteria criteria)
248             throws DataBackendException
249     {
250         List users = new Vector(0);
251 
252         try
253         {
254             DirContext ctx = bindAsAdmin();
255 
256             String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
257             String filter = LDAPSecurityConstants.getNameAttribute();
258 
259             filter = "(" + filter + "=*)";
260 
261             /*
262              * Create the default search controls.
263              */
264             SearchControls ctls = new SearchControls();
265 
266             NamingEnumeration answer =
267                     ctx.search(userBaseSearch, filter, ctls);
268 
269             while (answer.hasMore())
270             {
271                 SearchResult sr = (SearchResult) answer.next();
272                 Attributes attribs = sr.getAttributes();
273                 LDAPUser ldapUser = createLDAPUser();
274 
275                 ldapUser.setLDAPAttributes(attribs);
276                 ldapUser.setTemp("turbine.user", ldapUser);
277                 users.add(ldapUser);
278             }
279         }
280         catch (NamingException ex)
281         {
282             throw new DataBackendException(
283                     "The LDAP server specified is unavailable", ex);
284         }
285         return users;
286     }
287 
288     /***
289      * Retrieve a user from persistent storage using username as the
290      * key, and authenticate the user. The implementation may chose
291      * to authenticate to the server as the user whose data is being
292      * retrieved.
293      *
294      * @param username the name of the user.
295      * @param password the user supplied password.
296      * @return an User object.
297      * @exception PasswordMismatchException if the supplied password was
298      *            incorrect.
299      * @exception UnknownEntityException if the user's account does not
300      *            exist in the database.
301      * @exception DataBackendException Error accessing the data backend.
302      */
303     public User retrieve(String username, String password)
304             throws PasswordMismatchException,
305             UnknownEntityException, DataBackendException
306     {
307         User user = retrieve(username);
308 
309         authenticate(user, password);
310         return user;
311     }
312 
313     /***
314      * Save a User object to persistent storage. User's account is
315      * required to exist in the storage.
316      *
317      * @param user an User object to store.
318      * @throws UnknownEntityException if the user's account does not
319      *            exist in the database.
320      * @throws DataBackendException if there is an LDAP error
321      *
322      */
323     public void store(User user)
324             throws UnknownEntityException, DataBackendException
325     {
326         if (!accountExists(user))
327         {
328             throw new UnknownEntityException("The account '"
329                     + user.getName() + "' does not exist");
330         }
331 
332         try
333         {
334             LDAPUser ldapUser = (LDAPUser) user;
335             Attributes attrs = ldapUser.getLDAPAttributes();
336             String name = ldapUser.getDN();
337 
338             DirContext ctx = bindAsAdmin();
339 
340             ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, attrs);
341         }
342         catch (NamingException ex)
343         {
344             throw new DataBackendException("NamingException caught", ex);
345         }
346     }
347 
348     /***
349      * This method is not yet implemented.
350      * Saves User data when the session is unbound. The user account is required
351      * to exist in the storage.
352      *
353      * LastLogin, AccessCounter, persistent pull tools, and any data stored
354      * in the permData hashtable that is not mapped to a column will be saved.
355      *
356      * @exception UnknownEntityException if the user's account does not
357      *            exist in the database.
358      * @exception DataBackendException if there is a problem accessing the
359      *            storage.
360      */
361     public void saveOnSessionUnbind(User user)
362             throws UnknownEntityException, DataBackendException
363     {
364         if (!accountExists(user))
365         {
366             throw new UnknownEntityException("The account '" +
367                     user.getName() + "' does not exist");
368         }
369     }
370 
371     /***
372      * Authenticate a User with the specified password. If authentication
373      * is successful the method returns nothing. If there are any problems,
374      * exception was thrown.
375      *
376      * @param user a User object to authenticate.
377      * @param password the user supplied 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 authenticate(User user, String password)
385             throws PasswordMismatchException,
386             UnknownEntityException,
387             DataBackendException
388     {
389         LDAPUser ldapUser = (LDAPUser) user;
390 
391         try
392         {
393             bind(ldapUser.getDN(), password);
394         }
395         catch (AuthenticationException ex)
396         {
397             throw new PasswordMismatchException(
398                     "The given password for: "
399                     + ldapUser.getDN() + " is invalid\n");
400         }
401         catch (NamingException ex)
402         {
403             throw new DataBackendException(
404                     "NamingException caught:", ex);
405         }
406     }
407 
408     /***
409      * This method is not yet implemented
410      * Change the password for an User.
411      *
412      * @param user an User to change password for.
413      * @param newPass the new password.
414      * @param oldPass the old password.
415      * @exception PasswordMismatchException if the supplied password was
416      *            incorrect.
417      * @exception UnknownEntityException if the user's account does not
418      *            exist in the database.
419      * @exception DataBackendException Error accessing the data backend.
420      */
421     public void changePassword(User user, String oldPass, String newPass)
422             throws PasswordMismatchException,
423             UnknownEntityException, DataBackendException
424     {
425         throw new DataBackendException(
426                 "The method changePassword has no implementation.");
427     }
428 
429     /***
430      * This method is not yet implemented
431      * Forcibly sets new password for an User.
432      *
433      * This is supposed to be used by the administrator to change the forgotten
434      * or compromised passwords. Certain implementatations of this feature
435      * would require adminstrative level access to the authenticating
436      * server / program.
437      *
438      * @param user an User to change password for.
439      * @param password the new password.
440      * @exception UnknownEntityException if the user's record does not
441      *            exist in the database.
442      * @exception DataBackendException Error accessing the data backend.
443      */
444     public void forcePassword(User user, String password)
445             throws UnknownEntityException, DataBackendException
446     {
447         throw new DataBackendException(
448                 "The method forcePassword has no implementation.");
449     }
450 
451     /***
452      * Creates new user account with specified attributes.
453      *
454      * @param user the object describing account to be created.
455      * @param initialPassword Not used yet.
456      * @throws DataBackendException Error accessing the data backend.
457      * @throws EntityExistsException if the user account already exists.
458      */
459     public void createAccount(User user, String initialPassword)
460             throws EntityExistsException, DataBackendException
461     {
462         if (accountExists(user))
463         {
464             throw new EntityExistsException("The account '"
465                     + user.getName() + "' already exist");
466         }
467 
468         try
469         {
470             LDAPUser ldapUser = (LDAPUser) user;
471             Attributes attrs = ldapUser.getLDAPAttributes();
472             String name = ldapUser.getDN();
473 
474             DirContext ctx = bindAsAdmin();
475 
476             ctx.bind(name, null, attrs);
477         }
478         catch (NamingException ex)
479         {
480             throw new DataBackendException("NamingException caught", ex);
481         }
482     }
483 
484     /***
485      * Removes an user account from the system.
486      *
487      * @param user the object describing the account to be removed.
488      * @throws DataBackendException Error accessing the data backend.
489      * @throws UnknownEntityException if the user account is not present.
490      */
491     public void removeAccount(User user)
492             throws UnknownEntityException, DataBackendException
493     {
494         if (!accountExists(user))
495         {
496             throw new UnknownEntityException("The account '"
497                     + user.getName() + "' does not exist");
498         }
499 
500         try
501         {
502             LDAPUser ldapUser = (LDAPUser) user;
503             String name = ldapUser.getDN();
504 
505             DirContext ctx = bindAsAdmin();
506 
507             ctx.unbind(name);
508         }
509         catch (NamingException ex)
510         {
511             throw new DataBackendException("NamingException caught", ex);
512         }
513     }
514 
515     /***
516      * Bind as the admin user.
517      *
518      * @throws NamingException when an error occurs with the named server.
519      * @return a new DirContext.
520      */
521     public static DirContext bindAsAdmin()
522             throws NamingException
523     {
524         String adminUser = LDAPSecurityConstants.getAdminUsername();
525         String adminPassword = LDAPSecurityConstants.getAdminPassword();
526 
527         return bind(adminUser, adminPassword);
528     }
529 
530     /***
531      * Creates an initial context.
532      *
533      * @param username admin username supplied in TRP.
534      * @param password admin password supplied in TRP
535      * @throws NamingException when an error occurs with the named server.
536      * @return a new DirContext.
537      */
538     public static DirContext bind(String username, String password)
539             throws NamingException
540     {
541         String host = LDAPSecurityConstants.getLDAPHost();
542         String port = LDAPSecurityConstants.getLDAPPort();
543         String providerURL = new String("ldap://" + host + ":" + port);
544         String ldapProvider = LDAPSecurityConstants.getLDAPProvider();
545         String authentication = LDAPSecurityConstants.getLDAPAuthentication();
546 
547         /*
548          * creating an initial context using Sun's client
549          * LDAP Provider.
550          */
551         Hashtable env = new Hashtable();
552 
553         env.put(Context.INITIAL_CONTEXT_FACTORY, ldapProvider);
554         env.put(Context.PROVIDER_URL, providerURL);
555         env.put(Context.SECURITY_AUTHENTICATION, authentication);
556         env.put(Context.SECURITY_PRINCIPAL, username);
557         env.put(Context.SECURITY_CREDENTIALS, password);
558 
559         DirContext ctx = new javax.naming.directory.InitialDirContext(env);
560 
561         return ctx;
562     }
563 
564     /***
565      * Create a new instance of the LDAP User according to the value
566      * configured in TurbineResources.properties.
567      * @return a new instance of the LDAP User.
568      * @throws DataBackendException if there is an error creating the
569      */
570     private LDAPUser createLDAPUser()
571             throws DataBackendException
572     {
573         try
574         {
575             return (LDAPUser) TurbineSecurity.getUserInstance();
576         }
577         catch (ClassCastException ex)
578         {
579             throw new DataBackendException("ClassCastException:", ex);
580         }
581         catch (UnknownEntityException ex)
582         {
583             throw new DataBackendException("UnknownEntityException:", ex);
584         }
585     }
586 
587 }