Skip to content

Commit

Permalink
feat: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlkoch committed Aug 29, 2023
1 parent 9a1f97d commit 88e0f14
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 21 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,13 @@
<version>${geojson-jackson.version}</version>
</dependency>

<!-- JsonPath -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.8.0</version>
</dependency>

<!-- Keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
Expand Down
5 changes: 5 additions & 0 deletions shogun-lib/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@
<artifactId>tika-core</artifactId>
</dependency>

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>

<!-- Testing -->
<dependency>
<groupId>org.springframework</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,23 @@
package de.terrestris.shogun.lib.controller;

import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
import com.jayway.jsonpath.Criteria;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.filter.FilterCompiler;
import com.jayway.jsonpath.internal.path.PathCompiler;
import de.terrestris.shogun.lib.controller.security.permission.BasePermissionController;
import de.terrestris.shogun.lib.model.BaseEntity;
import de.terrestris.shogun.lib.model.User;
import de.terrestris.shogun.lib.service.BaseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
Expand All @@ -45,9 +50,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.lang.reflect.ParameterizedType;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;

// TODO Specify and type extension of BaseService
Expand Down Expand Up @@ -78,26 +81,47 @@ public abstract class BaseController<T extends BaseService<?, S>, S extends Base
description = "Unauthorized: You need to provide a bearer token",
content = @Content
),
@ApiResponse(
responseCode = "404",
description = "Not found: The provided ID does not exist (or you don't have the permission to delete it)"
),
// @ApiResponse(
// responseCode = "404",
// description = "Not found: The provided ID does not exist (or you don't have the permission to delete it)"
// ),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something internal went wrong while deleting the entity"
description = "Internal Server Error: Something internal went wrong while getting the entity list"
)
})
public Page<S> findAll(@PageableDefault(Integer.MAX_VALUE) @ParameterObject Pageable pageable) {
// TODO Custom annotation to get filterobject directly, similiar to @ParameterObect
public Page<S> findAll(@PageableDefault(Integer.MAX_VALUE) @ParameterObject Pageable pageable, @RequestParam(required = false) String filter) {
log.trace("Requested to return all entities of type {}", getGenericClassName());

// Path compiledPath = PathCompiler.compile("$.phoneNumbers[?(@.type=='home')]");
//
// Filter parsedFilter = Filter.parse("$.phoneNumbers[?(@.type=='home')]");
//
// Filter compiled = FilterCompiler.compile("$.phoneNumbers[?(@.type=='home')]");
// new FilterCompiler("$.phoneNumbers[?(@.type=='home')]");
//
// Criteria crit = Criteria.parse("$.phoneNumbers[?(@.type=='home')]");

try {
Page<S> persistedEntities = service.findAll(pageable);
// https://github.com/json-path/JsonPath
if (StringUtils.isNotEmpty(filter)) {
// TODO Parse and validate filter
//JsonPath peter = JsonPath.compile("$.phoneNumbers[?(@.type=='home')]");
JsonPath compiledFilter = JsonPath.compile(filter);
// Filter compiledFilter = Filter.parse(filter);

log.trace("Got filter " + compiledFilter.toString());
}

Page<S> persistedEntities = service.findAll(pageable, filter);

log.trace("Successfully got all entities of type {} (count: {})",
getGenericClassName(), persistedEntities.getTotalElements());

return persistedEntities;
} catch (AccessDeniedException ade) {
// TODO I don't think we'll ever get into this exception here
log.warn("Access to entity of type {} is denied", getGenericClassName());

throw new ResponseStatusException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,27 @@ AND gip.permission.name IN ('ADMIN', 'READ', 'CREATE_READ', 'CREATE_READ_UPDATE'
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Page<T> findAll(Pageable pageable);

@Query(
value = """
SELECT
e.*
FROM
#{#entityName} e
WHERE
:filter IS NULL OR CAST(row_to_json(e) AS JSONB) @@ CAST(:filter AS JSONPATH)
""",
countQuery = """
SELECT
COUNT(e.*)
FROM
#{#entityName} e
""",
nativeQuery = true
)
@QueryHints(
value = @QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"),
forCounting = false
)
Page<T> findAll(Pageable pageable, String filter);

}
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public boolean hasPermissionByGroupClassPermission(User user, BaseEntity entity,
* @return A page of entities.
*/
@Override
public Page<E> findAll(User user, Pageable pageable, BaseCrudRepository<E, Long> repository, Class<E> baseEntityClass) {
public Page<E> findAll(User user, Pageable pageable, String filter, BaseCrudRepository<E, Long> repository, Class<E> baseEntityClass) {
if (user == null) {
throw new RuntimeException("No user provided!");
}
Expand All @@ -272,15 +272,15 @@ public Page<E> findAll(User user, Pageable pageable, BaseCrudRepository<E, Long>
);

if (isAdmin) {
return repository.findAll(pageable);
return repository.findAll(pageable, filter);
}

// option B: user has permission through class permissions
Optional<UserClassPermission> userClassPermission = userClassPermissionService.findFor(baseEntityClass, user);
Optional<GroupClassPermission> groupClassPermission = groupClassPermissionService.findFor(baseEntityClass, user);

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

// option C: check instance permissions for each entity with a single query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ public interface EntityPermissionEvaluator<E> {
* with pagination. See {@link BaseEntityPermissionEvaluator#findAll(User, Pageable, BaseCrudRepository)} for the
* default implementation for {@link de.terrestris.shogun.lib.model.BaseEntity}.
*/
Page<E> findAll(User user, Pageable pageable, BaseCrudRepository<E, Long> repository, Class<E> baseEntityClass);
Page<E> findAll(User user, Pageable pageable, String filter, BaseCrudRepository<E, Long> repository, Class<E> baseEntityClass);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public class GroupPermissionEvaluator extends BaseEntityPermissionEvaluator<Grou
GroupProviderService groupProviderService;

@Override
public Page<Group> findAll(User user, Pageable pageable, BaseCrudRepository<Group, Long> repository,
public Page<Group> findAll(User user, Pageable pageable, String filter, BaseCrudRepository<Group, Long> repository,
Class<Group> baseEntityClass) {
Page<Group> groups = super.findAll(user, pageable, repository, baseEntityClass);
Page<Group> groups = super.findAll(user, pageable, filter, repository, baseEntityClass);

groups.forEach(u -> groupProviderService.setTransientRepresentations(u));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public class UserPermissionEvaluator extends BaseEntityPermissionEvaluator<User>
UserProviderService userProviderService;

@Override
public Page<User> findAll(User user, Pageable pageable, BaseCrudRepository<User, Long> repository,
public Page<User> findAll(User user, Pageable pageable, String filter, BaseCrudRepository<User, Long> repository,
Class<User> baseEntityClass) {
Page<User> users = super.findAll(user, pageable, repository, baseEntityClass);
Page<User> users = super.findAll(user, pageable, filter, repository, baseEntityClass);

users.forEach(u -> userProviderService.setTransientRepresentations(u));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public List<S> findAll() {
}

@Transactional(readOnly = true)
public Page<S> findAll(Pageable pageable) {
public Page<S> findAll(Pageable pageable, String filter) {
// note: security check is done in permission evaluator
Optional<User> userOpt = userProviderService.getUserBySession();

Expand All @@ -99,7 +99,12 @@ public Page<S> findAll(Pageable pageable) {
BaseEntityPermissionEvaluator entityPermissionEvaluator =
this.getPermissionEvaluatorForClass(entityClass.getCanonicalName());

return entityPermissionEvaluator.findAll(userOpt.orElse(null), pageable, repository, entityClass);
return entityPermissionEvaluator.findAll(userOpt.orElse(null), pageable, filter, repository, entityClass);
}

@Transactional(readOnly = true)
public Page<S> findAll(Pageable pageable) {
return this.findAll(pageable, null);
}

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

0 comments on commit 88e0f14

Please sign in to comment.