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