package fr.openwide.core.jpa.security.service;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.acls.domain.PermissionFactory;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import fr.openwide.core.jpa.security.business.person.model.GenericUser;
import fr.openwide.core.jpa.security.business.person.service.IGenericUserService;
import fr.openwide.core.jpa.security.hierarchy.IPermissionHierarchy;
import fr.openwide.core.jpa.security.model.CoreUserDetails;
public abstract class AbstractCorePermissionEvaluator<T extends GenericUser<T, ?>> implements ICorePermissionEvaluator {
@Autowired
private PermissionFactory permissionFactory;
@Autowired
private IPermissionHierarchy permissionHierarchy;
@Autowired
private IGenericUserService<T> personService;
@Autowired
private ISecurityService securityService;
/**
* Vérifie qu'un utilisateur possède la permission souhaitée
* @param user peut être <code>null</code> dans le cas d'une authentification anonyme
*/
protected abstract boolean hasPermission(T user, Object targetDomainObject, Permission permission);
protected T getUser(Authentication authentication) {
if (authentication == null) {
return null;
}
if (authentication.getPrincipal() instanceof UserDetails) {
return personService.getByUserName(((UserDetails) authentication.getPrincipal()).getUsername());
}
return null;
}
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
/*
* On applique les vérifications même pour un superUser, contrairement à hasPermission(Authentication, Object)
* En effet, il se peut que certaines permissions sur les objets soient attribuées en fonction de règles métier
* qui peuvent interdire même à l'administrateur d'exécuter une action (par exemple clôturer un dossier déjà clôturé)
*/
if (authentication == null) {
return false;
}
/*
* On donne une dérogation à l'utilisateur system car on risque sinon d'avoir des soucis dans les permission evaluator
* car on n'a alors pas de IUser associé et on ne peut pas déterminer le périmètre autorisé.
* Dans l'idéal, il faudrait utiliser l'objet Authentication jusque dans les *PermissionEvaluator.
*/
if (securityService.hasSystemRole(authentication)) {
return true;
}
T user = getUser(authentication);
List<Permission> permissions = resolvePermission(permission);
if (targetDomainObject instanceof Collection<?>) {
return checkObjectsPermissions(user, (Collection<?>) targetDomainObject, permissions);
} else {
return checkObjectsPermissions(user, Collections.singletonList(targetDomainObject), permissions);
}
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException();
}
protected Collection<? extends Permission> getAnonymousPermissions() {
return Collections.emptyList();
}
@Override
public boolean hasPermission(Authentication authentication, Object requiredPermission) {
if (isSuperUser(authentication)) {
return true;
}
Collection<? extends Permission> userPermissions = getPermissions(authentication);
List<Permission> requiredPermissions = resolvePermission(requiredPermission);
for (Permission permission : requiredPermissions) {
// Il faut posséder au moins une des permissions acceptées
if (userPermissions.contains(permission)) {
return true;
}
}
return false;
}
@Override
public Collection<? extends Permission> getPermissions(Authentication authentication) {
Collection<? extends Permission> userPermissions = getAnonymousPermissions();
if (authentication != null) {
Object userDetailsCandidate = authentication.getPrincipal();
if (userDetailsCandidate instanceof CoreUserDetails) {
CoreUserDetails userDetails = (CoreUserDetails) userDetailsCandidate;
userPermissions = userDetails.getPermissions();
}
}
return userPermissions;
}
@Override
public boolean isSuperUser(Authentication authentication) {
if (authentication != null) {
return securityService.hasSystemRole(authentication)
|| securityService.hasAdminRole(authentication);
}
return false;
}
protected boolean checkObjectsPermissions(T user, Collection<?> targetDomainObject, List<Permission> permissions) {
for (Object object : targetDomainObject) {
// il faut que tous les objets possèdent les permissions requises
boolean allowed = checkAcceptablePermissions(user, object, permissions);
if (! allowed) {
return false;
}
}
return true;
}
protected boolean checkAcceptablePermissions(T user, Object targetDomainObject, List<Permission> permissions) {
// les doublons de permissions sont retirés dans getAcceptablePermissions
List<Permission> acceptablePermissions = permissionHierarchy.getAcceptablePermissions(permissions);
for (Permission permission : acceptablePermissions) {
// Il faut posséder au moins une des permissions acceptées
boolean allowed = hasPermission(user, targetDomainObject, permission);
if (allowed) {
return true;
}
}
return false;
}
/**
* {@see org.springframework.security.acls.AclPermissionEvaluator#resolvePermission(Object)}
*/
private List<Permission> resolvePermission(Object permission) {
if (permission instanceof Permission) {
return Arrays.asList((Permission) permission);
} else if (permission instanceof Permission[]) {
return Arrays.asList((Permission[]) permission);
} else if (permission instanceof String) {
String permString = (String) permission;
String[] split = permString.split("\\|");
List<Permission> result = new ArrayList<Permission>();
for (String perm : split) {
Permission resolvedPermission = resolvePermissionByName(perm);
if (!result.contains(resolvedPermission)) {
result.add(resolvedPermission);
}
}
return result;
}
throw new IllegalStateException("Unsupported permission: " + permission);
}
private Permission resolvePermissionByName(String permission) {
Permission p;
try {
p = permissionFactory.buildFromName(permission);
} catch (IllegalArgumentException notfound) {
p = permissionFactory.buildFromName(permission.toUpperCase());
}
if (p == null) {
throw new IllegalStateException("Unsupported permission: " + permission);
}
return p;
}
}