Skip to content

Commit

Permalink
use AggregationStorePermissionExecutor for Aggregation Store Model (#…
Browse files Browse the repository at this point in the history
…2102)

Co-authored-by: Chandrasekar Rajasekar <[email protected]>
Co-authored-by: Aaron Klish <[email protected]>
  • Loading branch information
3 people authored May 18, 2021
1 parent 32856f2 commit 06fbda9
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public RequestScope(String baseUrlEndPoint,

Function<RequestScope, PermissionExecutor> permissionExecutorGenerator = elideSettings.getPermissionExecutor();
this.permissionExecutor = new MultiplexPermissionExecutor(
dictionary.getPermissionExecutors(this),
dictionary.buildPermissionExecutors(this),
(permissionExecutorGenerator == null)
? new ActivePermissionExecutor(this)
: permissionExecutorGenerator.apply(this),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public class EntityDictionary {

protected final ConcurrentHashMap<Pair<String, String>, Type<?>> bindJsonApiToEntity = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<Type<?>, EntityBinding> entityBindings = new ConcurrentHashMap<>();
@Getter
protected final ConcurrentHashMap<Type<?>, Function<RequestScope, PermissionExecutor>> entityPermissionExecutor =
new ConcurrentHashMap<>();
protected final CopyOnWriteArrayList<Type<?>> bindEntityRoots = new CopyOnWriteArrayList<>();
Expand Down Expand Up @@ -1088,7 +1089,7 @@ public void bindPermissionExecutor(Type<?> clz,
* @param scope - request scope to generate permission executor.
* @return Map of bound model type to its permission executor object.
*/
public Map<Type<?>, PermissionExecutor> getPermissionExecutors(RequestScope scope) {
public Map<Type<?>, PermissionExecutor> buildPermissionExecutors(RequestScope scope) {
return entityPermissionExecutor.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public <A extends Annotation> ExpressionResult checkSpecificFieldPermissionsDefe
ChangeSpec changeSpec,
Class<A> annotationClass,
String field) {
throw new UnsupportedOperationException();
return checkSpecificFieldPermissions(resource, changeSpec, annotationClass, field);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ private Expression buildAnyFieldOnlyExpression(final PermissionCondition conditi
OrExpression allFieldsExpression = new OrExpression(FAILURE, null);
List<String> fields = entityDictionary.getAllFields(resourceClass);

boolean fieldExpressionUsed = false;

for (String field : fields) {
if (requestedFields != null && !requestedFields.contains(field)) {
continue;
Expand All @@ -377,10 +379,15 @@ private Expression buildAnyFieldOnlyExpression(final PermissionCondition conditi
if (fieldExpression == null) {
return SUCCESSFUL_EXPRESSION;
}
fieldExpressionUsed = true;

allFieldsExpression = new OrExpression(allFieldsExpression, fieldExpression);
}

if (!fieldExpressionUsed) {
return SUCCESSFUL_EXPRESSION;
}

return new AnyFieldExpression(condition, allFieldsExpression);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
package com.yahoo.elide.datastores.aggregation;

import com.yahoo.elide.annotation.ReadPermission;
import com.yahoo.elide.core.RequestScope;
import com.yahoo.elide.core.datastore.DataStore;
import com.yahoo.elide.core.datastore.DataStoreTransaction;
import com.yahoo.elide.core.dictionary.ArgumentType;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.security.PermissionExecutor;
import com.yahoo.elide.core.security.checks.Check;
import com.yahoo.elide.core.security.checks.FilterExpressionCheck;
import com.yahoo.elide.core.security.checks.UserCheck;
import com.yahoo.elide.core.security.executors.ActivePermissionExecutor;
import com.yahoo.elide.core.security.executors.AggregationStorePermissionExecutor;
import com.yahoo.elide.core.type.ClassType;
import com.yahoo.elide.core.type.Type;
import com.yahoo.elide.core.utils.ClassScanner;
Expand Down Expand Up @@ -41,6 +43,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

/**
Expand All @@ -54,6 +57,9 @@ public class AggregationDataStore implements DataStore {
private final Set<Type<?>> dynamicCompiledClasses;
private final QueryLogger queryLogger;

private final Function<RequestScope, PermissionExecutor> aggPermissionExecutor =
AggregationStorePermissionExecutor::new;

/**
* These are the classes the Aggregation Store manages.
*/
Expand All @@ -71,14 +77,14 @@ public void populateEntityDictionary(EntityDictionary dictionary) {
dynamicCompiledClasses.forEach(dynamicLoadedClass -> {
dictionary.bindEntity(dynamicLoadedClass, Collections.singleton(Join.class));
validateModelExpressionChecks(dictionary, dynamicLoadedClass);
dictionary.bindPermissionExecutor(dynamicLoadedClass, ActivePermissionExecutor::new);
dictionary.bindPermissionExecutor(dynamicLoadedClass, aggPermissionExecutor);
});
}

ClassScanner.getAnnotatedClasses(AGGREGATION_STORE_CLASSES).forEach(cls -> {
dictionary.bindEntity(cls, Collections.singleton(Join.class));
validateModelExpressionChecks(dictionary, ClassType.of(cls));
dictionary.bindPermissionExecutor(cls, ActivePermissionExecutor::new);
dictionary.bindPermissionExecutor(cls, aggPermissionExecutor);
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2021, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.datastores.aggregation.checks;

import com.yahoo.elide.annotation.SecurityCheck;
import com.yahoo.elide.core.Path;
import com.yahoo.elide.core.filter.expression.FilterExpression;
import com.yahoo.elide.core.filter.predicates.PostfixPredicate;
import com.yahoo.elide.core.security.RequestScope;
import com.yahoo.elide.core.security.checks.FilterExpressionCheck;
import com.yahoo.elide.core.type.Type;
import com.yahoo.elide.datastores.aggregation.example.VideoGame;

/**
* Filter Expression Check for video game
*/
@SecurityCheck(VideoGameFilterCheck.NAME_FILTER)
public class VideoGameFilterCheck extends FilterExpressionCheck<VideoGame> {
public static final String NAME_FILTER = "player name filter";
@Override
public FilterExpression getFilterExpression(Type<?> entityClass, RequestScope requestScope) {
Path path = super.getFieldPath(entityClass, requestScope, "getPlayerName", "playerName");
return new PostfixPredicate(path, "Doe");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.yahoo.elide.datastores.aggregation.example;

import com.yahoo.elide.annotation.Include;
import com.yahoo.elide.annotation.ReadPermission;
import com.yahoo.elide.datastores.aggregation.annotation.DimensionFormula;
import com.yahoo.elide.datastores.aggregation.annotation.Join;
import com.yahoo.elide.datastores.aggregation.annotation.JoinType;
Expand All @@ -21,6 +22,7 @@
*/
@Include
@FromTable(name = "videoGames", dbConnectionName = "mycon")
@ReadPermission(expression = "admin.user or player name filter")
public class VideoGame {
@Setter
private Long id;
Expand Down Expand Up @@ -82,6 +84,7 @@ public Float getTimeSpentPerSession() {
return timeSpentPerSession;
}

@ReadPermission(expression = "operator")
@MetricFormula("{{timeSpentPerSession}} / 100")
public Float getTimeSpentPerGame() {
return timeSpentPerGame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.yahoo.elide.core.security.checks.prefab.Role;
import com.yahoo.elide.datastores.aggregation.AggregationDataStore;
import com.yahoo.elide.datastores.aggregation.checks.OperatorCheck;
import com.yahoo.elide.datastores.aggregation.checks.VideoGameFilterCheck;
import com.yahoo.elide.datastores.aggregation.example.PlayerStats;
import com.yahoo.elide.datastores.aggregation.framework.AggregationDataStoreTestHarness;
import com.yahoo.elide.datastores.aggregation.framework.SQLUnitTest;
Expand Down Expand Up @@ -106,6 +107,7 @@ public SecurityHjsonIntegrationTestResourceConfig() {
protected void configure() {
Map<String, Class<? extends Check>> map = new HashMap<>(TestCheckMappings.MAPPINGS);
map.put(OperatorCheck.OPERTOR_CHECK, OperatorCheck.class);
map.put(VideoGameFilterCheck.NAME_FILTER, VideoGameFilterCheck.class);
EntityDictionary dictionary = new EntityDictionary(map);

VALIDATOR.getElideSecurityConfig().getRoles().forEach(role ->
Expand Down Expand Up @@ -293,16 +295,47 @@ public void metricFormulaTest() throws Exception {
field("playerName", "Jon Doe")
),
selections(
field("timeSpent", 200),
field("sessions", 10),
field("timeSpentPerSession", 20.0),
field("timeSpent", 350),
field("sessions", 25),
field("timeSpentPerSession", 14.0),
field("playerName", "Jane Doe")
),
selections(
field("timeSpent", 300),
field("sessions", 10),
field("timeSpentPerSession", 30.0),
field("playerName", "Han")
)
)
)
).toResponse();

runQueryWithExpectedResult(graphQLRequest, expected);

//When admin = false

when(securityContextMock.isUserInRole("admin.user")).thenReturn(false);

expected = document(
selections(
field(
"videoGame",
selections(
field("timeSpent", 720),
field("sessions", 60),
field("timeSpentPerSession", 12.0),
field("playerName", "Jon Doe")
),
selections(
field("timeSpent", 350),
field("sessions", 25),
field("timeSpentPerSession", 14.0),
field("playerName", "Jane Doe")
)
)
)
).toResponse();
runQueryWithExpectedResult(graphQLRequest, expected);
}

/**
Expand Down Expand Up @@ -1635,4 +1668,67 @@ public void testUpsertWithDynamicModel() throws IOException {

runQueryWithExpectedError(graphQLRequest, expected);
}


//Security
@Test
public void testPermissionFilters() throws IOException {
when(securityContextMock.isUserInRole("admin.user")).thenReturn(false);

String graphQLRequest = document(
selection(
field(
"videoGame",
arguments(
argument("sort", "\"timeSpentPerSession\"")
),
selections(
field("timeSpent"),
field("sessions"),
field("timeSpentPerSession")
)
)
)
).toQuery();

//Records for Jon Doe and Jane Doe will only be aggregated.
String expected = document(
selections(
field(
"videoGame",
selections(
field("timeSpent", 1070),
field("sessions", 85),
field("timeSpentPerSession", 12.588235)
)
)
)
).toResponse();

runQueryWithExpectedResult(graphQLRequest, expected);

}

@Test
public void testFieldPermissions() throws IOException {
when(securityContextMock.isUserInRole("operator")).thenReturn(false);
String graphQLRequest = document(
selection(
field(
"videoGame",
selections(
field("timeSpent"),
field("sessions"),
field("timeSpentPerSession"),
field("timeSpentPerGame")
)
)
)
).toQuery();

String expected = "Exception while fetching data (/videoGame/edges[0]/node/timeSpentPerGame) : ReadPermission Denied";

runQueryWithExpectedError(graphQLRequest, expected);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.yahoo.elide.core.security.checks.Check;
import com.yahoo.elide.core.security.checks.prefab.Role;
import com.yahoo.elide.datastores.aggregation.checks.OperatorCheck;
import com.yahoo.elide.datastores.aggregation.checks.VideoGameFilterCheck;
import com.yahoo.elide.datastores.aggregation.framework.AggregationDataStoreTestHarness;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.ConnectionDetails;
import com.yahoo.elide.datastores.aggregation.queryengines.sql.dialects.SQLDialect;
Expand Down Expand Up @@ -70,6 +71,7 @@ public SecurityHjsonIntegrationTestResourceConfig() {
protected void configure() {
Map<String, Class<? extends Check>> map = new HashMap<>(TestCheckMappings.MAPPINGS);
map.put(OperatorCheck.OPERTOR_CHECK, OperatorCheck.class);
map.put(VideoGameFilterCheck.NAME_FILTER, VideoGameFilterCheck.class);
EntityDictionary dictionary = new EntityDictionary(map);

VALIDATOR.getElideSecurityConfig().getRoles().forEach(role ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ INSERT INTO videoGames VALUES (10, 50, 1);
INSERT INTO videoGames VALUES (20, 150, 1);
INSERT INTO videoGames VALUES (30, 520, 1);
INSERT INTO videoGames VALUES (10, 200, 2);
INSERT INTO videoGames VALUES (15, 150, 2);
INSERT INTO videoGames VALUES (10, 300, 3);


CREATE TABLE IF NOT EXISTS continents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
*/
package com.yahoo.elide.datastores.multiplex;

import com.yahoo.elide.core.RequestScope;
import com.yahoo.elide.core.datastore.DataStore;
import com.yahoo.elide.core.datastore.DataStoreTransaction;
import com.yahoo.elide.core.dictionary.EntityBinding;
import com.yahoo.elide.core.dictionary.EntityDictionary;
import com.yahoo.elide.core.security.PermissionExecutor;
import com.yahoo.elide.core.type.Type;

import lombok.AccessLevel;
import lombok.Setter;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
* Allows multiple database handlers to each process their own beans while keeping the main
Expand Down Expand Up @@ -68,6 +72,12 @@ public void populateEntityDictionary(EntityDictionary dictionary) {
// bind to multiplex dictionary
dictionary.bindEntity(binding);
}

for (Map.Entry<Type<?>, Function<RequestScope, PermissionExecutor>> entry
: subordinateDictionary.getEntityPermissionExecutor().entrySet()) {
dictionary.bindPermissionExecutor(entry.getKey(), entry.getValue());

}
}
}

Expand Down

0 comments on commit 06fbda9

Please sign in to comment.