Skip to content

Commit

Permalink
feat: also check group permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasLohoff committed May 12, 2023
1 parent d3437e1 commit f887da9
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ public interface BaseCrudRepository<T, ID> extends
List<T> findAll();

/**
* Returns a {@link Page} of entities for which the user with userId has permission via UserInstancePermission or
* GroupInstancePermission.
* Returns a {@link Page} of entities for which the user with userId has permission via UserInstancePermission.
*
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
* {@literal null}.
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be {@literal null}.
* @param userId ID of the authenticated user.
* @return A page of entities.
*/
Expand All @@ -53,10 +51,33 @@ where exists (
where uip.user_id = :userId and uip.entity_id = m.id and uip.permission_id = 1
)
""")
// todo: check group instance permissions
// todo: replace ID 1 with ids for READ / ADMIN
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Page<T> findAll(Pageable pageable, Long userId);

/**
* Returns a {@link Page} of entities for which the user with userId has permission via UserInstancePermission or GroupInstancePermission.
*
* @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be {@literal null}.
* @param userId ID of the authenticated user.
* @param groupIds All IDs of the groups of the authenticated user.
* @return A page of entities.
*/
@Query(nativeQuery = true, value = """
select * from shogun.#{#entityName} m
where exists (
select 1 from shogun.userinstancepermissions uip
where uip.user_id = :userId and uip.entity_id = m.id and uip.permission_id = 1
)
or exists (
select 1 from shogun.groupinstancepermissions gip
where gip.group_id in :groupIds and gip.entity_id = m.id and gip.permission_id = 1
)
""")
// todo: replace ID 1 with ids for READ / ADMIN
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Page<T> findAll(Pageable pageable, Long userId, List<Long> groupIds);

/**
* Returns a {@link Page} of entities without checking any permissions.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

import de.terrestris.shogun.lib.enumeration.PermissionType;
import de.terrestris.shogun.lib.model.BaseEntity;
import de.terrestris.shogun.lib.model.Group;
import de.terrestris.shogun.lib.model.User;
import de.terrestris.shogun.lib.model.security.permission.ClassPermission;
import de.terrestris.shogun.lib.model.security.permission.GroupClassPermission;
import de.terrestris.shogun.lib.model.security.permission.PermissionCollection;
import de.terrestris.shogun.lib.model.security.permission.UserClassPermission;
Expand All @@ -28,8 +30,11 @@
import de.terrestris.shogun.lib.service.security.permission.GroupInstancePermissionService;
import de.terrestris.shogun.lib.service.security.permission.UserClassPermissionService;
import de.terrestris.shogun.lib.service.security.permission.UserInstancePermissionService;
import de.terrestris.shogun.lib.service.security.provider.GroupProviderService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.GenericTypeResolver;
Expand All @@ -56,6 +61,9 @@ public abstract class BaseEntityPermissionEvaluator<E extends BaseEntity> implem
@Autowired
protected GroupClassPermissionService groupClassPermissionService;

@Autowired
protected GroupProviderService<UserRepresentation, GroupRepresentation> groupProviderService;

@Autowired
protected List<BaseCrudRepository> baseCrudRepositories;

Expand Down Expand Up @@ -249,7 +257,7 @@ public boolean hasPermissionByGroupClassPermission(User user, BaseEntity entity,
*/
@Override
public Page<E> findAll(User user, Pageable pageable, BaseCrudRepository<E, Long> repository) {
// check if user has role `ADMIN`
// option A: user has role `ADMIN`
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

List<GrantedAuthority> authorities = new ArrayList<>(authentication.getAuthorities());
Expand All @@ -261,22 +269,38 @@ public Page<E> findAll(User user, Pageable pageable, BaseCrudRepository<E, Long>
return repository.findAll(pageable);
}

// check if user has permission through class permissions
// option B: user has permission through class permissions
Class<? extends BaseEntity> baseEntityClass = getBaseEntityClass();
Optional<UserClassPermission> userClassPermissions = userClassPermissionService.findFor(baseEntityClass, user);
if (userClassPermissions.isPresent()) {
boolean hasReadPermission = userClassPermissions.get().getPermission().getPermissions().contains(PermissionType.READ) ||
userClassPermissions.get().getPermission().getPermissions().contains(PermissionType.ADMIN);
Optional<UserClassPermission> userClassPermission = userClassPermissionService.findFor(baseEntityClass, user);
Optional<GroupClassPermission> groupClassPermission = groupClassPermissionService.findFor(baseEntityClass, user);

if (hasReadPermission) {
return repository.findAll(pageable);
}
if (containsReadPermission(userClassPermission.orElse(null), groupClassPermission.orElse(null))) {
return repository.findAll(pageable);
}

// todo: check group class permissions
// option C: check instance permissions for each entity with a single query
List<Group<GroupRepresentation>> userGroups = groupProviderService.getGroupsForUser();
if (userGroups.isEmpty()) {
// user has no groups so only user instance permissions have to be checked
return repository.findAll(pageable, user.getId());
} else {
// check both user and group instance permissions
List<Long> groupIds = userGroups.stream()
.map(BaseEntity::getId)
.toList();
return repository.findAll(pageable, user.getId(), groupIds);
}
}

// checks instance permissions for each entity
return repository.findAll(pageable, user.getId());
private boolean containsReadPermission(ClassPermission ...classPermissions) {
return Arrays.stream(classPermissions).anyMatch(classPermission -> {
if (classPermission == null) {
return false;
}
Set<PermissionType> permissions = classPermission.getPermission().getPermissions();
return permissions.contains(PermissionType.READ) ||
permissions.contains(PermissionType.ADMIN);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public abstract class BaseService<T extends BaseCrudRepository<S, Long> & JpaSpe
protected UserProviderService userProviderService;

@Autowired
@Lazy
protected EntityPermissionEvaluator<S> entityPermissionEvaluator;

@PostFilter("hasRole('ROLE_ADMIN') or hasPermission(filterObject, 'READ')")
Expand Down

0 comments on commit f887da9

Please sign in to comment.