improve generics for collection save in service and dao layer
[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 encodeUserPassword(user, newPassword);
138 dao.update(user);
139
140 // authenticate the user again with the new password
141 UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
142 newAuthentication.setDetails(authentication.getDetails());
143 SecurityContextHolder.getContext().setAuthentication(newAuthentication);
144 userCache.removeUserFromCache(user.getUsername());
145
146 } else {
147 throw new AccessDeniedException("Can't change password as no Authentication object found in context for current user.");
148 }
149 }
150
151 /**
152 * make new password salt, encode and set it for the passed user
153 *
154 * @param user
155 * The user to set the new password for.
156 * @param newPassword
157 * the new password to be encoded and set for the <code>user</code>
158 */
159 @Override
160 public void encodeUserPassword(User user, String newPassword) {
161 Object salt = this.saltSource.getSalt(user);
162 String password = passwordEncoder.encodePassword(newPassword, salt);
163 user.setPassword(password);
164 }
165
166 @Override
167 @Transactional(readOnly=false)
168 @PreAuthorize("#username == authentication.name or hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
169 public void changePasswordForUser(String username, String newPassword) {
170 Assert.hasText(username);
171 Assert.hasText(newPassword);
172
173 try {
174 User user = dao.findUserByUsername(username);
175 if(user == null) {
176 throw new UsernameNotFoundException(username);
177 }
178
179 encodeUserPassword(user, newPassword);
180 dao.update(user);
181 userCache.removeUserFromCache(user.getUsername());
182 } catch(NonUniqueResultException nure) {
183 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
184 }
185 }
186
187 @Override
188 @Transactional(readOnly=false)
189 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
190 public void createUser(UserDetails user) {
191 Assert.isInstanceOf(User.class, user);
192 encodeUserPassword((User)user, user.getPassword());
193 dao.save((User)user);
194 }
195
196 @Override
197 @Transactional(readOnly=false)
198 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
199 public void deleteUser(String username) {
200 Assert.hasLength(username);
201
202 User user = dao.findUserByUsername(username);
203 if(user != null) {
204 dao.delete(user);
205 }
206
207 userCache.removeUserFromCache(username);
208 }
209
210 @Override
211 @Transactional(readOnly=false)
212 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
213 public void updateUser(UserDetails user) {
214 Assert.isInstanceOf(User.class, user);
215
216 dao.update((User)user);
217 userCache.removeUserFromCache(user.getUsername());
218 }
219
220 @Override
221 public boolean userExists(String username) {
222 Assert.hasText(username);
223
224 User user = dao.findUserByUsername(username);
225 return user != null;
226 }
227
228 /**
229 * <b>DO NOT CALL THIS METHOD IN LONG RUNNING SESSIONS OR CONVERSATIONS
230 * A THROWN UsernameNotFoundException WILL RENDER THE CONVERSATION UNUSABLE</b>
231 *
232 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
233 */
234 // NOTE: this method must not be secured since it is being used during the
235 // authentication process
236 @Override
237 public UserDetails loadUserByUsername(String username)
238 throws UsernameNotFoundException, DataAccessException {
239 Assert.hasText(username);
240 try {
241 User user = dao.findUserByUsername(username);
242 if(user == null) {
243 throw new UsernameNotFoundException(username);
244 }
245 return user;
246 } catch(NonUniqueResultException nure) {
247 throw new IncorrectResultSizeDataAccessException("More than one user found with name '" + username + "'", 1);
248 }
249 }
250
251
252 @Override
253 @Transactional(readOnly=false)
254 // @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_RUN_AS_ADMIN') or hasRole('ROLE_USER_MANAGER')")
255 public User save(User user) {
256 if(user.getId() == 0 || dao.load(user.getUuid()) == null){
257 createUser(user);
258 }else{
259 updateUser(user);
260 }
261 return user;
262 }
263
264 @Override
265 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
266 public UUID update(User user) {
267 updateUser(user);
268 return user.getUuid();
269 }
270
271 @Override
272 @Transactional(readOnly=false)
273 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
274 public UUID saveGrantedAuthority(GrantedAuthority grantedAuthority) {
275 return grantedAuthorityDao.save((GrantedAuthorityImpl)grantedAuthority).getUuid();
276 }
277
278
279
280 @Override
281 @Transactional(readOnly = true)
282 public List<User> listByUsername(String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
283 long numberOfResults = dao.countByUsername(queryString, matchmode, criteria);
284
285 List<User> results = new ArrayList<>();
286 if(numberOfResults > 0) {
287 results = dao.findByUsername(queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
288 }
289 return results;
290 }
291
292 /* ================================================
293 * overriding methods to secure them
294 * via the type level annotation @PreAuthorize
295 * ================================================ */
296
297 @Override
298 @Transactional(readOnly=false)
299 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
300 public DeleteResult delete(User persistentObject) {
301 return super.delete(persistentObject);
302 }
303
304 @Override
305 @Transactional(readOnly=false)
306 public DeleteResult delete(UUID userUuid) {
307 return delete(dao.load(userUuid));
308 }
309
310 @Override
311 @Transactional(readOnly=false)
312 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
313 public Map<UUID, User> save(Collection<? extends User> newInstances) {
314 Map<UUID, User> users = new HashMap<UUID, User>();
315 for (User user: newInstances){
316 createUser(user);
317 users.put(user.getUuid(), user);
318 }
319 return users;
320 }
321
322 @Override
323 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
324 public UUID saveOrUpdate(User transientObject) {
325 return super.saveOrUpdate(transientObject);
326 }
327
328 @Override
329 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
330 @Transactional(readOnly=false)
331 public User merge(User detachedObject) {
332 return super.merge(detachedObject);
333 }
334
335 @Override
336 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
337 @Transactional(readOnly=false)
338 public List<User> merge(List<User> detachedObjects) {
339 return super.merge(detachedObjects);
340 }
341
342 @Override
343 @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER_MANAGER')")
344 public Map<UUID, User> saveOrUpdate(Collection<User> transientInstances) {
345 return super.saveOrUpdate(transientInstances);
346 }
347
348 }