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 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
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
134
135 String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
136 String filter = LDAPSecurityConstants.getNameAttribute();
137
138 filter = "(" + filter + "=" + username + ")";
139
140
141
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
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
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
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
568
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 }