Spring Security hasPermission for Collection<Object>

Viewed 10634

I have working application secured with method-level security:

RestController:

@PreAuthorize("hasPermission(#product, 'WRITE')")
@RequestMapping(value = "/save", method = RequestMethod.POST)
public Product save(@RequestBody Product product) {
    return productService.save(product);
}

PermissionEvaluator:

public class SecurityPermissionEvaluator implements PermissionEvaluator {

    private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class);

    private final PermissionService permissionService;

    public SecurityPermissionEvaluator(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
        return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        // almost the same implementation
    }
}

And everything works fine until I implemented API which saves collection of objects. The logic of this service is to update existing entities and/or create new entities.

@PreAuthorize("hasPermission(#products, 'WRITE')")
@RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(@RequestBody Collection<Product> products) {
    return productService.save(products);
}

After this my permission service handles the collection object and looks like this now:

PemissionService:

public class PermissionService {

    public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
        if (targetDomainObject instanceof TopAppEntity) {
            if (((TopAppEntity) targetDomainObject).getId() == null) {
                // check authorities and give response
            } else {
                // check ACL and give response
            }
        } else if(targetDomainObject instanceof Collection) {
            boolean isAuthorized = false;
            Collection targetDomainObjects = (Collection) targetDomainObject;
            for (Object targetObject : targetDomainObjects) {
                isAuthorized = isAuthorized(user, targetObject, permission);
                if (!isAuthorized) break;
            }
            return isAuthorized;
        }
    }
}

My question is:

How I can handle collections using @PreAuthorize("hasPermission(#object, '...')") more elegant way? Is there some implementations in Spring Security for handling collections? At least, how can I optimize PemissionService for handling Collections?

4 Answers

In some cases it's enough a default implementation of SecurityExpressionRoot. If your permission evaluation is based on only analyzing, for example, an Owner of Product you could use the next expressions:

@GetMapping("")
@PostAuthorize("hasPermission(returnObject.![#this.owner],'ProductOwner','READ')")
public Collection<Product> getAllFiltering(<filters>) {...
@PostMapping("/collection")
@PreAuthorize("hasPermission(#products.![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> save(@RequestBody Collection<Product> products) {...
@PutMapping("/collection")
@PreAuthorize("hasPermission(@productRepository.findByIds(#products.![#this.id]).![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> update(@RequestBody Collection<Product> products) {...

In these cases your PermissionEvaluator must be able to process collection.You could also continue using your PermissionEvaluator for a single Product:

@GetMapping("/{id}")
@PostAuthorize("hasPermission({ returnObject.owner },'ProductOwner','READ')")
public Product getById(@PathVariable int id) {...

or make an implementation of PermissionEvaluator which analyzes whether an array or a single value was passed.

#products.![#this.owner] - see "6.5.17 Collection Projection"; { returnObject.owner } - see "6.5.3 Inline lists" here: https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html

Related