Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth) - Manage Children Glossary term authorization check for Owner, Domain, Remove link #11337

Merged
merged 4 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -58,7 +58,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
Loading