Skip to content

Commit

Permalink
feat(auth) - Manage Children Glossary term authorization check for Ow…
Browse files Browse the repository at this point in the history
…ner, Domain, Remove link (#11337)
  • Loading branch information
mkamalas committed Sep 27, 2024
1 parent 5cbd836 commit edd2831
Show file tree
Hide file tree
Showing 22 changed files with 127 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1192,13 +1192,15 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
.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("addOwner", new AddOwnerResolver(entityService, entityClient))
.dataFetcher("addOwners", new AddOwnersResolver(entityService, entityClient))
.dataFetcher(
"batchAddOwners", new BatchAddOwnersResolver(entityService, entityClient))
.dataFetcher("removeOwner", new RemoveOwnerResolver(entityService, entityClient))
.dataFetcher(
"batchRemoveOwners", new BatchRemoveOwnersResolver(entityService, entityClient))
.dataFetcher("addLink", new AddLinkResolver(entityService, this.entityClient))
.dataFetcher("removeLink", new RemoveLinkResolver(entityService))
.dataFetcher("removeLink", new RemoveLinkResolver(entityService, entityClient))
.dataFetcher("addGroupMembers", new AddGroupMembersResolver(this.groupService))
.dataFetcher("removeGroupMembers", new RemoveGroupMembersResolver(this.groupService))
.dataFetcher("createGroup", new CreateGroupResolver(this.groupService))
Expand All @@ -1212,7 +1214,8 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
.dataFetcher("deleteDomain", new DeleteDomainResolver(entityClient))
.dataFetcher(
"setDomain", new SetDomainResolver(this.entityClient, this.entityService))
.dataFetcher("batchSetDomain", new BatchSetDomainResolver(this.entityService))
.dataFetcher(
"batchSetDomain", new BatchSetDomainResolver(this.entityService, entityClient))
.dataFetcher(
"updateDeprecation",
new UpdateDeprecationResolver(this.entityClient, this.entityService))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
return GraphQLConcurrencyUtils.supplyAsync(
() -> {
if (!DomainUtils.isAuthorizedToUpdateDomainsForEntity(
environment.getContext(), entityUrn)) {
environment.getContext(), entityUrn, _entityClient)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
return GraphQLConcurrencyUtils.supplyAsync(
() -> {
if (!DomainUtils.isAuthorizedToUpdateDomainsForEntity(
environment.getContext(), entityUrn)) {
environment.getContext(), entityUrn, _entityClient)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
final Urn parentUrn = GlossaryUtils.getParentUrn(urn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentUrn, _entityClient)) {
if (GlossaryUtils.canUpdateGlossaryEntity(urn, context, _entityClient)) {
try {
final TermRelationshipType relationshipType = input.getRelationshipType();
final List<Urn> termUrns =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ 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 GraphQLConcurrencyUtils.supplyAsync(
() -> {
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient)) {
if (GlossaryUtils.canUpdateGlossaryEntity(entityUrn, context, _entityClient)) {
if (!_entityService.exists(context.getOperationContext(), entityUrn, true)) {
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 @@ -42,8 +42,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
final Urn parentUrn = GlossaryUtils.getParentUrn(urn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentUrn, _entityClient)) {
if (GlossaryUtils.canUpdateGlossaryEntity(urn, context, _entityClient)) {
try {
final TermRelationshipType relationshipType = input.getRelationshipType();
final List<Urn> termUrnsToRemove =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand All @@ -36,7 +35,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
Urn targetUrn = Urn.createFromString(input.getResourceUrn());

if (!LinkUtils.isAuthorizedToUpdateLinks(context, targetUrn)
&& !canUpdateGlossaryEntityLinks(targetUrn, context)) {
&& !GlossaryUtils.canUpdateGlossaryEntity(targetUrn, context, _entityClient)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
Expand Down Expand Up @@ -70,18 +69,4 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
this.getClass().getSimpleName(),
"get");
}

// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.linkedin.datahub.graphql.generated.OwnerInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -23,6 +24,7 @@
public class AddOwnerResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
Expand All @@ -41,7 +43,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
}

OwnerInput ownerInput = ownerInputBuilder.build();
OwnerUtils.validateAuthorizedToUpdateOwners(context, targetUrn);
OwnerUtils.validateAuthorizedToUpdateOwners(context, targetUrn, _entityClient);

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.linkedin.datahub.graphql.generated.OwnerInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -24,6 +25,7 @@
public class AddOwnersResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
Expand All @@ -35,7 +37,8 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
OwnerUtils.validateAuthorizedToUpdateOwners(environment.getContext(), targetUrn);
OwnerUtils.validateAuthorizedToUpdateOwners(
environment.getContext(), targetUrn, _entityClient);

OwnerUtils.validateAddOwnerInput(
context.getOperationContext(), owners, targetUrn, _entityService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -27,6 +28,7 @@
public class BatchAddOwnersResolver 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 @@ -80,7 +82,7 @@ private void validateInputResource(
"Malformed input provided: owners cannot be applied to subresources.");
}

OwnerUtils.validateAuthorizedToUpdateOwners(context, resourceUrn);
OwnerUtils.validateAuthorizedToUpdateOwners(context, resourceUrn, _entityClient);
LabelUtils.validateResource(
opContext,
resourceUrn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -24,6 +25,7 @@
public class BatchRemoveOwnersResolver 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 @@ -72,7 +74,7 @@ private void validateInputResource(ResourceRefInput resource, QueryContext conte
"Malformed input provided: owners cannot be removed from subresources.");
}

OwnerUtils.validateAuthorizedToUpdateOwners(context, resourceUrn);
OwnerUtils.validateAuthorizedToUpdateOwners(context, resourceUrn, _entityClient);
LabelUtils.validateResource(
context.getOperationContext(),
resourceUrn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.DomainUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.LabelUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -28,6 +29,7 @@
public class BatchSetDomainResolver 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 @@ -74,7 +76,7 @@ private void validateInputResources(List<ResourceRefInput> resources, QueryConte

private void validateInputResource(ResourceRefInput resource, QueryContext context) {
final Urn resourceUrn = UrnUtils.getUrn(resource.getResourceUrn());
if (!DomainUtils.isAuthorizedToUpdateDomainsForEntity(context, resourceUrn)) {
if (!DomainUtils.isAuthorizedToUpdateDomainsForEntity(context, resourceUrn, _entityClient)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import com.linkedin.datahub.graphql.concurrency.GraphQLConcurrencyUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.RemoveLinkInput;
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.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -21,6 +23,7 @@
public class RemoveLinkResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
Expand All @@ -31,7 +34,8 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
String linkUrl = input.getLinkUrl();
Urn targetUrn = Urn.createFromString(input.getResourceUrn());

if (!LinkUtils.isAuthorizedToUpdateLinks(context, targetUrn)) {
if (!LinkUtils.isAuthorizedToUpdateLinks(context, targetUrn)
&& !GlossaryUtils.canUpdateGlossaryEntity(targetUrn, context, _entityClient)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.linkedin.datahub.graphql.generated.RemoveOwnerInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand All @@ -22,6 +23,7 @@
public class RemoveOwnerResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final EntityService _entityService;
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
Expand All @@ -36,7 +38,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
? null
: Urn.createFromString(input.getOwnershipTypeUrn());

OwnerUtils.validateAuthorizedToUpdateOwners(context, targetUrn);
OwnerUtils.validateAuthorizedToUpdateOwners(context, targetUrn, _entityClient);

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw

private Boolean updateGlossaryTermName(
Urn targetUrn, UpdateNameInput input, QueryContext context) {
final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient)) {
if (GlossaryUtils.canUpdateGlossaryEntity(targetUrn, context, _entityClient)) {
try {
GlossaryTermInfo glossaryTermInfo =
(GlossaryTermInfo)
Expand Down Expand Up @@ -123,8 +122,7 @@ private Boolean updateGlossaryTermName(

private Boolean updateGlossaryNodeName(
Urn targetUrn, UpdateNameInput input, QueryContext context) {
final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient);
if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient)) {
if (GlossaryUtils.canUpdateGlossaryEntity(targetUrn, context, _entityClient)) {
try {
GlossaryNodeInfo glossaryNodeInfo =
(GlossaryNodeInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ public class DomainUtils {
private DomainUtils() {}

public static boolean isAuthorizedToUpdateDomainsForEntity(
@Nonnull QueryContext context, Urn entityUrn) {
@Nonnull QueryContext context, Urn entityUrn, EntityClient entityClient) {

if (GlossaryUtils.canUpdateGlossaryEntity(entityUrn, context, entityClient)) {
return true;
}

final DisjunctivePrivilegeGroup orPrivilegeGroups =
new DisjunctivePrivilegeGroup(
ImmutableList.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ public static boolean canManageGlossaries(@Nonnull QueryContext context) {
context.getOperationContext(), PoliciesConfig.MANAGE_GLOSSARIES_PRIVILEGE);
}

// 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
public static boolean canUpdateGlossaryEntity(
Urn targetUrn, QueryContext context, EntityClient entityClient) {
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);
}

/**
* Returns true if the current user is able to create, delete, or move Glossary Terms and Nodes
* under a parent Node. They can do this with either the global MANAGE_GLOSSARIES privilege, or if
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.linkedin.datahub.graphql.generated.OwnerInput;
import com.linkedin.datahub.graphql.generated.OwnershipType;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.authorization.PoliciesConfig;
import com.linkedin.metadata.entity.EntityService;
Expand Down Expand Up @@ -195,7 +196,12 @@ private static void removeOwnersIfExists(
}

public static void validateAuthorizedToUpdateOwners(
@Nonnull QueryContext context, Urn resourceUrn) {
@Nonnull QueryContext context, Urn resourceUrn, EntityClient entityClient) {

if (GlossaryUtils.canUpdateGlossaryEntity(resourceUrn, context, entityClient)) {
return;
}

final DisjunctivePrivilegeGroup orPrivilegeGroups =
new DisjunctivePrivilegeGroup(
ImmutableList.of(
Expand Down
Loading

0 comments on commit edd2831

Please sign in to comment.