Merge branch 'develop' of ssh://dev.e-taxonomy.eu/var/git/cdmlib into develop
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / UserService.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.api.service;
10
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.UUID;
17
18 import org.hibernate.NonUniqueResultException;
19 import org.hibernate.criterion.Criterion;
20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.context.annotation.Lazy;
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.model.permission.GrantedAuthorityImpl;
42 import eu.etaxonomy.cdm.model.permission.User;
43 import eu.etaxonomy.cdm.persistence.dao.permission.IGrantedAuthorityDao;
44 import eu.etaxonomy.cdm.persistence.dao.permission.IGroupDao;
45 import eu.etaxonomy.cdm.persistence.dao.permission.IUserDao;
46 import eu.etaxonomy.cdm.persistence.query.MatchMode;
47 import eu.etaxonomy.cdm.persistence.query.OrderHint;
48
49 /**
50 * Note: All group related functionality has been refactored into a GroupService.
51 * The will be removed in a future version.
52 */
53 @Service
54 @Transactional(readOnly = true)
55 // NOTE: no type level @PreAuthorize annotation for this class!
56 public class UserService extends ServiceBase<User,IUserDao> implements IUserService {
57
58 protected IGroupDao groupDao;
59
60 protected IGrantedAuthorityDao grantedAuthorityDao;
61
62 private SaltSource saltSource; // = new ReflectionSaltSource();
63
64 private PasswordEncoder passwordEncoder; // = new Md5PasswordEncoder();
65
66 private AuthenticationManager authenticationManager;
67
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 @Lazy // avoid dependency cycle coming from OAuth2ServerConfiguration.AuthorizationServerConfiguration.authenticationManager
90 public void setAuthenticationManager(AuthenticationManager authenticationManager) {
91 this.authenticationManager = authenticationManager;
92 }
93
94 @Override
95 @Autowired
96 protected void setDao(IUserDao dao) {
97 this.dao = dao;
98 }
99
100 @Autowired
101 public void setGroupDao(IGroupDao groupDao) {
102 this.groupDao = groupDao;
103 }
104
105 @Autowired
106 public void setGrantedAuthorityDao(IGrantedAuthorityDao grantedAuthorityDao) {
107 this.grantedAuthorityDao = grantedAuthorityDao;
108 }
109
110
111 /**
112 * Changes the own password of in the database of the user which is
113 * currently authenticated. Requires to supply the old password for security
114 * reasons. Refreshes the authentication in the SecurityContext after the
115 * password change by re-authenticating the user with the new password.
116 *
117 * @see org.springframework.security.provisioning.UserDetailsManager#changePassword(java.lang.String,
118 * java.lang.String)
119 */
120 @Override
121 @Transactional(readOnly=false)
122 @PreAuthorize("isAuthenticated()")
123 public void changePassword(String oldPassword, String newPassword) {
124 Assert.hasText(oldPassword);
125 Assert.hasText(newPassword);
126 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
127 if(authentication != null && authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
128
129 // get current authentication and load it from the persistence layer,
130 // to make sure we are modifying the instance which is
131 // attached to the hibernate session
132 User user = (User)authentication.getPrincipal();
133 user = dao.load(user.getUuid());
134
135 // check if old password is valid
136 authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), oldPassword));
137
138 // make new password and set it
139 Object salt = this.saltSource.getSalt(user);
140 String password = passwordEncoder.encodePassword(newPassword, salt);
141 user.setPassword(password);
142 dao.update(user);
143
144 // authenticate the user again with the new password
145 UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
146 newAuthentication.setDetails(authentication.getDetails());
147 SecurityContextHolder.getContext().setAuthentication(newAuthentication);
148 userCache.removeUserFromCache(user.getUsername());
149
150 } else {
151 throw new AccessDeniedException("Can't change password as no Authentication object found in context for current user.");
152 }
153 }
154
155 @Override
156 @Transactional(readOnly=false)
157 @PreAuthorize("#username == authentication.name or hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
158 public void changePasswordForUser(String username, String newPassword) {
159 Assert.hasText(username);
160 Assert.hasText(newPassword);
161
162 try {
163 User user = dao.findUserByUsername(username);
164 if(user == null) {
165 throw new UsernameNotFoundException(username);
166 }
167
168 Object salt = this.saltSource.getSalt(user);
169
170 String password = passwordEncoder.encodePassword(newPassword, salt);
171 user.setPassword(password);
172
173 dao.update(user);
174 userCache.removeUserFromCache(user.getUsername());
175 } catch(NonUniqueResultException nure) {
176 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
177 }
178 }
179
180 @Override
181 @Transactional(readOnly=false)
182 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
183 public void createUser(UserDetails user) {
184 Assert.isInstanceOf(User.class, user);
185
186 String rawPassword = user.getPassword();
187 Object salt = this.saltSource.getSalt(user);
188
189 String password = passwordEncoder.encodePassword(rawPassword, salt);
190 ((User)user).setPassword(password);
191
192 dao.save((User)user);
193 }
194
195 @Override
196 @Transactional(readOnly=false)
197 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
198 public void deleteUser(String username) {
199 Assert.hasLength(username);
200
201 User user = dao.findUserByUsername(username);
202 if(user != null) {
203 dao.delete(user);
204 }
205
206 userCache.removeUserFromCache(username);
207 }
208
209 @Override
210 @Transactional(readOnly=false)
211 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
212 public void updateUser(UserDetails user) {
213 Assert.isInstanceOf(User.class, user);
214
215 dao.update((User)user);
216 userCache.removeUserFromCache(user.getUsername());
217 }
218
219 @Override
220 public boolean userExists(String username) {
221 Assert.hasText(username);
222
223 User user = dao.findUserByUsername(username);
224 return user != null;
225 }
226
227 /**
228 * <b>DO NOT CALL THIS METHOD IN LONG RUNNING SESSIONS OR CONVERSATIONS
229 * A THROWN UsernameNotFoundException WILL RENDER THE CONVERSATION UNUSABLE</b>
230 *
231 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
232 */
233 // NOTE: this method must not be secured since it is being used during the
234 // authentication process
235 @Override
236 public UserDetails loadUserByUsername(String username)
237 throws UsernameNotFoundException, DataAccessException {
238 Assert.hasText(username);
239 try {
240 User user = dao.findUserByUsername(username);
241 if(user == null) {
242 throw new UsernameNotFoundException(username);
243 }
244 return user;
245 } catch(NonUniqueResultException nure) {
246 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
247 }
248 }
249
250
251 @Override
252 @Transactional(readOnly=false)
253 // @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_RUN_AS_ADMIN') or hasRole('ROLE_USER_MANAGER')")
254 public User save(User user) {
255 if(user.getId() == 0 || dao.load(user.getUuid()) == null){
256 createUser(user);
257 }else{
258 updateUser(user);
259 }
260 return user;
261 }
262
263 @Override
264 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
265 public UUID update(User user) {
266 updateUser(user);
267 return user.getUuid();
268 }
269
270 @Override
271 @Transactional(readOnly=false)
272 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
273 public UUID saveGrantedAuthority(GrantedAuthority grantedAuthority) {
274 return grantedAuthorityDao.save((GrantedAuthorityImpl)grantedAuthority).getUuid();
275 }
276
277
278
279 @Override
280 @Transactional(readOnly = true)
281 public List<User> listByUsername(String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
282 long numberOfResults = dao.countByUsername(queryString, matchmode, criteria);
283
284 List<User> results = new ArrayList<>();
285 if(numberOfResults > 0) {
286 results = dao.findByUsername(queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
287 }
288 return results;
289 }
290
291 /* ================================================
292 * overriding methods to secure them
293 * via the type level annotation @PreAuthorize
294 * ================================================ */
295
296 @Override
297 @Transactional(readOnly=false)
298 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
299 public DeleteResult delete(User persistentObject) {
300 return super.delete(persistentObject);
301 }
302
303 @Override
304 @Transactional(readOnly=false)
305 public DeleteResult delete(UUID userUuid) {
306 return delete(dao.load(userUuid));
307 }
308
309 @Override
310 @Transactional(readOnly=false)
311 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
312 public Map<UUID, User> save(Collection<User> newInstances) {
313 Map<UUID, User> users = new HashMap<UUID, User>();
314 for (User user: newInstances){
315 createUser(user);
316 users.put(user.getUuid(), user);
317 }
318 return users;
319 }
320
321 @Override
322 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
323 public UUID saveOrUpdate(User transientObject) {
324 return super.saveOrUpdate(transientObject);
325 }
326
327 @Override
328 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
329 @Transactional(readOnly=false)
330 public User merge(User detachedObject) {
331 return super.merge(detachedObject);
332 }
333
334 @Override
335 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
336 @Transactional(readOnly=false)
337 public List<User> merge(List<User> detachedObjects) {
338 return super.merge(detachedObjects);
339 }
340
341 @Override
342 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
343 public Map<UUID, User> saveOrUpdate(Collection<User> transientInstances) {
344 return super.saveOrUpdate(transientInstances);
345 }
346
347 }