diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java index 81279cac27ce0..198c9d86c2117 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java @@ -1634,6 +1634,9 @@ private void configurePolicyResolvers(final RuntimeWiring.Builder builder) { })).dataFetcher("resolvedRoles", new LoadableTypeBatchResolver<>(dataHubRoleType, (env) -> { final ActorFilter filter = env.getSource(); return filter.getRoles(); + })).dataFetcher("resolvedOwnershipTypes", new LoadableTypeBatchResolver<>(ownershipType, (env) -> { + final ActorFilter filter = env.getSource(); + return filter.getResourceOwnersTypes(); }))); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyInfoPolicyMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyInfoPolicyMapper.java index fdcbb7eda8338..b9a6bf07be8c8 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyInfoPolicyMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyInfoPolicyMapper.java @@ -1,7 +1,7 @@ package com.linkedin.datahub.graphql.resolvers.policy.mappers; +import com.linkedin.common.UrnArray; import com.linkedin.common.urn.Urn; -import com.linkedin.datahub.graphql.generated.ActorFilter; import com.linkedin.datahub.graphql.generated.Policy; import com.linkedin.datahub.graphql.generated.PolicyMatchCondition; import com.linkedin.datahub.graphql.generated.PolicyMatchCriterion; @@ -9,6 +9,7 @@ import com.linkedin.datahub.graphql.generated.PolicyMatchFilter; import com.linkedin.datahub.graphql.generated.PolicyState; import com.linkedin.datahub.graphql.generated.PolicyType; +import com.linkedin.datahub.graphql.generated.ActorFilter; import com.linkedin.datahub.graphql.generated.ResourceFilter; import com.linkedin.datahub.graphql.types.common.mappers.UrnToEntityMapper; import com.linkedin.datahub.graphql.types.mappers.ModelMapper; @@ -53,6 +54,10 @@ private ActorFilter mapActors(final DataHubActorFilter actorFilter) { result.setAllGroups(actorFilter.isAllGroups()); result.setAllUsers(actorFilter.isAllUsers()); result.setResourceOwners(actorFilter.isResourceOwners()); + UrnArray resourceOwnersTypes = actorFilter.getResourceOwnersTypes(); + if (resourceOwnersTypes != null) { + result.setResourceOwnersTypes(resourceOwnersTypes.stream().map(Urn::toString).collect(Collectors.toList())); + } if (actorFilter.hasGroups()) { result.setGroups(actorFilter.getGroups().stream().map(Urn::toString).collect(Collectors.toList())); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyUpdateInputInfoMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyUpdateInputInfoMapper.java index 1e0f41c68b32e..cb323b60dd465 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyUpdateInputInfoMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/policy/mappers/PolicyUpdateInputInfoMapper.java @@ -51,6 +51,9 @@ private DataHubActorFilter mapActors(final ActorFilterInput actorInput) { result.setAllGroups(actorInput.getAllGroups()); result.setAllUsers(actorInput.getAllUsers()); result.setResourceOwners(actorInput.getResourceOwners()); + if (actorInput.getResourceOwnersTypes() != null) { + result.setResourceOwnersTypes(new UrnArray(actorInput.getResourceOwnersTypes().stream().map(this::createUrn).collect(Collectors.toList()))); + } if (actorInput.getGroups() != null) { result.setGroups(new UrnArray(actorInput.getGroups().stream().map(this::createUrn).collect(Collectors.toList()))); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/policy/DataHubPolicyMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/policy/DataHubPolicyMapper.java index 3d28446872a22..167e1615fc4cc 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/policy/DataHubPolicyMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/policy/DataHubPolicyMapper.java @@ -1,5 +1,6 @@ package com.linkedin.datahub.graphql.types.policy; +import com.linkedin.common.UrnArray; import com.linkedin.common.urn.Urn; import com.linkedin.data.DataMap; import com.linkedin.datahub.graphql.generated.ActorFilter; @@ -67,6 +68,11 @@ private ActorFilter mapActors(final DataHubActorFilter actorFilter) { result.setAllGroups(actorFilter.isAllGroups()); result.setAllUsers(actorFilter.isAllUsers()); result.setResourceOwners(actorFilter.isResourceOwners()); + // Change here is not executed at the moment - leaving it for the future + UrnArray resourceOwnersTypes = actorFilter.getResourceOwnersTypes(); + if (resourceOwnersTypes != null) { + result.setResourceOwnersTypes(resourceOwnersTypes.stream().map(Urn::toString).collect(Collectors.toList())); + } if (actorFilter.hasGroups()) { result.setGroups(actorFilter.getGroups().stream().map(Urn::toString).collect(Collectors.toList())); } diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index c0c3076291444..e45ecfe519d8b 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -8007,6 +8007,16 @@ type ActorFilter { """ resourceOwners: Boolean! + """ + Set of OwnershipTypes to apply the policy to (if resourceOwners field is set to True) + """ + resourceOwnersTypes: [String!] + + """ + Set of OwnershipTypes to apply the policy to (if resourceOwners field is set to True), resolved. + """ + resolvedOwnershipTypes: [OwnershipTypeEntity!] + """ Whether the filter should apply to all users """ @@ -8135,6 +8145,11 @@ input ActorFilterInput { """ resourceOwners: Boolean! + """ + Set of OwnershipTypes to apply the policy to (if resourceOwners field is set to True) + """ + resourceOwnersTypes: [String!] + """ Whether the filter should apply to all users """ diff --git a/datahub-web-react/src/app/permissions/policy/ManagePolicies.tsx b/datahub-web-react/src/app/permissions/policy/ManagePolicies.tsx index 369e6a76cf7dd..08327d40a7165 100644 --- a/datahub-web-react/src/app/permissions/policy/ManagePolicies.tsx +++ b/datahub-web-react/src/app/permissions/policy/ManagePolicies.tsx @@ -109,6 +109,7 @@ const toPolicyInput = (policy: Omit): PolicyUpdateInput => { allUsers: policy.actors.allUsers, allGroups: policy.actors.allGroups, resourceOwners: policy.actors.resourceOwners, + resourceOwnersTypes: policy.actors.resourceOwnersTypes, }, }; if (policy.resources !== null && policy.resources !== undefined) { diff --git a/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx b/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx index 785d0226dfe19..31b9472a7e53b 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx @@ -1,10 +1,12 @@ import React from 'react'; import { Form, Select, Switch, Tag, Typography } from 'antd'; import styled from 'styled-components'; +import { Maybe } from 'graphql/jsutils/Maybe'; import { useEntityRegistry } from '../../useEntityRegistry'; import { ActorFilter, CorpUser, EntityType, PolicyType, SearchResult } from '../../../types.generated'; import { useGetSearchResultsLazyQuery } from '../../../graphql/search.generated'; +import { useListOwnershipTypesQuery } from '../../../graphql/ownership.generated'; import { CustomAvatar } from '../../shared/avatar'; type Props = { @@ -36,6 +38,10 @@ const SearchResultContent = styled.div` align-items: center; `; +const OwnershipWrapper = styled.div` + margin-top: 12px; +`; + /** * Component used to construct the "actors" portion of a DataHub * access Policy by populating an ActorFilter object. @@ -46,12 +52,37 @@ export default function PolicyActorForm({ policyType, actors, setActors }: Props // Search for actors while building policy. const [userSearch, { data: userSearchData }] = useGetSearchResultsLazyQuery(); const [groupSearch, { data: groupSearchData }] = useGetSearchResultsLazyQuery(); - + const { data: ownershipData } = useListOwnershipTypesQuery({ + variables: { + input: {}, + }, + }); + const ownershipTypes = + ownershipData?.listOwnershipTypes?.ownershipTypes.filter((type) => type.urn !== 'urn:li:ownershipType:none') || + []; + const ownershipTypesMap = Object.fromEntries(ownershipTypes.map((type) => [type.urn, type.info?.name])); // Toggle the "Owners" switch const onToggleAppliesToOwners = (value: boolean) => { setActors({ ...actors, resourceOwners: value, + resourceOwnersTypes: value ? actors.resourceOwnersTypes : null, + }); + }; + + const onSelectOwnershipTypeActor = (newType: string) => { + const newResourceOwnersTypes: Maybe = [...(actors.resourceOwnersTypes || []), newType]; + setActors({ + ...actors, + resourceOwnersTypes: newResourceOwnersTypes, + }); + }; + + const onDeselectOwnershipTypeActor = (type: string) => { + const newResourceOwnersTypes: Maybe = actors.resourceOwnersTypes?.filter((u: string) => u !== type); + setActors({ + ...actors, + resourceOwnersTypes: newResourceOwnersTypes?.length ? newResourceOwnersTypes : null, }); }; @@ -175,6 +206,7 @@ export default function PolicyActorForm({ policyType, actors, setActors }: Props // Select dropdown values. const usersSelectValue = actors.allUsers ? ['All'] : actors.users || []; const groupsSelectValue = actors.allGroups ? ['All'] : actors.groups || []; + const ownershipTypesSelectValue = actors.resourceOwnersTypes || []; const tagRender = (props) => { // eslint-disable-next-line react/prop-types @@ -215,6 +247,36 @@ export default function PolicyActorForm({ policyType, actors, setActors }: Props selected privileges. + {actors.resourceOwners && ( + + + List of types of ownership which will be used to match owners. If empty, the policies + will applied to any type of ownership. + + + + )} )} Users}> diff --git a/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx b/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx index da599f53c3b50..68e91983babdb 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx @@ -103,6 +103,22 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege ); }; + const resourceOwnersField = (actors) => { + if (!actors?.resourceOwners) { + return No; + } + if ((actors?.resolvedOwnershipTypes?.length ?? 0) > 0) { + return ( +
+ {actors?.resolvedOwnershipTypes?.map((type) => ( + {type.info.name} + ))} +
+ ); + } + return Yes - All owners; + }; + return ( @@ -180,7 +196,7 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege
Applies to Owners - {policy?.actors?.resourceOwners ? 'True' : 'False'} + {resourceOwnersField(policy?.actors)}
Applies to Users diff --git a/datahub-web-react/src/graphql/policy.graphql b/datahub-web-react/src/graphql/policy.graphql index cb2b66ffc61a5..4ff25d5aa946b 100644 --- a/datahub-web-react/src/graphql/policy.graphql +++ b/datahub-web-react/src/graphql/policy.graphql @@ -34,6 +34,13 @@ query listPolicies($input: ListPoliciesInput!) { allUsers allGroups resourceOwners + resourceOwnersTypes + resolvedOwnershipTypes { + urn + info { + name + } + } resolvedUsers { username urn diff --git a/metadata-models/src/main/pegasus/com/linkedin/policy/DataHubActorFilter.pdl b/metadata-models/src/main/pegasus/com/linkedin/policy/DataHubActorFilter.pdl index 307ba2ba3d09a..5fcb0819d9f69 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/policy/DataHubActorFilter.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/policy/DataHubActorFilter.pdl @@ -1,6 +1,7 @@ namespace com.linkedin.policy import com.linkedin.common.Urn +import com.linkedin.ownership.OwnershipTypeInfo /** * Information used to filter DataHub actors. @@ -23,6 +24,11 @@ record DataHubActorFilter { */ resourceOwners: boolean = false + /** + * Define type of ownership for the policy + */ + resourceOwnersTypes: optional array[Urn] + /** * Whether the filter should apply to all users. */ diff --git a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/PolicyEngine.java b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/PolicyEngine.java index 44b0c0d56f6fe..6a36fac7de4e0 100644 --- a/metadata-service/auth-impl/src/main/java/com/datahub/authorization/PolicyEngine.java +++ b/metadata-service/auth-impl/src/main/java/com/datahub/authorization/PolicyEngine.java @@ -2,15 +2,19 @@ import com.datahub.authentication.Authentication; import com.google.common.collect.ImmutableSet; +import com.linkedin.common.Owner; +import com.linkedin.common.Ownership; import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.UrnUtils; import com.linkedin.data.template.StringArray; import com.linkedin.entity.EntityResponse; +import com.linkedin.entity.EnvelopedAspect; import com.linkedin.entity.EnvelopedAspectMap; import com.linkedin.entity.client.EntityClient; import com.linkedin.identity.GroupMembership; import com.linkedin.identity.NativeGroupMembership; import com.linkedin.identity.RoleMembership; +import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.policy.DataHubActorFilter; import com.linkedin.policy.DataHubPolicyInfo; @@ -28,6 +32,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -307,11 +312,34 @@ private boolean isOwnerMatch( if (!actorFilter.isResourceOwners() || !requestResource.isPresent()) { return false; } - return isActorOwner(actor, requestResource.get(), context); + List ownershipTypes = actorFilter.getResourceOwnersTypes(); + return isActorOwner(actor, requestResource.get(), ownershipTypes, context); } - private boolean isActorOwner(Urn actor, ResolvedResourceSpec resourceSpec, PolicyEvaluationContext context) { - Set owners = resourceSpec.getOwners(); + private Set getOwnersForType(ResourceSpec resourceSpec, List ownershipTypes) { + Urn entityUrn = UrnUtils.getUrn(resourceSpec.getResource()); + EnvelopedAspect ownershipAspect; + try { + EntityResponse response = _entityClient.getV2(entityUrn.getEntityType(), entityUrn, + Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME), _systemAuthentication); + if (response == null || !response.getAspects().containsKey(Constants.OWNERSHIP_ASPECT_NAME)) { + return Collections.emptySet(); + } + ownershipAspect = response.getAspects().get(Constants.OWNERSHIP_ASPECT_NAME); + } catch (Exception e) { + log.error("Error while retrieving ownership aspect for urn {}", entityUrn, e); + return Collections.emptySet(); + } + Ownership ownership = new Ownership(ownershipAspect.getValue().data()); + Stream ownersStream = ownership.getOwners().stream(); + if (ownershipTypes != null) { + ownersStream = ownersStream.filter(owner -> ownershipTypes.contains(owner.getTypeUrn())); + } + return ownersStream.map(owner -> owner.getOwner().toString()).collect(Collectors.toSet()); + } + + private boolean isActorOwner(Urn actor, ResolvedResourceSpec resourceSpec, List ownershipTypes, PolicyEvaluationContext context) { + Set owners = this.getOwnersForType(resourceSpec.getSpec(), ownershipTypes); if (isUserOwner(actor, owners)) { return true; } diff --git a/metadata-service/auth-impl/src/test/java/com/datahub/authorization/PolicyEngineTest.java b/metadata-service/auth-impl/src/test/java/com/datahub/authorization/PolicyEngineTest.java index 732b7633aad1a..99d8fee309d91 100644 --- a/metadata-service/auth-impl/src/test/java/com/datahub/authorization/PolicyEngineTest.java +++ b/metadata-service/auth-impl/src/test/java/com/datahub/authorization/PolicyEngineTest.java @@ -21,6 +21,7 @@ import com.linkedin.identity.CorpUserInfo; import com.linkedin.identity.GroupMembership; import com.linkedin.identity.RoleMembership; +import com.linkedin.metadata.Constants; import com.linkedin.policy.DataHubActorFilter; import com.linkedin.policy.DataHubPolicyInfo; import com.linkedin.policy.DataHubResourceFilter; @@ -51,6 +52,10 @@ public class PolicyEngineTest { private static final String DOMAIN_URN = "urn:li:domain:domain1"; + private static final String OWNERSHIP_TYPE_URN = "urn:li:ownershipType:__system__technical_owner"; + + private static final String OTHER_OWNERSHIP_TYPE_URN = "urn:li:ownershipType:__system__data_steward"; + private EntityClient _entityClient; private PolicyEngine _policyEngine; @@ -507,6 +512,13 @@ public void testEvaluatePolicyActorFilterUserResourceOwnersMatch() throws Except resourceFilter.setType("dataset"); dataHubPolicyInfo.setResources(resourceFilter); + final EntityResponse entityResponse = new EntityResponse(); + final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); + aspectMap.put(OWNERSHIP_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(createOwnershipAspect(true, false).data()))); + entityResponse.setAspects(aspectMap); + when(_entityClient.getV2(eq(resourceUrn.getEntityType()), eq(resourceUrn), eq(Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME)), + any())).thenReturn(entityResponse); + ResolvedResourceSpec resourceSpec = buildResourceResolvers("dataset", RESOURCE_URN, ImmutableSet.of(AUTHORIZED_PRINCIPAL), Collections.emptySet()); // Assert authorized user can edit entity tags, because he is a user owner. @@ -520,6 +532,84 @@ public void testEvaluatePolicyActorFilterUserResourceOwnersMatch() throws Except eq(null), any()); } + @Test + public void testEvaluatePolicyActorFilterUserResourceOwnersTypeMatch() throws Exception { + + final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo(); + dataHubPolicyInfo.setType(METADATA_POLICY_TYPE); + dataHubPolicyInfo.setState(ACTIVE_POLICY_STATE); + dataHubPolicyInfo.setPrivileges(new StringArray("EDIT_ENTITY_TAGS")); + dataHubPolicyInfo.setDisplayName("My Test Display"); + dataHubPolicyInfo.setDescription("My test display!"); + dataHubPolicyInfo.setEditable(true); + + final DataHubActorFilter actorFilter = new DataHubActorFilter(); + actorFilter.setResourceOwners(true); + actorFilter.setAllUsers(false); + actorFilter.setAllGroups(false); + actorFilter.setResourceOwnersTypes(new UrnArray(ImmutableList.of(Urn.createFromString(OWNERSHIP_TYPE_URN)))); + dataHubPolicyInfo.setActors(actorFilter); + + final DataHubResourceFilter resourceFilter = new DataHubResourceFilter(); + resourceFilter.setAllResources(true); + resourceFilter.setType("dataset"); + dataHubPolicyInfo.setResources(resourceFilter); + + final EntityResponse entityResponse = new EntityResponse(); + final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); + aspectMap.put(OWNERSHIP_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(createOwnershipAspectWithTypeUrn(OWNERSHIP_TYPE_URN).data()))); + entityResponse.setAspects(aspectMap); + when(_entityClient.getV2(eq(resourceUrn.getEntityType()), eq(resourceUrn), eq(Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME)), + any())).thenReturn(entityResponse); + + ResolvedResourceSpec resourceSpec = + buildResourceResolvers("dataset", RESOURCE_URN, ImmutableSet.of(AUTHORIZED_PRINCIPAL), Collections.emptySet()); + + PolicyEngine.PolicyEvaluationResult result1 = + _policyEngine.evaluatePolicy(dataHubPolicyInfo, AUTHORIZED_PRINCIPAL, "EDIT_ENTITY_TAGS", + Optional.of(resourceSpec)); + assertTrue(result1.isGranted()); + } + + @Test + public void testEvaluatePolicyActorFilterUserResourceOwnersTypeNoMatch() throws Exception { + + final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo(); + dataHubPolicyInfo.setType(METADATA_POLICY_TYPE); + dataHubPolicyInfo.setState(ACTIVE_POLICY_STATE); + dataHubPolicyInfo.setPrivileges(new StringArray("EDIT_ENTITY_TAGS")); + dataHubPolicyInfo.setDisplayName("My Test Display"); + dataHubPolicyInfo.setDescription("My test display!"); + dataHubPolicyInfo.setEditable(true); + + final DataHubActorFilter actorFilter = new DataHubActorFilter(); + actorFilter.setResourceOwners(true); + actorFilter.setAllUsers(false); + actorFilter.setAllGroups(false); + actorFilter.setResourceOwnersTypes(new UrnArray(ImmutableList.of(Urn.createFromString(OWNERSHIP_TYPE_URN)))); + dataHubPolicyInfo.setActors(actorFilter); + + final DataHubResourceFilter resourceFilter = new DataHubResourceFilter(); + resourceFilter.setAllResources(true); + resourceFilter.setType("dataset"); + dataHubPolicyInfo.setResources(resourceFilter); + + final EntityResponse entityResponse = new EntityResponse(); + final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); + aspectMap.put(OWNERSHIP_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(createOwnershipAspectWithTypeUrn(OTHER_OWNERSHIP_TYPE_URN).data()))); + entityResponse.setAspects(aspectMap); + when(_entityClient.getV2(eq(resourceUrn.getEntityType()), eq(resourceUrn), eq(Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME)), + any())).thenReturn(entityResponse); + + ResolvedResourceSpec resourceSpec = + buildResourceResolvers("dataset", RESOURCE_URN, ImmutableSet.of(AUTHORIZED_PRINCIPAL), Collections.emptySet()); + + PolicyEngine.PolicyEvaluationResult result1 = + _policyEngine.evaluatePolicy(dataHubPolicyInfo, AUTHORIZED_PRINCIPAL, "EDIT_ENTITY_TAGS", + Optional.of(resourceSpec)); + assertFalse(result1.isGranted()); + } + @Test public void testEvaluatePolicyActorFilterGroupResourceOwnersMatch() throws Exception { @@ -542,6 +632,13 @@ public void testEvaluatePolicyActorFilterGroupResourceOwnersMatch() throws Excep resourceFilter.setType("dataset"); dataHubPolicyInfo.setResources(resourceFilter); + final EntityResponse entityResponse = new EntityResponse(); + final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); + aspectMap.put(OWNERSHIP_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(createOwnershipAspect(false, true).data()))); + entityResponse.setAspects(aspectMap); + when(_entityClient.getV2(eq(resourceUrn.getEntityType()), eq(resourceUrn), eq(Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME)), + any())).thenReturn(entityResponse); + ResolvedResourceSpec resourceSpec = buildResourceResolvers("dataset", RESOURCE_URN, ImmutableSet.of(AUTHORIZED_GROUP), Collections.emptySet()); // Assert authorized user can edit entity tags, because he is a user owner. @@ -905,6 +1002,12 @@ public void testGetGrantedPrivileges() throws Exception { _policyEngine.getGrantedPrivileges(policies, UrnUtils.getUrn(AUTHORIZED_PRINCIPAL), Optional.of(resourceSpec)), ImmutableList.of("PRIVILEGE_1")); + final EntityResponse entityResponse = new EntityResponse(); + final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); + aspectMap.put(OWNERSHIP_ASPECT_NAME, new EnvelopedAspect().setValue(new Aspect(createOwnershipAspect(true, false).data()))); + entityResponse.setAspects(aspectMap); + when(_entityClient.getV2(eq(resourceUrn.getEntityType()), eq(resourceUrn), eq(Collections.singleton(Constants.OWNERSHIP_ASPECT_NAME)), + any())).thenReturn(entityResponse); resourceSpec = buildResourceResolvers("dataset", RESOURCE_URN, Collections.singleton(AUTHORIZED_PRINCIPAL), Collections.singleton(DOMAIN_URN)); // Is owner assertEquals( @@ -1034,6 +1137,20 @@ private Ownership createOwnershipAspect(final Boolean addUserOwner, final Boolea return ownershipAspect; } + private Ownership createOwnershipAspectWithTypeUrn(final String typeUrn) throws Exception { + final Ownership ownershipAspect = new Ownership(); + final OwnerArray owners = new OwnerArray(); + + final Owner userOwner = new Owner(); + userOwner.setOwner(Urn.createFromString(AUTHORIZED_PRINCIPAL)); + userOwner.setTypeUrn(Urn.createFromString(typeUrn)); + owners.add(userOwner); + + ownershipAspect.setOwners(owners); + ownershipAspect.setLastModified(new AuditStamp().setTime(0).setActor(Urn.createFromString("urn:li:corpuser:foo"))); + return ownershipAspect; + } + private EntityResponse createAuthorizedEntityResponse() throws URISyntaxException { final EntityResponse entityResponse = new EntityResponse(); final EnvelopedAspectMap aspectMap = new EnvelopedAspectMap(); diff --git a/metadata-service/restli-api/src/main/snapshot/com.linkedin.entity.entities.snapshot.json b/metadata-service/restli-api/src/main/snapshot/com.linkedin.entity.entities.snapshot.json index e0ea8ed8bd81b..83ecaf41022c4 100644 --- a/metadata-service/restli-api/src/main/snapshot/com.linkedin.entity.entities.snapshot.json +++ b/metadata-service/restli-api/src/main/snapshot/com.linkedin.entity.entities.snapshot.json @@ -5287,6 +5287,14 @@ "type" : "boolean", "doc" : "Whether the filter should return true for owners of a particular resource.\nOnly applies to policies of type 'Metadata', which have a resource associated with them.", "default" : false + }, { + "name" : "resourceOwnersTypes", + "type" : { + "type" : "array", + "items" : "com.linkedin.common.Urn" + }, + "doc" : "Define type of ownership for the policy", + "optional" : true }, { "name" : "allUsers", "type" : "boolean", diff --git a/metadata-service/restli-api/src/main/snapshot/com.linkedin.platform.platform.snapshot.json b/metadata-service/restli-api/src/main/snapshot/com.linkedin.platform.platform.snapshot.json index dceb4ef1c1d75..2676c2687bd72 100644 --- a/metadata-service/restli-api/src/main/snapshot/com.linkedin.platform.platform.snapshot.json +++ b/metadata-service/restli-api/src/main/snapshot/com.linkedin.platform.platform.snapshot.json @@ -5281,6 +5281,14 @@ "type" : "boolean", "doc" : "Whether the filter should return true for owners of a particular resource.\nOnly applies to policies of type 'Metadata', which have a resource associated with them.", "default" : false + }, { + "name" : "resourceOwnersTypes", + "type" : { + "type" : "array", + "items" : "com.linkedin.common.Urn" + }, + "doc" : "Define type of ownership for the policy", + "optional" : true }, { "name" : "allUsers", "type" : "boolean",