TL DR: How to implement fine-grained access control in the flattened REST api approach that Spring-Data-Rest gives us?
So, I am creating an API using Spring-Data-Rest, where there are three main levels of access:
1) Admin - can see / update all groups
2) The owner of the group - can see / update the group and everything under it
3) Owner of a subgroup - can only see / update his own group. There is no recursive embedding, only one sub-level is allowed.
And the "group" is displayed as a resource (has a crud repository).
So far, so good - and I have implemented some access control for modification using the repository event handler, so on the create / write / delete side, I think I'm fine.
Now I need to move on to restricting the visibility of some items. This is normal for getting a single item, since I can use Pre / Post Authorize annotations and reference the principal.
The problem is with the findAll () methods - I have no easy way to filter out specific instances that I don't want to show based on the current principal. For example, the owner of a subgroup could see all groups by running GET / groups. Ideally, they should have elements to which they do not have access, not even visible at all.
For me, this sounds like writing custom @Query () annotations on the repository interfaces, but it doesn't seem feasible because:
I need to indicate to the principal in the request. SPeL is supposed to be supported, but doesn't seem to work with # # expressions at all (despite this blog post suggesting something else: https://spring.io/blog/2014/07/15/spel-support-in- spring-data-jpa-query-definitions ). I am using Spring-boot with 1.1.8.RELEASE and the Evans-RELEASE train for Spring -data in general.
The type of query that I need to write will differ depending on the access level, which cannot be realistically included in a single JPQL statement (if the administrator selects all groups and also receives all (sub) groups associated with the main user).
Therefore, it seems to me that I need to write some custom repository implementations for this and just reference the principal in the code. Well, that’s good - but for each repository it seems to me that I need to control access (I think that it will be almost all of them). This applies to findAll and various custom search methods.
Am I approaching this wrong? Is there another approach to dynamically limiting the visibility of elements based on the current user that will work better? In a flat namespace like Spring-data-rest, I imagine this will be a common problem.
In the previous project, I just solved it, exposing everything under / api / groups / {groupId} / ... and used the subresource locator as a single pinch point to control access to anything under it. There is no such luck in Spring -data-rest.
Update: now stumbles with the custom findAll () override method (this works for other methods defined on my user interface). Although this may be a separate issue - I'm blocked right now. Spring -data just doesn't call this when I do GET / groups, but I call the original. Oddly enough, it uses my query if I define it on the interface and mark it with @Query (maybe custom overrides of inline methods are no longer supported?).
public interface GroupRepository extends JpaRepository<Group, Long>, GroupCustomRepository {} public interface GroupCustomRepository { Page<Group> findAll(Pageable pageable); } public class GroupCustomRepositoryImpl extends SimpleJpaRepository<Group, Long> implements GroupCustomRepository { @Inject public GroupCustomRepositoryImpl(EntityManager em) { super(Group.class, em); } @Override public Page<Group> findAll(Pageable pageable) { MyPrincipal principal = (MyPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Page<Group> result; if (principal.isAdmin()) { result = findAll(pageable); } else { Specification<Group> spec = (root, query, cb) -> cb.or( cb.equal(root, principal.getGroup()), cb.and(cb.isNotNull(root.get(Group_.parentGroup)), cb.equal(root.get(Group_.parentGroup), principal.getGroup())) ); result = findAll(spec, pageable); } return result; } }
Update 2: Since I cannot access the main thing in @Query, and I cannot override it using a special method, I am on a brick wall. @PostFilter does not work either because the returned object is a page, not a collection.
I decided to simply disable / groups of only administrators, and everyone else uses different approaches (/ groups / search / somethingSpecific) using @ PostFilters / @ PostAuthorizations.
It does not seem to be very well connected with the HAL approach. Interested in how other people solve these problems with Spring-data-rest.