2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.api
.util
;
11 import java
.io
.Serializable
;
12 import java
.util
.Collection
;
13 import java
.util
.EnumSet
;
14 import java
.util
.HashSet
;
16 import java
.util
.UUID
;
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
;
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
;
48 * @author a.kohlbecker
51 public class CdmUserHelper
implements UserHelper
, Serializable
{
53 private static final long serialVersionUID
= -2521474709047255979L;
55 public static final Logger logger
= LogManager
.getLogger();
58 private ICdmPermissionEvaluator permissionEvaluator
;
62 @Qualifier("cdmRepository")
63 protected CdmRepository repo
;
65 private AuthenticationProvider runAsAuthenticationProvider
;
67 @Autowired(required
=false)
68 @Qualifier("runAsAuthenticationProvider")
69 public void setRunAsAuthenticationProvider(AuthenticationProvider runAsAuthenticationProvider
){
70 this.runAsAuthenticationProvider
= runAsAuthenticationProvider
;
71 runAsAutheticator
.setRunAsAuthenticationProvider(runAsAuthenticationProvider
);
74 RunAsAuthenticator runAsAutheticator
= new RunAsAuthenticator();
76 private SecurityContextAccess securityContextAccess
;
78 public CdmUserHelper(){
82 protected ICdmPermissionEvaluator
permissionEvaluator() {
83 return permissionEvaluator
;
86 protected CdmRepository
repo() {
91 public boolean userIsAutheticated() {
92 Authentication authentication
= getAuthentication();
93 if(authentication
!= null && !AnonymousAuthenticationToken
.class.equals(authentication
.getClass())){
94 return authentication
.isAuthenticated();
100 public boolean userIsAnnonymous() {
101 Authentication authentication
= getAuthentication();
102 return authentication
!= null
103 && authentication
.isAuthenticated()
104 && authentication
instanceof AnonymousAuthenticationToken
;
109 Authentication authentication
= getAuthentication();
110 if(authentication
!= null && authentication
.getPrincipal() != null && authentication
.getPrincipal() instanceof User
) {
111 return (User
) authentication
.getPrincipal();
117 public String
userName() {
118 Authentication authentication
= getAuthentication();
119 if(authentication
!= null) {
120 return authentication
.getName();
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());
137 public boolean userIs(IRoleProber iRoleProbe
) {
138 return iRoleProbe
.checkForRole(getAuthentication());
142 public boolean userHasPermission(CdmBase entity
, Object
... args
){
143 EnumSet
<CRUD
> crudSet
= crudSetFromArgs(args
);
145 return permissionEvaluator().hasPermission(getAuthentication(), entity
, crudSet
);
146 } catch (PermissionDeniedException e
){
153 * @deprecated not performance optimized by using the cache,
154 * use {@link #userHasPermission(Class, UUID, Object...)} instead
158 public boolean userHasPermission(Class
<?
extends CdmBase
> cdmType
, Integer entitiyId
, Object
... args
){
159 EnumSet
<CRUD
> crudSet
= crudSetFromArgs(args
);
161 CdmBase entity
= repo().getCommonService().find(cdmType
, entitiyId
);
162 return permissionEvaluator().hasPermission(getAuthentication(), entity
, crudSet
);
163 } catch (PermissionDeniedException e
){
170 public boolean userHasPermission(Class
<?
extends CdmBase
> cdmType
, UUID entitiyUuid
, Object
... args
){
171 EnumSet
<CRUD
> crudSet
= crudSetFromArgs(args
);
173 CdmBase entity
= entity(cdmType
, entitiyUuid
);
174 return permissionEvaluator().hasPermission(getAuthentication(), entity
, crudSet
);
175 } catch (PermissionDeniedException e
){
181 protected CdmBase
entity(Class
<?
extends CdmBase
> cdmType
, UUID entitiyUuid
) {
182 CdmBase entity
= entityFromCache(cdmType
, entitiyUuid
);
184 entity
= repo().getCommonService().find(cdmType
, entitiyUuid
);
185 if(getCache() != null && entity
!= null){
186 getCache().putToCache(entity
);
193 public boolean userHasPermission(Class
<?
extends CdmBase
> cdmType
, Object
... args
){
194 EnumSet
<CRUD
> crudSet
= crudSetFromArgs(args
);
196 return permissionEvaluator().hasPermission(getAuthentication(), cdmType
, crudSet
);
197 } catch (PermissionDeniedException e
){
204 public void logout() {
205 SecurityContext context
= SecurityContextHolder
.getContext();
206 context
.setAuthentication(null);
207 SecurityContextHolder
.clearContext();
210 private EnumSet
<CRUD
> crudSetFromArgs(Object
[] args
) {
211 EnumSet
<CRUD
> crudSet
= EnumSet
.noneOf(CRUD
.class);
212 for(int i
= 0; i
< args
.length
; i
++){
214 crudSet
.add(CRUD
.valueOf(args
[i
].toString()));
215 } catch (Exception e
){
216 throw new IllegalArgumentException("could not add " + args
[i
], e
);
222 private SecurityContext
currentSecurityContext() {
223 if(securityContextAccess
!= null){
224 return securityContextAccess
.currentSecurityContext();
226 return SecurityContextHolder
.getContext();
230 public Authentication
getAuthentication() {
231 return currentSecurityContext().getAuthentication();
235 public CdmAuthority
createAuthorityFor(String username
, CdmBase cdmEntity
, EnumSet
<CRUD
> crud
, String property
) {
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){
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
);
252 GrantedAuthorityImpl grantedAuthority
= repo().getGrantedAuthorityService().findAuthorityString(authority
.toString());
253 if(grantedAuthority
== null){
254 grantedAuthority
= authority
.asNewGrantedAuthority();
256 newAuthorityAdded
= user
.getGrantedAuthorities().add(grantedAuthority
);
257 } catch (CdmAuthorityParsingException e
) {
258 getRunAsAutheticator().restoreAuthentication();
259 throw new RuntimeException(e
);
261 repo().getSession().flush();
263 // in any case restore the previous authentication
264 getRunAsAutheticator().restoreAuthentication();
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
);
271 repo().commitTransaction(txStatus
);
272 return newAuthorityAdded ? authority
: null;
282 * @deprecated not performance optimized by using the cache,
283 * use {@link #createAuthorityFor(String, Class, UUID, EnumSet, String)} instead
287 public CdmAuthority
createAuthorityFor(String username
, Class
<?
extends CdmBase
> cdmType
, Integer entitiyId
, EnumSet
<CRUD
> crud
, String property
) {
289 CdmBase cdmEntity
= repo().getCommonService().find(cdmType
, entitiyId
);
290 return createAuthorityFor(username
, cdmEntity
, crud
, property
);
294 public CdmAuthority
createAuthorityFor(String username
, Class
<?
extends CdmBase
> cdmType
, UUID entitiyUuid
, EnumSet
<CRUD
> crud
, String property
) {
296 CdmBase cdmEntity
= entity(cdmType
, entitiyUuid
);
297 return createAuthorityFor(username
, cdmEntity
, crud
, property
);
301 public CdmAuthority
createAuthorityForCurrentUser(CdmBase cdmEntity
, EnumSet
<CRUD
> crud
, String property
) {
302 return createAuthorityFor(userName(), cdmEntity
, crud
, property
);
307 public CdmAuthority
createAuthorityForCurrentUser(Class
<?
extends CdmBase
> cdmType
, Integer entitiyId
, EnumSet
<CRUD
> crud
, String property
) {
308 return createAuthorityFor(userName(), cdmType
, entitiyId
, crud
, property
);
312 public CdmAuthority
createAuthorityForCurrentUser(Class
<?
extends CdmBase
> cdmType
, UUID entitiyUuid
, EnumSet
<CRUD
> crud
, String property
) {
313 return createAuthorityFor(userName(), cdmType
, entitiyUuid
, crud
, property
);
317 public void removeAuthorityForCurrentUser(CdmAuthority cdmAuthority
) {
318 removeAuthorityForCurrentUser(userName(), cdmAuthority
);
323 public void removeAuthorityForCurrentUser(String username
, CdmAuthority cdmAuthority
) {
325 TransactionStatus txStatus
= repo().startTransaction();
326 UserDetails userDetails
= repo().getUserService().loadUserByUsername(username
);
327 User user
= (User
)userDetails
;
328 if(userDetails
!= null){
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
);
335 getRunAsAutheticator().restoreAuthentication();
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
);
342 repo().commitTransaction(txStatus
);
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
){
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
);
358 matches
.add(cdmAuthority
);
362 } catch (CdmAuthorityParsingException e
) {
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
){
376 CdmAuthority cdmAuthority
= CdmAuthority
.fromGrantedAuthority(ga
);
377 if(cdmAuthority
.getPermissionClass().equals(permissionClass
)){
378 if(cdmAuthority
.getOperation().containsAll(crud
)){
379 matches
.add(cdmAuthority
);
382 } catch (CdmAuthorityParsingException e
) {
390 public void setSecurityContextAccess(SecurityContextAccess securityContextAccess
) {
391 this.securityContextAccess
= securityContextAccess
;
394 public RunAsAuthenticator
getRunAsAutheticator() {
395 if(runAsAutheticator
== null){
396 throw new RuntimeException("RunAsAuthenticator is missing! The application needs to be configured with security context.");
398 return runAsAutheticator
;
401 public ICdmEntityUuidCacher
getCache() {
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");
418 public CdmUserHelper
withCache(ICdmEntityUuidCacher cache
){
419 return new CachingCdmUserHelper(cache
);
422 class CachingCdmUserHelper
extends CdmUserHelper
{
424 private static final long serialVersionUID
= -5010082174809972831L;
426 private ICdmEntityUuidCacher cache
;
428 public CachingCdmUserHelper(ICdmEntityUuidCacher cache
){
433 public ICdmEntityUuidCacher
getCache() {
438 protected CdmRepository
repo() {
439 return CdmUserHelper
.this.repo
;
443 protected ICdmPermissionEvaluator
permissionEvaluator() {
444 return CdmUserHelper
.this.permissionEvaluator
;