From 94e7e51175660afbfb7b5cf198a3263f30d56f62 Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Tue, 9 May 2023 04:16:05 -0400 Subject: [PATCH] fix(privileges) Use glossary term manage children privileges for edit docs and links (#7985) --- .../datahub/graphql/GmsGraphQLEngine.java | 4 ++-- .../resolvers/mutate/AddLinkResolver.java | 18 +++++++++++++++++- .../mutate/UpdateDescriptionResolver.java | 17 ++++++++++++----- docs/authorization/policies.md | 4 ++-- docs/glossary/business-glossary.md | 7 +++++++ 5 files changed, 40 insertions(+), 10 deletions(-) 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 1289059e8a..38e3cd4eeb 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 @@ -819,13 +819,13 @@ public class GmsGraphQLEngine { .dataFetcher("createPolicy", new UpsertPolicyResolver(this.entityClient)) .dataFetcher("updatePolicy", new UpsertPolicyResolver(this.entityClient)) .dataFetcher("deletePolicy", new DeletePolicyResolver(this.entityClient)) - .dataFetcher("updateDescription", new UpdateDescriptionResolver(entityService)) + .dataFetcher("updateDescription", new UpdateDescriptionResolver(entityService, this.entityClient)) .dataFetcher("addOwner", new AddOwnerResolver(entityService)) .dataFetcher("addOwners", new AddOwnersResolver(entityService)) .dataFetcher("batchAddOwners", new BatchAddOwnersResolver(entityService)) .dataFetcher("removeOwner", new RemoveOwnerResolver(entityService)) .dataFetcher("batchRemoveOwners", new BatchRemoveOwnersResolver(entityService)) - .dataFetcher("addLink", new AddLinkResolver(entityService)) + .dataFetcher("addLink", new AddLinkResolver(entityService, this.entityClient)) .dataFetcher("removeLink", new RemoveLinkResolver(entityService)) .dataFetcher("addGroupMembers", new AddGroupMembersResolver(this.groupService)) .dataFetcher("removeGroupMembers", new RemoveGroupMembersResolver(this.groupService)) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddLinkResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddLinkResolver.java index 7b10a5ceeb..619ca95e7d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddLinkResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/AddLinkResolver.java @@ -6,7 +6,10 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; import com.linkedin.datahub.graphql.generated.AddLinkInput; +import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils; import com.linkedin.datahub.graphql.resolvers.mutate.util.LinkUtils; +import com.linkedin.entity.client.EntityClient; +import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -22,6 +25,7 @@ import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*; public class AddLinkResolver implements DataFetcher> { private final EntityService _entityService; + private final EntityClient _entityClient; @Override public CompletableFuture get(DataFetchingEnvironment environment) throws Exception { @@ -31,7 +35,7 @@ public class AddLinkResolver implements DataFetcher> String linkLabel = input.getLabel(); Urn targetUrn = Urn.createFromString(input.getResourceUrn()); - if (!LinkUtils.isAuthorizedToUpdateLinks(environment.getContext(), targetUrn)) { + if (!LinkUtils.isAuthorizedToUpdateLinks(environment.getContext(), targetUrn) && !canUpdateGlossaryEntityLinks(targetUrn, environment.getContext())) { throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); } @@ -60,4 +64,16 @@ public class AddLinkResolver implements DataFetcher> } }); } + + // Returns whether this is a glossary entity and whether you can edit this glossary entity with the + // Manage all children or Manage direct children privileges + private boolean canUpdateGlossaryEntityLinks(Urn targetUrn, QueryContext context) { + final boolean isGlossaryEntity = targetUrn.getEntityType().equals(Constants.GLOSSARY_TERM_ENTITY_NAME) + || targetUrn.getEntityType().equals(Constants.GLOSSARY_NODE_ENTITY_NAME); + if (!isGlossaryEntity) { + return false; + } + final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient); + return GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient); + } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateDescriptionResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateDescriptionResolver.java index c2fba695fc..10343a7a3a 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateDescriptionResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateDescriptionResolver.java @@ -5,7 +5,9 @@ import com.linkedin.common.urn.Urn; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.exception.AuthorizationException; import com.linkedin.datahub.graphql.generated.DescriptionUpdateInput; +import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils; import com.linkedin.datahub.graphql.resolvers.mutate.util.SiblingsUtils; +import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; import graphql.schema.DataFetcher; @@ -27,6 +29,7 @@ import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*; @RequiredArgsConstructor public class UpdateDescriptionResolver implements DataFetcher> { private final EntityService _entityService; + private final EntityClient _entityClient; @Override public CompletableFuture get(DataFetchingEnvironment environment) throws Exception { @@ -190,8 +193,10 @@ public class UpdateDescriptionResolver implements DataFetcher updateGlossaryTermDescription(Urn targetUrn, DescriptionUpdateInput input, QueryContext context) { return CompletableFuture.supplyAsync(() -> { - - if (!DescriptionUtils.isAuthorizedToUpdateDescription(context, targetUrn)) { + final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient); + if (!DescriptionUtils.isAuthorizedToUpdateDescription(context, targetUrn) + && !GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient) + ) { throw new AuthorizationException( "Unauthorized to perform this action. Please contact your DataHub administrator."); } @@ -214,8 +219,10 @@ public class UpdateDescriptionResolver implements DataFetcher updateGlossaryNodeDescription(Urn targetUrn, DescriptionUpdateInput input, QueryContext context) { return CompletableFuture.supplyAsync(() -> { - - if (!DescriptionUtils.isAuthorizedToUpdateDescription(context, targetUrn)) { + final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient); + if (!DescriptionUtils.isAuthorizedToUpdateDescription(context, targetUrn) + && !GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient) + ) { throw new AuthorizationException( "Unauthorized to perform this action. Please contact your DataHub administrator."); } @@ -259,7 +266,7 @@ public class UpdateDescriptionResolver implements DataFetcher updateNotebookDescription(Urn targetUrn, DescriptionUpdateInput input, QueryContext context) { return CompletableFuture.supplyAsync(() -> { diff --git a/docs/authorization/policies.md b/docs/authorization/policies.md index 27a8ded4ac..26d99af7c4 100644 --- a/docs/authorization/policies.md +++ b/docs/authorization/policies.md @@ -130,8 +130,8 @@ We currently support the following: | 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. | -| GlossaryNode | Manage Direct Glossary Children | Allow the actor to create and delete the direct children of the selected entities. | -| GlossaryNode | Manage All Glossary Children | Allow the actor to create and delete everything underneath the selected entities. | +| GlossaryNode | Manage Direct Glossary Children | Allow the actor to create, edit, and delete the direct children of the selected entities. | +| GlossaryNode | Manage All Glossary Children | Allow the actor to create, edit, and delete everything underneath the selected entities. | diff --git a/docs/glossary/business-glossary.md b/docs/glossary/business-glossary.md index 27d47acca6..faab6f12fc 100644 --- a/docs/glossary/business-glossary.md +++ b/docs/glossary/business-glossary.md @@ -85,6 +85,13 @@ In the modal that pops up you can select the Term you care about in one of two w ![](../imgs/glossary/add-term-modal.png) +## Privileges + +Glossary Terms and Term Groups abide by metadata policies like other entities. However, there are two special privileges provided for configuring privileges within your Business Glossary. + +- **Manage Direct Glossary Children**: If a user has this privilege on a Glossary Term Group, they will be able to create, edit, and delete Terms and Term Groups directly underneath the Term Group they have this privilege on. +- **Manage All Glossary Children**: If a user has this privilege on a Glossary Term Group, they will be able to create, edit, and delete any Term or Term Group anywhere underneath the Term Group they have this privilege on. This applies to the children of a child Term Group as well (and so on). + ## Managing Glossary with Git In many cases, it may be preferable to manage the Business Glossary in a version-control system like git. This can make