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