View Javadoc

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