mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-06-27 04:22:05 +00:00
Allow for domain-level permissions while creating/updating dataProduct
This commit is contained in:
parent
3bec47ef8a
commit
bfdf562642
@ -1,23 +1,28 @@
|
|||||||
package org.openmetadata.service.security.policyevaluator;
|
package org.openmetadata.service.security.policyevaluator;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.openmetadata.schema.EntityInterface;
|
import org.openmetadata.schema.EntityInterface;
|
||||||
|
import org.openmetadata.schema.entity.classification.Tag;
|
||||||
|
import org.openmetadata.schema.entity.data.GlossaryTerm;
|
||||||
import org.openmetadata.schema.type.EntityReference;
|
import org.openmetadata.schema.type.EntityReference;
|
||||||
import org.openmetadata.schema.type.TagLabel;
|
import org.openmetadata.schema.type.TagLabel;
|
||||||
import org.openmetadata.service.Entity;
|
import org.openmetadata.service.Entity;
|
||||||
import org.openmetadata.service.exception.EntityNotFoundException;
|
import org.openmetadata.service.exception.EntityNotFoundException;
|
||||||
import org.openmetadata.service.jdbi3.EntityRepository;
|
import org.openmetadata.service.jdbi3.EntityRepository;
|
||||||
import org.openmetadata.service.util.EntityUtil;
|
import org.openmetadata.service.util.EntityUtil.Fields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResourceContext used for CREATE operations where ownership, tags are inherited from the parent term.
|
* ResourceContext used for CREATE operations where ownership, tags are inherited from the parent term.
|
||||||
*
|
*
|
||||||
* <p>As multiple threads don't access this, the class is not thread-safe by design.
|
* <p>As multiple threads don't access this, the class is not thread-safe by design.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class CreateResourceContext<T extends EntityInterface> implements ResourceContextInterface {
|
public class CreateResourceContext<T extends EntityInterface> implements ResourceContextInterface {
|
||||||
@NonNull @Getter private final String resource;
|
@NonNull @Getter private final String resource;
|
||||||
private final EntityRepository<T> entityRepository;
|
private final EntityRepository<T> entityRepository;
|
||||||
@ -54,26 +59,47 @@ public class CreateResourceContext<T extends EntityInterface> implements Resourc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setParent(T entity) {
|
private void setParent(T entity) {
|
||||||
String fields = "";
|
Fields fields = new Fields(new HashSet<>());
|
||||||
if (entityRepository.isSupportsOwners()) {
|
if (entityRepository.isSupportsOwners()) {
|
||||||
fields = EntityUtil.addField(fields, Entity.FIELD_OWNERS);
|
fields.getFieldList().add(Entity.FIELD_OWNERS);
|
||||||
}
|
}
|
||||||
if (entityRepository.isSupportsTags()) {
|
if (entityRepository.isSupportsTags()) {
|
||||||
fields = EntityUtil.addField(fields, Entity.FIELD_TAGS);
|
fields.getFieldList().add(Entity.FIELD_TAGS);
|
||||||
}
|
}
|
||||||
if (entityRepository.isSupportsDomain()) {
|
if (entityRepository.isSupportsDomain()) {
|
||||||
fields = EntityUtil.addField(fields, Entity.FIELD_DOMAIN);
|
fields.getFieldList().add(Entity.FIELD_DOMAIN);
|
||||||
}
|
}
|
||||||
if (entityRepository.isSupportsReviewers()) {
|
if (entityRepository.isSupportsReviewers()) {
|
||||||
fields = EntityUtil.addField(fields, Entity.FIELD_REVIEWERS);
|
fields.getFieldList().add(Entity.FIELD_REVIEWERS);
|
||||||
}
|
|
||||||
if (entityRepository.isSupportsDomain()) {
|
|
||||||
fields = EntityUtil.addField(fields, Entity.FIELD_DOMAIN);
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
parentEntity = entityRepository.getParentEntity(entity, fields);
|
// First, check direct parent
|
||||||
|
parentEntity = entityRepository.getParentEntity(entity, fields.toString());
|
||||||
|
// If direct parent is not found, check for root-level parent
|
||||||
|
if (parentEntity == null) {
|
||||||
|
parentEntity = resolveRootParentEntity(entity, fields);
|
||||||
|
}
|
||||||
} catch (EntityNotFoundException e) {
|
} catch (EntityNotFoundException e) {
|
||||||
parentEntity = null;
|
parentEntity = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EntityInterface resolveRootParentEntity(T entity, Fields fields) {
|
||||||
|
try {
|
||||||
|
EntityReference rootReference =
|
||||||
|
switch (entityRepository.getEntityType()) {
|
||||||
|
case Entity.GLOSSARY_TERM -> ((GlossaryTerm) entity).getGlossary();
|
||||||
|
case Entity.TAG -> ((Tag) entity).getClassification();
|
||||||
|
case Entity.DATA_PRODUCT -> entity.getDomain();
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rootReference == null || rootReference.getId() == null) return null;
|
||||||
|
EntityRepository<?> rootRepository = Entity.getEntityRepository(rootReference.getType());
|
||||||
|
return rootRepository.get(null, rootReference.getId(), fields);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to resolve root parent entity: {}", e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
package org.openmetadata.service.security.policyevaluator;
|
package org.openmetadata.service.security.policyevaluator;
|
||||||
|
|
||||||
|
import static org.openmetadata.service.Entity.FIELD_OWNERS;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.openmetadata.schema.EntityInterface;
|
import org.openmetadata.schema.EntityInterface;
|
||||||
|
import org.openmetadata.schema.entity.classification.Tag;
|
||||||
|
import org.openmetadata.schema.entity.data.GlossaryTerm;
|
||||||
import org.openmetadata.schema.type.EntityReference;
|
import org.openmetadata.schema.type.EntityReference;
|
||||||
import org.openmetadata.schema.type.Include;
|
import org.openmetadata.schema.type.Include;
|
||||||
import org.openmetadata.schema.type.TagLabel;
|
import org.openmetadata.schema.type.TagLabel;
|
||||||
@ -22,6 +29,7 @@ import org.openmetadata.service.util.EntityUtil.Fields;
|
|||||||
*
|
*
|
||||||
* <p>As multiple threads don't access this, the class is not thread-safe by design.
|
* <p>As multiple threads don't access this, the class is not thread-safe by design.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ResourceContext<T extends EntityInterface> implements ResourceContextInterface {
|
public class ResourceContext<T extends EntityInterface> implements ResourceContextInterface {
|
||||||
@NonNull @Getter private final String resource;
|
@NonNull @Getter private final String resource;
|
||||||
private final EntityRepository<T> entityRepository;
|
private final EntityRepository<T> entityRepository;
|
||||||
@ -73,7 +81,36 @@ public class ResourceContext<T extends EntityInterface> implements ResourceConte
|
|||||||
} else if (Entity.USER.equals(entityRepository.getEntityType())) {
|
} else if (Entity.USER.equals(entityRepository.getEntityType())) {
|
||||||
return List.of(entity.getEntityReference()); // Owner for a user is same as the user
|
return List.of(entity.getEntityReference()); // Owner for a user is same as the user
|
||||||
}
|
}
|
||||||
return entity.getOwners();
|
|
||||||
|
// Check for parent owners
|
||||||
|
List<EntityReference> owners = entity.getOwners();
|
||||||
|
EntityInterface parentEntity = resolveParentEntity(entity);
|
||||||
|
if (parentEntity != null && parentEntity.getOwners() != null) {
|
||||||
|
if (owners == null) owners = new ArrayList<>();
|
||||||
|
owners.addAll(parentEntity.getOwners());
|
||||||
|
}
|
||||||
|
|
||||||
|
return owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntityInterface resolveParentEntity(T entity) {
|
||||||
|
Fields fields = new Fields(new HashSet<>(Collections.singleton(FIELD_OWNERS)));
|
||||||
|
try {
|
||||||
|
EntityReference parentReference =
|
||||||
|
switch (entityRepository.getEntityType()) {
|
||||||
|
case Entity.GLOSSARY_TERM -> ((GlossaryTerm) entity).getGlossary();
|
||||||
|
case Entity.TAG -> ((Tag) entity).getClassification();
|
||||||
|
case Entity.DATA_PRODUCT -> entity.getDomain();
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parentReference == null || parentReference.getId() == null) return null;
|
||||||
|
EntityRepository<?> rootRepository = Entity.getEntityRepository(parentReference.getType());
|
||||||
|
return rootRepository.get(null, parentReference.getId(), fields);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Failed to resolve parent entity: {}", e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,9 @@ import static org.mockito.ArgumentMatchers.isNull;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.openmetadata.common.utils.CommonUtil.listOf;
|
import static org.openmetadata.common.utils.CommonUtil.listOf;
|
||||||
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
|
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
|
||||||
|
import static org.openmetadata.schema.type.MetadataOperation.CREATE;
|
||||||
|
import static org.openmetadata.schema.type.MetadataOperation.EDIT_TAGS;
|
||||||
|
import static org.openmetadata.service.resources.EntityResourceTest.DATA_CONSUMER_ROLE_NAME;
|
||||||
import static org.openmetadata.service.security.policyevaluator.CompiledRule.parseExpression;
|
import static org.openmetadata.service.security.policyevaluator.CompiledRule.parseExpression;
|
||||||
import static org.openmetadata.service.security.policyevaluator.SubjectContext.TEAM_FIELDS;
|
import static org.openmetadata.service.security.policyevaluator.SubjectContext.TEAM_FIELDS;
|
||||||
|
|
||||||
@ -16,12 +19,20 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.openmetadata.schema.entity.data.Database;
|
||||||
|
import org.openmetadata.schema.entity.data.DatabaseSchema;
|
||||||
import org.openmetadata.schema.entity.data.Table;
|
import org.openmetadata.schema.entity.data.Table;
|
||||||
|
import org.openmetadata.schema.entity.domains.DataProduct;
|
||||||
|
import org.openmetadata.schema.entity.domains.Domain;
|
||||||
|
import org.openmetadata.schema.entity.policies.Policy;
|
||||||
|
import org.openmetadata.schema.entity.policies.accessControl.Rule;
|
||||||
import org.openmetadata.schema.entity.teams.Role;
|
import org.openmetadata.schema.entity.teams.Role;
|
||||||
import org.openmetadata.schema.entity.teams.Team;
|
import org.openmetadata.schema.entity.teams.Team;
|
||||||
import org.openmetadata.schema.entity.teams.User;
|
import org.openmetadata.schema.entity.teams.User;
|
||||||
@ -30,19 +41,35 @@ import org.openmetadata.schema.type.EntityReference;
|
|||||||
import org.openmetadata.schema.type.Include;
|
import org.openmetadata.schema.type.Include;
|
||||||
import org.openmetadata.schema.type.TagLabel;
|
import org.openmetadata.schema.type.TagLabel;
|
||||||
import org.openmetadata.service.Entity;
|
import org.openmetadata.service.Entity;
|
||||||
|
import org.openmetadata.service.jdbi3.DataProductRepository;
|
||||||
|
import org.openmetadata.service.jdbi3.DatabaseRepository;
|
||||||
|
import org.openmetadata.service.jdbi3.DatabaseSchemaRepository;
|
||||||
|
import org.openmetadata.service.jdbi3.DomainRepository;
|
||||||
import org.openmetadata.service.jdbi3.EntityRepository;
|
import org.openmetadata.service.jdbi3.EntityRepository;
|
||||||
import org.openmetadata.service.jdbi3.TableRepository;
|
import org.openmetadata.service.jdbi3.TableRepository;
|
||||||
import org.openmetadata.service.jdbi3.TeamRepository;
|
import org.openmetadata.service.jdbi3.TeamRepository;
|
||||||
import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext;
|
import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext;
|
||||||
|
import org.openmetadata.service.util.EntityUtil;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
class RuleEvaluatorTest {
|
class RuleEvaluatorTest {
|
||||||
private static final Table table = new Table().withName("table");
|
private static final Table table = new Table().withName("table");
|
||||||
private static User user;
|
private static User user;
|
||||||
private static EvaluationContext evaluationContext;
|
private static EvaluationContext evaluationContext;
|
||||||
private static SubjectContext subjectContext;
|
private static SubjectContext subjectContext;
|
||||||
private static ResourceContext<?> resourceContext;
|
private static ResourceContext<?> resourceContext;
|
||||||
|
private static final String DATA_CONSUMER_POLICY_NAME = "DataConsumerPolicy";
|
||||||
|
|
||||||
|
private static CreateResourceContext<?> createResourceContextSchema;
|
||||||
|
private static CreateResourceContext<?> createResourceContextDataProduct;
|
||||||
|
private static ResourceContext<DataProduct> resourceContextDataProduct;
|
||||||
|
|
||||||
|
private static User ownerUser;
|
||||||
|
private static User nonOwnerUser;
|
||||||
|
private static EntityReference ownerRef;
|
||||||
|
private static EntityReference databaseRef;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
@ -87,12 +114,77 @@ class RuleEvaluatorTest {
|
|||||||
Mockito.when(tableRepository.getAllTags(any()))
|
Mockito.when(tableRepository.getAllTags(any()))
|
||||||
.thenAnswer((Answer<List<TagLabel>>) invocationOnMock -> table.getTags());
|
.thenAnswer((Answer<List<TagLabel>>) invocationOnMock -> table.getTags());
|
||||||
|
|
||||||
|
DatabaseRepository databaseRepository = mock(DatabaseRepository.class);
|
||||||
|
Mockito.when(databaseRepository.getEntityType()).thenReturn(Entity.DATABASE);
|
||||||
|
Mockito.when(databaseRepository.isSupportsOwners()).thenReturn(Boolean.TRUE);
|
||||||
|
Entity.registerEntity(Database.class, Entity.DATABASE, databaseRepository);
|
||||||
|
|
||||||
|
DatabaseSchemaRepository databaseSchemaRepository = mock(DatabaseSchemaRepository.class);
|
||||||
|
Mockito.when(databaseSchemaRepository.getEntityType()).thenReturn(Entity.DATABASE_SCHEMA);
|
||||||
|
Mockito.when(databaseSchemaRepository.isSupportsOwners()).thenReturn(Boolean.TRUE);
|
||||||
|
Entity.registerEntity(DatabaseSchema.class, Entity.DATABASE_SCHEMA, databaseSchemaRepository);
|
||||||
|
|
||||||
|
DomainRepository domainRepository = mock(DomainRepository.class);
|
||||||
|
Mockito.when(domainRepository.getEntityType()).thenReturn(Entity.DOMAIN);
|
||||||
|
Mockito.when(domainRepository.isSupportsOwners()).thenReturn(Boolean.TRUE);
|
||||||
|
Entity.registerEntity(Domain.class, Entity.DOMAIN, domainRepository);
|
||||||
|
Mockito.when(domainRepository.get(isNull(), any(UUID.class), any(EntityUtil.Fields.class)))
|
||||||
|
.thenAnswer(
|
||||||
|
i ->
|
||||||
|
EntityRepository.CACHE_WITH_ID.get(
|
||||||
|
new ImmutablePair<>(Entity.DOMAIN, i.getArgument(1))));
|
||||||
|
|
||||||
|
DataProductRepository dataProductRepository = mock(DataProductRepository.class);
|
||||||
|
Mockito.when(dataProductRepository.getEntityType()).thenReturn(Entity.DATA_PRODUCT);
|
||||||
|
Mockito.when(dataProductRepository.isSupportsOwners()).thenReturn(Boolean.TRUE);
|
||||||
|
Entity.registerEntity(DataProduct.class, Entity.DATA_PRODUCT, dataProductRepository);
|
||||||
|
|
||||||
user = new User().withId(UUID.randomUUID()).withName("user");
|
user = new User().withId(UUID.randomUUID()).withName("user");
|
||||||
|
ownerUser = new User().withId(UUID.randomUUID()).withName("owner");
|
||||||
|
nonOwnerUser = new User().withId(UUID.randomUUID()).withName("nonOwner");
|
||||||
|
ownerRef = ownerUser.getEntityReference().withType(Entity.USER);
|
||||||
|
|
||||||
|
Database database = new Database().withId(UUID.randomUUID()).withName("testDB");
|
||||||
|
databaseRef = database.getEntityReference();
|
||||||
|
DatabaseSchema schema = new DatabaseSchema().withId(UUID.randomUUID()).withName("testSchema");
|
||||||
|
schema.setDatabase(databaseRef);
|
||||||
|
database.setOwners(List.of(ownerRef));
|
||||||
|
EntityRepository.CACHE_WITH_ID.put(
|
||||||
|
new ImmutablePair<>(Entity.DATABASE_SCHEMA, schema.getId()), schema);
|
||||||
|
EntityRepository.CACHE_WITH_ID.put(
|
||||||
|
new ImmutablePair<>(Entity.DATABASE, database.getId()), database);
|
||||||
|
Mockito.when(databaseSchemaRepository.getParentEntity(any(DatabaseSchema.class), anyString()))
|
||||||
|
.thenAnswer(
|
||||||
|
i -> {
|
||||||
|
DatabaseSchema cachedSchema = i.getArgument(0);
|
||||||
|
EntityReference dbRef = cachedSchema.getDatabase();
|
||||||
|
if (dbRef == null) return null;
|
||||||
|
Database db =
|
||||||
|
(Database)
|
||||||
|
EntityRepository.CACHE_WITH_ID.get(
|
||||||
|
new ImmutablePair<>(Entity.DATABASE, dbRef.getId()));
|
||||||
|
return db;
|
||||||
|
});
|
||||||
|
createResourceContextSchema =
|
||||||
|
Mockito.spy(new CreateResourceContext<>(Entity.DATABASE_SCHEMA, schema));
|
||||||
|
|
||||||
|
Domain domain = new Domain().withId(UUID.randomUUID()).withName("testDomain");
|
||||||
|
DataProduct dataProduct =
|
||||||
|
new DataProduct().withId(UUID.randomUUID()).withName("testDataProduct");
|
||||||
|
dataProduct.setDomain(domain.getEntityReference());
|
||||||
|
domain.setOwners(List.of(ownerRef));
|
||||||
|
EntityRepository.CACHE_WITH_ID.put(new ImmutablePair<>(Entity.DOMAIN, domain.getId()), domain);
|
||||||
|
EntityRepository.CACHE_WITH_ID.put(
|
||||||
|
new ImmutablePair<>(Entity.DATA_PRODUCT, dataProduct.getId()), dataProduct);
|
||||||
|
resourceContextDataProduct =
|
||||||
|
Mockito.spy(new ResourceContext<>(Entity.DATA_PRODUCT, dataProduct, dataProductRepository));
|
||||||
|
createResourceContextDataProduct =
|
||||||
|
Mockito.spy(new CreateResourceContext<>(Entity.DATA_PRODUCT, dataProduct));
|
||||||
|
|
||||||
resourceContext = new ResourceContext<>("table", table, mock(TableRepository.class));
|
resourceContext = new ResourceContext<>("table", table, mock(TableRepository.class));
|
||||||
subjectContext = new SubjectContext(user);
|
subjectContext = new SubjectContext(user);
|
||||||
RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext);
|
RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext);
|
||||||
evaluationContext =
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(ruleEvaluator).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -144,6 +236,73 @@ class RuleEvaluatorTest {
|
|||||||
.withName(user.getName())));
|
.withName(user.getName())));
|
||||||
assertTrue(evaluateExpression("noOwner() || isOwner()"));
|
assertTrue(evaluateExpression("noOwner() || isOwner()"));
|
||||||
assertFalse(evaluateExpression("!noOwner() && !isOwner()"));
|
assertFalse(evaluateExpression("!noOwner() && !isOwner()"));
|
||||||
|
|
||||||
|
// Verify that parent owner has the necessary permissions to create child entities -
|
||||||
|
// createResourceContext
|
||||||
|
Role dataConsumerRole = new Role().withId(UUID.randomUUID()).withName(DATA_CONSUMER_ROLE_NAME);
|
||||||
|
ownerUser.setRoles(List.of(dataConsumerRole.getEntityReference()));
|
||||||
|
Rule createRule =
|
||||||
|
new Rule()
|
||||||
|
.withResources(List.of("All"))
|
||||||
|
.withOperations(List.of(CREATE))
|
||||||
|
.withCondition("isOwner()")
|
||||||
|
.withEffect(Rule.Effect.ALLOW);
|
||||||
|
Policy policy = new Policy().withName(DATA_CONSUMER_POLICY_NAME).withRules(List.of(createRule));
|
||||||
|
List<CompiledRule> compiledRules = List.of(new CompiledRule(createRule));
|
||||||
|
PolicyContext policyContext =
|
||||||
|
new PolicyContext(
|
||||||
|
Entity.USER,
|
||||||
|
ownerUser.getName(),
|
||||||
|
DATA_CONSUMER_ROLE_NAME,
|
||||||
|
policy.getName(),
|
||||||
|
compiledRules);
|
||||||
|
|
||||||
|
subjectContext = new SubjectContext(ownerUser);
|
||||||
|
RuleEvaluator ruleEvaluator =
|
||||||
|
new RuleEvaluator(policyContext, subjectContext, createResourceContextSchema);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertTrue(evaluateExpression("isOwner()"));
|
||||||
|
|
||||||
|
subjectContext = new SubjectContext(nonOwnerUser);
|
||||||
|
ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, createResourceContextSchema);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertFalse(evaluateExpression("isOwner()"));
|
||||||
|
|
||||||
|
// Verify that domain owner has the necessary permissions to create/edit its dataProduct -
|
||||||
|
// ResourceContext (edit related permissions)
|
||||||
|
Rule editRule =
|
||||||
|
new Rule()
|
||||||
|
.withResources(List.of("All"))
|
||||||
|
.withOperations(List.of(CREATE, EDIT_TAGS))
|
||||||
|
.withCondition("isOwner()")
|
||||||
|
.withEffect(Rule.Effect.ALLOW);
|
||||||
|
policy = new Policy().withName(DATA_CONSUMER_POLICY_NAME).withRules(List.of(editRule));
|
||||||
|
compiledRules = List.of(new CompiledRule(editRule));
|
||||||
|
policyContext =
|
||||||
|
new PolicyContext(
|
||||||
|
Entity.USER,
|
||||||
|
ownerUser.getName(),
|
||||||
|
DATA_CONSUMER_ROLE_NAME,
|
||||||
|
policy.getName(),
|
||||||
|
compiledRules);
|
||||||
|
|
||||||
|
subjectContext = new SubjectContext(ownerUser);
|
||||||
|
ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContextDataProduct);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertTrue(evaluateExpression("isOwner()"));
|
||||||
|
ruleEvaluator =
|
||||||
|
new RuleEvaluator(policyContext, subjectContext, createResourceContextDataProduct);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertTrue(evaluateExpression("isOwner()"));
|
||||||
|
|
||||||
|
subjectContext = new SubjectContext(nonOwnerUser);
|
||||||
|
ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContextDataProduct);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertFalse(evaluateExpression("isOwner()"));
|
||||||
|
ruleEvaluator =
|
||||||
|
new RuleEvaluator(policyContext, subjectContext, createResourceContextDataProduct);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
assertFalse(evaluateExpression("isOwner()"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -363,7 +522,14 @@ class RuleEvaluatorTest {
|
|||||||
private void updatePolicyContext(String team) {
|
private void updatePolicyContext(String team) {
|
||||||
PolicyContext policyContext = new PolicyContext(Entity.TEAM, team, null, null, null);
|
PolicyContext policyContext = new PolicyContext(Entity.TEAM, team, null, null, null);
|
||||||
RuleEvaluator ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContext);
|
RuleEvaluator ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContext);
|
||||||
evaluationContext =
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(ruleEvaluator).build();
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void resetContext() {
|
||||||
|
subjectContext = new SubjectContext(user);
|
||||||
|
RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext);
|
||||||
|
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
|
||||||
|
LOG.info("Context reset to default state after test completion.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import base, { expect, Page } from '@playwright/test';
|
import base, { APIRequestContext, expect, Page } from '@playwright/test';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { SidebarItem } from '../../constant/sidebar';
|
import { SidebarItem } from '../../constant/sidebar';
|
||||||
@ -24,7 +24,11 @@ import { ClassificationClass } from '../../support/tag/ClassificationClass';
|
|||||||
import { TagClass } from '../../support/tag/TagClass';
|
import { TagClass } from '../../support/tag/TagClass';
|
||||||
import { UserClass } from '../../support/user/UserClass';
|
import { UserClass } from '../../support/user/UserClass';
|
||||||
import { performAdminLogin } from '../../utils/admin';
|
import { performAdminLogin } from '../../utils/admin';
|
||||||
import { getApiContext, redirectToHomePage } from '../../utils/common';
|
import {
|
||||||
|
clickOutside,
|
||||||
|
getApiContext,
|
||||||
|
redirectToHomePage,
|
||||||
|
} from '../../utils/common';
|
||||||
import { CustomPropertyTypeByName } from '../../utils/customProperty';
|
import { CustomPropertyTypeByName } from '../../utils/customProperty';
|
||||||
import {
|
import {
|
||||||
addAssetsToDataProduct,
|
addAssetsToDataProduct,
|
||||||
@ -36,15 +40,16 @@ import {
|
|||||||
createSubDomain,
|
createSubDomain,
|
||||||
removeAssetsFromDataProduct,
|
removeAssetsFromDataProduct,
|
||||||
selectDataProduct,
|
selectDataProduct,
|
||||||
|
selectDataProductFromTab,
|
||||||
selectDomain,
|
selectDomain,
|
||||||
selectSubDomain,
|
selectSubDomain,
|
||||||
setupAssetsForDomain,
|
setupAssetsForDomain,
|
||||||
|
setupDomainOwnershipTest,
|
||||||
verifyDataProductAssetsAfterDelete,
|
verifyDataProductAssetsAfterDelete,
|
||||||
verifyDomain,
|
verifyDomain,
|
||||||
} from '../../utils/domain';
|
} from '../../utils/domain';
|
||||||
import { sidebarClick } from '../../utils/sidebar';
|
import { sidebarClick } from '../../utils/sidebar';
|
||||||
import { performUserLogin, visitUserProfilePage } from '../../utils/user';
|
import { performUserLogin, visitUserProfilePage } from '../../utils/user';
|
||||||
|
|
||||||
const user = new UserClass();
|
const user = new UserClass();
|
||||||
|
|
||||||
const domain = new Domain();
|
const domain = new Domain();
|
||||||
@ -711,3 +716,105 @@ test.describe('Domains Rbac', () => {
|
|||||||
await afterAction();
|
await afterAction();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Data Consumer Domain Ownership', () => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
const classification = new ClassificationClass({
|
||||||
|
provider: 'system',
|
||||||
|
mutuallyExclusive: true,
|
||||||
|
});
|
||||||
|
const tag = new TagClass({
|
||||||
|
classification: classification.data.name,
|
||||||
|
});
|
||||||
|
const glossary = new Glossary();
|
||||||
|
const glossaryTerm = new GlossaryTerm(glossary);
|
||||||
|
|
||||||
|
let testResources: {
|
||||||
|
dataConsumerUser: UserClass;
|
||||||
|
domainForTest: Domain;
|
||||||
|
dataProductForTest: DataProduct;
|
||||||
|
cleanup: (apiContext1: APIRequestContext) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await classification.create(apiContext);
|
||||||
|
await tag.create(apiContext);
|
||||||
|
await glossary.create(apiContext);
|
||||||
|
await glossaryTerm.create(apiContext);
|
||||||
|
|
||||||
|
testResources = await setupDomainOwnershipTest(apiContext);
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll('Cleanup', async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
await tag.delete(apiContext);
|
||||||
|
await glossary.delete(apiContext);
|
||||||
|
await glossaryTerm.delete(apiContext);
|
||||||
|
await classification.delete(apiContext);
|
||||||
|
await testResources.cleanup(apiContext);
|
||||||
|
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Data consumer can manage domain as owner', async ({ browser }) => {
|
||||||
|
const { page: dataConsumerPage, afterAction: consumerAfterAction } =
|
||||||
|
await performUserLogin(browser, testResources.dataConsumerUser);
|
||||||
|
|
||||||
|
await test.step(
|
||||||
|
'Check domain management permissions for data consumer owner',
|
||||||
|
async () => {
|
||||||
|
await sidebarClick(dataConsumerPage, SidebarItem.DOMAIN);
|
||||||
|
await selectDomain(dataConsumerPage, testResources.domainForTest.data);
|
||||||
|
|
||||||
|
await dataConsumerPage.getByTestId('domain-details-add-button').click();
|
||||||
|
|
||||||
|
// check Data Products menu item is visible
|
||||||
|
await expect(
|
||||||
|
dataConsumerPage.getByRole('menuitem', {
|
||||||
|
name: 'Data Products',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await clickOutside(dataConsumerPage);
|
||||||
|
|
||||||
|
await selectDataProductFromTab(
|
||||||
|
dataConsumerPage,
|
||||||
|
testResources.dataProductForTest.data
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the user can edit owner, tags, glossary and domain experts
|
||||||
|
await expect(dataConsumerPage.getByTestId('edit-owner')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
dataConsumerPage.getByTestId('tags-container').getByTestId('add-tag')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
dataConsumerPage
|
||||||
|
.getByTestId('glossary-container')
|
||||||
|
.getByTestId('add-tag')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
dataConsumerPage.getByTestId('domain-expert-name').getByTestId('Add')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
dataConsumerPage.getByTestId('manage-button')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await addTagsAndGlossaryToDomain(dataConsumerPage, {
|
||||||
|
tagFqn: tag.responseData.fullyQualifiedName,
|
||||||
|
glossaryTermFqn: glossaryTerm.responseData.fullyQualifiedName,
|
||||||
|
isDomain: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await consumerAfterAction();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -10,9 +10,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import test, { expect, Page } from '@playwright/test';
|
import test, { APIRequestContext, expect, Page } from '@playwright/test';
|
||||||
import { get, isEmpty, isUndefined } from 'lodash';
|
import { get, isEmpty, isUndefined } from 'lodash';
|
||||||
import { SidebarItem } from '../constant/sidebar';
|
import { SidebarItem } from '../constant/sidebar';
|
||||||
|
import { PolicyClass } from '../support/access-control/PoliciesClass';
|
||||||
|
import { RolesClass } from '../support/access-control/RolesClass';
|
||||||
import { DataProduct } from '../support/domain/DataProduct';
|
import { DataProduct } from '../support/domain/DataProduct';
|
||||||
import { Domain } from '../support/domain/Domain';
|
import { Domain } from '../support/domain/Domain';
|
||||||
import { SubDomain } from '../support/domain/SubDomain';
|
import { SubDomain } from '../support/domain/SubDomain';
|
||||||
@ -21,6 +23,8 @@ import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
|
|||||||
import { EntityClass } from '../support/entity/EntityClass';
|
import { EntityClass } from '../support/entity/EntityClass';
|
||||||
import { TableClass } from '../support/entity/TableClass';
|
import { TableClass } from '../support/entity/TableClass';
|
||||||
import { TopicClass } from '../support/entity/TopicClass';
|
import { TopicClass } from '../support/entity/TopicClass';
|
||||||
|
import { TeamClass } from '../support/team/TeamClass';
|
||||||
|
import { UserClass } from '../support/user/UserClass';
|
||||||
import {
|
import {
|
||||||
closeFirstPopupAlert,
|
closeFirstPopupAlert,
|
||||||
descriptionBox,
|
descriptionBox,
|
||||||
@ -29,6 +33,7 @@ import {
|
|||||||
NAME_MAX_LENGTH_VALIDATION_ERROR,
|
NAME_MAX_LENGTH_VALIDATION_ERROR,
|
||||||
NAME_VALIDATION_ERROR,
|
NAME_VALIDATION_ERROR,
|
||||||
redirectToHomePage,
|
redirectToHomePage,
|
||||||
|
uuid,
|
||||||
} from './common';
|
} from './common';
|
||||||
import { addOwner } from './entity';
|
import { addOwner } from './entity';
|
||||||
import { sidebarClick } from './sidebar';
|
import { sidebarClick } from './sidebar';
|
||||||
@ -144,7 +149,10 @@ export const selectDataProductFromTab = async (
|
|||||||
const dpRes = page.waitForResponse(
|
const dpRes = page.waitForResponse(
|
||||||
'/api/v1/search/query?*&from=0&size=50&index=data_product_search_index'
|
'/api/v1/search/query?*&from=0&size=50&index=data_product_search_index'
|
||||||
);
|
);
|
||||||
await page.getByText('Data Products').click();
|
await page
|
||||||
|
.locator('.domain-details-page-tabs')
|
||||||
|
.getByText('Data Products')
|
||||||
|
.click();
|
||||||
|
|
||||||
await dpRes;
|
await dpRes;
|
||||||
|
|
||||||
@ -601,3 +609,107 @@ export const addTagsAndGlossaryToDomain = async (
|
|||||||
// Add glossary term
|
// Add glossary term
|
||||||
await addTagOrTerm('glossary', glossaryTermFqn);
|
await addTagOrTerm('glossary', glossaryTermFqn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the active domain is set to All Domains (DEFAULT_DOMAIN_VALUE)
|
||||||
|
*/
|
||||||
|
export const verifyActiveDomainIsDefault = async (page: Page) => {
|
||||||
|
await expect(page.getByTestId('domain-dropdown')).toContainText(
|
||||||
|
'All Domains'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a complete environment for domain ownership testing
|
||||||
|
* Creates user, policy, role, domain, data product and assigns ownership
|
||||||
|
* Returns all created objects and a cleanup function
|
||||||
|
*/
|
||||||
|
export const setupDomainOwnershipTest = async (apiContext: any) => {
|
||||||
|
// Create all necessary resources
|
||||||
|
const dataConsumerUser = new UserClass();
|
||||||
|
const id = uuid();
|
||||||
|
const domainForTest = new Domain({
|
||||||
|
name: `PW_Domain_Owner_Rule_Testing-${id}`,
|
||||||
|
displayName: `PW_Domain_Owner_Rule_Testing-${id}`,
|
||||||
|
description: 'playwright domain description',
|
||||||
|
domainType: 'Aggregate',
|
||||||
|
fullyQualifiedName: `PW_Domain_Owner_Rule_Testing-${id}`,
|
||||||
|
});
|
||||||
|
const dataProductForTest = new DataProduct(
|
||||||
|
domainForTest,
|
||||||
|
`PW_DataProduct_Owner_Rule-${id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await dataConsumerUser.create(apiContext);
|
||||||
|
await domainForTest.create(apiContext);
|
||||||
|
|
||||||
|
// Setup permissions
|
||||||
|
const dataConsumerPolicy = new PolicyClass();
|
||||||
|
const dataConsumerRole = new RolesClass();
|
||||||
|
|
||||||
|
// Create domain access policy
|
||||||
|
const domainRule = [
|
||||||
|
{
|
||||||
|
name: 'DomainRule',
|
||||||
|
description: '',
|
||||||
|
resources: ['dataProduct', 'domain'],
|
||||||
|
operations: ['All'],
|
||||||
|
effect: 'allow',
|
||||||
|
condition: 'isOwner()',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await dataConsumerPolicy.create(apiContext, domainRule);
|
||||||
|
await dataConsumerRole.create(apiContext, [
|
||||||
|
dataConsumerPolicy.responseData.name,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await dataProductForTest.create(apiContext);
|
||||||
|
|
||||||
|
// Create team for the user
|
||||||
|
const dataConsumerTeam = new TeamClass({
|
||||||
|
name: `PW_data_consumer_team-${id}`,
|
||||||
|
displayName: `PW Data Consumer Team ${id}`,
|
||||||
|
description: 'playwright data consumer team description',
|
||||||
|
teamType: 'Group',
|
||||||
|
users: [dataConsumerUser.responseData.id ?? ''],
|
||||||
|
defaultRoles: [dataConsumerRole.responseData.id ?? ''],
|
||||||
|
});
|
||||||
|
|
||||||
|
await dataConsumerTeam.create(apiContext);
|
||||||
|
|
||||||
|
// Set domain ownership
|
||||||
|
await domainForTest.patch({
|
||||||
|
apiContext,
|
||||||
|
patchData: [
|
||||||
|
{
|
||||||
|
op: 'add',
|
||||||
|
path: '/owners/0',
|
||||||
|
value: {
|
||||||
|
id: dataConsumerUser.responseData.id,
|
||||||
|
type: 'user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return cleanup function and all created resources
|
||||||
|
const cleanup = async (apiContext1: APIRequestContext) => {
|
||||||
|
await dataProductForTest.delete(apiContext1);
|
||||||
|
await domainForTest.delete(apiContext1);
|
||||||
|
await dataConsumerUser.delete(apiContext1);
|
||||||
|
await dataConsumerTeam.delete(apiContext1);
|
||||||
|
await dataConsumerPolicy.delete(apiContext1);
|
||||||
|
await dataConsumerRole.delete(apiContext1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataConsumerUser,
|
||||||
|
domainForTest,
|
||||||
|
dataProductForTest,
|
||||||
|
dataConsumerTeam,
|
||||||
|
dataConsumerPolicy,
|
||||||
|
dataConsumerRole,
|
||||||
|
cleanup,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -50,7 +50,6 @@ import {
|
|||||||
DataProduct,
|
DataProduct,
|
||||||
} from '../../../generated/entity/domains/dataProduct';
|
} from '../../../generated/entity/domains/dataProduct';
|
||||||
import { Domain } from '../../../generated/entity/domains/domain';
|
import { Domain } from '../../../generated/entity/domains/domain';
|
||||||
import { Operation } from '../../../generated/entity/policies/policy';
|
|
||||||
import { Style } from '../../../generated/type/tagLabel';
|
import { Style } from '../../../generated/type/tagLabel';
|
||||||
import { useFqn } from '../../../hooks/useFqn';
|
import { useFqn } from '../../../hooks/useFqn';
|
||||||
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
|
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface';
|
||||||
@ -59,10 +58,7 @@ import { getEntityDeleteMessage } from '../../../utils/CommonUtils';
|
|||||||
import { getQueryFilterToIncludeDomain } from '../../../utils/DomainUtils';
|
import { getQueryFilterToIncludeDomain } from '../../../utils/DomainUtils';
|
||||||
import { getEntityName } from '../../../utils/EntityUtils';
|
import { getEntityName } from '../../../utils/EntityUtils';
|
||||||
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
|
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
|
||||||
import {
|
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
|
||||||
checkPermission,
|
|
||||||
DEFAULT_ENTITY_PERMISSION,
|
|
||||||
} from '../../../utils/PermissionsUtils';
|
|
||||||
import { getDomainPath } from '../../../utils/RouterUtils';
|
import { getDomainPath } from '../../../utils/RouterUtils';
|
||||||
import {
|
import {
|
||||||
escapeESReservedCharacters,
|
escapeESReservedCharacters,
|
||||||
@ -102,7 +98,7 @@ const DataProductsDetailsPage = ({
|
|||||||
}: DataProductsDetailsPageProps) => {
|
}: DataProductsDetailsPageProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { getEntityPermission, permissions } = usePermissionProvider();
|
const { getEntityPermission } = usePermissionProvider();
|
||||||
const { tab: activeTab, version } =
|
const { tab: activeTab, version } =
|
||||||
useParams<{ tab: string; version: string }>();
|
useParams<{ tab: string; version: string }>();
|
||||||
const { fqn: dataProductFqn } = useFqn();
|
const { fqn: dataProductFqn } = useFqn();
|
||||||
@ -158,7 +154,7 @@ const DataProductsDetailsPage = ({
|
|||||||
const {
|
const {
|
||||||
editDisplayNamePermission,
|
editDisplayNamePermission,
|
||||||
editAllPermission,
|
editAllPermission,
|
||||||
deleteDataProductPermision,
|
deleteDataProductPermission,
|
||||||
} = useMemo(() => {
|
} = useMemo(() => {
|
||||||
if (isVersionsView) {
|
if (isVersionsView) {
|
||||||
return {
|
return {
|
||||||
@ -168,44 +164,17 @@ const DataProductsDetailsPage = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const editDescription = checkPermission(
|
|
||||||
Operation.EditDescription,
|
|
||||||
ResourceEntity.DATA_PRODUCT,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
|
|
||||||
const editOwner = checkPermission(
|
|
||||||
Operation.EditOwners,
|
|
||||||
ResourceEntity.DATA_PRODUCT,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
|
|
||||||
const editAll = checkPermission(
|
|
||||||
Operation.EditAll,
|
|
||||||
ResourceEntity.DATA_PRODUCT,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
|
|
||||||
const editDisplayName = checkPermission(
|
|
||||||
Operation.EditDisplayName,
|
|
||||||
ResourceEntity.DATA_PRODUCT,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteDataProduct = checkPermission(
|
|
||||||
Operation.Delete,
|
|
||||||
ResourceEntity.DATA_PRODUCT,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
editDescriptionPermission: editDescription || editAll,
|
editDescriptionPermission:
|
||||||
editOwnerPermission: editOwner || editAll,
|
dataProductPermission.EditDescription || dataProductPermission.EditAll,
|
||||||
editAllPermission: editAll,
|
editOwnerPermission:
|
||||||
editDisplayNamePermission: editDisplayName || editAll,
|
dataProductPermission.EditOwners || dataProductPermission.EditAll,
|
||||||
deleteDataProductPermision: deleteDataProduct,
|
editAllPermission: dataProductPermission.EditAll,
|
||||||
|
editDisplayNamePermission:
|
||||||
|
dataProductPermission.EditDisplayName || dataProductPermission.EditAll,
|
||||||
|
deleteDataProductPermission: dataProductPermission.Delete,
|
||||||
};
|
};
|
||||||
}, [permissions, isVersionsView]);
|
}, [dataProductPermission, isVersionsView]);
|
||||||
|
|
||||||
const fetchDataProductAssets = async () => {
|
const fetchDataProductAssets = async () => {
|
||||||
if (dataProduct) {
|
if (dataProduct) {
|
||||||
@ -287,7 +256,7 @@ const DataProductsDetailsPage = ({
|
|||||||
},
|
},
|
||||||
] as ItemType[])
|
] as ItemType[])
|
||||||
: []),
|
: []),
|
||||||
...(deleteDataProductPermision
|
...(deleteDataProductPermission
|
||||||
? ([
|
? ([
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
|
@ -27,7 +27,7 @@ import { useForm } from 'antd/lib/form/Form';
|
|||||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { cloneDeep, isEmpty, toString } from 'lodash';
|
import { cloneDeep, isEmpty, isEqual, toString } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -73,6 +73,7 @@ import { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
|||||||
import { Domain } from '../../../generated/entity/domains/domain';
|
import { Domain } from '../../../generated/entity/domains/domain';
|
||||||
import { ChangeDescription } from '../../../generated/entity/type';
|
import { ChangeDescription } from '../../../generated/entity/type';
|
||||||
import { Style } from '../../../generated/type/tagLabel';
|
import { Style } from '../../../generated/type/tagLabel';
|
||||||
|
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||||
import { useFqn } from '../../../hooks/useFqn';
|
import { useFqn } from '../../../hooks/useFqn';
|
||||||
import { addDataProducts } from '../../../rest/dataProductAPI';
|
import { addDataProducts } from '../../../rest/dataProductAPI';
|
||||||
import { addDomains } from '../../../rest/domainAPI';
|
import { addDomains } from '../../../rest/domainAPI';
|
||||||
@ -128,6 +129,8 @@ const DomainDetailsPage = ({
|
|||||||
const { tab: activeTab, version } =
|
const { tab: activeTab, version } =
|
||||||
useParams<{ tab: string; version: string }>();
|
useParams<{ tab: string; version: string }>();
|
||||||
const { fqn: domainFqn } = useFqn();
|
const { fqn: domainFqn } = useFqn();
|
||||||
|
const { currentUser } = useApplicationStore();
|
||||||
|
|
||||||
const assetTabRef = useRef<AssetsTabRef>(null);
|
const assetTabRef = useRef<AssetsTabRef>(null);
|
||||||
const dataProductsTabRef = useRef<DataProductsTabRef>(null);
|
const dataProductsTabRef = useRef<DataProductsTabRef>(null);
|
||||||
const [domainPermission, setDomainPermission] = useState<OperationPermission>(
|
const [domainPermission, setDomainPermission] = useState<OperationPermission>(
|
||||||
@ -153,6 +156,11 @@ const DomainDetailsPage = ({
|
|||||||
|
|
||||||
const isSubDomain = useMemo(() => !isEmpty(domain.parent), [domain]);
|
const isSubDomain = useMemo(() => !isEmpty(domain.parent), [domain]);
|
||||||
|
|
||||||
|
const isOwner = useMemo(
|
||||||
|
() => domain.owners?.some((owner) => isEqual(owner.id, currentUser?.id)),
|
||||||
|
[domain, currentUser]
|
||||||
|
);
|
||||||
|
|
||||||
const breadcrumbs = useMemo(() => {
|
const breadcrumbs = useMemo(() => {
|
||||||
if (!domainFqn) {
|
if (!domainFqn) {
|
||||||
return [];
|
return [];
|
||||||
@ -217,7 +225,7 @@ const DomainDetailsPage = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(permissions.dataProduct.Create
|
...(isOwner || permissions.dataProduct.Create
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t('label.data-product-plural'),
|
label: t('label.data-product-plural'),
|
||||||
@ -283,7 +291,7 @@ const DomainDetailsPage = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const addDataProduct = useCallback(
|
const addDataProduct = useCallback(
|
||||||
async (formData: CreateDataProduct) => {
|
async (formData: CreateDomain | CreateDataProduct) => {
|
||||||
const data = {
|
const data = {
|
||||||
...formData,
|
...formData,
|
||||||
domain: domain.fullyQualifiedName,
|
domain: domain.fullyQualifiedName,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user