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