From c923b8b9d25e2ecbb3beb2bd76f5935410bd1c85 Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Wed, 16 Aug 2023 10:38:40 +0530 Subject: [PATCH 1/9] New privilege for Associate tags --- .../exception/DataHubGraphQLErrorCode.java | 1 + .../exception/TagAuthorizationException.java | 16 ++ .../resolvers/mutate/AddTagResolver.java | 5 + .../resolvers/mutate/AddTagsResolver.java | 7 + .../mutate/BatchAddTagsResolver.java | 9 +- .../mutate/BatchRemoveTagsResolver.java | 12 ++ .../resolvers/mutate/RemoveTagResolver.java | 4 + .../resolvers/mutate/util/LabelUtils.java | 15 ++ .../src/main/resources/entity.graphql | 4 + .../src/app/entity/shared/utils.ts | 7 + .../permissions/policy/PolicyDetailsModal.tsx | 17 +- .../policy/PolicyPrivilegeForm.tsx | 165 ++++++++++++------ .../src/app/permissions/policy/policyUtils.ts | 25 ++- .../src/app/shared/tags/AddTagsTermsModal.tsx | 8 +- docs/authorization/policies.md | 1 + docs/authorization/roles.md | 1 + docs/tags.md | 1 + .../linkedin/policy/PolicyMatchCondition.pdl | 5 + .../datahub/authorization/PolicyEngine.java | 2 + ...com.linkedin.entity.entities.snapshot.json | 5 +- ...m.linkedin.platform.platform.snapshot.json | 5 +- .../authorization/PoliciesConfig.java | 7 +- 22 files changed, 256 insertions(+), 66 deletions(-) create mode 100644 datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java index db3e1dd03e419..6b72e83b1510f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java @@ -4,6 +4,7 @@ public enum DataHubGraphQLErrorCode { BAD_REQUEST(400), UNAUTHORIZED(403), NOT_FOUND(404), + UNAUTHORIZED_TAG_ERROR(405), SERVER_ERROR(500); private final int _code; diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java new file mode 100644 index 0000000000000..6e158af0d9e70 --- /dev/null +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java @@ -0,0 +1,16 @@ +package com.linkedin.datahub.graphql.exception; + + +/** + * Exception thrown when update fails due to ASSOCIATE_TAG privilege. + */ +public class TagAuthorizationException extends DataHubGraphQLException { + + public TagAuthorizationException(String message) { + super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR); + } + + public TagAuthorizationException(String message, Throwable cause) { + super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR); + } +} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java index 78d2341492b39..92fa619dc560e 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java @@ -6,6 +6,7 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; +import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.generated.TagAssociationInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -35,6 +36,10 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); } + if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { + throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + } + return CompletableFuture.supplyAsync(() -> { LabelUtils.validateResourceAndLabel( tagUrn, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java index 7174f3edffee6..4fa737dece8d7 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java @@ -7,6 +7,7 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; +import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.AddTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -43,6 +44,12 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); } + tagUrns.forEach((tagUrn) -> { + if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { + throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + } + }); + LabelUtils.validateResourceAndLabel( tagUrns, targetUrn, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java index 9c5cddb3c50bc..52ba259c6143b 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java @@ -4,6 +4,7 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; +import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.BatchAddTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -45,7 +46,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw return CompletableFuture.supplyAsync(() -> { // First, validate the batch - validateTags(tagUrns); + validateTags(tagUrns, context); if (resources.size() == 1 && resources.get(0).getSubResource() != null) { return handleAddTagsToSingleSchemaField(context, resources, tagUrns); @@ -117,9 +118,13 @@ private Boolean attemptBatchAddTagsWithSiblings( } } - private void validateTags(List tagUrns) { + private void validateTags(List tagUrns, QueryContext context) { for (Urn tagUrn : tagUrns) { LabelUtils.validateLabel(tagUrn, Constants.TAG_ENTITY_NAME, _entityService); + + if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { + throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + } } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java index ab432f0afcaec..5f7cd6486d6d0 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java @@ -4,6 +4,7 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; +import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.BatchRemoveTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -37,6 +38,9 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw return CompletableFuture.supplyAsync(() -> { // First, validate the batch + validateTags(tagUrns, context); + + // Next, validate the batch validateInputResources(resources, context); try { @@ -50,6 +54,14 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw }); } + private void validateTags(List tagUrns, QueryContext context) { + for (Urn tagUrn : tagUrns) { + if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { + throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + } + } + } + private void validateInputResources(List resources, QueryContext context) { for (ResourceRefInput resource : resources) { validateInputResource(resource, context); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java index 33a95c3576061..5fdb610a66188 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java @@ -5,6 +5,7 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; +import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.generated.TagAssociationInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -33,6 +34,9 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw if (!LabelUtils.isAuthorizedToUpdateTags(environment.getContext(), targetUrn, input.getSubResource())) { throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); } + if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { + throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + } return CompletableFuture.supplyAsync(() -> { LabelUtils.validateResourceAndLabel( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java index a93c7d5b333da..78c338c490805 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java @@ -201,6 +201,21 @@ public static boolean isAuthorizedToUpdateTags(@Nonnull QueryContext context, Ur orPrivilegeGroups); } + public static boolean isAuthorizedToAssociateTag(@Nonnull QueryContext context, Urn targetUrn) { + + // Decide whether the current principal should be allowed to associate the tag + final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of( + new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.ASSOCIATE_TAGS_PRIVILEGE.getType())) + )); + + return AuthorizationUtils.isAuthorized( + context.getAuthorizer(), + context.getActorUrn(), + targetUrn.getEntityType(), + targetUrn.toString(), + orPrivilegeGroups); + } + public static boolean isAuthorizedToUpdateTerms(@Nonnull QueryContext context, Urn targetUrn, String subResource) { Boolean isTargetingSchema = subResource != null && subResource.length() > 0; diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index b1f9d57300177..cfe4edc4d3c8c 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -8200,6 +8200,10 @@ enum PolicyMatchCondition { Whether the field matches the value """ EQUALS + """ + Whether the field does not the value + """ + NOT_EQUALS } """ diff --git a/datahub-web-react/src/app/entity/shared/utils.ts b/datahub-web-react/src/app/entity/shared/utils.ts index 7ec604785d1ff..0e2f7e46a69f4 100644 --- a/datahub-web-react/src/app/entity/shared/utils.ts +++ b/datahub-web-react/src/app/entity/shared/utils.ts @@ -145,6 +145,13 @@ export const handleBatchError = (urns, e, defaultMessage) => { duration: 3, }; } + if (urns.length > 1 && getGraphqlErrorCode(e) === 405) { + return { + content: + 'Only users granted permission to this tag can assign or remove it. The bulk edit being performed will not be saved. ', + duration: 3, + }; + } return defaultMessage; }; diff --git a/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx b/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx index 68e91983babdb..81889bb444083 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyDetailsModal.tsx @@ -3,9 +3,14 @@ import { Link } from 'react-router-dom'; import { Button, Divider, Modal, Tag, Typography } from 'antd'; import styled from 'styled-components'; import { useEntityRegistry } from '../../useEntityRegistry'; -import { Maybe, Policy, PolicyState, PolicyType } from '../../../types.generated'; +import { Maybe, Policy, PolicyMatchCondition, PolicyState, PolicyType } from '../../../types.generated'; import { useAppConfig } from '../../useAppConfig'; -import { convertLegacyResourceFilter, getFieldValues, mapResourceTypeToDisplayName } from './policyUtils'; +import { + convertLegacyResourceFilter, + getFieldCondition, + getFieldValues, + mapResourceTypeToDisplayName, +} from './policyUtils'; import AvatarsGroup from '../AvatarsGroup'; type PrivilegeOptionType = { @@ -69,6 +74,7 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege const resources = convertLegacyResourceFilter(policy?.resources); const resourceTypes = getFieldValues(resources?.filter, 'RESOURCE_TYPE') || []; const resourceEntities = getFieldValues(resources?.filter, 'RESOURCE_URN') || []; + const policyMatchCondition = getFieldCondition(resources?.filter, 'RESOURCE_URN'); const domains = getFieldValues(resources?.filter, 'DOMAIN') || []; const { @@ -157,6 +163,13 @@ export default function PolicyDetailsModal({ policy, visible, onClose, privilege ); })) || All} +
+ Asset Condition + + + {policyMatchCondition === PolicyMatchCondition.NotEquals ? 'Excludes' : 'Includes'} + +
Assets diff --git a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx index c57273c2ea3d9..a63e8b0138c87 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx @@ -1,6 +1,6 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; -import { Form, Select, Tag, Tooltip, Typography } from 'antd'; +import { Form, Select, Tag, Tooltip, Typography, Checkbox } from 'antd'; import styled from 'styled-components/macro'; import { useEntityRegistry } from '../../useEntityRegistry'; @@ -9,13 +9,14 @@ import { useGetSearchResultsForMultipleLazyQuery, useGetSearchResultsLazyQuery, } from '../../../graphql/search.generated'; -import { ResourceFilter, PolicyType, EntityType } from '../../../types.generated'; +import { ResourceFilter, PolicyType, EntityType, PolicyMatchCondition } from '../../../types.generated'; import { convertLegacyResourceFilter, createCriterionValue, createCriterionValueWithEntity, EMPTY_POLICY, getFieldValues, + getFieldCondition, mapResourceTypeToDisplayName, mapResourceTypeToEntityType, mapResourceTypeToPrivileges, @@ -65,6 +66,8 @@ export default function PolicyPrivilegeForm({ const resourceTypes = getFieldValues(resources.filter, 'RESOURCE_TYPE') || []; const resourceEntities = getFieldValues(resources.filter, 'RESOURCE_URN') || []; + const matchConditionInitial = getFieldCondition(resources.filter, 'RESOURCE_URN'); + const [matchCondition, setMatchCondition] = useState(matchConditionInitial); const getDisplayName = (entity) => { if (!entity) { return null; @@ -139,10 +142,12 @@ export default function PolicyPrivilegeForm({ }; setResources({ ...resources, - filter: setFieldValues(filter, 'RESOURCE_TYPE', [ - ...resourceTypes, - createCriterionValue(selectedResourceType), - ]), + filter: setFieldValues( + filter, + 'RESOURCE_TYPE', + [...resourceTypes, createCriterionValue(selectedResourceType)], + PolicyMatchCondition.Equals, + ), }); }; @@ -156,6 +161,7 @@ export default function PolicyPrivilegeForm({ filter, 'RESOURCE_TYPE', resourceTypes?.filter((criterionValue) => criterionValue.value !== deselectedResourceType), + PolicyMatchCondition.Equals, ), }); }; @@ -167,13 +173,18 @@ export default function PolicyPrivilegeForm({ }; setResources({ ...resources, - filter: setFieldValues(filter, 'RESOURCE_URN', [ - ...resourceEntities, - createCriterionValueWithEntity( - resource, - getEntityFromSearchResults(resourceSearchResults, resource) || null, - ), - ]), + filter: setFieldValues( + filter, + 'RESOURCE_URN', + [ + ...resourceEntities, + createCriterionValueWithEntity( + resource, + getEntityFromSearchResults(resourceSearchResults, resource) || null, + ), + ], + matchCondition, + ), }); }; @@ -188,19 +199,39 @@ export default function PolicyPrivilegeForm({ filter, 'RESOURCE_URN', resourceEntities?.filter((criterionValue) => criterionValue.value !== resource), + matchCondition, ), }); }; + const updateMatchConditionInResources = (excludeResource) => { + const filter = resources.filter || { + criteria: [], + }; + setResources({ + ...resources, + filter: setFieldValues( + filter, + 'RESOURCE_URN', + resourceEntities, + excludeResource ? PolicyMatchCondition.NotEquals : PolicyMatchCondition.Equals, + ), + }); + }; // When a domain is selected, add its urn to the list of domains const onSelectDomain = (domain) => { const filter = resources.filter || { criteria: [], }; - const updatedFilter = setFieldValues(filter, 'DOMAIN', [ - ...domains, - createCriterionValueWithEntity(domain, getEntityFromSearchResults(domainSearchResults, domain) || null), - ]); + const updatedFilter = setFieldValues( + filter, + 'DOMAIN', + [ + ...domains, + createCriterionValueWithEntity(domain, getEntityFromSearchResults(domainSearchResults, domain) || null), + ], + PolicyMatchCondition.Equals, + ); setResources({ ...resources, filter: updatedFilter, @@ -218,6 +249,7 @@ export default function PolicyPrivilegeForm({ filter, 'DOMAIN', domains?.filter((criterionValue) => criterionValue.value !== domain), + PolicyMatchCondition.Equals, ), }); }; @@ -276,6 +308,23 @@ export default function PolicyPrivilegeForm({ : displayStr; }; + const getResourceText = (policyMatch) => { + if (policyMatch === PolicyMatchCondition.Equals) { + return ( + + Search for specific resources the policy should apply to. If none is selected, policy is + applied to all resources of the given type. + + ); + } + return ( + + Search for specific resource(s) the policy exclusion should apply to. If none is selected, policy + is applied to all resources of the given type. + + ); + }; + return ( {showResourceFilterInput && ( @@ -309,37 +358,55 @@ export default function PolicyPrivilegeForm({ )} {showResourceFilterInput && ( - Resource}> - - Search for specific resources the policy should apply to. If none is selected, policy is - applied to all resources of the given type. - - - + <> + Resource Condition}> + + Selecting the checkbox below will exclude selected resource from the policy. If not + selected, resources selected will be included in the policy. + + { + setMatchCondition( + value?.target?.checked + ? PolicyMatchCondition.NotEquals + : PolicyMatchCondition.Equals, + ); + updateMatchConditionInResources(value?.target?.checked); + }} + > + Exclude Resources + + + Resource}> + {getResourceText(matchCondition)} + + + )} {showResourceFilterInput && ( Domain}> diff --git a/datahub-web-react/src/app/permissions/policy/policyUtils.ts b/datahub-web-react/src/app/permissions/policy/policyUtils.ts index c7af7342f6efa..3724c403e3d45 100644 --- a/datahub-web-react/src/app/permissions/policy/policyUtils.ts +++ b/datahub-web-react/src/app/permissions/policy/policyUtils.ts @@ -82,10 +82,11 @@ export const mapResourceTypeToPrivileges = ( export const createCriterion = ( resourceFieldType: string, fieldValues: Array, + condition: PolicyMatchCondition, ): PolicyMatchCriterion => ({ field: resourceFieldType, values: fieldValues, - condition: PolicyMatchCondition.Equals, + condition, }); export const createCriterionValue = (value: string): PolicyMatchCriterionValue => ({ value }); @@ -99,10 +100,18 @@ export const convertLegacyResourceFilter = (resourceFilter: Maybe(); if (resourceFilter.type) { - criteria.push(createCriterion('RESOURCE_TYPE', [createCriterionValue(resourceFilter.type)])); + criteria.push( + createCriterion('RESOURCE_TYPE', [createCriterionValue(resourceFilter.type)], PolicyMatchCondition.Equals), + ); } if (resourceFilter.resources && resourceFilter.resources.length > 0) { - criteria.push(createCriterion('RESOURCE_URN', resourceFilter.resources.map(createCriterionValue))); + criteria.push( + createCriterion( + 'RESOURCE_URN', + resourceFilter.resources.map(createCriterionValue), + PolicyMatchCondition.Equals, + ), + ); } return { filter: { @@ -115,14 +124,22 @@ export const getFieldValues = (filter: Maybe | undefined, res return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.values || []; }; +export const getFieldCondition = (filter: Maybe | undefined, resourceFieldType: string) => { + return ( + filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.condition || + PolicyMatchCondition.Equals + ); +}; + export const setFieldValues = ( filter: PolicyMatchFilter, resourceFieldType: string, fieldValues: Array, + condition: PolicyMatchCondition, ): PolicyMatchFilter => { const restCriteria = filter.criteria?.filter((criterion) => criterion.field !== resourceFieldType) || []; if (fieldValues.length === 0) { return { ...filter, criteria: restCriteria }; } - return { ...filter, criteria: [...restCriteria, createCriterion(resourceFieldType, fieldValues)] }; + return { ...filter, criteria: [...restCriteria, createCriterion(resourceFieldType, fieldValues, condition)] }; }; diff --git a/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx b/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx index 01e11ceb9a738..f74232172a6ff 100644 --- a/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx +++ b/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx @@ -268,7 +268,7 @@ export default function EditTagTermsModal({ .catch((e) => { message.destroy(); message.error( - handleBatchError(urns, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), + handleBatchError(resources, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), ); }) .finally(() => { @@ -298,7 +298,7 @@ export default function EditTagTermsModal({ .catch((e) => { message.destroy(); message.error( - handleBatchError(urns, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), + handleBatchError(resources, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), ); }) .finally(() => { @@ -328,7 +328,7 @@ export default function EditTagTermsModal({ .catch((e) => { message.destroy(); message.error( - handleBatchError(urns, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), + handleBatchError(resources, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), ); }) .finally(() => { @@ -358,7 +358,7 @@ export default function EditTagTermsModal({ .catch((e) => { message.destroy(); message.error( - handleBatchError(urns, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), + handleBatchError(resources, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), ); }) .finally(() => { diff --git a/docs/authorization/policies.md b/docs/authorization/policies.md index 27d8b15e5a73a..c6a3a904a0ae7 100644 --- a/docs/authorization/policies.md +++ b/docs/authorization/policies.md @@ -126,6 +126,7 @@ We currently support the following: | Dataset | View Dataset Usage | Allow actor to access usage metadata about a dataset both in the UI and in the GraphQL API. This includes example queries, number of queries, etc. Also applies to REST APIs when REST API Authorization is enabled. | | Dataset | View Dataset Profile | Allow actor to access a dataset's profile both in the UI and in the GraphQL API. This includes snapshot statistics like #rows, #columns, null percentage per field, etc. | | Tag | Edit Tag Color | Allow actor to change the color of a Tag. | +| Tag | Associate Tags | Allow actor to associate the tag to an asset. | | Group | Edit Group Members | Allow actor to add and remove members to a group. | | User | Edit User Profile | Allow actor to change the user's profile including display name, bio, title, profile image, etc. | | User + Group | Edit Contact Information | Allow actor to change the contact information such as email & chat handles. | diff --git a/docs/authorization/roles.md b/docs/authorization/roles.md index b25579072980d..f9e0354642417 100644 --- a/docs/authorization/roles.md +++ b/docs/authorization/roles.md @@ -117,6 +117,7 @@ These privileges are common to both Self-Hosted DataHub and Managed DataHub. | Manage Dataset Column Tags | :heavy_check_mark: | :heavy_check_mark: | :x: | | Manage Dataset Column Glossary Terms | :heavy_check_mark: | :heavy_check_mark: | :x: | | Edit Tag Color | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Associate Tags | :heavy_check_mark: | :heavy_check_mark: | :x: | | Edit User Profile | :heavy_check_mark: | :heavy_check_mark: | :x: | | Edit Contact Info | :heavy_check_mark: | :heavy_check_mark: | :x: | diff --git a/docs/tags.md b/docs/tags.md index 945b514dc7b47..0ff3cb7408fb2 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -17,6 +17,7 @@ What you need to add tags: * **Edit Tags** metadata privilege to add tags at the entity level * **Edit Dataset Column Tags** to edit tags at the column level +* **Associate Tags** metadata privilege at tag entity to associate that tag at the entity level or column level You can create these privileges by creating a new [Metadata Policy](./authorization/policies.md). diff --git a/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl b/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl index 0c51e7072ebd2..4ae3384a1d84b 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl @@ -8,4 +8,9 @@ enum PolicyMatchCondition { * Whether the field matches the value */ EQUALS + + /** + * Whether the field matches the value + */ + NOT_EQUALS } \ No newline at end of file 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 6a36fac7de4e0..0ef939c73d909 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 @@ -251,6 +251,8 @@ private boolean checkCriterion(final PolicyMatchCriterion criterion, final Resol private boolean checkCondition(Set fieldValues, String filterValue, PolicyMatchCondition condition) { if (condition == PolicyMatchCondition.EQUALS) { return fieldValues.contains(filterValue); + } else if (condition == PolicyMatchCondition.NOT_EQUALS) { + return !fieldValues.contains(filterValue); } log.error("Unsupported condition {}", condition); return false; 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 83ecaf41022c4..5b0818fc0a257 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 @@ -5234,9 +5234,10 @@ "type" : "enum", "name" : "PolicyMatchCondition", "doc" : "The matching condition in a filter criterion", - "symbols" : [ "EQUALS" ], + "symbols" : [ "EQUALS", "NOT_EQUALS" ], "symbolDocs" : { - "EQUALS" : "Whether the field matches the value" + "EQUALS" : "Whether the field matches the value", + "NOT_EQUALS" : "Whether the field matches the value" } }, "doc" : "The condition for the criterion", 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 2676c2687bd72..27f3913141d09 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 @@ -5228,9 +5228,10 @@ "type" : "enum", "name" : "PolicyMatchCondition", "doc" : "The matching condition in a filter criterion", - "symbols" : [ "EQUALS" ], + "symbols" : [ "EQUALS", "NOT_EQUALS" ], "symbolDocs" : { - "EQUALS" : "Whether the field matches the value" + "EQUALS" : "Whether the field matches the value", + "NOT_EQUALS" : "Whether the field matches the value" } }, "doc" : "The condition for the criterion", diff --git a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java index c46d02a6eadf0..0395343cf89e9 100644 --- a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java +++ b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java @@ -265,6 +265,11 @@ public class PoliciesConfig { "Edit Tag Color", "The ability to change the color of a Tag."); + public static final Privilege ASSOCIATE_TAGS_PRIVILEGE = Privilege.of( + "ASSOCIATE_TAGS_PRIVILEGE", + "Associate Tags", + "The ability to associate this tag to an asset."); + // Group Privileges public static final Privilege EDIT_GROUP_MEMBERS_PRIVILEGE = Privilege.of( "EDIT_GROUP_MEMBERS", @@ -430,7 +435,7 @@ public class PoliciesConfig { "Tags", "Tags indexed by DataHub", ImmutableList.of(VIEW_ENTITY_PAGE_PRIVILEGE, EDIT_ENTITY_OWNERS_PRIVILEGE, EDIT_TAG_COLOR_PRIVILEGE, - EDIT_ENTITY_DOCS_PRIVILEGE, EDIT_ENTITY_PRIVILEGE, DELETE_ENTITY_PRIVILEGE) + ASSOCIATE_TAGS_PRIVILEGE, EDIT_ENTITY_DOCS_PRIVILEGE, EDIT_ENTITY_PRIVILEGE, DELETE_ENTITY_PRIVILEGE) ); // Container Privileges From f60a413afd0c52dcab75f7d66ddee06ae579d27c Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Wed, 16 Aug 2023 19:03:26 +0530 Subject: [PATCH 2/9] Add Associate_tags policy for admin and editors --- metadata-service/war/src/main/resources/boot/policies.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/metadata-service/war/src/main/resources/boot/policies.json b/metadata-service/war/src/main/resources/boot/policies.json index 3fddf3456ecd7..ce45883805a1c 100644 --- a/metadata-service/war/src/main/resources/boot/policies.json +++ b/metadata-service/war/src/main/resources/boot/policies.json @@ -258,7 +258,8 @@ "GET_ENTITY_PRIVILEGE", "GET_TIMELINE_PRIVILEGE", "PRODUCE_PLATFORM_EVENT_PRIVILEGE", - "MANAGE_DATA_PRODUCTS" + "MANAGE_DATA_PRODUCTS", + "ASSOCIATE_TAGS_PRIVILEGE" ], "displayName":"Admins - Metadata Policy", "description":"Admins have all metadata privileges.", @@ -341,7 +342,8 @@ "GET_ENTITY_PRIVILEGE", "GET_TIMELINE_PRIVILEGE", "PRODUCE_PLATFORM_EVENT_PRIVILEGE", - "MANAGE_DATA_PRODUCTS" + "MANAGE_DATA_PRODUCTS", + "ASSOCIATE_TAGS_PRIVILEGE" ], "displayName":"Editors - Metadata Policy", "description":"Editors have all metadata privileges.", From 5067773a49117c0435f6d1d5594f016392780fed Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Fri, 18 Aug 2023 20:58:30 +0530 Subject: [PATCH 3/9] Set Tag Authorization error as 403 error code --- .../exception/DataHubGraphQLErrorCode.java | 1 - .../exception/TagAuthorizationException.java | 16 ---------------- .../graphql/resolvers/mutate/AddTagResolver.java | 3 +-- .../resolvers/mutate/AddTagsResolver.java | 3 +-- .../resolvers/mutate/BatchAddTagsResolver.java | 3 +-- .../mutate/BatchRemoveTagsResolver.java | 3 +-- .../resolvers/mutate/RemoveTagResolver.java | 3 +-- datahub-web-react/src/app/entity/shared/utils.ts | 13 ++++++------- 8 files changed, 11 insertions(+), 34 deletions(-) delete mode 100644 datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java index 6b72e83b1510f..db3e1dd03e419 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubGraphQLErrorCode.java @@ -4,7 +4,6 @@ public enum DataHubGraphQLErrorCode { BAD_REQUEST(400), UNAUTHORIZED(403), NOT_FOUND(404), - UNAUTHORIZED_TAG_ERROR(405), SERVER_ERROR(500); private final int _code; diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java deleted file mode 100644 index 6e158af0d9e70..0000000000000 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/TagAuthorizationException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.linkedin.datahub.graphql.exception; - - -/** - * Exception thrown when update fails due to ASSOCIATE_TAG privilege. - */ -public class TagAuthorizationException extends DataHubGraphQLException { - - public TagAuthorizationException(String message) { - super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR); - } - - public TagAuthorizationException(String message, Throwable cause) { - super(message, DataHubGraphQLErrorCode.UNAUTHORIZED_TAG_ERROR); - } -} diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java index 92fa619dc560e..6c00ae37cdf81 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java @@ -6,7 +6,6 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.generated.TagAssociationInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -37,7 +36,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw } if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); } return CompletableFuture.supplyAsync(() -> { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java index 4fa737dece8d7..64e3ed1c0e663 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java @@ -7,7 +7,6 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.AddTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -46,7 +45,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw tagUrns.forEach((tagUrn) -> { if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); } }); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java index 52ba259c6143b..87436e7c0e465 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java @@ -4,7 +4,6 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.BatchAddTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -123,7 +122,7 @@ private void validateTags(List tagUrns, QueryContext context) { LabelUtils.validateLabel(tagUrn, Constants.TAG_ENTITY_NAME, _entityService); if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { - throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); } } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java index 5f7cd6486d6d0..af30e1c4ee957 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java @@ -4,7 +4,6 @@ import com.linkedin.common.urn.UrnUtils; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.BatchRemoveTagsInput; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -57,7 +56,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw private void validateTags(List tagUrns, QueryContext context) { for (Urn tagUrn : tagUrns) { if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { - throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); } } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java index 5fdb610a66188..9eaa87179cbf5 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java @@ -5,7 +5,6 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; -import com.linkedin.datahub.graphql.exception.TagAuthorizationException; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.generated.TagAssociationInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils; @@ -35,7 +34,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); } if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new TagAuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); } return CompletableFuture.supplyAsync(() -> { diff --git a/datahub-web-react/src/app/entity/shared/utils.ts b/datahub-web-react/src/app/entity/shared/utils.ts index 0e2f7e46a69f4..712b5f61f002a 100644 --- a/datahub-web-react/src/app/entity/shared/utils.ts +++ b/datahub-web-react/src/app/entity/shared/utils.ts @@ -139,19 +139,18 @@ function getGraphqlErrorCode(e) { export const handleBatchError = (urns, e, defaultMessage) => { if (urns.length > 1 && getGraphqlErrorCode(e) === 403) { + if (e.message === 'Only users granted permission to this tag can assign or remove it') { + return { + content: `${e.message}. The bulk edit being performed will not be saved.`, + duration: 3, + }; + } return { content: 'Your bulk edit selection included entities that you are unauthorized to update. The bulk edit being performed will not be saved.', duration: 3, }; } - if (urns.length > 1 && getGraphqlErrorCode(e) === 405) { - return { - content: - 'Only users granted permission to this tag can assign or remove it. The bulk edit being performed will not be saved. ', - duration: 3, - }; - } return defaultMessage; }; From 2750e54683d392070fa1fc2a656f80d22db05854 Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Sun, 27 Aug 2023 08:48:42 +0530 Subject: [PATCH 4/9] Add Associate_Tags privilege for datahub --- metadata-service/war/src/main/resources/boot/policies.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metadata-service/war/src/main/resources/boot/policies.json b/metadata-service/war/src/main/resources/boot/policies.json index c750bc8f8c37a..00f6eda7b43ad 100644 --- a/metadata-service/war/src/main/resources/boot/policies.json +++ b/metadata-service/war/src/main/resources/boot/policies.json @@ -63,7 +63,8 @@ "GET_TIMELINE_PRIVILEGE", "PRODUCE_PLATFORM_EVENT_PRIVILEGE", "MANAGE_DATA_PRODUCTS", - "MANAGE_GLOBAL_OWNERSHIP_TYPES" + "MANAGE_GLOBAL_OWNERSHIP_TYPES", + "ASSOCIATE_TAGS_PRIVILEGE" ], "displayName":"Root User - Edit and View All Resources", "description":"Grants full edit and view privileges for all resources to root 'datahub' root user.", From 80efa9427ead79d4dc0b853b385c07eed24488b3 Mon Sep 17 00:00:00 2001 From: Meenakshi Kamalaseshan Radha <62914384+mkamalas@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:12:19 +0530 Subject: [PATCH 5/9] For NOT_EQUALS, check all resources match the condition --- .../main/java/com/datahub/authorization/PolicyEngine.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 0ef939c73d909..81066567732a3 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 @@ -243,6 +243,12 @@ private boolean checkCriterion(final PolicyMatchCriterion criterion, final Resol } Set fieldValues = resource.getFieldValues(resourceFieldType); + //For PolicyMatchCondition.NOT_EQUALS, we need to make sure the condition is not satistified for all of the resources specified + if (criterion.getCondition() == PolicyMatchCondition.NOT_EQUALS) { + return criterion.getValues() + .stream() + .allMatch(filterValue -> checkCondition(fieldValues, filterValue, criterion.getCondition())); + } return criterion.getValues() .stream() .anyMatch(filterValue -> checkCondition(fieldValues, filterValue, criterion.getCondition())); From 193a09343985d03940e0b77706126e6066941750 Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Fri, 30 Aug 2024 13:26:32 +0530 Subject: [PATCH 6/9] Format and linting fixes --- .../resolvers/mutate/AddTagResolver.java | 5 +- .../resolvers/mutate/AddTagsResolver.java | 14 +++-- .../mutate/BatchAddTagsResolver.java | 9 +-- .../mutate/BatchRemoveTagsResolver.java | 3 +- .../resolvers/mutate/RemoveTagResolver.java | 3 +- .../resolvers/mutate/util/LabelUtils.java | 8 ++- .../policy/PolicyPrivilegeForm.tsx | 59 ++++++++++--------- .../src/app/permissions/policy/policyUtils.ts | 8 ++- .../datahub/authorization/PolicyEngine.java | 10 ++-- .../authorization/PoliciesConfig.java | 9 +-- 10 files changed, 74 insertions(+), 54 deletions(-) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java index cf1359712dfb7..55ab957ff911e 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagResolver.java @@ -39,9 +39,10 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw } if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException( + "Only users granted permission to this tag can assign or remove it"); } - + return GraphQLConcurrencyUtils.supplyAsync( () -> { LabelUtils.validateResourceAndLabel( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java index 46380263bca9f..89f338216785a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddTagsResolver.java @@ -43,12 +43,14 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw "Unauthorized to perform this action. Please contact your DataHub administrator."); } - tagUrns.forEach((tagUrn) -> { - if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); - } - }); - + tagUrns.forEach( + (tagUrn) -> { + if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { + throw new AuthorizationException( + "Only users granted permission to this tag can assign or remove it"); + } + }); + LabelUtils.validateResourceAndLabel( context.getOperationContext(), tagUrns, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java index ff53c83a7a881..eddc4c52f4f6f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchAddTagsResolver.java @@ -15,7 +15,6 @@ import com.linkedin.metadata.entity.EntityService; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; -import io.datahubproject.metadata.context.OperationContext; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -129,10 +128,12 @@ private Boolean attemptBatchAddTagsWithSiblings( private void validateTags(@Nonnull QueryContext context, List tagUrns) { for (Urn tagUrn : tagUrns) { - LabelUtils.validateLabel(context.getOperationContext(), tagUrn, Constants.TAG_ENTITY_NAME, _entityService); - + LabelUtils.validateLabel( + context.getOperationContext(), tagUrn, Constants.TAG_ENTITY_NAME, _entityService); + if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { - throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException( + "Only users granted permission to this tag can assign or remove it"); } } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java index 820c80bec0905..ee2ef38cd9572 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/BatchRemoveTagsResolver.java @@ -63,7 +63,8 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw private void validateTags(List tagUrns, QueryContext context) { for (Urn tagUrn : tagUrns) { if (!LabelUtils.isAuthorizedToAssociateTag(context, tagUrn)) { - throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException( + "Only users granted permission to this tag can assign or remove it"); } } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java index 02e0916ad8bf3..635fa51fc82e3 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/RemoveTagResolver.java @@ -37,7 +37,8 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw "Unauthorized to perform this action. Please contact your DataHub administrator."); } if (!LabelUtils.isAuthorizedToAssociateTag(environment.getContext(), tagUrn)) { - throw new AuthorizationException("Only users granted permission to this tag can assign or remove it"); + throw new AuthorizationException( + "Only users granted permission to this tag can assign or remove it"); } return GraphQLConcurrencyUtils.supplyAsync( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java index 199f16b4bcf6b..dfa4221f8f68e 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java @@ -261,9 +261,11 @@ public static boolean isAuthorizedToUpdateTags( public static boolean isAuthorizedToAssociateTag(@Nonnull QueryContext context, Urn targetUrn) { // Decide whether the current principal should be allowed to associate the tag - final DisjunctivePrivilegeGroup orPrivilegeGroups = new DisjunctivePrivilegeGroup(ImmutableList.of( - new ConjunctivePrivilegeGroup(ImmutableList.of(PoliciesConfig.ASSOCIATE_TAGS_PRIVILEGE.getType())) - )); + final DisjunctivePrivilegeGroup orPrivilegeGroups = + new DisjunctivePrivilegeGroup( + ImmutableList.of( + new ConjunctivePrivilegeGroup( + ImmutableList.of(PoliciesConfig.ASSOCIATE_TAGS_PRIVILEGE.getType())))); return AuthorizationUtils.isAuthorized( context.getAuthorizer(), diff --git a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx index 4c106850182e0..1fef250a8e251 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { Form, Select, Tag, Tooltip, Typography, Tag as CustomTag, Checkbox } from 'antd'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; import styled from 'styled-components/macro'; import { useEntityRegistry } from '../../useEntityRegistry'; @@ -183,7 +184,12 @@ export default function PolicyPrivilegeForm({ }; setResources({ ...resources, - filter: setFieldValues(filter, 'TYPE', [...resourceTypes, createCriterionValue(selectedResourceType)], PolicyMatchCondition.Equals), + filter: setFieldValues( + filter, + 'TYPE', + [...resourceTypes, createCriterionValue(selectedResourceType)], + PolicyMatchCondition.Equals, + ), }); }; @@ -209,15 +215,18 @@ export default function PolicyPrivilegeForm({ }; setResources({ ...resources, - filter: setFieldValues(filter, 'URN', [ - ...resourceEntities, - createCriterionValueWithEntity( - resource, - getEntityFromSearchResults(resourceSearchResults, resource) || null, - ), - ], - matchCondition, - ), + filter: setFieldValues( + filter, + 'URN', + [ + ...resourceEntities, + createCriterionValueWithEntity( + resource, + getEntityFromSearchResults(resourceSearchResults, resource) || null, + ), + ], + matchCondition, + ), }); }; @@ -258,13 +267,10 @@ export default function PolicyPrivilegeForm({ }; const domainEntity = domainObj || getEntityFromSearchResults(domainSearchResults, domainUrn); const updatedFilter = setFieldValues( - filter, - 'DOMAIN', - [ - ...domains, - createCriterionValueWithEntity(domainUrn, domainEntity || null), - ], - PolicyMatchCondition.Equals, + filter, + 'DOMAIN', + [...domains, createCriterionValueWithEntity(domainUrn, domainEntity || null)], + PolicyMatchCondition.Equals, ); setResources({ ...resources, @@ -412,7 +418,7 @@ export default function PolicyPrivilegeForm({ setSelectedTags(editTags); setResources({ ...resources, - filter: setFieldValues(filter, 'TAG', [...(newTag as any)]), + filter: setFieldValues(filter, 'TAG', [...(newTag as any)], PolicyMatchCondition.Equals), }); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -426,7 +432,7 @@ export default function PolicyPrivilegeForm({ setResources({ ...resources, - filter: setFieldValues(filter, 'TAG', [...tags, urn as any]), + filter: setFieldValues(filter, 'TAG', [...tags, urn as any], PolicyMatchCondition.Equals), }); setSelectedTags([...(selectedTags as any), selectedTagOption]); if (inputEl && inputEl.current) { @@ -448,6 +454,7 @@ export default function PolicyPrivilegeForm({ filter, 'TAG', tags?.filter((criterionValue) => (criterionValue as any) !== urn), + PolicyMatchCondition.Equals, ), }); }; @@ -540,13 +547,11 @@ export default function PolicyPrivilegeForm({ { + onChange={(event: CheckboxChangeEvent) => { setMatchCondition( - value?.target?.checked - ? PolicyMatchCondition.NotEquals - : PolicyMatchCondition.Equals, + event.target.checked ? PolicyMatchCondition.NotEquals : PolicyMatchCondition.Equals, ); - updateMatchConditionInResources(value?.target?.checked); + updateMatchConditionInResources(event.target.checked); }} > Exclude Resources @@ -576,9 +581,9 @@ export default function PolicyPrivilegeForm({ )} > {resourceSearchResults?.map((result) => ( - - {renderSearchResult(result)} - + + {renderSearchResult(result)} + ))} diff --git a/datahub-web-react/src/app/permissions/policy/policyUtils.ts b/datahub-web-react/src/app/permissions/policy/policyUtils.ts index 5e20f105f0dca..dd2f61773d009 100644 --- a/datahub-web-react/src/app/permissions/policy/policyUtils.ts +++ b/datahub-web-react/src/app/permissions/policy/policyUtils.ts @@ -103,10 +103,14 @@ export const convertLegacyResourceFilter = (resourceFilter: Maybe(); if (resourceFilter.type) { - criteria.push(createCriterion('TYPE', [createCriterionValue(resourceFilter.type)]), PolicyMatchCondition.Equals,); + criteria.push( + createCriterion('TYPE', [createCriterionValue(resourceFilter.type)], PolicyMatchCondition.Equals), + ); } if (resourceFilter.resources && resourceFilter.resources.length > 0) { - criteria.push(createCriterion('URN', resourceFilter.resources.map(createCriterionValue), PolicyMatchCondition.Equals)); + criteria.push( + createCriterion('URN', resourceFilter.resources.map(createCriterionValue), PolicyMatchCondition.Equals), + ); } return { filter: { 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 39ef3ea100718..94e3f8870d823 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 @@ -245,11 +245,13 @@ private boolean checkCriterion( } Set fieldValues = resource.getFieldValues(entityFieldType); - //For PolicyMatchCondition.NOT_EQUALS, we need to make sure the condition is not satistified for all of the resources specified + + // For PolicyMatchCondition.NOT_EQUALS, we need to make sure the condition is not satistified + // for all of the resources specified if (criterion.getCondition() == PolicyMatchCondition.NOT_EQUALS) { - return criterion.getValues() - .stream() - .allMatch(filterValue -> checkCondition(fieldValues, filterValue, criterion.getCondition())); + return criterion.getValues().stream() + .allMatch( + filterValue -> checkCondition(fieldValues, filterValue, criterion.getCondition())); } return criterion.getValues().stream() .anyMatch( diff --git a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java index 0d694f3e45aa6..705d3994c20d7 100644 --- a/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java +++ b/metadata-utils/src/main/java/com/linkedin/metadata/authorization/PoliciesConfig.java @@ -376,10 +376,11 @@ public class PoliciesConfig { public static final Privilege EDIT_TAG_COLOR_PRIVILEGE = Privilege.of("EDIT_TAG_COLOR", "Edit Tag Color", "The ability to change the color of a Tag."); - public static final Privilege ASSOCIATE_TAGS_PRIVILEGE = Privilege.of( - "ASSOCIATE_TAGS_PRIVILEGE", - "Associate Tags", - "The ability to associate this tag to an asset."); + public static final Privilege ASSOCIATE_TAGS_PRIVILEGE = + Privilege.of( + "ASSOCIATE_TAGS_PRIVILEGE", + "Associate Tags", + "The ability to associate this tag to an asset."); // Group Privileges public static final Privilege EDIT_GROUP_MEMBERS_PRIVILEGE = From f37a80717b1335aec5c60eb648d660250c852446 Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Fri, 30 Aug 2024 21:52:15 +0530 Subject: [PATCH 7/9] Fix documentation for NOT_EQUALS --- datahub-graphql-core/src/main/resources/entity.graphql | 2 +- docs/authorization/roles.md | 2 +- .../main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl | 2 +- .../main/snapshot/com.linkedin.entity.entities.snapshot.json | 2 +- .../main/snapshot/com.linkedin.platform.platform.snapshot.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index 3c95c37e51613..46ddf4d1d48cd 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -9153,7 +9153,7 @@ enum PolicyMatchCondition { """ EQUALS """ - Whether the field does not the value + Whether the field does not match the value """ NOT_EQUALS } diff --git a/docs/authorization/roles.md b/docs/authorization/roles.md index 3cc9f9a33ac43..15f36c21143c7 100644 --- a/docs/authorization/roles.md +++ b/docs/authorization/roles.md @@ -129,7 +129,7 @@ These privileges are common to both Self-Hosted DataHub and DataHub Cloud. | Edit Dataset Column Glossary Terms | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to edit the column (field) glossary terms associated with a dataset schema. | | Edit Dataset Column Descriptions | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to edit the column (field) descriptions associated with a dataset schema. | | Edit Tag Color | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to change the color of a Tag. | -| Associate Tags | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to to associate the tag to an entity. | +| Associate Tags | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to associate the tag to an entity. | | Edit Lineage | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to add and remove lineage edges for this entity. | | Edit Dataset Queries | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to edit the Queries for a Dataset. | | Manage Data Products | :heavy_check_mark: | :heavy_check_mark: | :x: | The ability to create, edit, and delete Data Products within a Domain | diff --git a/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl b/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl index 4ae3384a1d84b..4c1e936d48d5d 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/policy/PolicyMatchCondition.pdl @@ -10,7 +10,7 @@ enum PolicyMatchCondition { EQUALS /** - * Whether the field matches the value + * Whether the field does not match the value */ NOT_EQUALS } \ No newline at end of file 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 056919f9e12c0..541575cc33a8a 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 @@ -5477,7 +5477,7 @@ "symbols" : [ "EQUALS", "NOT_EQUALS" ], "symbolDocs" : { "EQUALS" : "Whether the field matches the value", - "NOT_EQUALS" : "Whether the field matches the value" + "NOT_EQUALS" : "Whether the field does not match the value" } }, "doc" : "The condition for the criterion", 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 07d7175bb7ec6..4ad7010c3e8e2 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 @@ -5471,7 +5471,7 @@ "symbols" : [ "EQUALS", "NOT_EQUALS" ], "symbolDocs" : { "EQUALS" : "Whether the field matches the value", - "NOT_EQUALS" : "Whether the field matches the value" + "NOT_EQUALS" : "Whether the field does not match the value" } }, "doc" : "The condition for the criterion", From 4c1aa72cd2b27be161ae5b636d1324f7bd9e9f40 Mon Sep 17 00:00:00 2001 From: Kamalaseshan Radha Date: Mon, 9 Sep 2024 11:22:10 +0530 Subject: [PATCH 8/9] Resolve merge conflict issue --- .../src/app/permissions/policy/PolicyPrivilegeForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx index 1fef250a8e251..4a863eb1fddfb 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyPrivilegeForm.tsx @@ -107,7 +107,7 @@ export default function PolicyPrivilegeForm({ const resourceTypes = getFieldValues(resources.filter, 'TYPE') || []; const resourceEntities = getFieldValues(resources.filter, 'URN') || []; - const matchConditionInitial = getFieldCondition(resources.filter, 'RESOURCE_URN'); + const matchConditionInitial = getFieldCondition(resources.filter, 'URN'); const [matchCondition, setMatchCondition] = useState(matchConditionInitial); const getDisplayName = (entity) => { if (!entity) { @@ -254,7 +254,7 @@ export default function PolicyPrivilegeForm({ ...resources, filter: setFieldValues( filter, - 'RESOURCE_URN', + 'URN', resourceEntities, excludeResource ? PolicyMatchCondition.NotEquals : PolicyMatchCondition.Equals, ), From f703c9518765d04fa729aa58d58b796c71324e47 Mon Sep 17 00:00:00 2001 From: Meenakshi Kamalaseshan Radha <62914384+mkamalas@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:07:57 +0530 Subject: [PATCH 9/9] Update metadata-service/auth-impl/src/main/java/com/datahub/authorization/PolicyEngine.java Co-authored-by: RyanHolstien --- .../src/main/java/com/datahub/authorization/PolicyEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 94e3f8870d823..1608cf2379704 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 @@ -246,7 +246,7 @@ private boolean checkCriterion( Set fieldValues = resource.getFieldValues(entityFieldType); - // For PolicyMatchCondition.NOT_EQUALS, we need to make sure the condition is not satistified + // For PolicyMatchCondition.NOT_EQUALS, we need to make sure the condition is not satisfied // for all of the resources specified if (criterion.getCondition() == PolicyMatchCondition.NOT_EQUALS) { return criterion.getValues().stream()