Skip to content

Commit

Permalink
feat(privileges) Create privileges to allow for managing children of …
Browse files Browse the repository at this point in the history
…entities (#6346)


Co-authored-by: Chris Collins <[email protected]>
Co-authored-by: Chris Collins <[email protected]>
  • Loading branch information
3 people authored Nov 3, 2022
1 parent 3301836 commit 8c783da
Show file tree
Hide file tree
Showing 25 changed files with 586 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
import com.linkedin.datahub.graphql.resolvers.domain.SetDomainResolver;
import com.linkedin.datahub.graphql.resolvers.domain.UnsetDomainResolver;
import com.linkedin.datahub.graphql.resolvers.entity.EntityExistsResolver;
import com.linkedin.datahub.graphql.resolvers.entity.EntityPrivilegesResolver;
import com.linkedin.datahub.graphql.resolvers.glossary.AddRelatedTermsResolver;
import com.linkedin.datahub.graphql.resolvers.glossary.CreateGlossaryNodeResolver;
import com.linkedin.datahub.graphql.resolvers.glossary.CreateGlossaryTermResolver;
Expand Down Expand Up @@ -799,10 +800,10 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
.dataFetcher("reportOperation", new ReportOperationResolver(this.entityClient))
.dataFetcher("createGlossaryTerm", new CreateGlossaryTermResolver(this.entityClient, this.entityService))
.dataFetcher("createGlossaryNode", new CreateGlossaryNodeResolver(this.entityClient, this.entityService))
.dataFetcher("updateParentNode", new UpdateParentNodeResolver(entityService))
.dataFetcher("updateParentNode", new UpdateParentNodeResolver(this.entityService, this.entityClient))
.dataFetcher("deleteGlossaryEntity",
new DeleteGlossaryEntityResolver(this.entityClient, this.entityService))
.dataFetcher("updateName", new UpdateNameResolver(entityService))
.dataFetcher("updateName", new UpdateNameResolver(this.entityService, this.entityClient))
.dataFetcher("addRelatedTerms", new AddRelatedTermsResolver(this.entityService))
.dataFetcher("removeRelatedTerms", new RemoveRelatedTermsResolver(this.entityService))
.dataFetcher("createNativeUserResetToken", new CreateNativeUserResetTokenResolver(this.nativeUserService))
Expand Down Expand Up @@ -1007,12 +1008,14 @@ private void configureGlossaryTermResolvers(final RuntimeWiring.Builder builder)
builder.type("GlossaryTerm", typeWiring -> typeWiring
.dataFetcher("schemaMetadata", new AspectResolver())
.dataFetcher("parentNodes", new ParentNodesResolver(entityClient))
.dataFetcher("privileges", new EntityPrivilegesResolver(entityClient))
);
}

private void configureGlossaryNodeResolvers(final RuntimeWiring.Builder builder) {
builder.type("GlossaryNode", typeWiring -> typeWiring
.dataFetcher("parentNodes", new ParentNodesResolver(entityClient))
.dataFetcher("privileges", new EntityPrivilegesResolver(entityClient))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.metadata.authorization.PoliciesConfig;

import java.time.Clock;
import java.util.Optional;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -64,10 +65,6 @@ public static boolean canManageDomains(@Nonnull QueryContext context) {
return isAuthorized(context, Optional.empty(), PoliciesConfig.MANAGE_DOMAINS_PRIVILEGE);
}

public static boolean canManageGlossaries(@Nonnull QueryContext context) {
return isAuthorized(context, Optional.empty(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE);
}

/**
* Returns true if the current used is able to create Tags. This is true if the user has the 'Manage Tags' or 'Create Tags' platform privilege.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Optional;



public class AccessTokenUtil {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.linkedin.datahub.graphql.resolvers.entity;

import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.Entity;
import com.linkedin.datahub.graphql.generated.EntityPrivileges;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CompletableFuture;

@Slf4j
public class EntityPrivilegesResolver implements DataFetcher<CompletableFuture<EntityPrivileges>> {

private final EntityClient _entityClient;

public EntityPrivilegesResolver(final EntityClient entityClient) {
_entityClient = entityClient;
}

@Override
public CompletableFuture<EntityPrivileges> get(DataFetchingEnvironment environment) {
final QueryContext context = environment.getContext();
final String urnString = ((Entity) environment.getSource()).getUrn();
final Urn urn = UrnUtils.getUrn(urnString);

return CompletableFuture.supplyAsync(() -> {
switch (urn.getEntityType()) {
case Constants.GLOSSARY_TERM_ENTITY_NAME:
return getGlossaryTermPrivileges(urn, context);
case Constants.GLOSSARY_NODE_ENTITY_NAME:
return getGlossaryNodePrivileges(urn, context);
default:
log.warn("Tried to get entity privileges for entity type {} but nothing is implemented for it yet", urn.getEntityType());
return new EntityPrivileges();
}
});
}

private EntityPrivileges getGlossaryTermPrivileges(Urn termUrn, QueryContext context) {
final EntityPrivileges result = new EntityPrivileges();
result.setCanManageEntity(false);
if (GlossaryUtils.canManageGlossaries(context)) {
result.setCanManageEntity(true);
return result;
}
Urn parentNodeUrn = GlossaryUtils.getParentUrn(termUrn, context, _entityClient);
if (parentNodeUrn != null) {
Boolean canManage = GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn);
result.setCanManageEntity(canManage);
}
return result;
}

private EntityPrivileges getGlossaryNodePrivileges(Urn nodeUrn, QueryContext context) {
final EntityPrivileges result = new EntityPrivileges();
result.setCanManageEntity(false);
if (GlossaryUtils.canManageGlossaries(context)) {
result.setCanManageEntity(true);
result.setCanManageChildren(true);
return result;
}
Boolean canManageChildren = GlossaryUtils.canManageChildrenEntities(context, nodeUrn);
result.setCanManageChildren(canManageChildren);

Urn parentNodeUrn = GlossaryUtils.getParentUrn(nodeUrn, context, _entityClient);
if (parentNodeUrn != null) {
Boolean canManage = GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn);
result.setCanManageEntity(canManage);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.RelatedTermsInput;
import com.linkedin.datahub.graphql.generated.TermRelationshipType;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.glossary.GlossaryRelatedTerms;
Expand Down Expand Up @@ -38,7 +38,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
final RelatedTermsInput input = bindArgument(environment.getArgument("input"), RelatedTermsInput.class);

return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries(context)) {
if (GlossaryUtils.canManageGlossaries(context)) {
try {
final TermRelationshipType relationshipType = input.getRelationshipType();
final Urn urn = Urn.createFromString(input.getUrn());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.linkedin.datahub.graphql.resolvers.glossary;

import com.linkedin.common.urn.GlossaryNodeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.data.template.SetMode;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
import com.linkedin.datahub.graphql.generated.OwnershipType;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.events.metadata.ChangeType;
Expand Down Expand Up @@ -41,9 +43,10 @@ public CompletableFuture<String> get(DataFetchingEnvironment environment) throws

final QueryContext context = environment.getContext();
final CreateGlossaryEntityInput input = bindArgument(environment.getArgument("input"), CreateGlossaryEntityInput.class);
final Urn parentNode = input.getParentNode() != null ? UrnUtils.getUrn(input.getParentNode()) : null;

return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries(context)) {
if (GlossaryUtils.canManageChildrenEntities(context, parentNode)) {
try {
final GlossaryNodeKey key = new GlossaryNodeKey();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.linkedin.datahub.graphql.resolvers.glossary;

import com.linkedin.common.urn.GlossaryNodeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.data.template.SetMode;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.CreateGlossaryEntityInput;
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
import com.linkedin.datahub.graphql.generated.OwnershipType;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.events.metadata.ChangeType;
Expand Down Expand Up @@ -41,9 +43,10 @@ public CompletableFuture<String> get(DataFetchingEnvironment environment) throws

final QueryContext context = environment.getContext();
final CreateGlossaryEntityInput input = bindArgument(environment.getArgument("input"), CreateGlossaryEntityInput.class);
final Urn parentNode = input.getParentNode() != null ? UrnUtils.getUrn(input.getParentNode()) : null;

return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries((context))) {
if (GlossaryUtils.canManageChildrenEntities(context, parentNode)) {
try {
final GlossaryTermKey key = new GlossaryTermKey();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.r2.RemoteInvocationException;
Expand All @@ -28,9 +28,10 @@ public DeleteGlossaryEntityResolver(final EntityClient entityClient, EntityServi
public CompletableFuture<Boolean> get(final DataFetchingEnvironment environment) throws Exception {
final QueryContext context = environment.getContext();
final Urn entityUrn = Urn.createFromString(environment.getArgument("urn"));
final Urn parentNodeUrn = GlossaryUtils.getParentUrn(entityUrn, context, _entityClient);

return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries(environment.getContext())) {
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn)) {
if (!_entityService.exists(entityUrn)) {
throw new RuntimeException(String.format("This urn does not exist: %s", entityUrn));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.RelatedTermsInput;
import com.linkedin.datahub.graphql.generated.TermRelationshipType;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.glossary.GlossaryRelatedTerms;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.entity.EntityService;
Expand Down Expand Up @@ -37,7 +37,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
final RelatedTermsInput input = bindArgument(environment.getArgument("input"), RelatedTermsInput.class);

return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries(context)) {
if (GlossaryUtils.canManageGlossaries(context)) {
try {
final TermRelationshipType relationshipType = input.getRelationshipType();
final Urn urn = Urn.createFromString(input.getUrn());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.UpdateNameInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.domain.DomainProperties;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.glossary.GlossaryTermInfo;
import com.linkedin.glossary.GlossaryNodeInfo;
import com.linkedin.identity.CorpGroupInfo;
Expand All @@ -29,6 +31,7 @@
public class UpdateNameResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
Expand Down Expand Up @@ -62,7 +65,8 @@ private Boolean updateGlossaryTermName(
UpdateNameInput input,
QueryContext context
) {
if (AuthorizationUtils.canManageGlossaries(context)) {
final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn)) {
try {
GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) getAspectFromEntity(
targetUrn.toString(), Constants.GLOSSARY_TERM_INFO_ASPECT_NAME, _entityService, null);
Expand All @@ -86,7 +90,8 @@ private Boolean updateGlossaryNodeName(
UpdateNameInput input,
QueryContext context
) {
if (AuthorizationUtils.canManageGlossaries(context)) {
final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn)) {
try {
GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) getAspectFromEntity(
targetUrn.toString(), Constants.GLOSSARY_NODE_INFO_ASPECT_NAME, _entityService, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import com.linkedin.common.urn.GlossaryNodeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.UpdateParentNodeInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.GlossaryUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.glossary.GlossaryTermInfo;
import com.linkedin.glossary.GlossaryNodeInfo;
import com.linkedin.metadata.Constants;
Expand All @@ -27,10 +28,12 @@
public class UpdateParentNodeResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
final UpdateParentNodeInput input = bindArgument(environment.getArgument("input"), UpdateParentNodeInput.class);
final QueryContext context = environment.getContext();
Urn targetUrn = Urn.createFromString(input.getResourceUrn());
log.info("Updating parent node. input: {}", input.toString());

Expand All @@ -43,7 +46,9 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
throw new IllegalArgumentException(String.format("Failed to update %s. %s either does not exist or is not a glossaryNode.", targetUrn, parentNodeUrn));
}
return CompletableFuture.supplyAsync(() -> {
if (AuthorizationUtils.canManageGlossaries(environment.getContext())) {
Urn currentParentUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient);
// need to be able to manage current parent node and new parent node
if (GlossaryUtils.canManageChildrenEntities(context, currentParentUrn) && GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn)) {
switch (targetUrn.getEntityType()) {
case Constants.GLOSSARY_TERM_ENTITY_NAME:
return updateGlossaryTermParentNode(targetUrn, parentNodeUrn, input, environment.getContext());
Expand Down
Loading

0 comments on commit 8c783da

Please sign in to comment.