cdmlib part of fix #4121 (Changing password does not work)
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / UserService.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.cdm.api.service;
11
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.UUID;
18
19 import org.hibernate.NonUniqueResultException;
20 import org.hibernate.criterion.Criterion;
21 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.dao.DataAccessException;
23 import org.springframework.dao.IncorrectResultSizeDataAccessException;
24 import org.springframework.security.access.AccessDeniedException;
25 import org.springframework.security.access.prepost.PreAuthorize;
26 import org.springframework.security.authentication.AuthenticationManager;
27 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
28 import org.springframework.security.authentication.dao.SaltSource;
29 import org.springframework.security.authentication.encoding.PasswordEncoder;
30 import org.springframework.security.core.Authentication;
31 import org.springframework.security.core.GrantedAuthority;
32 import org.springframework.security.core.context.SecurityContextHolder;
33 import org.springframework.security.core.userdetails.UserCache;
34 import org.springframework.security.core.userdetails.UserDetails;
35 import org.springframework.security.core.userdetails.UsernameNotFoundException;
36 import org.springframework.security.core.userdetails.cache.NullUserCache;
37 import org.springframework.stereotype.Service;
38 import org.springframework.transaction.annotation.Transactional;
39 import org.springframework.util.Assert;
40
41 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
42 import eu.etaxonomy.cdm.model.common.GrantedAuthorityImpl;
43 import eu.etaxonomy.cdm.model.common.Group;
44 import eu.etaxonomy.cdm.model.common.User;
45 import eu.etaxonomy.cdm.persistence.dao.common.IGrantedAuthorityDao;
46 import eu.etaxonomy.cdm.persistence.dao.common.IGroupDao;
47 import eu.etaxonomy.cdm.persistence.dao.common.IUserDao;
48 import eu.etaxonomy.cdm.persistence.query.MatchMode;
49 import eu.etaxonomy.cdm.persistence.query.OrderHint;
50
51 /**
52 * Note: All group related functionality has been refactored into a GroupService. The will be removed in a future version.
53 */
54 @Service
55 @Transactional(readOnly = true)
56 // NOTE: no type level @PreAuthorize annotation for this class!
57 public class UserService extends ServiceBase<User,IUserDao> implements IUserService {
58
59 protected IGroupDao groupDao;
60
61 protected IGrantedAuthorityDao grantedAuthorityDao;
62
63 private SaltSource saltSource; // = new ReflectionSaltSource();
64
65 private PasswordEncoder passwordEncoder; // = new Md5PasswordEncoder();
66
67 private AuthenticationManager authenticationManager;
68
69 private UserCache userCache = new NullUserCache();
70
71 @Autowired(required = false)
72 public void setUserCache(UserCache userCache) {
73 Assert.notNull(userCache, "userCache cannot be null");
74 this.userCache = userCache;
75 }
76
77 @Autowired(required = false)
78 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
79
80 this.passwordEncoder = passwordEncoder;
81 }
82
83 @Autowired(required = false)
84 public void setSaltSource(SaltSource saltSource) {
85 this.saltSource = saltSource;
86 }
87
88 @Autowired(required= false)
89 public void setAuthenticationManager(AuthenticationManager authenticationManager) {
90 this.authenticationManager = authenticationManager;
91 }
92
93 @Override
94 @Autowired
95 protected void setDao(IUserDao dao) {
96 this.dao = dao;
97 }
98
99 @Autowired
100 public void setGroupDao(IGroupDao groupDao) {
101 this.groupDao = groupDao;
102 }
103
104 @Autowired
105 public void setGrantedAuthorityDao(IGrantedAuthorityDao grantedAuthorityDao) {
106 this.grantedAuthorityDao = grantedAuthorityDao;
107 }
108
109 /**
110 * Changes the own password of in the database of the user which is
111 * currently authenticated. Requires to supply the old password for security
112 * reasons. Refreshes the authentication in the SecurityContext after the
113 * password change by re-authenticating the user with the new password.
114 *
115 * @see org.springframework.security.provisioning.UserDetailsManager#changePassword(java.lang.String,
116 * java.lang.String)
117 */
118 @Override
119 @Transactional(readOnly=false)
120 @PreAuthorize("isAuthenticated()")
121 public void changePassword(String oldPassword, String newPassword) {
122 Assert.hasText(oldPassword);
123 Assert.hasText(newPassword);
124 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
125 if(authentication != null && authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
126
127 // get current authentication and load it from the persistence layer,
128 // to make sure we are modifying the instance which is
129 // attached to the hibernate session
130 User user = (User)authentication.getPrincipal();
131 user = dao.load(user.getUuid());
132
133 // check if old password is valid
134 authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), oldPassword));
135
136 // make new password and set it
137 Object salt = this.saltSource.getSalt(user);
138 String password = passwordEncoder.encodePassword(newPassword, salt);
139 user.setPassword(password);
140 dao.update(user);
141
142 // authenticate the user again with the new password
143 UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
144 newAuthentication.setDetails(authentication.getDetails());
145 SecurityContextHolder.getContext().setAuthentication(newAuthentication);
146 userCache.removeUserFromCache(user.getUsername());
147
148 } else {
149 throw new AccessDeniedException("Can't change password as no Authentication object found in context for current user.");
150 }
151 }
152
153 /* (non-Javadoc)
154 * @see eu.etaxonomy.cdm.api.service.IUserService#changePasswordForUser(java.lang.String, java.lang.String)
155 */
156 @Override
157 @Transactional(readOnly=false)
158 @PreAuthorize("#username == authentication.name or hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
159 public void changePasswordForUser(String username, String newPassword) {
160 Assert.hasText(username);
161 Assert.hasText(newPassword);
162
163 try {
164 User user = dao.findUserByUsername(username);
165 if(user == null) {
166 throw new UsernameNotFoundException(username);
167 }
168
169 Object salt = this.saltSource.getSalt(user);
170
171 String password = passwordEncoder.encodePassword(newPassword, salt);
172 user.setPassword(password);
173
174 dao.update(user);
175 userCache.removeUserFromCache(user.getUsername());
176 } catch(NonUniqueResultException nure) {
177 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
178 }
179 }
180
181 /* (non-Javadoc)
182 * @see org.springframework.security.provisioning.UserDetailsManager#createUser(org.springframework.security.core.userdetails.UserDetails)
183 */
184 @Override
185 @Transactional(readOnly=false)
186 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
187 public void createUser(UserDetails user) {
188 Assert.isInstanceOf(User.class, user);
189
190 String rawPassword = user.getPassword();
191 Object salt = this.saltSource.getSalt(user);
192
193 String password = passwordEncoder.encodePassword(rawPassword, salt);
194 ((User)user).setPassword(password);
195
196 UUID userUUID = dao.save((User)user);
197
198
199 }
200
201
202
203 /* (non-Javadoc)
204 * @see org.springframework.security.provisioning.UserDetailsManager#deleteUser(java.lang.String)
205 */
206 @Override
207 @Transactional(readOnly=false)
208 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
209 public void deleteUser(String username) {
210 Assert.hasLength(username);
211
212 User user = dao.findUserByUsername(username);
213 if(user != null) {
214 dao.delete(user);
215 }
216
217 userCache.removeUserFromCache(username);
218 }
219
220 /* (non-Javadoc)
221 * @see org.springframework.security.provisioning.UserDetailsManager#updateUser(org.springframework.security.core.userdetails.UserDetails)
222 */
223 @Override
224 @Transactional(readOnly=false)
225 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
226 public void updateUser(UserDetails user) {
227 Assert.isInstanceOf(User.class, user);
228
229 dao.update((User)user);
230 userCache.removeUserFromCache(user.getUsername());
231 }
232
233 /* (non-Javadoc)
234 * @see org.springframework.security.provisioning.UserDetailsManager#userExists(java.lang.String)
235 */
236 @Override
237 public boolean userExists(String username) {
238 Assert.hasText(username);
239
240 User user = dao.findUserByUsername(username);
241 return user != null;
242 }
243
244 /**
245 * <b>DO NOT CALL THIS METHOD IN LONG RUNNING SESSIONS OR CONVERSATIONS
246 * A THROWN UsernameNotFoundException WILL RENDER THE CONVERSATION UNUSABLE</b>
247 *
248 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
249 */
250 // NOTE: this method must not be secured since it is being used during the
251 // authentication process
252 @Override
253 public UserDetails loadUserByUsername(String username)
254 throws UsernameNotFoundException, DataAccessException {
255 Assert.hasText(username);
256 try {
257 User user = dao.findUserByUsername(username);
258 if(user == null) {
259 throw new UsernameNotFoundException(username);
260 }
261 return user;
262 } catch(NonUniqueResultException nure) {
263 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
264 }
265 }
266
267 /* (non-Javadoc)
268 * @see org.springframework.security.provisioning.GroupManager#addGroupAuthority(java.lang.String, org.springframework.security.core.GrantedAuthority)
269 */
270 @Override
271 @Deprecated // use GroupService instead
272 @Transactional(readOnly=false)
273 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
274 public void addGroupAuthority(String groupName, GrantedAuthority authority) {
275 Assert.hasText(groupName);
276 Assert.notNull(authority);
277
278 Group group = groupDao.findGroupByName(groupName);
279 if(group.getGrantedAuthorities().add(authority)) {
280 groupDao.update(group);
281 }
282 }
283
284 /* (non-Javadoc)
285 * @see org.springframework.security.provisioning.GroupManager#addUserToGroup(java.lang.String, java.lang.String)
286 */
287 @Override
288 @Deprecated // use GroupService instead
289 @Transactional(readOnly=false)
290 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
291 public void addUserToGroup(String username, String groupName) {
292 Assert.hasText(username);
293 Assert.hasText(groupName);
294
295 Group group = groupDao.findGroupByName(groupName);
296 User user = dao.findUserByUsername(username);
297
298 if(group.addMember(user)) {
299 groupDao.update(group);
300 userCache.removeUserFromCache(user.getUsername());
301 }
302 }
303
304 /* (non-Javadoc)
305 * @see org.springframework.security.provisioning.GroupManager#createGroup(java.lang.String, java.util.List)
306 */
307 @Override
308 @Deprecated // use GroupService instead
309 @Transactional(readOnly=false)
310 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
311 public void createGroup(String groupName, List<GrantedAuthority> authorities) {
312 Assert.hasText(groupName);
313 Assert.notNull(authorities);
314
315 Group group = Group.NewInstance(groupName);
316
317 for(GrantedAuthority authority : authorities) {
318 group.getGrantedAuthorities().add(authority);
319 }
320
321 groupDao.save(group);
322 }
323
324 /* (non-Javadoc)
325 * @see org.springframework.security.provisioning.GroupManager#deleteGroup(java.lang.String)
326 */
327 @Override
328 @Deprecated // use GroupService instead
329 @Transactional(readOnly=false)
330 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
331 public void deleteGroup(String groupName) {
332 Assert.hasText(groupName);
333
334 Group group = groupDao.findGroupByName(groupName);
335 groupDao.delete(group);
336 }
337
338 /* (non-Javadoc)
339 * @see org.springframework.security.provisioning.GroupManager#findAllGroups()
340 */
341 @Override
342 @Deprecated // use GroupService instead
343 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
344 public List<String> findAllGroups() {
345 return groupDao.listNames(null,null);
346 }
347
348 /* (non-Javadoc)
349 * @see org.springframework.security.provisioning.GroupManager#findGroupAuthorities(java.lang.String)
350 */
351 @Override
352 @Deprecated // use GroupService instead
353 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
354 public List<GrantedAuthority> findGroupAuthorities(String groupName) {
355 Assert.hasText(groupName);
356 Group group = groupDao.findGroupByName(groupName);
357
358 return new ArrayList<GrantedAuthority>(group.getGrantedAuthorities());
359 }
360
361 /* (non-Javadoc)
362 * @see org.springframework.security.provisioning.GroupManager#findUsersInGroup(java.lang.String)
363 */
364 @Override
365 @Deprecated // use GroupService instead
366 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
367 public List<String> findUsersInGroup(String groupName) {
368 Assert.hasText(groupName);
369 Group group = groupDao.findGroupByName(groupName);
370
371 List<String> users = groupDao.listMembers(group, null, null);
372
373 return users;
374 }
375
376 /* (non-Javadoc)
377 * @see org.springframework.security.provisioning.GroupManager#removeGroupAuthority(java.lang.String, org.springframework.security.core.GrantedAuthority)
378 */
379 @Override
380 @Deprecated // use GroupService instead
381 @Transactional(readOnly=false)
382 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
383 public void removeGroupAuthority(String groupName, GrantedAuthority authority) {
384 Assert.hasText(groupName);
385 Assert.notNull(authority);
386
387 Group group = groupDao.findGroupByName(groupName);
388
389 if(group.getGrantedAuthorities().remove(authority)) {
390 groupDao.update(group);
391 }
392 }
393
394 /* (non-Javadoc)
395 * @see org.springframework.security.provisioning.GroupManager#removeUserFromGroup(java.lang.String, java.lang.String)
396 */
397 @Override
398 @Deprecated // use GroupService instead
399 @Transactional(readOnly=false)
400 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
401 public void removeUserFromGroup(String username, String groupName) {
402 Assert.hasText(username);
403 Assert.hasText(groupName);
404
405 Group group = groupDao.findGroupByName(groupName);
406 User user = dao.findUserByUsername(username);
407
408 if(group.removeMember(user)) {
409 groupDao.update(group);
410 userCache.removeUserFromCache(user.getUsername());
411 }
412 }
413
414 /* (non-Javadoc)
415 * @see org.springframework.security.provisioning.GroupManager#renameGroup(java.lang.String, java.lang.String)
416 */
417 @Override
418 @Deprecated // use GroupService instead
419 @Transactional(readOnly=false)
420 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
421 public void renameGroup(String oldName, String newName) {
422 Assert.hasText(oldName);
423 Assert.hasText(newName);
424
425 Group group = groupDao.findGroupByName(oldName);
426
427 group.setName(newName);
428 groupDao.update(group);
429 }
430
431 /* (non-Javadoc)
432 * @see eu.etaxonomy.cdm.api.service.ServiceBase#save(eu.etaxonomy.cdm.model.common.CdmBase)
433 */
434 @Override
435 @Transactional(readOnly=false)
436 // @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_RUN_AS_ADMIN') or hasRole('ROLE_USER_MANAGER')")
437 public UUID save(User user) {
438 if(user.getId() == 0 || dao.load(user.getUuid()) == null){
439 createUser(user);
440 }else{
441 updateUser(user);
442 }
443 return user.getUuid();
444 }
445
446 /* (non-Javadoc)
447 * @see eu.etaxonomy.cdm.api.service.ServiceBase#update(eu.etaxonomy.cdm.model.common.CdmBase)
448 */
449 @Override
450 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
451 public UUID update(User user) {
452 updateUser(user);
453 return user.getUuid();
454 }
455
456 /* (non-Javadoc)
457 * @see eu.etaxonomy.cdm.api.service.IUserService#saveGrantedAuthority(org.springframework.security.core.GrantedAuthority)
458 */
459 @Override
460 @Transactional(readOnly=false)
461 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
462 public UUID saveGrantedAuthority(GrantedAuthority grantedAuthority) {
463 return grantedAuthorityDao.save((GrantedAuthorityImpl)grantedAuthority);
464 }
465
466
467
468 /* (non-Javadoc)
469 * @see eu.etaxonomy.cdm.api.service.IUserService#listByUsername(java.lang.String, eu.etaxonomy.cdm.persistence.query.MatchMode, java.util.List, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
470 */
471 @Override
472 @Transactional(readOnly = true)
473 public List<User> listByUsername(String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
474 Integer numberOfResults = dao.countByUsername(queryString, matchmode, criteria);
475
476 List<User> results = new ArrayList<User>();
477 if(numberOfResults > 0) {
478 results = dao.findByUsername(queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
479 }
480 return results;
481 }
482
483 /* ================================================
484 * overriding methods to secure them
485 * via the type level annotation @PreAuthorize
486 * ================================================ */
487
488 @Override
489 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
490 public UUID delete(User persistentObject) throws ReferencedObjectUndeletableException {
491 return super.delete(persistentObject);
492 }
493
494 @Override
495 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
496 public Map<UUID, User> save(Collection<User> newInstances) {
497 Map<UUID, User> users = new HashMap<UUID, User>();
498 for (User user: newInstances){
499 createUser(user);
500 users.put(user.getUuid(), user);
501 }
502 return users;
503 }
504
505 @Override
506 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
507 public UUID saveOrUpdate(User transientObject) {
508 return super.saveOrUpdate(transientObject);
509 }
510
511 @Override
512 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
513 public Map<UUID, User> saveOrUpdate(Collection<User> transientInstances) {
514 return super.saveOrUpdate(transientInstances);
515 }
516
517 }