Fixes #7004 Backend : conditionAllow should not be applicable to create operation (#8483)

This commit is contained in:
Suresh Srinivas 2022-11-01 12:46:18 -07:00 committed by GitHub
parent 19a3f1f47b
commit 8a008022c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 236 additions and 197 deletions

View File

@ -124,6 +124,7 @@ public final class Entity {
//
public static final String ADMIN_USER_NAME = "admin";
public static final String ORGANIZATION_NAME = "Organization";
public static final String ORGANIZATION_POLICY_NAME = "OrganizationPolicy";
public static final String INGESTION_BOT_NAME = "ingestion-bot";
public static final String INGESTION_BOT_ROLE = "IngestionBotRole";
public static final String PROFILER_BOT_NAME = "profiler-bot";

View File

@ -12,7 +12,9 @@ public class ResourceRegistry {
private ResourceRegistry() {}
public static void add(List<ResourceDescriptor> resourceDescriptors) {
public static void initialize(List<ResourceDescriptor> resourceDescriptors) {
RESOURCE_DESCRIPTORS.clear();
;
RESOURCE_DESCRIPTORS.addAll(resourceDescriptors);
RESOURCE_DESCRIPTORS.sort(Comparator.comparing(ResourceDescriptor::getName));
}

View File

@ -142,10 +142,10 @@ public abstract class EntityRepository<T extends EntityInterface> {
protected final boolean supportsFollower;
/** Fields that can be updated during PATCH operation */
private final Fields patchFields;
@Getter private final Fields patchFields;
/** Fields that can be updated during PUT operation */
protected final Fields putFields;
@Getter protected final Fields putFields;
EntityRepository(
String collectionPath,

View File

@ -280,6 +280,16 @@ public final class CollectionRegistry {
} catch (Exception ex) {
LOG.warn("Encountered exception ", ex);
}
// Call upgrade method, if it exists
try {
Method upgradeMethod = resource.getClass().getMethod("upgrade");
upgradeMethod.invoke(resource);
} catch (NoSuchMethodException ignored) {
// Method does not exist and initialize is not called
} catch (Exception ex) {
LOG.warn("Encountered exception ", ex);
}
return resource;
}

View File

@ -20,6 +20,7 @@ import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.service.Entity;
import org.openmetadata.service.OpenMetadataApplicationConfig;
import org.openmetadata.service.jdbi3.EntityRepository;
import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.security.Authorizer;
@ -50,6 +51,19 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
this.authorizer = authorizer;
}
/** Method used for initializing a resource, such as creating default policies, roles, etc. */
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Nothing to do in the default implementation
}
/**
* Method used for upgrading a resource such as adding new fields to entities, etc. that can't be done in bootstrap
* migrate
*/
protected void upgrade() throws IOException {
// Nothing to do in the default implementation
}
public final Fields getFields(String fields) {
if (fields != null && fields.equals("*")) {
return new Fields(allowedFields, String.join(",", allowedFields));

View File

@ -91,7 +91,7 @@ public class WebAnalyticEventResource extends EntityResource<WebAnalyticEvent, W
}
}
@SuppressWarnings("unused") // Method used for reflection of webAnalyticEventTypes
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Find the existing webAnalyticEventTypes and add them from json files
List<WebAnalyticEvent> webAnalyticEvents =

View File

@ -85,7 +85,7 @@ public class BotResource extends EntityResource<Bot, BotRepository> {
super(Bot.class, new BotRepository(dao), authorizer);
}
@SuppressWarnings("unused") // Method is used by reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Load system bots
List<Bot> bots = dao.getEntitiesFromSeedData();

View File

@ -74,7 +74,7 @@ public class TestDefinitionResource extends EntityResource<TestDefinition, TestD
super(TestDefinition.class, new TestDefinitionRepository(dao), authorizer);
}
@SuppressWarnings("unused") // Method used for reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Find tag definitions and load tag categories from the json file, if necessary
List<TestDefinition> testDefinitions = dao.getEntitiesFromSeedData(".*json/data/tests/.*\\.json$");

View File

@ -89,7 +89,7 @@ public class WebhookResource extends EntityResource<Webhook, WebhookRepository>
webhookDAO = dao.webhookDAO();
}
@SuppressWarnings("unused") // Method used for reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) {
try {
List<String> listAllWebhooks = webhookDAO.listAllWebhooks(webhookDAO.getTableName());

View File

@ -55,6 +55,7 @@ import org.openmetadata.schema.type.EntityHistory;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Function;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.ResourceDescriptor;
import org.openmetadata.service.Entity;
import org.openmetadata.service.FunctionList;
@ -95,11 +96,26 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
super(Policy.class, new PolicyRepository(dao), authorizer);
}
@SuppressWarnings("unused") // Method is used by reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Load any existing rules from database, before loading seed data.
dao.initSeedDataFromResources();
ResourceRegistry.add(listOrEmpty(PolicyResource.getResourceDescriptors()));
ResourceRegistry.initialize(listOrEmpty(PolicyResource.getResourceDescriptors()));
}
@Override
public void upgrade() throws IOException {
// OrganizationPolicy rule change
Policy originalOrgPolicy = dao.getByName(null, Entity.ORGANIZATION_POLICY_NAME, dao.getPatchFields());
Policy updatedOrgPolicy = JsonUtils.readValue(JsonUtils.pojoToJson(originalOrgPolicy), Policy.class);
// Rules are in alphabetical order - change second rule "OrganizationPolicy-Owner-Rule"
// from ALL operation to remove CREATE operation and allow all the other operations for the owner
updatedOrgPolicy
.getRules()
.get(1)
.withOperations(List.of(MetadataOperation.EDIT_ALL, MetadataOperation.VIEW_ALL, MetadataOperation.DELETE));
dao.patch(null, originalOrgPolicy.getId(), "admin", JsonUtils.getJsonPatch(originalOrgPolicy, updatedOrgPolicy));
}
public static class PolicyList extends ResultList<Policy> {

View File

@ -99,6 +99,7 @@ public class IngestionPipelineResource extends EntityResource<IngestionPipeline,
super(IngestionPipeline.class, new IngestionPipelineRepository(dao), authorizer);
}
@Override
public void initialize(OpenMetadataApplicationConfig config) {
this.openMetadataApplicationConfig = config;
this.pipelineServiceClient = new AirflowRESTClient(openMetadataApplicationConfig.getAirflowConfiguration());

View File

@ -89,7 +89,7 @@ public class RoleResource extends EntityResource<Role, RoleRepository> {
super(Role.class, new RoleRepository(collectionDAO), authorizer);
}
@SuppressWarnings("unused") // Method used for reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
List<Role> roles = dao.getEntitiesFromSeedData();
for (Role role : roles) {

View File

@ -90,7 +90,7 @@ public class TeamResource extends EntityResource<Team, TeamRepository> {
super(Team.class, new TeamRepository(dao), authorizer);
}
@SuppressWarnings("unused") // Method used for reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
dao.initOrganization();
}

View File

@ -150,6 +150,7 @@ public class UserResource extends EntityResource<User, UserRepository> {
authHandler = authenticatorHandler;
}
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
this.authenticationConfiguration = config.getAuthenticationConfiguration();
SmtpSettings smtpSettings = config.getSmtpSettings();

View File

@ -91,7 +91,7 @@ public class TypeResource extends EntityResource<Type, TypeRepository> {
super(Type.class, new TypeRepository(dao), authorizer);
}
@SuppressWarnings("unused") // Method used for reflection
@Override
public void initialize(OpenMetadataApplicationConfig config) throws IOException {
// Load types defined in OpenMetadata schemas
long now = System.currentTimeMillis();

View File

@ -219,7 +219,7 @@ public class CompiledRule extends Rule {
return Boolean.TRUE.equals(expression.getValue(evaluationContext, Boolean.class));
}
static boolean overrideAccess(Access newAccess, Access currentAccess) {
public static boolean overrideAccess(Access newAccess, Access currentAccess) {
// Lower the ordinal number of access overrides higher ordinal number
return currentAccess.ordinal() > newAccess.ordinal();
}

View File

@ -39,8 +39,8 @@ public class OperationContext {
return operations;
}
public boolean isCreateOperation() {
return operations.contains(MetadataOperation.CREATE);
public static List<MetadataOperation> getAllOperations(MetadataOperation... exclude) {
return getOperations("", exclude);
}
public static boolean isEditOperation(MetadataOperation operation) {
@ -50,4 +50,22 @@ public class OperationContext {
public static boolean isViewOperation(MetadataOperation operation) {
return operation.value().startsWith("View");
}
public static List<MetadataOperation> getViewOperations(MetadataOperation... exclude) {
return getOperations("View", exclude);
}
private static List<MetadataOperation> getOperations(String startsWith, MetadataOperation... exclude) {
List<MetadataOperation> list = new ArrayList<>();
List<MetadataOperation> excludeList = new ArrayList<>(List.of(exclude));
if (!excludeList.isEmpty()) {
excludeList.add(MetadataOperation.ALL); // If any operation is excluded then 'All' operation is excluded
}
for (MetadataOperation operation : MetadataOperation.values()) {
if (!excludeList.contains(operation) && operation.value().startsWith(startsWith)) {
list.add(operation);
}
}
return list;
}
}

View File

@ -274,6 +274,12 @@ public final class JsonUtils {
return Json.createDiff(source.asJsonObject(), dest.asJsonObject());
}
public static JsonPatch getJsonPatch(Object v1, Object v2) throws JsonProcessingException {
JsonValue source = readJson(JsonUtils.pojoToJson(v1));
JsonValue dest = readJson(JsonUtils.pojoToJson(v2));
return Json.createDiff(source.asJsonObject(), dest.asJsonObject());
}
public static JsonValue readJson(String s) {
try (JsonReader reader = Json.createReader(new StringReader(s))) {
return reader.readValue();

View File

@ -367,18 +367,6 @@
"EditCustomFields"
]
},
{
"name" : "type",
"operations" : [
"Create",
"Delete",
"ViewAll",
"EditAll",
"EditDescription",
"EditDisplayName",
"EditCustomFields"
]
},
{
"name" : "user",
"operations" : [
@ -441,6 +429,17 @@
"ViewAll"
]
},
{
"name" : "type",
"operations" : [
"Create",
"Delete",
"ViewAll",
"EditAll",
"EditDescription",
"EditDisplayName"
]
},
{
"name" : "webAnalyticEvent",
"operations" : [
@ -451,27 +450,5 @@
"EditDescription",
"EditDisplayName"
]
},
{
"name" : "type",
"operations" : [
"Create",
"Delete",
"ViewAll",
"EditAll",
"EditDescription",
"EditDisplayName"
]
},
{
"name" : "type",
"operations" : [
"Create",
"Delete",
"ViewAll",
"EditAll",
"EditDescription",
"EditDisplayName"
]
}
]

View File

@ -10,7 +10,7 @@
"name": "OrganizationPolicy-Owner-Rule",
"description" : "Allow all the operations on an entity for the owner.",
"resources" : ["all"],
"operations": ["All"],
"operations": ["EditAll", "ViewAll", "Delete"],
"effect": "allow",
"condition": "isOwner()"
},

View File

@ -15,34 +15,29 @@ package org.openmetadata.service.resources.permissions;
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openmetadata.schema.type.MetadataOperation.ALL;
import static org.openmetadata.schema.type.MetadataOperation.CREATE;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_DESCRIPTION;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_DISPLAY_NAME;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_LINEAGE;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_OWNER;
import static org.openmetadata.schema.type.MetadataOperation.EDIT_TAGS;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_ALL;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_BASIC;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_DATA_PROFILE;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_QUERIES;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_SAMPLE_DATA;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_TESTS;
import static org.openmetadata.schema.type.MetadataOperation.VIEW_USAGE;
import static org.openmetadata.schema.type.Permission.Access.ALLOW;
import static org.openmetadata.schema.type.Permission.Access.CONDITIONAL_ALLOW;
import static org.openmetadata.schema.type.Permission.Access.NOT_ALLOW;
import static org.openmetadata.service.security.policyevaluator.OperationContext.getAllOperations;
import static org.openmetadata.service.security.policyevaluator.OperationContext.getViewOperations;
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.service.util.TestUtils.assertResponse;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.client.WebTarget;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.BeforeAll;
@ -69,6 +64,7 @@ import org.openmetadata.service.resources.permissions.PermissionsResource.Resour
import org.openmetadata.service.resources.policies.PolicyResource;
import org.openmetadata.service.resources.policies.PolicyResourceTest;
import org.openmetadata.service.security.SecurityUtil;
import org.openmetadata.service.security.policyevaluator.CompiledRule;
import org.openmetadata.service.security.policyevaluator.PolicyEvaluator;
import org.openmetadata.service.util.TestUtils;
@ -76,7 +72,10 @@ import org.openmetadata.service.util.TestUtils;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PermissionsResourceTest extends OpenMetadataApplicationTest {
private static final String ORG_POLICY_NAME = "OrganizationPolicy";
private static List<Rule> ORG_RULES;
private static Rule ORG_IS_OWNER_RULE;
private static Rule ORG_NO_OWNER_RULE;
private static final List<MetadataOperation> ORG_IS_OWNER_RULE_OPERATIONS = getAllOperations(CREATE);
private static final List<MetadataOperation> ORG_NO_OWNER_RULE_OPERATIONS = List.of(EDIT_OWNER);
private static final String DATA_STEWARD_ROLE_NAME = "DataSteward";
private static Policy DATA_STEWARD_POLICY;
@ -87,23 +86,16 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
private static Policy DATA_CONSUMER_POLICY;
private static final String DATA_CONSUMER_POLICY_NAME = "DataConsumerPolicy";
private static List<Rule> DATA_CONSUMER_RULES;
private static List<MetadataOperation> DATA_CONSUMER_ALLOWED =
List.of(
VIEW_ALL,
VIEW_BASIC,
VIEW_USAGE,
VIEW_DATA_PROFILE,
VIEW_SAMPLE_DATA,
VIEW_BASIC,
VIEW_TESTS,
VIEW_QUERIES,
EDIT_DESCRIPTION,
EDIT_TAGS);
private static List<MetadataOperation> DATA_STEWARD_ALLOWED = new ArrayList<>(DATA_CONSUMER_ALLOWED);
private static final List<MetadataOperation> DATA_CONSUMER_ALLOWED = getViewOperations();
static {
// Additional permissions over DATA_CONSUMER
DATA_CONSUMER_ALLOWED.addAll(List.of(EDIT_DESCRIPTION, EDIT_TAGS));
}
private static final List<MetadataOperation> DATA_STEWARD_ALLOWED = new ArrayList<>(DATA_CONSUMER_ALLOWED);
static {
// DATA_CONSUMER + additional operations
DATA_STEWARD_ALLOWED.addAll(List.of(EDIT_OWNER, EDIT_DISPLAY_NAME, EDIT_LINEAGE));
}
@ -119,7 +111,10 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
Policy orgPolicy =
policyResourceTest.getEntityByName(ORG_POLICY_NAME, null, PolicyResource.FIELDS, ADMIN_AUTH_HEADERS);
ORG_RULES = orgPolicy.getRules();
List<Rule> orgRules = orgPolicy.getRules();
// Rules are alphabetically ordered
ORG_NO_OWNER_RULE = orgRules.get(0);
ORG_IS_OWNER_RULE = orgRules.get(1);
DATA_STEWARD_POLICY =
policyResourceTest.getEntityByName(DATA_STEWARD_POLICY_NAME, null, PolicyResource.FIELDS, ADMIN_AUTH_HEADERS);
@ -154,64 +149,79 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
@Test
void get_dataConsumer_permissions() throws HttpResponseException {
// Ensure data consumer has permissions based on his role and the inherited roles
List<MetadataOperation> conditional = List.of(ALL); // All operations are conditionally allowed
//
// Validate permissions for all resources as logged-in user
//
Map<String, String> authHeaders = SecurityUtil.authHeaders(DATA_CONSUMER_USER_NAME + "@open-metadata.org");
List<ResourcePermission> actualPermissions = getPermissions(authHeaders);
assertDataConsumerPermissions(actualPermissions, conditional);
ResourcePermissionsBuilder permissionsBuilder = new ResourcePermissionsBuilder();
permissionsBuilder.setPermission(
DATA_CONSUMER_ALLOWED, ALLOW, DATA_CONSUMER_ROLE_NAME, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES.get(0));
// Set permissions based on alphabetical order of roles
permissionsBuilder.setPermission(
ORG_NO_OWNER_RULE_OPERATIONS, CONDITIONAL_ALLOW, null, ORG_POLICY_NAME, ORG_NO_OWNER_RULE);
permissionsBuilder.setPermission(
ORG_IS_OWNER_RULE_OPERATIONS, CONDITIONAL_ALLOW, null, ORG_POLICY_NAME, ORG_IS_OWNER_RULE);
assertResourcePermissions(permissionsBuilder.getResourcePermissions(), actualPermissions);
// Validate permissions for all resources for data consumer as admin
actualPermissions = getPermissions(DATA_CONSUMER_USER_NAME, ADMIN_AUTH_HEADERS);
assertDataConsumerPermissions(actualPermissions, conditional);
assertResourcePermissions(permissionsBuilder.getResourcePermissions(), actualPermissions);
// Validate permission as logged-in user for each resource one at a time
ResourcePermission actualPermission;
for (ResourceDescriptor rd : ResourceRegistry.listResourceDescriptors()) {
ResourcePermission actualPermission = getPermission(rd.getName(), null, authHeaders);
assertDataConsumerPermission(actualPermission, conditional);
actualPermission = getPermission(rd.getName(), null, authHeaders);
assertResourcePermission(permissionsBuilder.getPermission(rd.getName()), actualPermission);
}
// Validate permission of data consumer as admin user for each resource one at a time
for (ResourceDescriptor rd : ResourceRegistry.listResourceDescriptors()) {
ResourcePermission actualPermission = getPermission(rd.getName(), DATA_CONSUMER_USER_NAME, authHeaders);
assertDataConsumerPermission(actualPermission, conditional);
actualPermission = getPermission(rd.getName(), DATA_CONSUMER_USER_NAME, authHeaders);
assertResourcePermission(permissionsBuilder.getPermission(rd.getName()), actualPermission);
}
//
// Test getting permissions for an entity as an owner
//
// Create an entity with data consumer as owner
TableResourceTest tableResourceTest = new TableResourceTest();
CreateTable createTable =
tableResourceTest.createRequest("permissionTest").withOwner(DATA_CONSUMER_USER.getEntityReference());
Table table1 = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS);
// Data consumer must have all operations allowed based on Organization policy as an owner
ResourcePermission actualPermission = getPermission(Entity.TABLE, table1.getId(), null, authHeaders);
assertAllOperationsAllowed(actualPermission);
// get permissions by resource entity name
actualPermission = getPermissionByName(Entity.TABLE, table1.getFullyQualifiedName(), null, authHeaders);
assertAllOperationsAllowed(actualPermission);
// Admin getting permissions for a specific resource on for Data consumer
actualPermission = getPermission(Entity.TABLE, table1.getId(), DATA_CONSUMER_USER_NAME, ADMIN_AUTH_HEADERS);
assertAllOperationsAllowed(actualPermission);
// Create another table with a different owner and make sure data consumer does not have permission as non owner
createTable = tableResourceTest.createRequest("permissionTest1").withOwner(DATA_STEWARD_USER.getEntityReference());
Table table2 = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS);
//
// Test getting permissions for an entity user does not own
//
permissionsBuilder = new ResourcePermissionsBuilder();
permissionsBuilder.setPermission(
DATA_CONSUMER_ALLOWED, ALLOW, DATA_CONSUMER_ROLE_NAME, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES.get(0));
// Organization policy of no owner or isOwner does not apply. Hence, not added to expected permissions
// Create a table with a different owner and make sure data consumer does not have permission as non owner
TableResourceTest tableResourceTest = new TableResourceTest();
CreateTable createTable =
tableResourceTest.createRequest("permissionTest1").withOwner(DATA_STEWARD_USER.getEntityReference());
Table table2 = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS);
// Data consumer has non-owner permissions
actualPermission = getPermission(Entity.TABLE, table2.getId(), null, authHeaders);
// Note that conditional list is empty. All the required context to resolve is met when requesting permission of
// a specific resource (both subject and resource context). Only Deny, Allow, NotAllow permissions are expected.
assertDataConsumerPermission(actualPermission, Collections.emptyList());
assertResourcePermission(permissionsBuilder.getPermission(Entity.TABLE), actualPermission);
//
// Test getting permissions for an entity as an owner - where ORG_POLICY isOwner becomes effective
//
// Create an entity with data consumer as owner
createTable = tableResourceTest.createRequest("permissionTest").withOwner(DATA_CONSUMER_USER.getEntityReference());
Table table1 = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS);
// Data consumer must have all operations except create allowed based on Organization policy as an owner
permissionsBuilder.setPermission(ORG_IS_OWNER_RULE_OPERATIONS, ALLOW, null, ORG_POLICY_NAME, ORG_IS_OWNER_RULE);
actualPermission = getPermission(Entity.TABLE, table1.getId(), null, authHeaders);
assertResourcePermission(permissionsBuilder.getPermission(Entity.TABLE), actualPermission);
// get permissions by resource entity name
actualPermission = getPermissionByName(Entity.TABLE, table1.getFullyQualifiedName(), null, authHeaders);
assertResourcePermission(permissionsBuilder.getPermission(Entity.TABLE), actualPermission);
// Admin getting permissions for a specific resource on for Data consumer
actualPermission = getPermission(Entity.TABLE, table1.getId(), DATA_CONSUMER_USER_NAME, ADMIN_AUTH_HEADERS);
assertResourcePermission(permissionsBuilder.getPermission(Entity.TABLE), actualPermission);
}
@Test
@ -219,9 +229,16 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
Map<String, String> authHeaders = SecurityUtil.authHeaders(DATA_STEWARD_USER_NAME + "@open-metadata.org");
List<ResourcePermission> actualPermissions = getPermissions(authHeaders);
for (ResourcePermission actualPermission : actualPermissions) { // For all resources
assertDataStewardPermission(actualPermission);
}
ResourcePermissionsBuilder permissionsBuilder = new ResourcePermissionsBuilder();
permissionsBuilder.setPermission(
DATA_STEWARD_ALLOWED, ALLOW, DATA_STEWARD_ROLE_NAME, DATA_STEWARD_POLICY_NAME, DATA_STEWARD_RULES.get(0));
// Set permissions based on alphabetical order of roles
permissionsBuilder.setPermission(
ORG_NO_OWNER_RULE_OPERATIONS, CONDITIONAL_ALLOW, null, ORG_POLICY_NAME, ORG_NO_OWNER_RULE);
permissionsBuilder.setPermission(
ORG_IS_OWNER_RULE_OPERATIONS, CONDITIONAL_ALLOW, null, ORG_POLICY_NAME, ORG_IS_OWNER_RULE);
assertResourcePermissions(permissionsBuilder.getResourcePermissions(), actualPermissions);
}
@Test
@ -229,97 +246,40 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
// Get permissions for DATA_CONSUMER policy and assert it is correct
List<UUID> policies = new ArrayList<>(List.of(DATA_CONSUMER_POLICY.getId()));
List<ResourcePermission> actual = getPermissionsForPolicies(policies, ADMIN_AUTH_HEADERS);
for (ResourcePermission actualPermission : actual) { // For every resource
for (Permission permission : actualPermission.getPermissions()) {
if (DATA_CONSUMER_ALLOWED.contains(permission.getOperation())) {
assertPermissionAllowed(permission, null, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES);
} else {
assertPermissionNotAllowed(permission);
}
}
}
ResourcePermissionsBuilder permissionsBuilder = new ResourcePermissionsBuilder();
permissionsBuilder.setPermission(
DATA_CONSUMER_ALLOWED, ALLOW, null, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES.get(0));
assertResourcePermissions(permissionsBuilder.getResourcePermissions(), actual);
// Get permissions for DATA_CONSUMER and DATA_STEWARD policies and assert it is correct
policies.add(DATA_STEWARD_POLICY.getId());
actual = getPermissionsForPolicies(policies, ADMIN_AUTH_HEADERS);
for (ResourcePermission actualPermission : actual) { // For every resource
for (Permission permission : actualPermission.getPermissions()) {
if (DATA_CONSUMER_ALLOWED.contains(permission.getOperation())) {
assertPermissionAllowed(permission, null, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES);
} else if (DATA_STEWARD_ALLOWED.contains(permission.getOperation())) {
assertPermissionAllowed(permission, null, DATA_STEWARD_POLICY_NAME, DATA_STEWARD_RULES);
} else {
assertPermissionNotAllowed(permission);
}
permissionsBuilder.setPermission(
DATA_STEWARD_ALLOWED, ALLOW, null, DATA_STEWARD_POLICY_NAME, DATA_STEWARD_RULES.get(0));
assertResourcePermissions(permissionsBuilder.getResourcePermissions(), actual);
}
private void assertResourcePermissions(List<ResourcePermission> expected, List<ResourcePermission> actual) {
assertEquals(expected.size(), actual.size());
Comparator<ResourcePermission> resourcePermissionComparator = Comparator.comparing(ResourcePermission::getResource);
expected.sort(resourcePermissionComparator);
actual.sort(resourcePermissionComparator);
for (int i = 0; i < expected.size(); i++) {
assertResourcePermission(expected.get(i), actual.get(i));
}
}
private void assertDataStewardPermission(ResourcePermission actualPermission) {
// Only allowed operations in DataConsumerRole. All other operations are conditionalAllow by default
for (Permission permission : actualPermission.getPermissions()) {
if (DATA_STEWARD_ALLOWED.contains(permission.getOperation())) {
assertPermissionAllowed(permission, DATA_STEWARD_ROLE_NAME, DATA_STEWARD_POLICY_NAME, DATA_STEWARD_RULES);
} else {
assertPermissionConditional(permission, null, ORG_POLICY_NAME, ORG_RULES);
private void assertResourcePermission(ResourcePermission expected, ResourcePermission actual) {
assertEquals(expected.getPermissions().size(), actual.getPermissions().size());
Comparator<Permission> operationComparator = Comparator.comparing(Permission::getOperation);
expected.getPermissions().sort(operationComparator);
actual.getPermissions().sort(operationComparator);
for (int i = 0; i < expected.getPermissions().size(); i++) {
// Using for loop to compare instead of equals to help with debugging the difference between the lists
assertEquals(expected.getPermissions().get(i), actual.getPermissions().get(i));
}
}
}
private void assertAllOperationsAllowed(ResourcePermission actualPermission) {
assertEquals(Entity.TABLE, actualPermission.getResource());
for (Permission permission : actualPermission.getPermissions()) {
assertEquals(ALLOW, permission.getAccess());
assertTrue(List.of(ORG_POLICY_NAME, DATA_CONSUMER_POLICY_NAME).contains(permission.getPolicy()));
}
}
private void assertDataConsumerPermissions(
List<ResourcePermission> actualPermissions, List<MetadataOperation> conditional) {
// Only allowed operations in DataConsumerRole. All other operations are notAllow by default
for (ResourcePermission actualPermission : actualPermissions) { // For every resource
assertDataConsumerPermission(actualPermission, conditional); // assert permission
}
}
private void assertDataConsumerPermission(ResourcePermission actualPermission, List<MetadataOperation> conditional) {
// Only allowed operations in DataConsumerRole. All other operations are conditional allow or not allow
for (Permission permission : actualPermission.getPermissions()) {
if (DATA_CONSUMER_ALLOWED.contains(permission.getOperation())) {
assertPermissionAllowed(permission, DATA_CONSUMER_ROLE_NAME, DATA_CONSUMER_POLICY_NAME, DATA_CONSUMER_RULES);
} else if (conditional.contains(permission.getOperation()) || conditional.contains(ALL)) {
assertPermissionConditional(permission, null, ORG_POLICY_NAME, ORG_RULES);
} else {
assertPermissionNotAllowed(permission);
}
}
}
private void assertPermissionAllowed(
Permission permission, String expectedRole, String expectedPolicy, List<Rule> expectedRules) {
assertPermission(permission, ALLOW, expectedRole, expectedPolicy, expectedRules);
}
private void assertPermissionConditional(
Permission permission, String expectedRole, String expectedPolicy, List<Rule> expectedRules) {
assertPermission(permission, CONDITIONAL_ALLOW, expectedRole, expectedPolicy, expectedRules);
}
private void assertPermissionNotAllowed(Permission permission) {
assertPermission(permission, NOT_ALLOW, null, null, null);
}
private void assertPermission(
Permission permission,
Access expectedAccess,
String expectedRole,
String expectedPolicy,
List<Rule> expectedRules) {
assertEquals(expectedAccess, permission.getAccess(), permission.toString());
assertEquals(expectedRole, permission.getRole(), permission.toString());
assertEquals(expectedPolicy, permission.getPolicy(), permission.toString());
assertTrue(expectedRules == null || expectedRules.contains(permission.getRule()));
}
public List<ResourcePermission> getPermissions(Map<String, String> authHeaders) throws HttpResponseException {
WebTarget target = getResource("permissions");
@ -362,4 +322,37 @@ class PermissionsResourceTest extends OpenMetadataApplicationTest {
}
return TestUtils.get(target, ResourcePermissionList.class, authHeaders).getData();
}
/** Build permissions based on role, policies, etc. for testing purposes */
public static class ResourcePermissionsBuilder {
@Getter
private final List<ResourcePermission> resourcePermissions = PolicyEvaluator.getResourcePermissions(NOT_ALLOW);
public void setPermission(
List<MetadataOperation> operations, Access access, String role, String policy, Rule rule) {
resourcePermissions.forEach(rp -> setPermission(rp, operations, access, role, policy, rule));
}
public ResourcePermission getPermission(String resource) {
return resourcePermissions.stream()
.filter(resourcePermission -> resourcePermission.getResource().equals(resource))
.findAny()
.orElse(null);
}
private void setPermission(
ResourcePermission resourcePermission,
List<MetadataOperation> operations,
Access access,
String role,
String policy,
Rule rule) {
for (Permission permission : resourcePermission.getPermissions()) {
if (operations.contains(permission.getOperation())
&& CompiledRule.overrideAccess(access, permission.getAccess())) {
permission.withAccess(access).withRole(role).withPolicy(policy).withRule(rule);
}
}
}
}
}