Skip to content
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 @@ -28,6 +28,7 @@
import com.linkedin.datahub.graphql.resolvers.MeResolver;
import com.linkedin.datahub.graphql.resolvers.ResolverUtils;
import com.linkedin.datahub.graphql.resolvers.application.BatchSetApplicationResolver;
import com.linkedin.datahub.graphql.resolvers.application.BatchUnsetApplicationResolver;
import com.linkedin.datahub.graphql.resolvers.application.CreateApplicationResolver;
import com.linkedin.datahub.graphql.resolvers.application.DeleteApplicationResolver;
import com.linkedin.datahub.graphql.resolvers.assertion.AssertionRunEventResolver;
Expand Down Expand Up @@ -1394,6 +1395,9 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
new DeleteApplicationResolver(this.entityClient, this.applicationService))
.dataFetcher(
"batchSetApplication", new BatchSetApplicationResolver(this.applicationService))
.dataFetcher(
"batchUnsetApplication",
new BatchUnsetApplicationResolver(this.applicationService))
.dataFetcher(
"createOwnershipType", new CreateOwnershipTypeResolver(this.ownershipTypeService))
.dataFetcher(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
import static com.linkedin.metadata.authorization.ApiOperation.MANAGE;

import com.datahub.authorization.AuthUtil;
import com.datahub.authorization.ConjunctivePrivilegeGroup;
import com.datahub.authorization.DisjunctivePrivilegeGroup;
import com.google.common.collect.ImmutableList;
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.metadata.authorization.PoliciesConfig;
import com.linkedin.metadata.service.ApplicationService;
import java.util.List;
import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -22,4 +31,53 @@ public static boolean canManageApplications(@Nonnull QueryContext context) {
return AuthUtil.isAuthorizedEntityType(
context.getOperationContext(), MANAGE, List.of(APPLICATION_ENTITY_NAME));
}

/**
* Verifies that the current user is authorized to edit applications on a specific resource
* entity.
*
* @throws AuthorizationException if the user is not authorized
*/
public static void verifyEditApplicationsAuthorization(
@Nonnull Urn resourceUrn, @Nonnull QueryContext context) {
if (!AuthorizationUtils.isAuthorized(
context,
resourceUrn.getEntityType(),
resourceUrn.toString(),
new DisjunctivePrivilegeGroup(
ImmutableList.of(
new ConjunctivePrivilegeGroup(
ImmutableList.of(PoliciesConfig.EDIT_ENTITY_APPLICATIONS_PRIVILEGE.getType())),
new ConjunctivePrivilegeGroup(
ImmutableList.of(PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType())))))) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
}

/**
* Verifies that all resources exist and that the current user is authorized to edit applications
* on them.
*
* @param resources List of resource URN strings to verify
* @param applicationService Service to verify entity existence
* @param context Query context with operation context and authorization info
* @param operationName Name of the operation being performed (for error messages)
* @throws RuntimeException if any resource does not exist
* @throws AuthorizationException if the user is not authorized for any resource
*/
public static void verifyResourcesExistAndAuthorized(
@Nonnull List<String> resources,
@Nonnull ApplicationService applicationService,
@Nonnull QueryContext context,
@Nonnull String operationName) {
for (String resource : resources) {
Urn resourceUrn = UrnUtils.getUrn(resource);
if (!applicationService.verifyEntityExists(context.getOperationContext(), resourceUrn)) {
throw new RuntimeException(
String.format("Failed to %s, %s in resources does not exist", operationName, resource));
}
verifyEditApplicationsAuthorization(resourceUrn, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package com.linkedin.datahub.graphql.resolvers.application;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.application.ApplicationAuthorizationUtils.verifyResourcesExistAndAuthorized;

import com.datahub.authorization.ConjunctivePrivilegeGroup;
import com.datahub.authorization.DisjunctivePrivilegeGroup;
import com.google.common.collect.ImmutableList;
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.concurrency.GraphQLConcurrencyUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.BatchSetApplicationInput;
import com.linkedin.metadata.authorization.PoliciesConfig;
import com.linkedin.metadata.service.ApplicationService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
Expand Down Expand Up @@ -63,29 +58,7 @@ public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throw
}

private void verifyResources(List<String> resources, QueryContext context) {
for (String resource : resources) {
if (!applicationService.verifyEntityExists(
context.getOperationContext(), UrnUtils.getUrn(resource))) {
throw new RuntimeException(
String.format(
"Failed to batch set Application, %s in resources does not exist", resource));
}
Urn resourceUrn = UrnUtils.getUrn(resource);
if (!AuthorizationUtils.isAuthorized(
context,
resourceUrn.getEntityType(),
resourceUrn.toString(),
new DisjunctivePrivilegeGroup(
ImmutableList.of(
new ConjunctivePrivilegeGroup(
ImmutableList.of(
PoliciesConfig.EDIT_ENTITY_APPLICATIONS_PRIVILEGE.getType())),
new ConjunctivePrivilegeGroup(
ImmutableList.of(PoliciesConfig.EDIT_ENTITY_PRIVILEGE.getType())))))) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
}
verifyResourcesExistAndAuthorized(resources, applicationService, context, "set_application");
}

private void verifyApplication(String maybeApplicationUrn, QueryContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.linkedin.datahub.graphql.resolvers.application;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.application.ApplicationAuthorizationUtils.verifyResourcesExistAndAuthorized;

import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.concurrency.GraphQLConcurrencyUtils;
import com.linkedin.datahub.graphql.generated.BatchUnsetApplicationInput;
import com.linkedin.metadata.service.ApplicationService;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class BatchUnsetApplicationResolver implements DataFetcher<CompletableFuture<Boolean>> {

private final ApplicationService applicationService;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
final QueryContext context = environment.getContext();
final BatchUnsetApplicationInput input =
bindArgument(environment.getArgument("input"), BatchUnsetApplicationInput.class);
final String applicationUrn = input.getApplicationUrn();
final List<String> resources = input.getResourceUrns();

return GraphQLConcurrencyUtils.supplyAsync(
() -> {
verifyResources(resources, context);
verifyApplication(applicationUrn, context);

try {
List<Urn> resourceUrns =
resources.stream().map(UrnUtils::getUrn).collect(Collectors.toList());
batchUnsetApplication(applicationUrn, resourceUrns, context);
return true;
} catch (Exception e) {
log.error("Failed to perform update against input {}, {}", input, e.getMessage());
throw new RuntimeException(
String.format("Failed to perform update against input %s", input), e);
}
},
this.getClass().getSimpleName(),
"get");
}

private void verifyResources(List<String> resources, QueryContext context) {
verifyResourcesExistAndAuthorized(resources, applicationService, context, "unset_application");
}

private void verifyApplication(String applicationUrn, QueryContext context) {
if (!applicationService.verifyEntityExists(
context.getOperationContext(), UrnUtils.getUrn(applicationUrn))) {
throw new RuntimeException(
String.format(
"Failed to batch unset Application, Application urn %s does not exist",
applicationUrn));
}
}

private void batchUnsetApplication(
@Nonnull String applicationUrn, List<Urn> resources, QueryContext context) {
try {
applicationService.batchUnsetApplication(
context.getOperationContext(),
UrnUtils.getUrn(applicationUrn),
resources,
UrnUtils.getUrn(context.getActorUrn()));
} catch (Exception e) {
throw new RuntimeException(
String.format(
"Failed to batch unset Application %s from resources with urns %s!",
applicationUrn, resources),
e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.linkedin.datahub.graphql.generated.Application;
import com.linkedin.datahub.graphql.generated.ApplicationAssociation;
import com.linkedin.datahub.graphql.generated.EntityType;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

Expand All @@ -25,6 +27,13 @@ public static ApplicationAssociation map(
return INSTANCE.apply(context, applications, entityUrn);
}

public static List<ApplicationAssociation> mapList(
@Nullable final QueryContext context,
@Nonnull final com.linkedin.application.Applications applications,
@Nonnull final String entityUrn) {
return INSTANCE.applyList(context, applications, entityUrn);
}

public ApplicationAssociation apply(
@Nullable final QueryContext context,
@Nonnull final com.linkedin.application.Applications applications,
Expand All @@ -43,4 +52,26 @@ public ApplicationAssociation apply(
}
return null;
}

public List<ApplicationAssociation> applyList(
@Nullable final QueryContext context,
@Nonnull final com.linkedin.application.Applications applications,
@Nonnull final String entityUrn) {
return applications.getApplications().stream()
.filter(
applicationUrn ->
context == null || canView(context.getOperationContext(), applicationUrn))
.map(
applicationUrn -> {
ApplicationAssociation association = new ApplicationAssociation();
association.setApplication(
Application.builder()
.setType(EntityType.APPLICATION)
.setUrn(applicationUrn.toString())
.build());
association.setAssociatedUrn(entityUrn);
return association;
})
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ private static void mapDomains(
private static void mapApplicationAssociation(
@Nullable final QueryContext context, @Nonnull Chart chart, @Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
chart.setApplication(ApplicationAssociationMapper.map(context, applications, chart.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, chart.getUrn());
chart.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
chart.setApplication(applicationAssociations.get(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,12 @@ private static void mapApplicationAssociation(
@Nonnull Dashboard dashboard,
@Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
dashboard.setApplication(
ApplicationAssociationMapper.map(context, applications, dashboard.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, dashboard.getUrn());
dashboard.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
dashboard.setApplication(applicationAssociations.get(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ private static void mapDomains(
private static void mapApplicationAssociation(
@Nullable final QueryContext context, @Nonnull DataFlow dataFlow, @Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
dataFlow.setApplication(
ApplicationAssociationMapper.map(context, applications, dataFlow.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, dataFlow.getUrn());
dataFlow.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
dataFlow.setApplication(applicationAssociations.get(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,13 @@ private void mapApplicationAssociation(
if (aspectMap.containsKey(APPLICATION_MEMBERSHIP_ASPECT_NAME)) {
final Applications applications =
new Applications(aspectMap.get(APPLICATION_MEMBERSHIP_ASPECT_NAME).getValue().data());
dataJob.setApplication(
ApplicationAssociationMapper.map(context, applications, dataJob.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, dataJob.getUrn());
dataJob.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
dataJob.setApplication(applicationAssociations.get(0));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ private static void mapApplicationAssociation(
@Nonnull DataProduct dataProduct,
@Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
dataProduct.setApplication(
ApplicationAssociationMapper.map(context, applications, dataProduct.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, dataProduct.getUrn());
dataProduct.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
dataProduct.setApplication(applicationAssociations.get(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,6 @@ public Dataset apply(
(dataset, dataMap) ->
dataset.setDataPlatformInstance(
DataPlatformInstanceAspectMapper.map(context, new DataPlatformInstance(dataMap))));
mappingHelper.mapToResult(
"applications", (dataset, dataMap) -> mapApplicationAssociation(context, dataset, dataMap));
mappingHelper.mapToResult(
SIBLINGS_ASPECT_NAME,
(dataset, dataMap) ->
Expand Down Expand Up @@ -324,7 +322,12 @@ private static void mapDomains(
private static void mapApplicationAssociation(
@Nullable final QueryContext context, @Nonnull Dataset dataset, @Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
dataset.setApplication(
ApplicationAssociationMapper.map(context, applications, dataset.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, dataset.getUrn());
dataset.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
dataset.setApplication(applicationAssociations.get(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,12 @@ private static void mapApplicationAssociation(
@Nonnull GlossaryTerm glossaryTerm,
@Nonnull DataMap dataMap) {
final Applications applications = new Applications(dataMap);
glossaryTerm.setApplication(
ApplicationAssociationMapper.map(context, applications, glossaryTerm.getUrn()));
final java.util.List<com.linkedin.datahub.graphql.generated.ApplicationAssociation>
applicationAssociations =
ApplicationAssociationMapper.mapList(context, applications, glossaryTerm.getUrn());
glossaryTerm.setApplications(applicationAssociations);
if (applicationAssociations != null && !applicationAssociations.isEmpty()) {
glossaryTerm.setApplication(applicationAssociations.get(0));
}
}
}
Loading
Loading