1 package org.apache.turbine.services.security.ldap;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
137
138 String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
139 String filter = LDAPSecurityConstants.getNameAttribute();
140
141 filter = "(" + filter + "=" + username + ")";
142
143
144
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
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
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
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
571
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 }