Fixes #5558 Add support for enumerating Operations and Resources for authoring policies (#5561)

This commit is contained in:
Suresh Srinivas 2022-07-04 10:07:47 -07:00 committed by GitHub
parent 7e14dfa0d0
commit cf2b336abd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 223 additions and 181 deletions

View File

@ -51,6 +51,9 @@ public final class Entity {
// Canonical entity name to corresponding EntityRepository map
private static final Map<String, EntityRepository<?>> ENTITY_REPOSITORY_MAP = new HashMap<>();
// List of entities
private static final List<String> ENTITY_LIST = new ArrayList<>();
// Common field names
public static final String FIELD_OWNER = "owner";
public static final String FIELD_NAME = "name";
@ -138,6 +141,9 @@ public final class Entity {
DAO_MAP.put(entity, dao);
ENTITY_REPOSITORY_MAP.put(entity, entityRepository);
CANONICAL_ENTITY_NAME_MAP.put(entity.toLowerCase(Locale.ROOT), entity);
ENTITY_LIST.add(entity);
Collections.sort(ENTITY_LIST);
LOG.info(
"Registering entity {} {} {} {}",
clazz,
@ -146,6 +152,10 @@ public final class Entity {
entityRepository.getClass().getSimpleName());
}
public static List<String> listEntities() {
return Collections.unmodifiableList(ENTITY_LIST);
}
public static EntityReference getEntityReference(EntityReference ref) throws IOException {
return ref == null ? null : getEntityReferenceById(ref.getType(), ref.getId(), Include.NON_DELETED);
}

View File

@ -31,7 +31,6 @@ import org.openmetadata.catalog.entity.data.Location;
import org.openmetadata.catalog.entity.policies.Policy;
import org.openmetadata.catalog.entity.policies.accessControl.Rule;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.jdbi3.EntityRepository.EntityUpdater;
import org.openmetadata.catalog.resources.policies.PolicyResource;
import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.MetadataOperation;
@ -142,10 +141,8 @@ public class PolicyRepository extends EntityRepository<Policy> {
LOG.debug("Validating rules for {} policy: {}", PolicyType.AccessControl, policy.getName());
Set<MetadataOperation> operations = new HashSet<>();
for (Object ruleObject : policy.getRules()) {
// Cast to access control policy Rule.
Rule rule = JsonUtils.readValue(JsonUtils.getJsonStructure(ruleObject).toString(), Rule.class);
List<Rule> rules = EntityUtil.resolveRules(policy.getRules());
for (Rule rule : rules) {
if (rule.getOperation() == null) {
throw new IllegalArgumentException(
CatalogExceptionMessage.invalidPolicyOperationNull(rule.getName(), policy.getName()));
@ -192,7 +189,6 @@ public class PolicyRepository extends EntityRepository<Policy> {
if (original.getPolicyType() != updated.getPolicyType()) {
throw new IllegalArgumentException(CatalogExceptionMessage.readOnlyAttribute(POLICY, "policyType"));
}
recordChange("policyUrl", original.getPolicyUrl(), updated.getPolicyUrl());
recordChange(ENABLED, original.getEnabled(), updated.getEnabled());
recordChange("rules", original.getRules(), updated.getRules());
updateLocation(original, updated);

View File

@ -165,7 +165,7 @@ public class LineageResource {
public Response addLineage(
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid AddLineage addLineage)
throws IOException {
SecurityUtil.checkAdminRoleOrPermissions(authorizer, securityContext, null, MetadataOperation.UpdateLineage);
SecurityUtil.checkAdminRoleOrPermissions(authorizer, securityContext, null, MetadataOperation.EDIT_LINEAGE);
dao.addLineage(addLineage);
return Response.status(Status.OK).build();
}
@ -201,7 +201,7 @@ public class LineageResource {
@Parameter(description = "Entity id", required = true, schema = @Schema(type = "string")) @PathParam("toId")
String toId)
throws IOException {
SecurityUtil.checkAdminRoleOrPermissions(authorizer, securityContext, null, MetadataOperation.UpdateLineage);
SecurityUtil.checkAdminRoleOrPermissions(authorizer, securityContext, null, MetadataOperation.EDIT_LINEAGE);
boolean deleted = dao.deleteLineage(fromEntity, fromId, toEntity, toId);
if (!deleted) {

View File

@ -27,6 +27,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.json.JsonPatch;
import javax.validation.Valid;
@ -48,6 +50,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.catalog.CatalogApplicationConfig;
import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.api.policies.CreatePolicy;
@ -62,8 +65,10 @@ import org.openmetadata.catalog.security.policyevaluator.PolicyEvaluator;
import org.openmetadata.catalog.type.EntityHistory;
import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.Include;
import org.openmetadata.catalog.type.MetadataOperation;
import org.openmetadata.catalog.util.ResultList;
@Slf4j
@Path("/v1/policies")
@Api(value = "Policies collection", tags = "Policies collection")
@Produces(MediaType.APPLICATION_JSON)
@ -71,6 +76,7 @@ import org.openmetadata.catalog.util.ResultList;
@Collection(name = "policies")
public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
public static final String COLLECTION_PATH = "v1/policies/";
public static final List<String> RESOURCES = new ArrayList<>();
@Override
public Policy addHref(UriInfo uriInfo, Policy policy) {
@ -87,6 +93,7 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
// Set up the PolicyEvaluator, before loading seed data.
PolicyEvaluator policyEvaluator = PolicyEvaluator.getInstance();
policyEvaluator.setPolicyRepository(dao);
// Load any existing rules from database, before loading seed data.
policyEvaluator.load();
dao.initSeedDataFromResources();
@ -103,6 +110,17 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
}
}
public static class OperationList extends ResultList<MetadataOperation> {
@SuppressWarnings("unused")
OperationList() {
// Empty constructor needed for deserialization
}
public OperationList(List<MetadataOperation> data, String beforeCursor, String afterCursor, int total) {
super(data, beforeCursor, afterCursor, total);
}
}
public static final String FIELDS = "owner,location";
@GET
@ -268,6 +286,30 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
return dao.getVersion(id, version);
}
@GET
@Path("/resources")
@Operation(
operationId = "listPolicyResources",
summary = "Get list of policy resources used in authoring a policy.",
tags = "policies",
description = "Get all the resources used in policy authoring",
responses = {
@ApiResponse(responseCode = "200", description = "policy", content = @Content(mediaType = "application/json")),
@ApiResponse(
responseCode = "404",
description = "Policy for instance {id} and version {version} is" + " " + "not found")
})
public List<String> listPolicyResources(@Context UriInfo uriInfo, @Context SecurityContext securityContext)
throws IOException {
if (RESOURCES.isEmpty()) {
// Load set of resource types
RESOURCES.addAll(Entity.listEntities());
RESOURCES.add("lineage");
Collections.sort(RESOURCES);
}
return RESOURCES;
}
@POST
@Operation(
operationId = "createPolicy",
@ -367,7 +409,6 @@ public class PolicyResource extends EntityResource<Policy, PolicyRepository> {
private Policy getPolicy(CreatePolicy create, String user) {
Policy policy =
copy(new Policy(), create, user)
.withPolicyUrl(create.getPolicyUrl())
.withPolicyType(create.getPolicyType())
.withRules(create.getRules())
.withEnabled(create.getEnabled());

View File

@ -75,7 +75,7 @@ import org.openmetadata.catalog.util.ResultList;
@Api(value = "Roles collection", tags = "Roles collection")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Collection(name = "roles", order = 1) // Load after PolicyResource at Order 0
@Collection(name = "roles", order = 2) // Load after PolicyResource at Order 0
// policies exist before
// loading roles
@Slf4j

View File

@ -40,6 +40,7 @@ import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.api.data.TermReference;
import org.openmetadata.catalog.entity.data.GlossaryTerm;
import org.openmetadata.catalog.entity.data.Table;
import org.openmetadata.catalog.entity.policies.accessControl.Rule;
import org.openmetadata.catalog.entity.type.CustomProperty;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.exception.EntityNotFoundException;
@ -352,4 +353,13 @@ public final class EntityUtil {
.withFullyQualifiedName(from.getFullyQualifiedName())
.withDeleted(from.getDeleted());
}
public static List<Rule> resolveRules(List<Object> rules) throws IOException {
List<Rule> resolvedRules = new ArrayList<>();
for (Object ruleObject : rules) {
// Cast to access control policy Rule.
resolvedRules.add(JsonUtils.readValue(JsonUtils.getJsonStructure(ruleObject).toString(), Rule.class));
}
return resolvedRules;
}
}

View File

@ -25,16 +25,16 @@ public class JsonPatchUtils {
String path = jsonPatchMap.get("path").toString();
if (path.contains(FIELD_DESCRIPTION)) {
return MetadataOperation.UpdateDescription;
return MetadataOperation.EDIT_DESCRIPTION;
}
if (path.contains("tags")) {
return MetadataOperation.UpdateTags;
return MetadataOperation.EDIT_TAGS;
}
if (path.contains(FIELD_OWNER)) {
return MetadataOperation.UpdateOwner;
return MetadataOperation.EDIT_OWNER;
}
if (path.startsWith("/users")) { // Ability to update users within a team.
return MetadataOperation.UpdateTeam;
return MetadataOperation.TEAM_EDIT_USERS;
}
return null;
}

View File

@ -7,20 +7,20 @@
"enabled": true,
"rules": [
{
"name": "DataConsumerPolicy-UpdateDescription",
"operation": "UpdateDescription",
"name": "DataConsumerPolicy-editDescription",
"operation": "EditDescription",
"allow": true,
"priority": 1000
},
{
"name": "DataConsumerPolicy-UpdateOwner",
"operation": "UpdateOwner",
"name": "DataConsumerPolicy-editOwner",
"operation": "EditOwner",
"allow": true,
"priority": 1000
},
{
"name": "DataConsumerPolicy-UpdateTags",
"operation": "UpdateTags",
"name": "DataConsumerPolicy-editTags",
"operation": "EditTags",
"allow": true,
"priority": 1000
}

View File

@ -5,29 +5,28 @@
"description": "Policy for Data Steward Role to perform operations on metadata entities",
"policyType": "AccessControl",
"enabled": true,
"deleted": false,
"rules": [
{
"name": "DataStewardPolicy-UpdateDescription",
"operation": "UpdateDescription",
"name": "DataStewardPolicy-editDescription",
"operation": "EditDescription",
"allow": true,
"priority": 1000
},
{
"name": "DataStewardPolicy-UpdateLineage",
"operation": "UpdateLineage",
"name": "DataStewardPolicy-editLineage",
"operation": "EditLineage",
"allow": true,
"priority": 1000
},
{
"name": "DataStewardPolicy-UpdateOwner",
"operation": "UpdateOwner",
"name": "DataStewardPolicy-editOwner",
"operation": "EditOwner",
"allow": true,
"priority": 1000
},
{
"name": "DataStewardPolicy-UpdateTags",
"operation": "UpdateTags",
"name": "DataStewardPolicy-editTags",
"operation": "EditTags",
"allow": true,
"priority": 1000
}

View File

@ -24,11 +24,6 @@
"description": "Owner of this Policy.",
"$ref": "../../type/entityReference.json"
},
"policyUrl": {
"description": "Link to a well documented definition of this Policy.",
"type": "string",
"format": "uri"
},
"policyType": {
"$ref": "../../entity/policies/policy.json#/definitions/policyType"
},

View File

@ -11,24 +11,27 @@
"description": "This schema defines all possible operations on metadata of data entities.",
"type": "string",
"enum": [
"SuggestDescription",
"SuggestTags",
"UpdateDescription",
"UpdateOwner",
"UpdateTags",
"UpdateLineage",
"DecryptTokens",
"UpdateTeam"
],
"javaEnums": [
{ "name": "SuggestDescription" },
{ "name": "SuggestTags" },
{ "name": "UpdateDescription" },
{ "name": "UpdateOwner" },
{ "name": "UpdateTags" },
{ "name": "UpdateLineage" },
{ "name": "DecryptTokens" },
{ "name": "UpdateTeam" }
"Create",
"Delete",
"ViewAll",
"ViewUsage",
"ViewTests",
"TableViewQueries",
"TableViewDataProfile",
"TableViewSampleData",
"EditAll",
"EditDescription",
"EditTags",
"EditOwner",
"EditTier",
"EditCustomFields",
"EditLineage",
"EditReviewers",
"EditTests",
"TableEditQueries",
"TableEditDataProfile",
"TableEditSampleData",
"TeamEditUsers"
]
}
},
@ -72,6 +75,8 @@
"default": false
}
},
"required": ["name"],
"required": [
"name"
],
"additionalProperties": false
}

View File

@ -62,12 +62,6 @@
"$ref": "../../type/entityReference.json",
"default": null
},
"policyUrl": {
"description": "Link to a well documented definition of this Policy.",
"type": "string",
"format": "uri",
"default": null
},
"href": {
"description": "Link to the resource corresponding to this entity.",
"$ref": "../../type/basic.json#/definitions/href"
@ -97,7 +91,7 @@
"$ref": "../../type/entityHistory.json#/definitions/changeDescription"
},
"rules": {
"description": "Rules that the policy has.",
"description": "Set of rules that the policy contains.",
"$ref": "#/definitions/rules"
},
"location": {

View File

@ -1576,7 +1576,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
assertResponse(
() -> patchEntity(entity.getId(), originalJson, entity, authHeaders(userName + "@open-metadata.org")),
FORBIDDEN,
noPermission(userName, "UpdateDescription"));
noPermission(userName, "editDescription"));
// Revert to original.
entity.setDescription(originalDescription);
return entity;

View File

@ -114,11 +114,11 @@ public class LineageResourceTest extends CatalogApplicationTest {
assertResponse(
() -> addEdge(TABLES.get(1), TABLES.get(2), null, authHeaders),
FORBIDDEN,
noPermission(userName, "UpdateLineage"));
noPermission(userName, "EditLineage"));
assertResponse(
() -> deleteEdge(TABLES.get(1), TABLES.get(2), authHeaders),
FORBIDDEN,
noPermission(userName, "UpdateLineage"));
noPermission(userName, "EditLineage"));
return;
}

View File

@ -80,48 +80,32 @@ class PermissionsResourceTest extends CatalogApplicationTest {
}
private Stream<Arguments> getPermissionsTestParams() {
HashMap<MetadataOperation, Boolean> adminPermissions = new HashMap<>();
HashMap<MetadataOperation, Boolean> dataStewardPermissions = new HashMap<>();
HashMap<MetadataOperation, Boolean> dataConsumerPermissions = new HashMap<>();
for (MetadataOperation op : MetadataOperation.values()) {
adminPermissions.put(op, Boolean.TRUE);
dataStewardPermissions.put(op, Boolean.FALSE);
dataConsumerPermissions.put(op, Boolean.FALSE);
}
dataStewardPermissions.put(MetadataOperation.EDIT_DESCRIPTION, Boolean.TRUE);
dataStewardPermissions.put(MetadataOperation.EDIT_LINEAGE, Boolean.TRUE);
dataStewardPermissions.put(MetadataOperation.EDIT_OWNER, Boolean.TRUE);
dataStewardPermissions.put(MetadataOperation.EDIT_TAGS, Boolean.TRUE);
// put(MetadataOperation.DecryptTokens, Boolean.FALSE);
dataStewardPermissions.put(MetadataOperation.TEAM_EDIT_USERS, Boolean.FALSE);
dataConsumerPermissions.put(MetadataOperation.EDIT_DESCRIPTION, Boolean.TRUE);
dataConsumerPermissions.put(MetadataOperation.EDIT_LINEAGE, Boolean.FALSE);
dataConsumerPermissions.put(MetadataOperation.EDIT_OWNER, Boolean.TRUE);
dataConsumerPermissions.put(MetadataOperation.EDIT_TAGS, Boolean.TRUE);
// put(MetadataOperation.DecryptTokens, Boolean.FALSE);
dataConsumerPermissions.put(MetadataOperation.TEAM_EDIT_USERS, Boolean.FALSE);
return Stream.of(
Arguments.of(
TestUtils.ADMIN_USER_NAME,
new HashMap<MetadataOperation, Boolean>() {
{
put(MetadataOperation.SuggestDescription, Boolean.TRUE);
put(MetadataOperation.SuggestTags, Boolean.TRUE);
put(MetadataOperation.UpdateDescription, Boolean.TRUE);
put(MetadataOperation.UpdateLineage, Boolean.TRUE);
put(MetadataOperation.UpdateOwner, Boolean.TRUE);
put(MetadataOperation.UpdateTags, Boolean.TRUE);
put(MetadataOperation.DecryptTokens, Boolean.TRUE);
put(MetadataOperation.UpdateTeam, Boolean.TRUE);
}
}),
Arguments.of(
DATA_STEWARD_USER_NAME,
new HashMap<MetadataOperation, Boolean>() {
{
put(MetadataOperation.UpdateDescription, Boolean.TRUE);
put(MetadataOperation.UpdateLineage, Boolean.TRUE);
put(MetadataOperation.UpdateOwner, Boolean.TRUE);
put(MetadataOperation.UpdateTags, Boolean.TRUE);
put(MetadataOperation.SuggestDescription, Boolean.FALSE);
put(MetadataOperation.SuggestTags, Boolean.FALSE);
put(MetadataOperation.DecryptTokens, Boolean.FALSE);
put(MetadataOperation.UpdateTeam, Boolean.FALSE);
}
}),
Arguments.of(
DATA_CONSUMER_USER_NAME,
new HashMap<MetadataOperation, Boolean>() {
{
put(MetadataOperation.SuggestDescription, Boolean.FALSE);
put(MetadataOperation.SuggestTags, Boolean.FALSE);
put(MetadataOperation.UpdateDescription, Boolean.TRUE);
put(MetadataOperation.UpdateLineage, Boolean.FALSE);
put(MetadataOperation.UpdateOwner, Boolean.TRUE);
put(MetadataOperation.UpdateTags, Boolean.TRUE);
put(MetadataOperation.DecryptTokens, Boolean.FALSE);
put(MetadataOperation.UpdateTeam, Boolean.FALSE);
}
}));
Arguments.of(TestUtils.ADMIN_USER_NAME, adminPermissions),
Arguments.of(DATA_STEWARD_USER_NAME, dataStewardPermissions),
Arguments.of(DATA_CONSUMER_USER_NAME, dataConsumerPermissions));
}
}

View File

@ -15,7 +15,7 @@ package org.openmetadata.catalog.resources.policies;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openmetadata.catalog.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.catalog.util.TestUtils.UpdateType.MINOR_UPDATE;
import static org.openmetadata.catalog.util.TestUtils.assertListNotNull;
@ -30,6 +30,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.ws.rs.client.WebTarget;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.BeforeAll;
@ -50,6 +52,7 @@ import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.FieldChange;
import org.openmetadata.catalog.type.MetadataOperation;
import org.openmetadata.catalog.type.PolicyType;
import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.PolicyUtils;
import org.openmetadata.catalog.util.TestUtils;
@ -77,8 +80,13 @@ public class PolicyResourceTest extends EntityResourceTest<Policy, CreatePolicy>
}
@Override
@SneakyThrows
public void validateCreatedEntity(Policy policy, CreatePolicy createRequest, Map<String, String> authHeaders) {
assertEquals(createRequest.getPolicyUrl(), policy.getPolicyUrl());
assertEquals(createRequest.getPolicyType(), policy.getPolicyType());
if (createRequest.getLocation() != null) {
assertEquals(createRequest.getLocation(), policy.getLocation().getId());
}
assertEquals(createRequest.getRules(), EntityUtil.resolveRules(policy.getRules()));
}
@Override
@ -120,8 +128,8 @@ public class PolicyResourceTest extends EntityResourceTest<Policy, CreatePolicy>
@Test
void post_AccessControlPolicyWithValidRules_200_ok(TestInfo test) throws IOException {
List<Rule> rules = new ArrayList<>();
rules.add(PolicyUtils.accessControlRule(null, null, MetadataOperation.UpdateDescription, true, 0));
rules.add(PolicyUtils.accessControlRule(null, null, "DataConsumer", MetadataOperation.UpdateTags, true, 1));
rules.add(PolicyUtils.accessControlRule(null, null, MetadataOperation.EDIT_DESCRIPTION, true, 0));
rules.add(PolicyUtils.accessControlRule(null, null, "DataConsumer", MetadataOperation.EDIT_TAGS, true, 1));
CreatePolicy create = createAccessControlPolicyWithRules(getEntityName(test), rules);
createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
}
@ -143,46 +151,31 @@ public class PolicyResourceTest extends EntityResourceTest<Policy, CreatePolicy>
@Test
void post_AccessControlPolicyWithDuplicateRules_400_error(TestInfo test) {
List<Rule> rules = new ArrayList<>();
rules.add(PolicyUtils.accessControlRule("rule1", null, null, MetadataOperation.UpdateDescription, true, 0));
rules.add(PolicyUtils.accessControlRule("rule2", null, null, MetadataOperation.UpdateTags, true, 1));
rules.add(PolicyUtils.accessControlRule("rule3", null, null, MetadataOperation.UpdateTags, true, 1));
CreatePolicy create = createAccessControlPolicyWithRules(getEntityName(test), rules);
rules.add(PolicyUtils.accessControlRule("rule1", null, null, MetadataOperation.EDIT_DESCRIPTION, true, 0));
rules.add(PolicyUtils.accessControlRule("rule2", null, null, MetadataOperation.EDIT_TAGS, true, 1));
rules.add(PolicyUtils.accessControlRule("rule3", null, null, MetadataOperation.EDIT_TAGS, true, 1));
String policyName = getEntityName(test);
CreatePolicy create = createAccessControlPolicyWithRules(policyName, rules);
assertResponseContains(
() -> createEntity(create, ADMIN_AUTH_HEADERS),
BAD_REQUEST,
String.format(
"Found multiple rules with operation UpdateTags within policy %s. "
"Found multiple rules with operation EditTags within policy %s. "
+ "Please ensure that operation across all rules within the policy are distinct",
getEntityName(test)));
policyName));
}
@Test
void patch_PolicyAttributes_200_ok(TestInfo test) throws IOException {
Policy policy = createAndCheckEntity(createRequest(test), ADMIN_AUTH_HEADERS).withLocation(null);
URI uri = null;
try {
uri = new URI("http://www.example.com/policy1");
} catch (URISyntaxException e) {
fail("could not construct URI for test");
}
// Add policyUrl which was previously null and set enabled to false
// Set enabled to false
String origJson = JsonUtils.pojoToJson(policy);
policy.setPolicyUrl(uri);
policy.setEnabled(false);
ChangeDescription change = getChangeDescription(policy.getVersion());
change.getFieldsAdded().add(new FieldChange().withName("policyUrl").withNewValue(uri));
change.getFieldsUpdated().add(new FieldChange().withName("enabled").withOldValue(true).withNewValue(false));
policy = patchEntityAndCheck(policy, origJson, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
// Remove policyUrl
origJson = JsonUtils.pojoToJson(policy);
policy.setPolicyUrl(null);
change = getChangeDescription(policy.getVersion());
change.getFieldsDeleted().add(new FieldChange().withName("policyUrl").withOldValue(uri));
policy = patchEntityAndCheck(policy, origJson, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
EntityReference locationReference = location.getEntityReference();
// Add new field location
@ -193,6 +186,15 @@ public class PolicyResourceTest extends EntityResourceTest<Policy, CreatePolicy>
patchEntityAndCheck(policy, origJson, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
}
@Test
public void get_policyResources() throws HttpResponseException {
// Get list of policy resources and make sure it has all the entities and other resources
List<String> resources = getPolicyResources(ADMIN_AUTH_HEADERS);
List<String> entities = Entity.listEntities();
assertTrue(resources.containsAll(entities));
assertTrue(resources.contains("lineage"));
}
@Override
public Policy validateGetWithDifferentFields(Policy policy, boolean byName) throws HttpResponseException {
String fields = "";
@ -228,4 +230,9 @@ public class PolicyResourceTest extends EntityResourceTest<Policy, CreatePolicy>
CreateLocation createLocation = locationResourceTest.createRequest(LOCATION_NAME, "", "", null);
return TestUtils.post(getResource("locations"), createLocation, Location.class, ADMIN_AUTH_HEADERS);
}
public final List<String> getPolicyResources(Map<String, String> authHeaders) throws HttpResponseException {
WebTarget target = getResource(collectionName + "/resources");
return (List<String>) TestUtils.get(target, List.class, authHeaders);
}
}

View File

@ -190,7 +190,7 @@ public class TeamResourceTest extends EntityResourceTest<Team, CreateTeam> {
patchEntity(
team.getId(), originalJson, team, SecurityUtil.authHeaders(randomUserName + "@open-metadata.org")),
FORBIDDEN,
CatalogExceptionMessage.noPermission(randomUserName, "UpdateTeam"));
CatalogExceptionMessage.noPermission(randomUserName, "TeamEditUsers"));
// Ensure user with UpdateTeam permission can add users to a team.
User teamManagerUser = createTeamManager(test);
@ -208,7 +208,10 @@ public class TeamResourceTest extends EntityResourceTest<Team, CreateTeam> {
private User createTeamManager(TestInfo testInfo) throws HttpResponseException {
// Create a rule that can update team
Rule rule =
new Rule().withName("TeamManagerPolicy-UpdateTeam").withAllow(true).withOperation(MetadataOperation.UpdateTeam);
new Rule()
.withName("TeamManagerPolicy-UpdateTeam")
.withAllow(true)
.withOperation(MetadataOperation.TEAM_EDIT_USERS);
// Create a policy with the rule
PolicyResourceTest policyResourceTest = new PolicyResourceTest();

View File

@ -589,7 +589,7 @@ const DashboardDetails = ({
Boolean(owner)
)}
isOwner={hasEditAccess()}
permission={Operation.UpdateDescription}
permission={Operation.EditDescription}
position="top">
<button
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"
@ -633,7 +633,7 @@ const DashboardDetails = ({
Boolean(owner)
)}
isOwner={hasEditAccess()}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="left"
trigger="click">
<TagsContainer

View File

@ -1118,7 +1118,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
<p>You do not have permission to edit the lineage</p>
</Fragment>
}
permission={Operation.UpdateLineage}>
permission={Operation.EditLineage}>
<ControlButton
className={classNames(
'tw-h-9 tw-w-9 tw-rounded-full tw-px-1 tw-shadow-lg tw-cursor-pointer',
@ -1128,7 +1128,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
},
{
'tw-opacity-40':
!userPermissions[Operation.UpdateLineage] &&
!userPermissions[Operation.EditLineage] &&
!isAuthDisabled &&
!isAdminUser,
}

View File

@ -364,7 +364,7 @@ const EntityTable = ({
isAdminUser ||
hasEditAccess ||
isAuthDisabled ||
userPermissions[Operation.UpdateDescription];
userPermissions[Operation.EditDescription];
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const getColumnName = (cell: any) => {
@ -654,7 +654,7 @@ const EntityTable = ({
<NonAdminAction
html={getHtmlForNonAdminAction(Boolean(owner))}
isOwner={hasEditAccess}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="left"
trigger="click">
<TagsContainer

View File

@ -308,12 +308,12 @@ const GlossaryDetails = ({ isHasAccess, glossary, updateGlossary }: props) => {
<NonAdminAction
html={<p>{TITLE_FOR_UPDATE_OWNER}</p>}
isOwner={isOwner()}
permission={Operation.UpdateOwner}
permission={Operation.EditOwner}
position="left">
<Button
data-testid="owner-dropdown"
disabled={
!userPermissions[Operation.UpdateOwner] &&
!userPermissions[Operation.EditOwner] &&
!isAuthDisabled &&
!hasEditAccess
}
@ -421,7 +421,7 @@ const GlossaryDetails = ({ isHasAccess, glossary, updateGlossary }: props) => {
)}
<NonAdminAction
isOwner={Boolean(glossary.owner)}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="bottom"
title={TITLE_FOR_NON_OWNER_ACTION}
trigger="click">

View File

@ -246,7 +246,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
}
isOwner={hasEditAccess || Boolean(owner && !currentUser)}
key={i}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="left">
<CardListItem
card={card}
@ -283,7 +283,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
return (
isAdminUser ||
isAuthDisabled ||
userPermissions[Operation.UpdateTeam] ||
userPermissions[Operation.TeamEditUsers] ||
!hasEditAccess
);
};

View File

@ -260,7 +260,7 @@ const MlModelFeaturesList: FC<MlModelFeaturesListProp> = ({
<NonAdminAction
html={getHtmlForNonAdminAction(Boolean(owner))}
isOwner={hasEditAccess}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="left"
trigger="click">
<TagsContainer
@ -313,7 +313,7 @@ const MlModelFeaturesList: FC<MlModelFeaturesListProp> = ({
<NonAdminAction
html={getHtmlForNonAdminAction(Boolean(owner))}
isOwner={hasEditAccess}
permission={Operation.UpdateDescription}
permission={Operation.EditDescription}
position="top">
<button
className="tw-self-start tw-w-8 tw-h-auto focus:tw-outline-none"

View File

@ -103,15 +103,15 @@ const AddRuleModal: FC<AddRuleProps> = ({
value={data.operation}
onChange={onChangeHandler}>
<option value="">Select Operation</option>
<option value={Operation.UpdateDescription}>
Update Description
<option value={Operation.EditDescription}>
Edit Description
</option>
<option value={Operation.UpdateLineage}>
Update Lineage
<option value={Operation.EditLineage}>Edit Lineage</option>
<option value={Operation.EditOwner}>Edit Owner</option>
<option value={Operation.EditTags}>Edit Tags</option>
<option value={Operation.TeamEditUsers}>
Edit Team Users
</option>
<option value={Operation.UpdateOwner}>Update Owner</option>
<option value={Operation.UpdateTags}>Update Tags</option>
<option value={Operation.UpdateTeam}>Update Teams</option>
</select>
{errorData?.operation && errorMsg(errorData.operation)}
</div>

View File

@ -135,7 +135,7 @@ const TeamDetails = ({
isHidden: !(
hasAccess ||
isOwner() ||
userPermissions[Operation.UpdateOwner]
userPermissions[Operation.EditOwner]
),
position: 4,
},
@ -347,7 +347,7 @@ const TeamDetails = ({
? `as ${teamUsersSearchText}.`
: `added yet.`}
</p>
{isActionAllowed(userPermissions[Operation.UpdateTeam]) ? (
{isActionAllowed(userPermissions[Operation.TeamEditUsers]) ? (
<>
<p>Would like to start adding some?</p>
<Button

View File

@ -116,7 +116,7 @@ const OwnerWidget = ({
return false;
}
return userPermissions[Operation.UpdateOwner];
return userPermissions[Operation.EditOwner];
}
}

View File

@ -74,7 +74,7 @@ const Description: FC<DescriptionProps> = ({
return (
isAdminUser ||
Boolean(hasEditAccess) ||
userPermissions[Operation.UpdateDescription] ||
userPermissions[Operation.EditDescription] ||
isAuthDisabled
);
};

View File

@ -70,7 +70,7 @@ const DescriptionV1 = ({
<NonAdminAction
html={getHtmlForNonAdminAction(Boolean(owner))}
isOwner={hasEditAccess}
permission={Operation.UpdateDescription}
permission={Operation.EditDescription}
position="right">
<button
className="focus:tw-outline-none tw-text-primary"

View File

@ -450,7 +450,7 @@ const EntityPageInfo = ({
<NonAdminAction
html={getHtmlForNonAdminAction(Boolean(owner))}
isOwner={hasEditAccess}
permission={Operation.UpdateTags}
permission={Operation.EditTags}
position="bottom"
trigger="click">
<div

View File

@ -573,13 +573,11 @@ declare module 'Models' {
};
export interface UserPermissions {
UpdateOwner: boolean;
UpdateDescription: boolean;
SuggestDescription: boolean;
UpdateLineage: boolean;
SuggestTags: boolean;
UpdateTags: boolean;
UpdateTeam: boolean;
EditOwner: boolean;
EditDescription: boolean;
EditLineage: boolean;
EditTags: boolean;
TeamEditUsers: boolean;
}
export interface EditorContentRef {
getEditorContent: () => string;

View File

@ -94,35 +94,35 @@ export const mockGetPolicyWithRuleData = {
updatedBy: 'admin',
rules: [
{
name: 'DataStewardRoleAccessControlPolicy-UpdateDescription',
name: 'DataStewardRoleAccessControlPolicy-editDescription',
allow: true,
enabled: true,
priority: 1000,
operation: 'UpdateDescription',
operation: 'EditDescription',
userRoleAttr: 'DataSteward',
},
{
name: 'DataStewardRoleAccessControlPolicy-UpdateLineage',
name: 'DataStewardRoleAccessControlPolicy-editLineage',
allow: true,
enabled: true,
priority: 1000,
operation: 'UpdateLineage',
operation: 'EditLineage',
userRoleAttr: 'DataSteward',
},
{
name: 'DataStewardRoleAccessControlPolicy-UpdateOwner',
name: 'DataStewardRoleAccessControlPolicy-editOwner',
allow: true,
enabled: true,
priority: 1000,
operation: 'UpdateOwner',
operation: 'EditOwner',
userRoleAttr: 'DataSteward',
},
{
name: 'DataStewardRoleAccessControlPolicy-UpdateTags',
name: 'DataStewardRoleAccessControlPolicy-editTags',
allow: true,
enabled: true,
priority: 1000,
operation: 'UpdateTags',
operation: 'EditTags',
userRoleAttr: 'DataSteward',
},
],

View File

@ -591,7 +591,7 @@ const TagsPage = () => {
)}
</div>
<NonAdminAction
permission={Operation.UpdateDescription}
permission={Operation.EditDescription}
position="left"
title={TITLE_FOR_NON_ADMIN_ACTION}>
<button

View File

@ -227,7 +227,7 @@ const UserCard = ({
<NonAdminAction
html={<>You do not have permission to update the team.</>}
isOwner={isOwner}
permission={Operation.UpdateTeam}
permission={Operation.TeamEditUsers}
position="bottom">
<span
className={classNames('tw-h-8 tw-rounded tw-mb-3', {
@ -235,7 +235,7 @@ const UserCard = ({
!isAdminUser &&
!isAuthDisabled &&
!isOwner &&
!userPermissions[Operation.UpdateTeam],
!userPermissions[Operation.TeamEditUsers],
})}
data-testid="remove"
onClick={() => onRemove?.(item.id as string)}>

View File

@ -445,7 +445,7 @@ const TeamsPage = () => {
{isAdminUser ||
isAuthDisabled ||
isOwner() ||
userPermissions[Operation.UpdateTeam] ? (
userPermissions[Operation.TeamEditUsers] ? (
<>
<p>Would like to start adding some?</p>
<Button
@ -780,7 +780,7 @@ const TeamsPage = () => {
</Fragment>
}
isOwner={isOwner()}
permission={Operation.UpdateTeam}
permission={Operation.TeamEditUsers}
position="bottom">
<Button
className={classNames(
@ -789,7 +789,7 @@ const TeamsPage = () => {
'tw-opacity-40':
!isAdminUser &&
!isAuthDisabled &&
!userPermissions[Operation.UpdateTeam] &&
!userPermissions[Operation.TeamEditUsers] &&
!isOwner(),
}
)}