mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-08 13:36:32 +00:00
This commit is contained in:
parent
7e14dfa0d0
commit
cf2b336abd
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -116,7 +116,7 @@ const OwnerWidget = ({
|
||||
return false;
|
||||
}
|
||||
|
||||
return userPermissions[Operation.UpdateOwner];
|
||||
return userPermissions[Operation.EditOwner];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -74,7 +74,7 @@ const Description: FC<DescriptionProps> = ({
|
||||
return (
|
||||
isAdminUser ||
|
||||
Boolean(hasEditAccess) ||
|
||||
userPermissions[Operation.UpdateDescription] ||
|
||||
userPermissions[Operation.EditDescription] ||
|
||||
isAuthDisabled
|
||||
);
|
||||
};
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -450,7 +450,7 @@ const EntityPageInfo = ({
|
||||
<NonAdminAction
|
||||
html={getHtmlForNonAdminAction(Boolean(owner))}
|
||||
isOwner={hasEditAccess}
|
||||
permission={Operation.UpdateTags}
|
||||
permission={Operation.EditTags}
|
||||
position="bottom"
|
||||
trigger="click">
|
||||
<div
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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',
|
||||
},
|
||||
],
|
||||
|
||||
@ -591,7 +591,7 @@ const TagsPage = () => {
|
||||
)}
|
||||
</div>
|
||||
<NonAdminAction
|
||||
permission={Operation.UpdateDescription}
|
||||
permission={Operation.EditDescription}
|
||||
position="left"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<button
|
||||
|
||||
@ -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)}>
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user