Skip to content

Commit 37f1416

Browse files
authored
feat(GH-219): add support for row level filtering query provider (#274)
* feat: add initial support for restricted keys query provider abstraction * fix: code cleanup * fix: failing IdClass query tests * feat: add RestrictedKeysObjectProvider Spring Boot autoconfiguration * feat: add restricted keys provider test for Relay query * fix: polish tests * fix: add support for restricted keys count query * fix: add RestrictedKeysProvider Javadoc * fix: update Javadoc * feat: support RestricedKeysProvider for singular queries
1 parent dae311b commit 37f1416

File tree

12 files changed

+641
-51
lines changed

12 files changed

+641
-51
lines changed

graphql-jpa-query-boot-starter/src/main/java/com/introproventures/graphql/jpa/query/boot/autoconfigure/GraphQLJpaQueryAutoConfiguration.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor;
3535
import com.introproventures.graphql.jpa.query.schema.GraphQLExecutorContextFactory;
3636
import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
37+
import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider;
3738
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
3839
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutorContextFactory;
3940
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
41+
4042
import graphql.GraphQL;
4143
import graphql.GraphQLContext;
4244
import graphql.execution.instrumentation.Instrumentation;
@@ -55,8 +57,13 @@ public static class DefaultGraphQLJpaQueryConfiguration {
5557
@Bean
5658
@ConditionalOnMissingBean
5759
@ConditionalOnSingleCandidate(EntityManagerFactory.class)
58-
public GraphQLSchemaBuilder graphQLJpaSchemaBuilder(final EntityManagerFactory entityManagerFactory) {
59-
return new GraphQLJpaSchemaBuilder(entityManagerFactory.createEntityManager());
60+
public GraphQLSchemaBuilder graphQLJpaSchemaBuilder(final EntityManagerFactory entityManagerFactory,
61+
ObjectProvider<RestrictedKeysProvider> restrictedKeysProvider) {
62+
GraphQLJpaSchemaBuilder bean = new GraphQLJpaSchemaBuilder(entityManagerFactory.createEntityManager());
63+
64+
restrictedKeysProvider.ifAvailable(bean::restrictedKeysProvider);
65+
66+
return bean;
6067
}
6168

6269
@Bean

graphql-jpa-query-boot-starter/src/test/java/com/introproventures/graphql/jpa/query/boot/test/boot/autoconfigure/GraphQLJpaQueryAutoConfigurationTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.introproventures.graphql.jpa.query.schema.GraphQLExecutionInputFactory;
3535
import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor;
3636
import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
37+
import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider;
3738
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
3839
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutorContextFactory;
3940
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
@@ -63,6 +64,8 @@ static class Application {
6364
@MockBean
6465
private Supplier<GraphQLContext> graphqlContext;
6566

67+
@MockBean
68+
private RestrictedKeysProvider restrictedKeysProvider;
6669
}
6770

6871
@Autowired(required=false)
@@ -85,6 +88,9 @@ static class Application {
8588

8689
@Autowired
8790
private ObjectProvider<Supplier<GraphQLContext>> graphqlContext;
91+
92+
@Autowired
93+
private ObjectProvider<RestrictedKeysProvider> restrictedKeysObjectProvider;
8894

8995
@Autowired
9096
private GraphQLSchema graphQLSchema;
@@ -96,6 +102,9 @@ public void contextIsAutoConfigured() {
96102

97103
assertThat(graphQLSchemaBuilder).isNotNull()
98104
.isInstanceOf(GraphQLJpaSchemaBuilder.class);
105+
106+
assertThat(GraphQLJpaSchemaBuilder.class.cast(graphQLSchemaBuilder)
107+
.getRestrictedKeysProvider()).isEqualTo(restrictedKeysObjectProvider.getObject());
99108

100109
assertThat(executorContextFactory).isNotNull()
101110
.isInstanceOf(GraphQLJpaExecutorContextFactory.class);

graphql-jpa-query-schema/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@
7777
<scope>test</scope>
7878
</dependency>
7979

80+
<dependency>
81+
<groupId>org.springframework.boot</groupId>
82+
<artifactId>spring-boot-starter-security</artifactId>
83+
<scope>test</scope>
84+
</dependency>
85+
86+
<dependency>
87+
<groupId>org.springframework.security</groupId>
88+
<artifactId>spring-security-test</artifactId>
89+
<scope>test</scope>
90+
</dependency>
91+
8092
<dependency>
8193
<groupId>com.fasterxml.jackson.core</groupId>
8294
<artifactId>jackson-databind</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.introproventures.graphql.jpa.query.schema;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import java.util.function.Function;
6+
7+
import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult;
8+
9+
/**
10+
* The RestrictedKeysProvider functional interface should provide a list of restricted keys in order to filter records
11+
* at runtime based on user security context based on EntityIntrospection descriptor.
12+
*
13+
* The return argument uses Optional<List<Object>> return type:
14+
* The non-empty list will restrict the query to provided keys
15+
* The empty list will run the query unrestricted.
16+
* The empty Optional will block running the query and return empty result.
17+
*
18+
*/
19+
@FunctionalInterface
20+
public interface RestrictedKeysProvider extends Function<EntityIntrospectionResult, Optional<List<Object>>> {
21+
22+
/**
23+
* Applies this restricted keys provider function to the given argument of entityDescriptor.
24+
*
25+
* @param entityDescriptor the function argument
26+
* @return the function result with optional list of keys
27+
*/
28+
@Override
29+
Optional<List<Object>> apply(EntityIntrospectionResult entityDescriptor);
30+
31+
}

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.getSelectionField;
2424
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.searchByFieldName;
2525

26-
import java.util.Collections;
26+
import java.util.ArrayList;
2727
import java.util.List;
2828
import java.util.Optional;
2929

@@ -74,21 +74,32 @@ public PagedResult<Object> get(DataFetchingEnvironment environment) {
7474
final PagedResult.Builder<Object> pagedResult = PagedResult.builder()
7575
.withOffset(firstResult)
7676
.withLimit(maxResults);
77+
Optional<List<Object>> restrictedKeys = queryFactory.getRestrictedKeys(environment);
78+
7779
if (recordsSelection.isPresent()) {
78-
List<Object> keys = Collections.emptyList();
79-
80-
if (pageArgument.isPresent() || enableDefaultMaxResults) {
81-
keys = queryFactory.queryKeys(environment, firstResult, maxResults);
82-
}
83-
84-
final List<Object> resultList = queryFactory.queryResultList(environment,
85-
maxResults,
86-
keys);
87-
pagedResult.withSelect(resultList);
80+
if (restrictedKeys.isPresent()) {
81+
final List<Object> queryKeys = new ArrayList<>();
82+
83+
if (pageArgument.isPresent() || enableDefaultMaxResults) {
84+
queryKeys.addAll(queryFactory.queryKeys(environment,
85+
firstResult,
86+
maxResults,
87+
restrictedKeys.get()));
88+
}
89+
else {
90+
queryKeys.addAll(restrictedKeys.get());
91+
}
92+
93+
final List<Object> resultList = queryFactory.queryResultList(environment,
94+
maxResults,
95+
queryKeys);
96+
pagedResult.withSelect(resultList);
97+
}
8898
}
8999

90100
if (totalSelection.isPresent() || pagesSelection.isPresent()) {
91-
final Long total = queryFactory.queryTotalCount(environment);
101+
final Long total = queryFactory.queryTotalCount(environment,
102+
restrictedKeys);
92103

93104
pagedResult.withTotal(total);
94105
}

0 commit comments

Comments
 (0)