74b97c16c2aa46980ac070676cd0bc4f933829eb
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / util / CdmUserHelper.java
1 /**
2 * Copyright (C) 2017 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.util;
10
11 import java.io.Serializable;
12 import java.util.Collection;
13 import java.util.EnumSet;
14 import java.util.HashSet;
15 import java.util.Set;
16 import java.util.UUID;
17
18 import org.apache.logging.log4j.LogManager;
19 import org.apache.logging.log4j.Logger;
20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.beans.factory.annotation.Qualifier;
22 import org.springframework.context.annotation.Lazy;
23 import org.springframework.security.authentication.AnonymousAuthenticationToken;
24 import org.springframework.security.authentication.AuthenticationProvider;
25 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
26 import org.springframework.security.core.Authentication;
27 import org.springframework.security.core.GrantedAuthority;
28 import org.springframework.security.core.context.SecurityContext;
29 import org.springframework.security.core.context.SecurityContextHolder;
30 import org.springframework.security.core.userdetails.UserDetails;
31 import org.springframework.transaction.TransactionStatus;
32
33 import eu.etaxonomy.cdm.api.application.CdmRepository;
34 import eu.etaxonomy.cdm.api.application.RunAsAuthenticator;
35 import eu.etaxonomy.cdm.database.PermissionDeniedException;
36 import eu.etaxonomy.cdm.model.ICdmEntityUuidCacher;
37 import eu.etaxonomy.cdm.model.common.CdmBase;
38 import eu.etaxonomy.cdm.model.permission.CRUD;
39 import eu.etaxonomy.cdm.model.permission.GrantedAuthorityImpl;
40 import eu.etaxonomy.cdm.model.permission.PermissionClass;
41 import eu.etaxonomy.cdm.model.permission.User;
42 import eu.etaxonomy.cdm.persistence.permission.CdmAuthority;
43 import eu.etaxonomy.cdm.persistence.permission.CdmAuthorityParsingException;
44 import eu.etaxonomy.cdm.persistence.permission.ICdmPermissionEvaluator;
45 import eu.etaxonomy.cdm.persistence.permission.Role;
46
47 /**
48 * @author a.kohlbecker
49 * @since May 19, 2017
50 */
51 public class CdmUserHelper implements UserHelper, Serializable {
52
53 private static final long serialVersionUID = -2521474709047255979L;
54
55 public static final Logger logger = LogManager.getLogger();
56
57 @Autowired
58 private ICdmPermissionEvaluator permissionEvaluator;
59
60 @Autowired
61 @Lazy
62 @Qualifier("cdmRepository")
63 protected CdmRepository repo;
64
65 private AuthenticationProvider runAsAuthenticationProvider;
66
67 @Autowired(required=false)
68 @Qualifier("runAsAuthenticationProvider")
69 public void setRunAsAuthenticationProvider(AuthenticationProvider runAsAuthenticationProvider){
70 this.runAsAuthenticationProvider = runAsAuthenticationProvider;
71 runAsAutheticator.setRunAsAuthenticationProvider(runAsAuthenticationProvider);
72 }
73
74 RunAsAuthenticator runAsAutheticator = new RunAsAuthenticator();
75
76 private SecurityContextAccess securityContextAccess;
77
78 public CdmUserHelper(){
79 super();
80 }
81
82 protected ICdmPermissionEvaluator permissionEvaluator() {
83 return permissionEvaluator;
84 }
85
86 protected CdmRepository repo() {
87 return repo;
88 }
89
90 @Override
91 public boolean userIsAutheticated() {
92 Authentication authentication = getAuthentication();
93 if(authentication != null && !AnonymousAuthenticationToken.class.equals(authentication.getClass())){
94 return authentication.isAuthenticated();
95 }
96 return false;
97 }
98
99 @Override
100 public boolean userIsAnnonymous() {
101 Authentication authentication = getAuthentication();
102 return authentication != null
103 && authentication.isAuthenticated()
104 && authentication instanceof AnonymousAuthenticationToken;
105 }
106
107 @Override
108 public User user() {
109 Authentication authentication = getAuthentication();
110 if(authentication != null && authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
111 return (User) authentication.getPrincipal();
112 }
113 return null;
114 }
115
116 @Override
117 public String userName() {
118 Authentication authentication = getAuthentication();
119 if(authentication != null) {
120 return authentication.getName();
121 }
122 return null;
123 }
124
125 @Override
126 public boolean userIsAdmin() {
127 Authentication authentication = getAuthentication();
128 if(authentication != null) {
129 return authentication.getAuthorities().stream().anyMatch(a -> {
130 return a.getAuthority().equals(Role.ROLE_ADMIN.getAuthority());
131 });
132 }
133 return false;
134 }
135
136 @Override
137 public boolean userIs(IRoleProber iRoleProbe) {
138 return iRoleProbe.checkForRole(getAuthentication());
139 }
140
141 @Override
142 public boolean userHasPermission(CdmBase entity, Object ... args){
143 EnumSet<CRUD> crudSet = crudSetFromArgs(args);
144 try {
145 return permissionEvaluator().hasPermission(getAuthentication(), entity, crudSet);
146 } catch (PermissionDeniedException e){
147 //IGNORE
148 }
149 return false;
150 }
151
152 /**
153 * @deprecated not performance optimized by using the cache,
154 * use {@link #userHasPermission(Class, UUID, Object...)} instead
155 */
156 @Override
157 @Deprecated
158 public boolean userHasPermission(Class<? extends CdmBase> cdmType, Integer entitiyId, Object ... args){
159 EnumSet<CRUD> crudSet = crudSetFromArgs(args);
160 try {
161 CdmBase entity = repo().getCommonService().find(cdmType, entitiyId);
162 return permissionEvaluator().hasPermission(getAuthentication(), entity, crudSet);
163 } catch (PermissionDeniedException e){
164 //IGNORE
165 }
166 return false;
167 }
168
169 @Override
170 public boolean userHasPermission(Class<? extends CdmBase> cdmType, UUID entitiyUuid, Object ... args){
171 EnumSet<CRUD> crudSet = crudSetFromArgs(args);
172 try {
173 CdmBase entity = entity(cdmType, entitiyUuid);
174 return permissionEvaluator().hasPermission(getAuthentication(), entity, crudSet);
175 } catch (PermissionDeniedException e){
176 //IGNORE
177 }
178 return false;
179 }
180
181 protected CdmBase entity(Class<? extends CdmBase> cdmType, UUID entitiyUuid) {
182 CdmBase entity = entityFromCache(cdmType, entitiyUuid);
183 if(entity == null){
184 entity = repo().getCommonService().find(cdmType, entitiyUuid);
185 if(getCache() != null && entity != null){
186 getCache().putToCache(entity);
187 }
188 }
189 return entity;
190 }
191
192 @Override
193 public boolean userHasPermission(Class<? extends CdmBase> cdmType, Object ... args){
194 EnumSet<CRUD> crudSet = crudSetFromArgs(args);
195 try {
196 return permissionEvaluator().hasPermission(getAuthentication(), cdmType, crudSet);
197 } catch (PermissionDeniedException e){
198 //IGNORE
199 }
200 return false;
201 }
202
203 @Override
204 public void logout() {
205 SecurityContext context = SecurityContextHolder.getContext();
206 context.setAuthentication(null);
207 SecurityContextHolder.clearContext();
208 }
209
210 private EnumSet<CRUD> crudSetFromArgs(Object[] args) {
211 EnumSet<CRUD> crudSet = EnumSet.noneOf(CRUD.class);
212 for(int i = 0; i < args.length; i++){
213 try {
214 crudSet.add(CRUD.valueOf(args[i].toString()));
215 } catch (Exception e){
216 throw new IllegalArgumentException("could not add " + args[i], e);
217 }
218 }
219 return crudSet;
220 }
221
222 private SecurityContext currentSecurityContext() {
223 if(securityContextAccess != null){
224 return securityContextAccess.currentSecurityContext();
225 }
226 return SecurityContextHolder.getContext();
227 }
228
229 @Override
230 public Authentication getAuthentication() {
231 return currentSecurityContext().getAuthentication();
232 }
233
234 @Override
235 public CdmAuthority createAuthorityFor(String username, CdmBase cdmEntity, EnumSet<CRUD> crud, String property) {
236
237 TransactionStatus txStatus = repo().startTransaction();
238 UserDetails userDetails = repo().getUserService().loadUserByUsername(username);
239 boolean newAuthorityAdded = false;
240 CdmAuthority authority = null;
241 User user = (User)userDetails;
242 if(userDetails != null){
243 try{
244 // flush all pending transactions before changing the authentication,
245 // see https://dev.e-taxonomy.eu/redmine/issues/8066 for discussion
246 // in case of problems we may want to use another transaction propagation level
247 // instead (PROPAGATION_REQUIRES_NEW) which would require to reload the cdm entity.
248 repo().getSession().flush();
249 getRunAsAutheticator().runAsAuthentication(Role.ROLE_USER_MANAGER);
250 authority = new CdmAuthority(cdmEntity, property, crud);
251 try {
252 GrantedAuthorityImpl grantedAuthority = repo().getGrantedAuthorityService().findAuthorityString(authority.toString());
253 if(grantedAuthority == null){
254 grantedAuthority = authority.asNewGrantedAuthority();
255 }
256 newAuthorityAdded = user.getGrantedAuthorities().add(grantedAuthority);
257 } catch (CdmAuthorityParsingException e) {
258 getRunAsAutheticator().restoreAuthentication();
259 throw new RuntimeException(e);
260 }
261 repo().getSession().flush();
262 } finally {
263 // in any case restore the previous authentication
264 getRunAsAutheticator().restoreAuthentication();
265 }
266 logger.debug("new authority for " + username + ": " + authority.toString());
267 Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
268 SecurityContextHolder.getContext().setAuthentication(authentication);
269 logger.debug("security context refreshed with user " + username);
270 }
271 repo().commitTransaction(txStatus);
272 return newAuthorityAdded ? authority : null;
273
274 }
275
276 /**
277 * @param username
278 * @param cdmType
279 * @param entitiyId
280 * @param crud
281 * @return
282 * @deprecated not performance optimized by using the cache,
283 * use {@link #createAuthorityFor(String, Class, UUID, EnumSet, String)} instead
284 */
285 @Override
286 @Deprecated
287 public CdmAuthority createAuthorityFor(String username, Class<? extends CdmBase> cdmType, Integer entitiyId, EnumSet<CRUD> crud, String property) {
288
289 CdmBase cdmEntity = repo().getCommonService().find(cdmType, entitiyId);
290 return createAuthorityFor(username, cdmEntity, crud, property);
291 }
292
293 @Override
294 public CdmAuthority createAuthorityFor(String username, Class<? extends CdmBase> cdmType, UUID entitiyUuid, EnumSet<CRUD> crud, String property) {
295
296 CdmBase cdmEntity = entity(cdmType, entitiyUuid);
297 return createAuthorityFor(username, cdmEntity, crud, property);
298 }
299
300 @Override
301 public CdmAuthority createAuthorityForCurrentUser(CdmBase cdmEntity, EnumSet<CRUD> crud, String property) {
302 return createAuthorityFor(userName(), cdmEntity, crud, property);
303
304 }
305
306 @Override
307 public CdmAuthority createAuthorityForCurrentUser(Class<? extends CdmBase> cdmType, Integer entitiyId, EnumSet<CRUD> crud, String property) {
308 return createAuthorityFor(userName(), cdmType, entitiyId, crud, property);
309 }
310
311 @Override
312 public CdmAuthority createAuthorityForCurrentUser(Class<? extends CdmBase> cdmType, UUID entitiyUuid, EnumSet<CRUD> crud, String property) {
313 return createAuthorityFor(userName(), cdmType, entitiyUuid, crud, property);
314 }
315
316 @Override
317 public void removeAuthorityForCurrentUser(CdmAuthority cdmAuthority) {
318 removeAuthorityForCurrentUser(userName(), cdmAuthority);
319
320 }
321
322 @Override
323 public void removeAuthorityForCurrentUser(String username, CdmAuthority cdmAuthority) {
324
325 TransactionStatus txStatus = repo().startTransaction();
326 UserDetails userDetails = repo().getUserService().loadUserByUsername(username);
327 User user = (User)userDetails;
328 if(userDetails != null){
329 try {
330 getRunAsAutheticator().runAsAuthentication(Role.ROLE_USER_MANAGER);
331 user.getGrantedAuthorities().remove(cdmAuthority);
332 repo().getSession().flush();
333 logger.debug("security context refreshed with user " + username);
334 } finally {
335 getRunAsAutheticator().restoreAuthentication();
336 }
337 logger.debug("authority removed from " + username + ": " + cdmAuthority.toString());
338 Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
339 SecurityContextHolder.getContext().setAuthentication(authentication);
340 logger.debug("security context refreshed with user " + username);
341 }
342 repo().commitTransaction(txStatus);
343 }
344
345 @Override
346 public Collection<CdmAuthority> findUserPermissions(CdmBase cdmEntity, EnumSet<CRUD> crud) {
347 Set<CdmAuthority> matches = new HashSet<>();
348 PermissionClass permissionClass = PermissionClass.getValueOf(cdmEntity);
349 Collection<? extends GrantedAuthority> authorities = getAuthentication().getAuthorities();
350 for(GrantedAuthority ga : authorities){
351 try {
352 CdmAuthority cdmAuthority = CdmAuthority.fromGrantedAuthority(ga);
353 if(cdmAuthority.getPermissionClass().equals(permissionClass)){
354 if(cdmAuthority.getOperation().containsAll(crud)){
355 if(cdmAuthority.hasTargetUuid() && cdmAuthority.getTargetUUID().equals(cdmEntity.getUuid())){
356 matches.add(cdmAuthority);
357 } else {
358 matches.add(cdmAuthority);
359 }
360 }
361 }
362 } catch (CdmAuthorityParsingException e) {
363 continue;
364 }
365 }
366 return matches;
367 }
368
369 @Override
370 public <T extends CdmBase> Collection<CdmAuthority> findUserPermissions(Class<T> cdmType, EnumSet<CRUD> crud) {
371 Set<CdmAuthority> matches = new HashSet<>();
372 PermissionClass permissionClass = PermissionClass.getValueOf(cdmType);
373 Collection<? extends GrantedAuthority> authorities = getAuthentication().getAuthorities();
374 for(GrantedAuthority ga : authorities){
375 try {
376 CdmAuthority cdmAuthority = CdmAuthority.fromGrantedAuthority(ga);
377 if(cdmAuthority.getPermissionClass().equals(permissionClass)){
378 if(cdmAuthority.getOperation().containsAll(crud)){
379 matches.add(cdmAuthority);
380 }
381 }
382 } catch (CdmAuthorityParsingException e) {
383 continue;
384 }
385 }
386 return matches;
387 }
388
389 @Override
390 public void setSecurityContextAccess(SecurityContextAccess securityContextAccess) {
391 this.securityContextAccess = securityContextAccess;
392 }
393
394 public RunAsAuthenticator getRunAsAutheticator() {
395 if(runAsAutheticator == null){
396 throw new RuntimeException("RunAsAuthenticator is missing! The application needs to be configured with security context.");
397 }
398 return runAsAutheticator;
399 }
400
401 public ICdmEntityUuidCacher getCache() {
402 return null;
403 }
404
405 private CdmBase entityFromCache(Class<? extends CdmBase> cdmType, UUID entitiyUuid) {
406 CdmBase entity = null;
407 if(getCache() != null){
408 entity = getCache().getFromCache(entitiyUuid);
409 if(entity != null && !cdmType.isAssignableFrom(entity.getClass())){
410 logger.error("Entity with " + entitiyUuid + " does not match the required type");
411 entity = null;
412 }
413 }
414 return entity;
415 }
416
417 @Override
418 public CdmUserHelper withCache(ICdmEntityUuidCacher cache){
419 return new CachingCdmUserHelper(cache);
420 }
421
422 class CachingCdmUserHelper extends CdmUserHelper{
423
424 private static final long serialVersionUID = -5010082174809972831L;
425
426 private ICdmEntityUuidCacher cache;
427
428 public CachingCdmUserHelper(ICdmEntityUuidCacher cache){
429 this.cache = cache;
430 }
431
432 @Override
433 public ICdmEntityUuidCacher getCache() {
434 return cache;
435 }
436
437 @Override
438 protected CdmRepository repo() {
439 return CdmUserHelper.this.repo;
440 }
441
442 @Override
443 protected ICdmPermissionEvaluator permissionEvaluator() {
444 return CdmUserHelper.this.permissionEvaluator;
445 }
446 }
447
448 }