From 560b157af6b35fe1807d400bfc5671cdcb3d9b51 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sat, 20 Jun 2020 19:04:36 -0700 Subject: [PATCH 01/10] feat: add initial support for restricted keys query provider abstraction --- graphql-jpa-query-schema/pom.xml | 12 ++ .../query/schema/RestrictedKeysProvider.java | 12 ++ .../impl/GraphQLJpaQueryDataFetcher.java | 29 ++-- .../schema/impl/GraphQLJpaQueryFactory.java | 80 ++++++++- .../schema/impl/GraphQLJpaSchemaBuilder.java | 15 ++ .../relay/GraphQLJpaRelayDataFetcher.java | 32 ++-- .../RestrictedKeysProviderTests.java | 161 ++++++++++++++++++ .../resources/RestrictedKeysProviderTests.sql | 6 + 8 files changed, 318 insertions(+), 29 deletions(-) create mode 100644 graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java create mode 100644 graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java create mode 100644 graphql-jpa-query-schema/src/test/resources/RestrictedKeysProviderTests.sql diff --git a/graphql-jpa-query-schema/pom.xml b/graphql-jpa-query-schema/pom.xml index 1b81a4b12..0f7934d79 100644 --- a/graphql-jpa-query-schema/pom.xml +++ b/graphql-jpa-query-schema/pom.xml @@ -77,6 +77,18 @@ test + + org.springframework.boot + spring-boot-starter-security + test + + + + org.springframework.security + spring-security-test + test + + com.fasterxml.jackson.core jackson-databind diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java new file mode 100644 index 000000000..5a5c21ede --- /dev/null +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java @@ -0,0 +1,12 @@ +package com.introproventures.graphql.jpa.query.schema; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult; + +@FunctionalInterface +public interface RestrictedKeysProvider extends Function>> { + +} diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java index f68b04921..49ae8b429 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java @@ -23,7 +23,7 @@ import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.getSelectionField; import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.searchByFieldName; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -75,16 +75,23 @@ public PagedResult get(DataFetchingEnvironment environment) { .withOffset(firstResult) .withLimit(maxResults); if (recordsSelection.isPresent()) { - List keys = Collections.emptyList(); - - if (pageArgument.isPresent() || enableDefaultMaxResults) { - keys = queryFactory.queryKeys(environment, firstResult, maxResults); - } - - final List resultList = queryFactory.queryResultList(environment, - maxResults, - keys); - pagedResult.withSelect(resultList); + Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); + + if (restrictedKeys.isPresent()) { + final List queryKeys = new ArrayList<>(); + + if (pageArgument.isPresent() || enableDefaultMaxResults) { + queryKeys.addAll(queryFactory.queryKeys(environment, + firstResult, + maxResults, + restrictedKeys.get())); + } + + final List resultList = queryFactory.queryResultList(environment, + maxResults, + queryKeys); + pagedResult.withSelect(resultList); + } } if (totalSelection.isPresent() || pagesSelection.isPresent()) { diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 5f69b40c8..725ef84d1 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -45,9 +45,11 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -72,12 +74,16 @@ import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.introproventures.graphql.jpa.query.annotation.GraphQLDefaultOrderBy; import com.introproventures.graphql.jpa.query.introspection.ReflectionUtil; +import com.introproventures.graphql.jpa.query.schema.JavaScalars; +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; +import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult; import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult.AttributePropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria; import com.introproventures.graphql.jpa.query.support.GraphQLSupport; @@ -105,6 +111,7 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; @@ -136,6 +143,7 @@ public final class GraphQLJpaQueryFactory { private final String selectNodeName; private final GraphQLObjectType entityObjectType; private final int defaultFetchSize; + private final RestrictedKeysProvider restrictedKeysProvider; private GraphQLJpaQueryFactory(Builder builder) { this.entityManager = builder.entityManager; @@ -145,6 +153,7 @@ private GraphQLJpaQueryFactory(Builder builder) { this.toManyDefaultOptional = builder.toManyDefaultOptional; this.defaultDistinct = builder.defaultDistinct; this.defaultFetchSize = builder.defaultFetchSize; + this.restrictedKeysProvider = builder.restrictedKeysProvider; } public DataFetchingEnvironment getQueryEnvironment(DataFetchingEnvironment environment, @@ -157,17 +166,43 @@ public DataFetchingEnvironment getQueryEnvironment(DataFetchingEnvironment envir .build(); } + public Optional> getRestrictedKeys(DataFetchingEnvironment environment) { + EntityIntrospectionResult entityDescriptor = EntityIntrospector.introspect(entityType); + + Optional> restrictedKeys = restrictedKeysProvider.apply(entityDescriptor); + List restrictedKeysValues = new ArrayList<>(); + + if (restrictedKeys.isPresent() && hasIdAttribute()) { + restrictedKeys.get() + .stream() + .filter(key -> !"*".equals(key)) + .map(key -> { + Type idType = entityType.getIdType(); + Class clazz = idType.getJavaType(); + GraphQLScalarType scalar = JavaScalars.of(clazz); + + return scalar.getCoercing().parseValue(key); + }) + .forEach(restrictedKeysValues::add); + + return Optional.of(restrictedKeysValues); + } + + return Optional.empty(); + } + public List queryKeys(DataFetchingEnvironment environment, int firstResult, - int maxResults) { + int maxResults, + List restrictedKeys) { MergedField queryField = resolveQueryField(environment.getField()); // Override query environment with associated entity object type and final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField); - TypedQuery keysQuery = getKeysQuery(queryEnvironment, - queryEnvironment.getField()); + queryEnvironment.getField(), + restrictedKeys); keysQuery.setFirstResult(firstResult) .setMaxResults(maxResults); @@ -179,6 +214,15 @@ public List queryKeys(DataFetchingEnvironment environment, return keysQuery.getResultList(); } + class DefaultKeysSupplier implements Supplier>> { + + @Override + public Optional> get() { + // TODO Auto-generated method stub + return null; + } + } + public List queryResultList(DataFetchingEnvironment environment, int maxResults, List keys) { @@ -294,7 +338,7 @@ protected TypedQuery getCountQuery(DataFetchingEnvironment environment, Fi return entityManager.createQuery(query); } - protected TypedQuery getKeysQuery(DataFetchingEnvironment environment, Field field) { + protected TypedQuery getKeysQuery(DataFetchingEnvironment environment, Field field, List keys) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery query = cb.createQuery(Object.class); Root from = query.from(entityType); @@ -316,8 +360,13 @@ protected TypedQuery getKeysQuery(DataFetchingEnvironment environment, F List predicates = field.getArguments().stream() .map(it -> getPredicate(field, cb, from, null, queryEnvironment, it)) - .filter(it -> it != null) + .filter(it -> it != null) .collect(Collectors.toList()); + + if (!keys.isEmpty() && hasIdAttribute()) { + Predicate restrictions = from.get(idAttributeName()).in(keys); + predicates.add(restrictions); + } query.where(predicates.toArray(new Predicate[0])); @@ -1889,6 +1938,13 @@ public interface IBuildStage { */ public IBuildStage withDefaultFetchSize(int defaultFetchSize); + /** + * Builder method for restrictedKeysProvider parameter. + * @param restrictedKeysProvider field to set + * @return builder + */ + public IBuildStage withRestrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider); + /** * Builder method of the builder. * @return built class @@ -1902,6 +1958,7 @@ public interface IBuildStage { */ public static final class Builder implements IEntityManagerStage, IEntityTypeStage, IEntityObjectTypeStage, ISelectNodeNameStage, IBuildStage { + private RestrictedKeysProvider restrictedKeysProvider; private EntityManager entityManager; private EntityType entityType; private GraphQLObjectType entityObjectType; @@ -1955,9 +2012,22 @@ public IBuildStage withDefaultFetchSize(int defaultFetchSize) { return this; } + @Override + public IBuildStage withRestrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider) { + this.restrictedKeysProvider = restrictedKeysProvider; + return this; + } + @Override public GraphQLJpaQueryFactory build() { + Objects.requireNonNull(restrictedKeysProvider, "restrictedKeysProvider must not be null"); + return new GraphQLJpaQueryFactory(this); } } + + + public Function>> getRestrictedKeysProvider() { + return restrictedKeysProvider; + } } diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java index 75af0c9d1..0364711e9 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -55,6 +56,7 @@ import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; import com.introproventures.graphql.jpa.query.schema.JavaScalars; import com.introproventures.graphql.jpa.query.schema.NamingStrategy; +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult.AttributePropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria; import com.introproventures.graphql.jpa.query.schema.relay.GraphQLJpaRelayDataFetcher; @@ -131,6 +133,8 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder { private int defaultFetchSize = 100; private int defaultPageLimitSize = 100; private boolean enableDefaultMaxResults = true; + + private RestrictedKeysProvider restrictedKeysProvider = (entityDescriptor) -> Optional.of(Collections.emptyList()); private final Relay relay = new Relay(); @@ -218,6 +222,7 @@ private GraphQLFieldDefinition getQueryFieldByIdDefinition(EntityType entityT .withEntityObjectType(entityObjectType) .withSelectNodeName(entityObjectType.getName()) .withToManyDefaultOptional(toManyDefaultOptional) + .withRestrictedKeysProvider(restrictedKeysProvider) .build(); DataFetcher dataFetcher = GraphQLJpaSimpleDataFetcher.builder() @@ -260,6 +265,7 @@ private GraphQLFieldDefinition getQueryFieldSelectDefinition(EntityType entit .withToManyDefaultOptional(toManyDefaultOptional) .withDefaultDistinct(isDefaultDistinct) .withDefaultFetchSize(defaultFetchSize) + .withRestrictedKeysProvider(restrictedKeysProvider) .build(); if(enableRelay) { @@ -337,6 +343,7 @@ private GraphQLFieldDefinition getQueryFieldStreamDefinition(EntityType entit .withSelectNodeName(SELECT_DISTINCT_PARAM_NAME) .withToManyDefaultOptional(toManyDefaultOptional) .withDefaultDistinct(isDefaultDistinct) + .withRestrictedKeysProvider(restrictedKeysProvider) .build(); DataFetcher dataFetcher = GraphQLJpaStreamDataFetcher.builder() @@ -923,6 +930,7 @@ && isNotIgnoredOrder(attribute) ) { .withEntityObjectType(entityObjectType) .withSelectNodeName(entityObjectType.getName()) .withDefaultDistinct(isDefaultDistinct) + .withRestrictedKeysProvider(restrictedKeysProvider) .build(); String dataLoaderKey = baseEntity.getName() + "." + attribute.getName(); @@ -958,6 +966,7 @@ else if (attribute instanceof PluralAttribute .withEntityObjectType(entityObjectType) .withSelectNodeName(entityObjectType.getName()) .withDefaultDistinct(isDefaultDistinct) + .withRestrictedKeysProvider(restrictedKeysProvider) .build(); String dataLoaderKey = baseEntity.getName() + "." + attribute.getName(); @@ -1454,5 +1463,11 @@ public GraphQLJpaSchemaBuilder enableDefaultMaxResults(boolean enableDefaultMaxR return this; } + + public GraphQLJpaSchemaBuilder restrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider) { + this.restrictedKeysProvider = restrictedKeysProvider; + + return this; + } } \ No newline at end of file diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java index bd31c0cc6..3a136249f 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java @@ -3,7 +3,7 @@ import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.getSelectionField; import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.searchByFieldName; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -12,6 +12,7 @@ import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaQueryFactory; import com.introproventures.graphql.jpa.query.schema.impl.PagedResult; + import graphql.language.Field; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -60,18 +61,23 @@ public Page get(DataFetchingEnvironment environment) throws Exception { .withLimit(maxResults); if (edgesSelection.isPresent()) { - List keys = Collections.emptyList(); - - if (enableDefaultMaxResults || firstArgument.isPresent() || afterArgument.isPresent()) { - keys = queryFactory.queryKeys(environment, - firstResult, - maxResults); - } - - final List resultList = queryFactory.queryResultList(environment, - maxResults, - keys); - pagedResult.withSelect(resultList); + Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); + + if (restrictedKeys.isPresent()) { + final List queryKeys = new ArrayList<>(); + + if (enableDefaultMaxResults || firstArgument.isPresent() || afterArgument.isPresent()) { + queryKeys.addAll(queryFactory.queryKeys(environment, + firstResult, + maxResults, + restrictedKeys.get())); + } + + final List resultList = queryFactory.queryResultList(environment, + maxResults, + queryKeys); + pagedResult.withSelect(resultList); + } } if (pageInfoSelection.isPresent()) { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java new file mode 100644 index 000000000..6991c7a55 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java @@ -0,0 +1,161 @@ +package com.introproventures.graphql.jpa.query.restricted; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.metamodel.Metamodel; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.util.Assert; + +import com.introproventures.graphql.jpa.query.AbstractSpringBootTestSupport; +import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; +import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.model.uuid.Thing; + +@SpringBootTest(properties = "spring.datasource.data=RestrictedKeysProviderTests.sql") +public class RestrictedKeysProviderTests extends AbstractSpringBootTestSupport { + + @SpringBootApplication + @EntityScan(basePackageClasses = Thing.class) + static class Application { + + @Bean + public GraphQLExecutor graphQLExecutor(final GraphQLSchemaBuilder graphQLSchemaBuilder) { + return new GraphQLJpaExecutor(graphQLSchemaBuilder.build()); + } + + @Bean + public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManager) { + + return new GraphQLJpaSchemaBuilder(entityManager) + .name("GraphQLBooks") + .description("Books JPA test schema") + .restrictedKeysProvider(restrictedKeysProvider(entityManager.getMetamodel())); + } + + public RestrictedKeysProvider restrictedKeysProvider(Metamodel metamodel) { + return (entityDescriptor) -> { + Authentication token = SecurityContextHolder.getContext() + .getAuthentication(); + if (token == null) { + return Optional.empty(); + } + + String entityName = metamodel.entity(entityDescriptor.getEntity()) + .getName(); + + List ids = token.getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .map(authority -> authority.split(":")) + .filter(permission -> entityName.equals(permission[0])) + .filter(permission -> "read".equals(permission[1])) + .map(permission -> permission[2]) + .collect(Collectors.toList()); + + return ids.isEmpty() ? Optional.empty() // restrict query if no permissions exists + : Optional.of(ids); // execute query with restricted keys + }; + } + } + + @Autowired + private GraphQLExecutor executor; + + @Test + public void contextLoads() { + Assert.isAssignable(GraphQLExecutor.class, executor.getClass()); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1") + public void testRestrictedThingQuery() { + //given + String query = "query RestrictedThingQuery { Things { select {id type } } }"; + String expected = "{Things={select=[{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:*") + public void testNonRestrictedThingQuery() { + //given + String query = "query RestrictedThingQuery { Things { select {id type } } }"; + String expected = "{Things={select=[" + + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}, " + + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1, type=Thing2}, " + + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbd1, type=Thing3}" + + "]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "NotThing:*") + public void testRestrictAllThingQuery() { + //given + String query = "query RestrictedThingQuery { Things { select {id type } } }"; + String expected = "{Things={select=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithAnonymousUser + public void testRestrictAllThingQueryForAnonymous() { + //given + String query = "query RestrictedThingQuery { Things { select {id type } } }"; + String expected = "{Things={select=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + public void testRestrictAllThingQueryWithNullAuthentication() { + //given + String query = "query RestrictedThingQuery { Things { select {id type } } }"; + String expected = "{Things={select=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + +} diff --git a/graphql-jpa-query-schema/src/test/resources/RestrictedKeysProviderTests.sql b/graphql-jpa-query-schema/src/test/resources/RestrictedKeysProviderTests.sql new file mode 100644 index 000000000..e6274a9b5 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/resources/RestrictedKeysProviderTests.sql @@ -0,0 +1,6 @@ +-- Things +insert into thing (id, type) values + ('2D1EBC5B7D2741979CF0E84451C5BBB1', 'Thing1'), + ('2D1EBC5B7D2741979CF0E84451C5BBC1', 'Thing2'), + ('2D1EBC5B7D2741979CF0E84451C5BBD1', 'Thing3'); + From 05bb25ecee5347e4a9b4e18993dcda48613784bf Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sat, 20 Jun 2020 19:06:21 -0700 Subject: [PATCH 02/10] fix: code cleanup --- .../jpa/query/schema/impl/GraphQLJpaQueryFactory.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 725ef84d1..92e955bf7 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -49,7 +49,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -214,15 +213,6 @@ public List queryKeys(DataFetchingEnvironment environment, return keysQuery.getResultList(); } - class DefaultKeysSupplier implements Supplier>> { - - @Override - public Optional> get() { - // TODO Auto-generated method stub - return null; - } - } - public List queryResultList(DataFetchingEnvironment environment, int maxResults, List keys) { From 9b4b89f65d6dcefc052f32fba3ab3ba23e4a344e Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 21 Jun 2020 11:49:42 -0700 Subject: [PATCH 03/10] fix: failing IdClass query tests --- .../schema/impl/GraphQLJpaQueryFactory.java | 2 +- .../RestrictedKeysProviderTests.java | 31 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 92e955bf7..96027a5b7 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -171,7 +171,7 @@ public Optional> getRestrictedKeys(DataFetchingEnvironment environm Optional> restrictedKeys = restrictedKeysProvider.apply(entityDescriptor); List restrictedKeysValues = new ArrayList<>(); - if (restrictedKeys.isPresent() && hasIdAttribute()) { + if (restrictedKeys.isPresent()) { restrictedKeys.get() .stream() .filter(key -> !"*".equals(key)) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java index 6991c7a55..ebc981d7c 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -62,19 +63,25 @@ public RestrictedKeysProvider restrictedKeysProvider(Metamodel metamodel) { String entityName = metamodel.entity(entityDescriptor.getEntity()) .getName(); - List ids = token.getAuthorities() - .stream() - .map(GrantedAuthority::getAuthority) - .map(authority -> authority.split(":")) - .filter(permission -> entityName.equals(permission[0])) - .filter(permission -> "read".equals(permission[1])) - .map(permission -> permission[2]) - .collect(Collectors.toList()); + List keys = resolveKeys(entityName, token.getAuthorities()); - return ids.isEmpty() ? Optional.empty() // restrict query if no permissions exists - : Optional.of(ids); // execute query with restricted keys + return keys.isEmpty() ? Optional.empty() // restrict query if no permissions exists + : Optional.of(keys); // execute query with restricted keys }; } + + private List resolveKeys(String entityName, Collection grantedAuthorities) { + return grantedAuthorities.stream() + .filter(GrantedAuthority.class::isInstance) + .map(GrantedAuthority.class::cast) + .map(GrantedAuthority::getAuthority) + .map(authority -> authority.split(":")) + .filter(permission -> entityName.equals(permission[0])) + .filter(permission -> "read".equals(permission[1])) + .map(permission -> permission[2]) + .collect(Collectors.toList()); + + } } @Autowired @@ -118,8 +125,8 @@ public void testNonRestrictedThingQuery() { } @Test - @WithMockUser(value = "spring", authorities = "NotThing:*") - public void testRestrictAllThingQuery() { + @WithMockUser(value = "spring", authorities = "OtherThing:*") + public void testRestrictAllOtherThingQuery() { //given String query = "query RestrictedThingQuery { Things { select {id type } } }"; String expected = "{Things={select=[]}}"; From abacecb63fd5dd58c72f5082e1eee89d8171ce15 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 21 Jun 2020 12:01:19 -0700 Subject: [PATCH 04/10] feat: add RestrictedKeysObjectProvider Spring Boot autoconfiguration --- .../GraphQLJpaQueryAutoConfiguration.java | 11 +++++++++-- .../GraphQLJpaQueryAutoConfigurationTest.java | 9 +++++++++ .../query/schema/impl/GraphQLJpaSchemaBuilder.java | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/graphql-jpa-query-boot-starter/src/main/java/com/introproventures/graphql/jpa/query/boot/autoconfigure/GraphQLJpaQueryAutoConfiguration.java b/graphql-jpa-query-boot-starter/src/main/java/com/introproventures/graphql/jpa/query/boot/autoconfigure/GraphQLJpaQueryAutoConfiguration.java index 52cd18882..f5c51fa30 100644 --- a/graphql-jpa-query-boot-starter/src/main/java/com/introproventures/graphql/jpa/query/boot/autoconfigure/GraphQLJpaQueryAutoConfiguration.java +++ b/graphql-jpa-query-boot-starter/src/main/java/com/introproventures/graphql/jpa/query/boot/autoconfigure/GraphQLJpaQueryAutoConfiguration.java @@ -34,9 +34,11 @@ import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; import com.introproventures.graphql.jpa.query.schema.GraphQLExecutorContextFactory; import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutorContextFactory; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; + import graphql.GraphQL; import graphql.GraphQLContext; import graphql.execution.instrumentation.Instrumentation; @@ -55,8 +57,13 @@ public static class DefaultGraphQLJpaQueryConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(EntityManagerFactory.class) - public GraphQLSchemaBuilder graphQLJpaSchemaBuilder(final EntityManagerFactory entityManagerFactory) { - return new GraphQLJpaSchemaBuilder(entityManagerFactory.createEntityManager()); + public GraphQLSchemaBuilder graphQLJpaSchemaBuilder(final EntityManagerFactory entityManagerFactory, + ObjectProvider restrictedKeysProvider) { + GraphQLJpaSchemaBuilder bean = new GraphQLJpaSchemaBuilder(entityManagerFactory.createEntityManager()); + + restrictedKeysProvider.ifAvailable(bean::restrictedKeysProvider); + + return bean; } @Bean diff --git a/graphql-jpa-query-boot-starter/src/test/java/com/introproventures/graphql/jpa/query/boot/test/boot/autoconfigure/GraphQLJpaQueryAutoConfigurationTest.java b/graphql-jpa-query-boot-starter/src/test/java/com/introproventures/graphql/jpa/query/boot/test/boot/autoconfigure/GraphQLJpaQueryAutoConfigurationTest.java index 285877cbe..509065c5f 100644 --- a/graphql-jpa-query-boot-starter/src/test/java/com/introproventures/graphql/jpa/query/boot/test/boot/autoconfigure/GraphQLJpaQueryAutoConfigurationTest.java +++ b/graphql-jpa-query-boot-starter/src/test/java/com/introproventures/graphql/jpa/query/boot/test/boot/autoconfigure/GraphQLJpaQueryAutoConfigurationTest.java @@ -34,6 +34,7 @@ import com.introproventures.graphql.jpa.query.schema.GraphQLExecutionInputFactory; import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutorContextFactory; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; @@ -63,6 +64,8 @@ static class Application { @MockBean private Supplier graphqlContext; + @MockBean + private RestrictedKeysProvider restrictedKeysProvider; } @Autowired(required=false) @@ -85,6 +88,9 @@ static class Application { @Autowired private ObjectProvider> graphqlContext; + + @Autowired + private ObjectProvider restrictedKeysObjectProvider; @Autowired private GraphQLSchema graphQLSchema; @@ -96,6 +102,9 @@ public void contextIsAutoConfigured() { assertThat(graphQLSchemaBuilder).isNotNull() .isInstanceOf(GraphQLJpaSchemaBuilder.class); + + assertThat(GraphQLJpaSchemaBuilder.class.cast(graphQLSchemaBuilder) + .getRestrictedKeysProvider()).isEqualTo(restrictedKeysObjectProvider.getObject()); assertThat(executorContextFactory).isNotNull() .isInstanceOf(GraphQLJpaExecutorContextFactory.class); diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java index 0364711e9..8b7a1c30a 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java @@ -1470,4 +1470,8 @@ public GraphQLJpaSchemaBuilder restrictedKeysProvider(RestrictedKeysProvider res return this; } + public RestrictedKeysProvider getRestrictedKeysProvider() { + return restrictedKeysProvider; + } + } \ No newline at end of file From 61bd65993091532a37a5bca8cc93d426d4a3de5a Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 21 Jun 2020 12:20:39 -0700 Subject: [PATCH 05/10] feat: add restricted keys provider test for Relay query --- .../impl/GraphQLJpaQueryDataFetcher.java | 3 + .../relay/GraphQLJpaRelayDataFetcher.java | 3 + .../RestrictedKeysProviderRelayTests.java | 128 ++++++++++++++++++ .../RestrictedKeysProviderTests.java | 43 +----- .../SpringSecurityRestrictedKeysProvider.java | 55 ++++++++ 5 files changed, 190 insertions(+), 42 deletions(-) create mode 100644 graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java create mode 100644 graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/SpringSecurityRestrictedKeysProvider.java diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java index 49ae8b429..d502a0809 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java @@ -85,6 +85,9 @@ public PagedResult get(DataFetchingEnvironment environment) { firstResult, maxResults, restrictedKeys.get())); + } + else { + queryKeys.addAll(restrictedKeys.get()); } final List resultList = queryFactory.queryResultList(environment, diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java index 3a136249f..04927ea1b 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java @@ -72,6 +72,9 @@ public Page get(DataFetchingEnvironment environment) throws Exception { maxResults, restrictedKeys.get())); } + else { + queryKeys.addAll(restrictedKeys.get()); + } final List resultList = queryFactory.queryResultList(environment, maxResults, diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java new file mode 100644 index 000000000..de4b64343 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java @@ -0,0 +1,128 @@ +package com.introproventures.graphql.jpa.query.restricted; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.persistence.EntityManager; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.util.Assert; + +import com.introproventures.graphql.jpa.query.AbstractSpringBootTestSupport; +import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; +import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; +import com.introproventures.graphql.jpa.query.schema.model.uuid.Thing; + +@SpringBootTest(properties = "spring.datasource.data=RestrictedKeysProviderTests.sql") +public class RestrictedKeysProviderRelayTests extends AbstractSpringBootTestSupport { + + @SpringBootApplication + @EntityScan(basePackageClasses = Thing.class) + static class Application { + + @Bean + public GraphQLExecutor graphQLExecutor(final GraphQLSchemaBuilder graphQLSchemaBuilder) { + return new GraphQLJpaExecutor(graphQLSchemaBuilder.build()); + } + + @Bean + public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManager) { + + return new GraphQLJpaSchemaBuilder(entityManager) + .name("GraphQLBooks") + .description("Books JPA test schema") + .enableRelay(true) + .restrictedKeysProvider(new SpringSecurityRestrictedKeysProvider(entityManager.getMetamodel())); + } + } + + @Autowired + private GraphQLExecutor executor; + + @Test + public void contextLoads() { + Assert.isAssignable(GraphQLExecutor.class, executor.getClass()); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1") + public void testRestrictedThingQuery() { + //given + String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; + String expected = "{things={edges=[{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:*") + public void testNonRestrictedThingQuery() { + //given + String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; + String expected = "{things={edges=[" + + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}, " + + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1, type=Thing2}}, " + + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbd1, type=Thing3}}" + + "]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "OtherThing:*") + public void testRestrictAllOtherThingQuery() { + //given + String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; + String expected = "{things={edges=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithAnonymousUser + public void testRestrictAllThingQueryForAnonymous() { + //given + String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; + String expected = "{things={edges=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + public void testRestrictAllThingQueryWithNullAuthentication() { + //given + String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; + String expected = "{things={edges=[]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + +} diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java index ebc981d7c..30e51e60a 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java @@ -2,13 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javax.persistence.EntityManager; -import javax.persistence.metamodel.Metamodel; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,9 +10,6 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.util.Assert; @@ -26,7 +17,6 @@ import com.introproventures.graphql.jpa.query.AbstractSpringBootTestSupport; import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder; -import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; import com.introproventures.graphql.jpa.query.schema.model.uuid.Thing; @@ -49,38 +39,7 @@ public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManag return new GraphQLJpaSchemaBuilder(entityManager) .name("GraphQLBooks") .description("Books JPA test schema") - .restrictedKeysProvider(restrictedKeysProvider(entityManager.getMetamodel())); - } - - public RestrictedKeysProvider restrictedKeysProvider(Metamodel metamodel) { - return (entityDescriptor) -> { - Authentication token = SecurityContextHolder.getContext() - .getAuthentication(); - if (token == null) { - return Optional.empty(); - } - - String entityName = metamodel.entity(entityDescriptor.getEntity()) - .getName(); - - List keys = resolveKeys(entityName, token.getAuthorities()); - - return keys.isEmpty() ? Optional.empty() // restrict query if no permissions exists - : Optional.of(keys); // execute query with restricted keys - }; - } - - private List resolveKeys(String entityName, Collection grantedAuthorities) { - return grantedAuthorities.stream() - .filter(GrantedAuthority.class::isInstance) - .map(GrantedAuthority.class::cast) - .map(GrantedAuthority::getAuthority) - .map(authority -> authority.split(":")) - .filter(permission -> entityName.equals(permission[0])) - .filter(permission -> "read".equals(permission[1])) - .map(permission -> permission[2]) - .collect(Collectors.toList()); - + .restrictedKeysProvider(new SpringSecurityRestrictedKeysProvider(entityManager.getMetamodel())); } } diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/SpringSecurityRestrictedKeysProvider.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/SpringSecurityRestrictedKeysProvider.java new file mode 100644 index 000000000..03d386f4b --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/SpringSecurityRestrictedKeysProvider.java @@ -0,0 +1,55 @@ +package com.introproventures.graphql.jpa.query.restricted; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.persistence.metamodel.Metamodel; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider; +import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult; + +public class SpringSecurityRestrictedKeysProvider implements RestrictedKeysProvider { + + private final Metamodel metamodel; + + public SpringSecurityRestrictedKeysProvider(Metamodel metamodel) { + this.metamodel = metamodel; + } + + @Override + public Optional> apply(EntityIntrospectionResult entityDescriptor) { + Authentication token = SecurityContextHolder.getContext() + .getAuthentication(); + if (token == null) { + return Optional.empty(); + } + + String entityName = metamodel.entity(entityDescriptor.getEntity()) + .getName(); + + List keys = resolveKeys(entityName, token.getAuthorities()); + + return keys.isEmpty() ? Optional.empty() // restrict query if no permissions exists + : Optional.of(keys); // execute query with restricted keys + } + + private List resolveKeys(String entityName, Collection grantedAuthorities) { + return grantedAuthorities.stream() + .filter(GrantedAuthority.class::isInstance) + .map(GrantedAuthority.class::cast) + .map(GrantedAuthority::getAuthority) + .map(authority -> authority.split(":")) + .filter(permission -> entityName.equals(permission[0])) + .filter(permission -> "read".equals(permission[1])) + .map(permission -> permission[2]) + .collect(Collectors.toList()); + + } + +} From 85822da6a22ab2202ace067a5d9243062e8b1c64 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 21 Jun 2020 12:56:38 -0700 Subject: [PATCH 06/10] fix: polish tests --- ...oviderTests.java => RestrictedKeysProviderSelectTests.java} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/{RestrictedKeysProviderTests.java => RestrictedKeysProviderSelectTests.java} (94%) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java similarity index 94% rename from graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java rename to graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java index 30e51e60a..d7b5c1503 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java @@ -22,7 +22,7 @@ import com.introproventures.graphql.jpa.query.schema.model.uuid.Thing; @SpringBootTest(properties = "spring.datasource.data=RestrictedKeysProviderTests.sql") -public class RestrictedKeysProviderTests extends AbstractSpringBootTestSupport { +public class RestrictedKeysProviderSelectTests extends AbstractSpringBootTestSupport { @SpringBootApplication @EntityScan(basePackageClasses = Thing.class) @@ -39,6 +39,7 @@ public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManag return new GraphQLJpaSchemaBuilder(entityManager) .name("GraphQLBooks") .description("Books JPA test schema") + .enableDefaultMaxResults(false) .restrictedKeysProvider(new SpringSecurityRestrictedKeysProvider(entityManager.getMetamodel())); } } From 53ecffd131ce2cb65270c34719109d3ffdfb52e7 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 21 Jun 2020 18:50:12 -0700 Subject: [PATCH 07/10] fix: add support for restricted keys count query --- .../impl/GraphQLJpaQueryDataFetcher.java | 7 ++-- .../schema/impl/GraphQLJpaQueryFactory.java | 31 ++++++++++----- .../relay/GraphQLJpaRelayDataFetcher.java | 9 +++-- .../RestrictedKeysProviderRelayTests.java | 10 +++-- .../RestrictedKeysProviderSelectTests.java | 39 ++++++++++++++----- 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java index d502a0809..8905e67c2 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryDataFetcher.java @@ -74,9 +74,9 @@ public PagedResult get(DataFetchingEnvironment environment) { final PagedResult.Builder pagedResult = PagedResult.builder() .withOffset(firstResult) .withLimit(maxResults); + Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); + if (recordsSelection.isPresent()) { - Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); - if (restrictedKeys.isPresent()) { final List queryKeys = new ArrayList<>(); @@ -98,7 +98,8 @@ public PagedResult get(DataFetchingEnvironment environment) { } if (totalSelection.isPresent() || pagesSelection.isPresent()) { - final Long total = queryFactory.queryTotalCount(environment); + final Long total = queryFactory.queryTotalCount(environment, + restrictedKeys); pagedResult.withTotal(total); } diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 96027a5b7..76f1df8c3 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -280,19 +280,27 @@ protected Object querySingleResult(final DataFetchingEnvironment environment) { return query.getSingleResult(); } - public Long queryTotalCount(DataFetchingEnvironment environment) { + public Long queryTotalCount(DataFetchingEnvironment environment, + Optional> restrictedKeys) { final MergedField queryField = flattenEmbeddedIdArguments(environment.getField()); final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField); + + if (restrictedKeys.isPresent()) { - TypedQuery countQuery = getCountQuery(queryEnvironment, queryEnvironment.getField()); - - if (logger.isDebugEnabled()) { - logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery)); + TypedQuery countQuery = getCountQuery(queryEnvironment, + queryEnvironment.getField(), + restrictedKeys.get()); + + if (logger.isDebugEnabled()) { + logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery)); + } + + return countQuery.getSingleResult(); } - - return countQuery.getSingleResult(); + + return 0L; } protected TypedQuery getQuery(DataFetchingEnvironment environment, Field field, boolean isDistinct, Object... keys) { @@ -305,7 +313,7 @@ protected TypedQuery getQuery(DataFetchingEnvironment environment, Field return entityManager.createQuery(criteriaQuery); } - protected TypedQuery getCountQuery(DataFetchingEnvironment environment, Field field) { + protected TypedQuery getCountQuery(DataFetchingEnvironment environment, Field field, List keys) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery query = cb.createQuery(Long.class); Root root = query.from(entityType); @@ -322,7 +330,12 @@ protected TypedQuery getCountQuery(DataFetchingEnvironment environment, Fi .map(it -> getPredicate(field, cb, root, null, queryEnvironment, it)) .filter(it -> it != null) .collect(Collectors.toList()); - + + if (!keys.isEmpty() && hasIdAttribute()) { + Predicate restrictions = root.get(idAttributeName()).in(keys); + predicates.add(restrictions); + } + query.where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(query); diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java index 04927ea1b..56c222d3b 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/relay/GraphQLJpaRelayDataFetcher.java @@ -49,7 +49,7 @@ public Page get(DataFetchingEnvironment environment) throws Exception { final Integer first = firstArgument.orElse(defaultFirstSize); final String after = afterArgument.orElse(new OffsetBasedCursor(0L).toConnectionCursor() - .toString()); + .toString()); final OffsetBasedCursor cursor = OffsetBasedCursor.fromCursor(after); @@ -60,9 +60,9 @@ public Page get(DataFetchingEnvironment environment) throws Exception { .withOffset(firstResult) .withLimit(maxResults); + Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); + if (edgesSelection.isPresent()) { - Optional> restrictedKeys = queryFactory.getRestrictedKeys(environment); - if (restrictedKeys.isPresent()) { final List queryKeys = new ArrayList<>(); @@ -84,7 +84,8 @@ public Page get(DataFetchingEnvironment environment) throws Exception { } if (pageInfoSelection.isPresent()) { - final Long total = queryFactory.queryTotalCount(environment); + final Long total = queryFactory.queryTotalCount(environment, + restrictedKeys); pagedResult.withTotal(total); } diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java index de4b64343..ee2da8f39 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderRelayTests.java @@ -56,8 +56,9 @@ public void contextLoads() { @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1") public void testRestrictedThingQuery() { //given - String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; - String expected = "{things={edges=[{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}]}}"; + String query = "query RestrictedThingQuery { things { pageInfo { hasNextPage, startCursor, endCursor } edges { node { id type } } } }"; + String expected = "{things={pageInfo={hasNextPage=false, startCursor=b2Zmc2V0PTE=, endCursor=b2Zmc2V0PTE=}, " + + "edges=[{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}]}}"; //when Object result = executor.execute(query).getData(); @@ -70,8 +71,9 @@ public void testRestrictedThingQuery() { @WithMockUser(value = "spring", authorities = "Thing:read:*") public void testNonRestrictedThingQuery() { //given - String query = "query RestrictedThingQuery { things { edges { node {id type } } } }"; - String expected = "{things={edges=[" + String query = "query RestrictedThingQuery { things { pageInfo { hasNextPage, startCursor, endCursor } edges { node {id type } } } }"; + String expected = "{things={pageInfo={hasNextPage=false, startCursor=b2Zmc2V0PTE=, endCursor=b2Zmc2V0PTM=}, " + + "edges=[" + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}, " + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1, type=Thing2}}, " + "{node={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbd1, type=Thing3}}" diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java index d7b5c1503..42ed0969a 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java @@ -56,8 +56,8 @@ public void contextLoads() { @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1") public void testRestrictedThingQuery() { //given - String query = "query RestrictedThingQuery { Things { select {id type } } }"; - String expected = "{Things={select=[{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}]}}"; + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=1, select=[{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}]}}"; //when Object result = executor.execute(query).getData(); @@ -66,12 +66,31 @@ public void testRestrictedThingQuery() { assertThat(result.toString()).isEqualTo(expected); } + @Test + @WithMockUser(value = "spring", authorities = {"Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1", + "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1"}) + public void testRestrictedThingQueryMultiple() { + //given + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=2, select=[" + + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}, " + + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1, type=Thing2}" + + "]}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test @WithMockUser(value = "spring", authorities = "Thing:read:*") public void testNonRestrictedThingQuery() { //given - String query = "query RestrictedThingQuery { Things { select {id type } } }"; - String expected = "{Things={select=[" + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=3, select=[" + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}, " + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbc1, type=Thing2}, " + "{id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbd1, type=Thing3}" @@ -88,8 +107,8 @@ public void testNonRestrictedThingQuery() { @WithMockUser(value = "spring", authorities = "OtherThing:*") public void testRestrictAllOtherThingQuery() { //given - String query = "query RestrictedThingQuery { Things { select {id type } } }"; - String expected = "{Things={select=[]}}"; + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=0, select=[]}}"; //when Object result = executor.execute(query).getData(); @@ -102,8 +121,8 @@ public void testRestrictAllOtherThingQuery() { @WithAnonymousUser public void testRestrictAllThingQueryForAnonymous() { //given - String query = "query RestrictedThingQuery { Things { select {id type } } }"; - String expected = "{Things={select=[]}}"; + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=0, select=[]}}"; //when Object result = executor.execute(query).getData(); @@ -115,8 +134,8 @@ public void testRestrictAllThingQueryForAnonymous() { @Test public void testRestrictAllThingQueryWithNullAuthentication() { //given - String query = "query RestrictedThingQuery { Things { select {id type } } }"; - String expected = "{Things={select=[]}}"; + String query = "query RestrictedThingQuery { Things { total select {id type } } }"; + String expected = "{Things={total=0, select=[]}}"; //when Object result = executor.execute(query).getData(); From e5b6f3493cc2f635318bc6e502c6939262adb021 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 22 Jun 2020 09:30:47 -0700 Subject: [PATCH 08/10] fix: add RestrictedKeysProvider Javadoc --- .../jpa/query/schema/RestrictedKeysProvider.java | 10 ++++++++++ .../jpa/query/schema/impl/GraphQLJpaQueryFactory.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java index 5a5c21ede..40787c1c9 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java @@ -6,6 +6,16 @@ import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult; +/** + * The RestrictedKeysProvider functional interface should provide a list of restricted keys in order to filter records + * at runtime based on user security context based on EntityIntrospection descriptor. + * + * The return argument uses Optional> return type. + * The non-empty list will restrict the query to provided keys + * The empty Optional will block running the query and return empty result. + * The empty list will run the query unrestricted. + * + */ @FunctionalInterface public interface RestrictedKeysProvider extends Function>> { diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 76f1df8c3..427003e2c 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -2030,7 +2030,7 @@ public GraphQLJpaQueryFactory build() { } - public Function>> getRestrictedKeysProvider() { + public RestrictedKeysProvider getRestrictedKeysProvider() { return restrictedKeysProvider; } } From 9dab5ab8f3f268381771e5cd6947546eb8111400 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 22 Jun 2020 16:00:27 -0700 Subject: [PATCH 09/10] fix: update Javadoc --- .../jpa/query/schema/RestrictedKeysProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java index 40787c1c9..995d97e0c 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/RestrictedKeysProvider.java @@ -10,13 +10,22 @@ * The RestrictedKeysProvider functional interface should provide a list of restricted keys in order to filter records * at runtime based on user security context based on EntityIntrospection descriptor. * - * The return argument uses Optional> return type. + * The return argument uses Optional> return type: * The non-empty list will restrict the query to provided keys - * The empty Optional will block running the query and return empty result. * The empty list will run the query unrestricted. + * The empty Optional will block running the query and return empty result. * */ @FunctionalInterface public interface RestrictedKeysProvider extends Function>> { + /** + * Applies this restricted keys provider function to the given argument of entityDescriptor. + * + * @param entityDescriptor the function argument + * @return the function result with optional list of keys + */ + @Override + Optional> apply(EntityIntrospectionResult entityDescriptor); + } From c9cfac7d712611668c70de261163d602c8c72e06 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Mon, 22 Jun 2020 16:01:28 -0700 Subject: [PATCH 10/10] feat: support RestricedKeysProvider for singular queries --- .../schema/impl/GraphQLJpaQueryFactory.java | 25 +++++-- .../RestrictedKeysProviderSelectTests.java | 71 ++++++++++++++++++- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java index 427003e2c..4da39f914 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaQueryFactory.java @@ -270,14 +270,25 @@ protected Object querySingleResult(final DataFetchingEnvironment environment) { final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField); - - TypedQuery query = getQuery(queryEnvironment, queryEnvironment.getField(), true); - - if (logger.isDebugEnabled()) { - logger.info("\nGraphQL JPQL Single Result Query String:\n {}", getJPQLQueryString(query)); + + Optional> restrictedKeys = getRestrictedKeys(queryEnvironment); + + if (restrictedKeys.isPresent()) { + + TypedQuery query = getQuery(queryEnvironment, + queryEnvironment.getField(), + true, + restrictedKeys.get() + .toArray()); + + if (logger.isDebugEnabled()) { + logger.info("\nGraphQL JPQL Single Result Query String:\n {}", getJPQLQueryString(query)); + } + + return query.getSingleResult(); } - - return query.getSingleResult(); + + return null; } public Long queryTotalCount(DataFetchingEnvironment environment, diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java index 42ed0969a..991044b52 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/restricted/RestrictedKeysProviderSelectTests.java @@ -142,6 +142,75 @@ public void testRestrictAllThingQueryWithNullAuthentication() { //then assertThat(result.toString()).isEqualTo(expected); - } + } + + @Test + public void testRestrictThingQueryWithNullAuthentication() { + //given + String query = "query { Thing(id: \"2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1\") { id type } }"; + String expected = "{Thing=null}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithAnonymousUser + public void testRestrictThingQueryWithAnonymouseAuthentication() { + //given + String query = "query { Thing(id: \"2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1\") { id type } }"; + String expected = "{Thing=null}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb2") + public void testRestrictThingQueryWithoutPermission() { + //given + String query = "query { Thing(id: \"2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1\") { id type } }"; + String expected = "{Thing=null}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1") + public void testRestrictThingQueryWithPermission() { + //given + String query = "query { Thing(id: \"2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1\") { id type } }"; + String expected = "{Thing={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } + + @Test + @WithMockUser(value = "spring", authorities = "Thing:read:*") + public void testRestrictThingQueryWithWildcardPermission() { + //given + String query = "query { Thing(id: \"2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1\") { id type } }"; + String expected = "{Thing={id=2d1ebc5b-7d27-4197-9cf0-e84451c5bbb1, type=Thing1}}"; + + //when + Object result = executor.execute(query).getData(); + + //then + assertThat(result.toString()).isEqualTo(expected); + } }