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 }