Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use AggregationStorePermissionExecutor for Aggregation Store Model #2102

Merged
merged 2 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is called a bunch of times. Since we only have user check on fields, I guess it would make sense to do what checkSpecificFieldPermissions in here.

}

/**
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