1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.services.security.ldap;
18
19 import java.security.Principal;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Vector;
23 import javax.naming.NamingEnumeration;
24 import javax.naming.directory.Attributes;
25 import javax.naming.directory.BasicAttributes;
26 import javax.naming.directory.DirContext;
27 import javax.naming.directory.SearchResult;
28 import javax.servlet.ServletConfig;
29
30
31 import org.apache.jetspeed.om.profile.Profile;
32 import org.apache.jetspeed.om.security.JetspeedUser;
33 import org.apache.jetspeed.om.security.UserNamePrincipal;
34 import org.apache.jetspeed.om.security.ldap.LDAPUser;
35 import org.apache.jetspeed.services.JetspeedLDAP;
36 import org.apache.jetspeed.services.JetspeedSecurity;
37 import org.apache.jetspeed.services.Profiler;
38 import org.apache.jetspeed.services.PsmlManager;
39 import org.apache.jetspeed.services.ldap.LDAPURL;
40 import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
41 import org.apache.jetspeed.services.logging.JetspeedLogger;
42 import org.apache.jetspeed.services.rundata.JetspeedRunData;
43 import org.apache.jetspeed.services.rundata.JetspeedRunDataService;
44 import org.apache.jetspeed.services.security.CredentialsManagement;
45 import org.apache.jetspeed.services.security.JetspeedSecurityException;
46 import org.apache.jetspeed.services.security.JetspeedSecurityService;
47 import org.apache.jetspeed.services.security.NotUniqueUserException;
48 import org.apache.jetspeed.services.security.UnknownUserException;
49 import org.apache.jetspeed.services.security.UserException;
50 import org.apache.jetspeed.services.security.UserManagement;
51
52
53 import org.apache.turbine.services.InitializationException;
54 import org.apache.turbine.services.TurbineBaseService;
55 import org.apache.turbine.services.TurbineServices;
56 import org.apache.turbine.services.resources.ResourceService;
57 import org.apache.turbine.services.rundata.RunDataService;
58
59 /***
60 *
61 * @author <a href="mailto:ender@kilicoglu.nom.tr">Ender KILICOGLU</a>
62 * @author <a href="mailto:sami.leino@netorek.fi">Sami Leino</a>
63 *
64 * @version $Id: LDAPUserManagement.java,v 1.10 2004/02/23 03:52:33 jford Exp $
65 *
66 */
67 public class LDAPUserManagement extends TurbineBaseService
68 implements UserManagement,
69 CredentialsManagement
70 {
71 /***
72 * Static initialization of the logger for this class
73 */
74 private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(LDAPUserManagement.class.getName());
75
76
77 private final static String CONFIG_SECURE_PASSWORDS_KEY = "secure.passwords";
78 private final static String CONFIG_SECURE_PASSWORDS_ALGORITHM = "secure.passwords.algorithm";
79 private final static String CONFIG_SECURE_PASSWORDS_SUFFIX = "secure.passwords.suffix";
80 private final static String CONFIG_NEWUSER_ROLES = "newuser.roles";
81 private final static String[] DEFAULT_CONFIG_NEWUSER_ROLES = { "user" };
82
83 private final static String[] ATTRS = { "ou", "userPassword", "uid", "mail", "sn", "givenName",
84 "uidNumber", "name", "objectdata", "objectClass",
85 "usergrouprole", "lastlogindate", "lastmodifieddate",
86 "creationdate", "confirm", "disabled" };
87
88
89 protected static boolean securePasswords = false;
90 protected static String passwordsAlgorithm = "crypt";
91 protected static String passwordsSuffix = "{crypt}";
92
93
94 protected JetspeedRunDataService runDataService = null;
95 protected String roles[] = null;
96
97
98
99
100
101
102 /***
103 * Retrieves a <code>JetspeedUser</code> given the primary principle.
104 * The principal can be any valid Jetspeed Security Principal:
105 * <code>org.apache.jetspeed.om.security.UserNamePrincipal</code>
106 * <code>org.apache.jetspeed.om.security.UserIdPrincipal</code>
107 *
108 * The security service may optionally check the current user context
109 * to determine if the requestor has permission to perform this action.
110 *
111 * @param principal a principal identity to be retrieved.
112 *
113 * @return a <code>JetspeedUser</code> associated to the principal identity.
114 * @exception UserException when the security provider has a general failure retrieving a user.
115 * @exception UnknownUserException when the security provider cannot match
116 * the principal identity to a user.
117 * @exception InsufficientPrivilegeException when the requestor is denied
118 * due to insufficient privilege
119 */
120 public JetspeedUser getUser(Principal principal)
121 throws JetspeedSecurityException
122 {
123 BasicAttributes attr = new BasicAttributes();
124 Vector userurls = new Vector();
125 LDAPUser user = null;
126
127 try
128 {
129 userurls = JetspeedLDAP.search(JetspeedLDAP.buildURL("ou=users"),
130 "(&(uid="+principal.getName()+")(objectclass=jetspeeduser))", ATTRS, true);
131 }
132 catch (Exception e)
133 {
134 logger.error( "Failed to retrieve user '" + principal.getName() + "'", e );
135 throw new UserException("Failed to retrieve user '" + principal.getName() + "'", e);
136 }
137
138 if (userurls.size() == 1)
139 {
140 user = new LDAPUser((LDAPURL) ((Vector)userurls.elementAt(0)).firstElement());
141 return user;
142 }
143 else if(userurls.size() > 1)
144 {
145 throw new UserException("Multiple Users with same username '" + principal.getName() + "'");
146 }
147 else
148 {
149 throw new UnknownUserException("Unknown user '" + principal.getName() + "'");
150 }
151 }
152
153 /***
154 * Retrieves a collection of all <code>JetspeedUser</code>s.
155 * The security service may optionally check the current user context
156 * to determine if the requestor has permission to perform this action.
157 *
158 * @return a collection of <code>JetspeedUser</code> entities.
159 * @exception UserException when the security provider has a general failure retrieving users.
160 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
161 */
162 public Iterator getUsers()
163 throws JetspeedSecurityException
164 {
165 String filter = "(objectclass=jetspeeduser)";
166 return getUsersUsingLDAPSpecificFilter(filter, null);
167 }
168
169 /***
170 * Retrieves a collection of <code>JetspeedUser</code>s filtered by a security
171 * provider-specific query string. For example SQL, OQL, JDOQL.
172 * The security service may optionally check the current user context
173 * to determine if the requestor has permission to perform this action.
174 *
175 * @return a collection of <code>JetspeedUser</code> entities.
176 * @exception UserException when the security provider has a general failure retrieving users.
177 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
178 *
179 */
180 public Iterator getUsers(String filter)
181 throws JetspeedSecurityException
182 {
183
184 return getUsersUsingLDAPSpecificFilter(filter, null);
185 }
186
187 /***
188 * Retrieves a collection of <code>JetspeedUser</code>s filtered by a security
189 * provider-specific query string. For example SQL, OQL, JDOQL.
190 * The security service may optionally check the current user context
191 * to determine if the requestor has permission to perform this action.
192 *
193 * @return a collection of <code>JetspeedUser</code> entities.
194 * @exception UserException when the security provider has a general failure retrieving users.
195 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
196 */
197 protected Iterator getUsersUsingLDAPSpecificFilter(String filter, String[] attributesToFetch)
198 throws JetspeedSecurityException
199 {
200 String baseDN = "ou=users";
201 NamingEnumeration userEnum = null;
202 List resultList = new Vector(1024);
203
204 try
205 {
206 LDAPURL url = JetspeedLDAP.buildURL( baseDN );
207 DirContext ctx = JetspeedLDAP.getService().connect(url);
208 userEnum = JetspeedLDAP.search(ctx, url.getDN(), filter, attributesToFetch, JetspeedLDAP.getService().SUB);
209
210 while (userEnum.hasMoreElements())
211 {
212 LDAPUser user = buildUser(((SearchResult)userEnum.nextElement()).getAttributes());
213 resultList.add( user );
214 }
215
216 JetspeedLDAP.getService().checkAndCloseContext(ctx);
217 }
218 catch ( Exception e )
219 {
220 logger.error( "Failed to retrieve user with filter:" + filter, e );
221 throw new UserException( "Failed to retrieve user with filter:" + filter, e );
222 }
223
224 return ( resultList.iterator() );
225 }
226
227 protected LDAPUser buildUser(Attributes attributes)
228 {
229 return new LDAPUser(attributes);
230 }
231
232 /***
233 * Saves a <code>JetspeedUser</code>'s attributes into permanent storage.
234 * The user's account is required to exist in the storage.
235 * The security service may optionally check the current user context
236 * to determine if the requestor has permission to perform this action.
237 *
238 * @exception UserException when the security provider has a general failure retrieving users.
239 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
240 */
241 public void saveUser(JetspeedUser user)
242 throws JetspeedSecurityException
243 {
244 if(!accountExists(user, true))
245 {
246 throw new UnknownUserException("Cannot save user '" + user.getUserName() +
247 "', User doesn't exist");
248 }
249 try
250 {
251 ((LDAPUser)user).update(false);
252 }
253 catch(Exception e)
254 {
255 logger.error( "Failed to save user object ", e);
256 throw new UserException("Failed to save user object ", e);
257 }
258 }
259
260 /***
261 * Adds a <code>JetspeedUser</code> into permanent storage.
262 * The security service can throw a <code>NotUniqueUserException</code> when the public
263 * credentials fail to meet the security provider-specific unique constraints.
264 * The security service may optionally check the current user context
265 * to determine if the requestor has permission to perform this action.
266 *
267 * @exception UserException when the security provider has a general failure retrieving users.
268 * @exception NotUniqueUserException when the public credentials fail to meet
269 * the security provider-specific unique constraints.
270 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
271 */
272 public void addUser(JetspeedUser user)
273 throws JetspeedSecurityException
274 {
275 if(accountExists(user))
276 {
277 throw new NotUniqueUserException("The account '" +
278 user.getUserName() + "' already exists");
279 }
280
281 String initialPassword = user.getPassword();
282 String encrypted = JetspeedSecurity.encryptPassword(initialPassword);
283 user.setPassword(encrypted);
284 ((LDAPUser)user).update(true);
285
286 addDefaultPSML(user);
287 }
288
289
290
291
292
293
294
295
296 protected void addDefaultPSML(JetspeedUser user)
297 throws JetspeedSecurityException
298 {
299 for (int ix = 0; ix < roles.length; ix++)
300 {
301 try
302 {
303
304 JetspeedSecurity.grantRole(user.getUserName(),
305 JetspeedSecurity.getRole(roles[ix]).getName());
306 }
307 catch(Exception e)
308 {
309 logger.error("Could not grant role: " + roles[ix] + " to user " + user.getUserName(), e);
310 }
311 }
312 try
313 {
314 JetspeedRunData rundata = getRunData();
315 if (rundata != null)
316 {
317 Profile profile = Profiler.createProfile();
318 profile.setUser(user);
319 profile.setMediaType("html");
320 Profiler.createProfile(getRunData(), profile);
321 }
322 }
323 catch (Exception e)
324 {
325 logger.error( "Failed to create profile for new user ", e );
326 removeUser(new UserNamePrincipal(user.getUserName()));
327 throw new UserException("Failed to create profile for new user ", e);
328 }
329 }
330
331 /***
332 * Removes a <code>JetspeedUser</code> from the permanent store.
333 * The security service may optionally check the current user context
334 * to determine if the requestor has permission to perform this action.
335 *
336 * @param principal the principal identity to be retrieved.
337 * @exception UserException when the security provider has a general failure retrieving a user.
338 * @exception UnknownUserException when the security provider cannot match
339 * the principal identity to a user.
340 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
341 */
342 public void removeUser(Principal principal)
343 throws JetspeedSecurityException
344 {
345 BasicAttributes attr= new BasicAttributes();
346 Vector userurls = new Vector();
347 LDAPUser user = (LDAPUser)getUser(principal);
348
349 try
350 {
351 JetspeedLDAP.deleteEntry(user.getldapurl());
352 PsmlManager.removeUserDocuments(user);
353 }
354 catch(Exception e)
355 {
356 logger.error( "Failed to remove account '" + user.getUserName() + "'", e );
357 throw new UserException("Failed to remove account '" +
358 user.getUserName() + "'", e);
359 }
360
361 }
362
363
364
365
366
367 /***
368 * Allows for a user to change their own password.
369 *
370 * @param user the JetspeedUser to change password
371 * @param oldPassword the current password supplied by the user.
372 * @param newPassword the current password requested by the user.
373 * @exception UserException when the security provider has a general failure retrieving a user.
374 * @exception UnknownUserException when the security provider cannot match
375 * the principal identity to a user.
376 * @exception InsufficientPrivilegeException when the requestor is denied due to insufficient privilege
377 */
378 public void changePassword( JetspeedUser user,
379 String oldPassword,
380 String newPassword )
381 throws JetspeedSecurityException
382 {
383 oldPassword = JetspeedSecurity.convertPassword(oldPassword);
384 newPassword = JetspeedSecurity.convertPassword(newPassword);
385
386 if(!accountExists(user))
387 {
388 throw new UnknownUserException("The account '" +
389 user.getUserName() + "' does not exist");
390 }
391 else if (!passwordsMatch(user, oldPassword))
392 {
393 throw new UserException(
394 "The supplied old password for '" + user.getUserName() +
395 "' was incorrect");
396 }
397
398 String encrypted = JetspeedSecurity.encryptPassword( newPassword );
399 user.setPassword(encrypted);
400
401
402
403
404 saveUser(user);
405 }
406
407 /***
408 * Forcibly sets new password for a User.
409 *
410 * Provides an administrator the ability to change the forgotten or
411 * compromised passwords. Certain implementatations of this feature
412 * would require administrative level access to the authenticating
413 * server / program.
414 *
415 * @param user the user to change the password for.
416 * @param password the new password.
417 *
418 * @exception UserException when the security provider has a general
419 * failure retrieving a user.
420 * @exception UnknownUserException when the security provider cannot match
421 * the principal identity to a user.
422 * @exception InsufficientPrivilegeException when the requestor is
423 * denied due to insufficient privilege
424 */
425 public void forcePassword( JetspeedUser user, String password )
426 throws JetspeedSecurityException
427 {
428 if(!accountExists(user))
429 {
430 throw new UnknownUserException("The account '" +
431 user.getUserName() + "' does not exist");
432 }
433
434 String encrypted = JetspeedSecurity.encryptPassword( password );
435 user.setPassword(encrypted);
436
437
438
439
440
441 saveUser(user);
442 }
443
444 /***
445 * This method provides client-side encryption of passwords.
446 *
447 * If <code>secure.passwords</code> are enabled in
448 * JetspeedSecurity.properties,
449 * the password will be encrypted, if not, it will be returned unchanged.
450 * The <code>secure.passwords.algorithm</code> property can be used
451 * to chose which digest algorithm should be used for performing the
452 * encryption. <code>SHA</code> is used by default.
453 *
454 * @param password the password to process
455 *
456 * @return processed password
457 *
458 */
459 public String encryptPassword( String password )
460 throws JetspeedSecurityException
461 {
462 if (securePasswords == false)
463 {
464 return password;
465 }
466 else if(password == null)
467 {
468 return null;
469 }
470 else if(password.startsWith(passwordsSuffix))
471 {
472
473 return password;
474 }
475
476 return passwordsSuffix + UnixCrypt.crypt(password);
477 }
478
479 /***
480 * <p>Check's if user's current password matches with the
481 * supplied password.</p>
482 *
483 * @param user User whose password will be checked
484 * @param suppliedPassword Password to match
485 *
486 * @return True if passwords match.
487 *
488 */
489 public static boolean passwordsMatch(JetspeedUser user, String suppliedPassword)
490 {
491 if (securePasswords == false)
492 {
493 return user.getPassword().equals(suppliedPassword);
494 }
495 else
496 {
497 return UnixCrypt.matches(user.getPassword().substring(passwordsSuffix.length()), suppliedPassword);
498 }
499 }
500
501
502
503
504
505 /***
506 * This is the early initialization method called by the
507 * Turbine <code>Service</code> framework
508 * @param conf The <code>ServletConfig</code>
509 * @exception throws a <code>InitializationException</code> if the service
510 * fails to initialize
511 */
512 public synchronized void init(ServletConfig conf)
513 throws InitializationException
514 {
515 if (getInit()) return;
516
517 super.init(conf);
518
519
520 ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance())
521 .getResources(JetspeedSecurityService.SERVICE_NAME);
522
523 securePasswords = serviceConf.getBoolean(CONFIG_SECURE_PASSWORDS_KEY,
524 securePasswords);
525 passwordsAlgorithm = serviceConf.getString(CONFIG_SECURE_PASSWORDS_ALGORITHM,
526 passwordsAlgorithm);
527 passwordsSuffix = serviceConf.getString(CONFIG_SECURE_PASSWORDS_SUFFIX,
528 passwordsSuffix);
529
530 try
531 {
532 roles = serviceConf.getStringArray(CONFIG_NEWUSER_ROLES);
533 }
534 catch (Exception e)
535 {
536 }
537
538 if (null == roles || roles.length == 0)
539 {
540 roles = DEFAULT_CONFIG_NEWUSER_ROLES;
541 }
542
543 this.runDataService = (JetspeedRunDataService)TurbineServices.getInstance()
544 .getService(RunDataService.SERVICE_NAME);
545
546 setInit(true);
547 }
548
549
550
551
552
553 /***
554 * Check whether a specified user's account exists.
555 *
556 * The login name is used for looking up the account.
557 *
558 * @param user The user to be checked.
559 * @param checkUniqueId Make sure that we aren't
560 * overwriting another user
561 * with different id.
562 *
563 * @return true If the specified account exists
564 *
565 * @throws UserException If there was a general db access error
566 *
567 */
568 protected boolean accountExists( JetspeedUser user )
569 throws UserException
570 {
571 return accountExists(user, false);
572 }
573
574 protected boolean accountExists( JetspeedUser user, boolean checkUniqueId )
575 throws UserException
576 {
577 UserNamePrincipal principal = new UserNamePrincipal(user.getUserName());
578
579 try
580 {
581 getUser(principal);
582 return true;
583 }
584 catch (Exception e)
585 {
586 return false;
587 }
588 }
589
590 protected JetspeedRunData getRunData()
591 {
592 JetspeedRunData rundata = null;
593
594 if (this.runDataService != null)
595 {
596 rundata = this.runDataService.getCurrentRunData();
597 }
598
599 return rundata;
600 }
601
602 }