Skip to content

Commit 678c9f3

Browse files
authored
feat(policies): prevent system policy deletion and non-editable policy modification (datahub-project#14247)
1 parent 002ac82 commit 678c9f3

File tree

14 files changed

+681
-2
lines changed

14 files changed

+681
-2
lines changed

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/exception/DataHubDataFetcherExceptionHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.linkedin.datahub.graphql.exception;
22

3+
import com.linkedin.metadata.entity.validation.ValidationException;
34
import graphql.PublicApi;
45
import graphql.execution.DataFetcherExceptionHandler;
56
import graphql.execution.DataFetcherExceptionHandlerParameters;
@@ -41,7 +42,15 @@ public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(
4142
message = graphQLException.getMessage();
4243
}
4344

44-
if (illException == null && graphQLException == null) {
45+
ValidationException validationException =
46+
findFirstThrowableCauseOfClass(exception, ValidationException.class);
47+
if (validationException != null) {
48+
log.error("Failed to execute", validationException);
49+
errorCode = DataHubGraphQLErrorCode.BAD_REQUEST;
50+
message = validationException.getMessage();
51+
}
52+
53+
if (illException == null && graphQLException == null && validationException == null) {
4554
log.error("Failed to execute", exception);
4655
}
4756
DataHubGraphQLError error = new DataHubGraphQLError(message, path, sourceLocation, errorCode);

entity-registry/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ dependencies {
5757
}
5858
compileTestJava.dependsOn tasks.getByPath(':entity-registry:custom-test-model:modelDeploy')
5959

60+
spotlessJava.dependsOn generateTestDataTemplate

entity-registry/custom-test-model/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ configurations {
7878
}
7979
}
8080

81+
spotlessJava.dependsOn generateTestDataTemplate
82+
8183
//artifacts {
8284
// builtModels(modelArtifact)
8385
//}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.linkedin.metadata.aspect.validation;
2+
3+
import static com.linkedin.metadata.Constants.SYSTEM_ACTOR;
4+
import static com.linkedin.metadata.Constants.SYSTEM_POLICY_ONE;
5+
import static com.linkedin.metadata.Constants.SYSTEM_POLICY_ZERO;
6+
7+
import com.google.common.collect.ImmutableSet;
8+
import com.linkedin.common.urn.Urn;
9+
import com.linkedin.entity.Aspect;
10+
import com.linkedin.events.metadata.ChangeType;
11+
import com.linkedin.metadata.Constants;
12+
import com.linkedin.metadata.aspect.RetrieverContext;
13+
import com.linkedin.metadata.aspect.batch.BatchItem;
14+
import com.linkedin.metadata.aspect.batch.ChangeMCP;
15+
import com.linkedin.metadata.aspect.plugins.config.AspectPluginConfig;
16+
import com.linkedin.metadata.aspect.plugins.validation.AspectPayloadValidator;
17+
import com.linkedin.metadata.aspect.plugins.validation.AspectValidationException;
18+
import com.linkedin.metadata.aspect.plugins.validation.ValidationExceptionCollection;
19+
import com.linkedin.policy.DataHubPolicyInfo;
20+
import java.util.Collection;
21+
import java.util.Set;
22+
import java.util.stream.Stream;
23+
import javax.annotation.Nonnull;
24+
import javax.annotation.Nullable;
25+
import lombok.Getter;
26+
import lombok.Setter;
27+
import lombok.experimental.Accessors;
28+
import lombok.extern.slf4j.Slf4j;
29+
30+
/**
31+
* Validator to ensure that system policies (i. e: Those can come natively with DataHub) are not
32+
* deleted. *
33+
*/
34+
@Slf4j
35+
@Setter
36+
@Getter
37+
@Accessors(chain = true)
38+
public class SystemPolicyValidator extends AspectPayloadValidator {
39+
@Nonnull private AspectPluginConfig config;
40+
@Nullable private Set<Urn> systemPolicyUrns;
41+
42+
private Set<ChangeType> MODIFY_CHANGE_TYPES =
43+
ImmutableSet.of(ChangeType.PATCH, ChangeType.UPDATE, ChangeType.UPSERT);
44+
45+
@Override
46+
protected Stream<AspectValidationException> validateProposedAspects(
47+
@Nonnull Collection<? extends BatchItem> mcpItems,
48+
@Nonnull RetrieverContext retrieverContext) {
49+
50+
ValidationExceptionCollection exceptions = ValidationExceptionCollection.newCollection();
51+
52+
mcpItems.forEach(
53+
i -> {
54+
final Urn entityUrn = i.getUrn();
55+
if (isSystemPolicy(entityUrn) && ChangeType.DELETE.equals(i.getChangeType())) {
56+
exceptions.addException(
57+
AspectValidationException.forItem(i, "System policies can not be deleted."));
58+
} else if (MODIFY_CHANGE_TYPES.contains(i.getChangeType())
59+
&&
60+
// We don't know actor or we know it isn't the system actor, system actor is allowed
61+
// to modify to allow
62+
// reingestion of base policies if they have for example been directly modified in the
63+
// database
64+
(i.getAuditStamp() == null
65+
|| !SYSTEM_ACTOR.equals(i.getAuditStamp().getActor().toString()))) {
66+
final Aspect aspect =
67+
retrieverContext
68+
.getAspectRetriever()
69+
.getLatestAspectObject(entityUrn, Constants.DATAHUB_POLICY_INFO_ASPECT_NAME);
70+
if (aspect != null) {
71+
DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo(aspect.data());
72+
if (!dataHubPolicyInfo.isEditable()) {
73+
exceptions.addException(
74+
AspectValidationException.forItem(
75+
i, "Attempting to edit not editable policy."));
76+
}
77+
}
78+
}
79+
});
80+
81+
return exceptions.streamAllExceptions();
82+
}
83+
84+
private boolean isSystemPolicy(Urn entityUrn) {
85+
return SYSTEM_POLICY_ZERO.equals(entityUrn)
86+
|| SYSTEM_POLICY_ONE.equals(entityUrn)
87+
|| (systemPolicyUrns != null && systemPolicyUrns.contains(entityUrn));
88+
}
89+
90+
@Override
91+
protected Stream<AspectValidationException> validatePreCommitAspects(
92+
@Nonnull Collection<ChangeMCP> changeMCPs, @Nonnull RetrieverContext retrieverContext) {
93+
return Stream.empty();
94+
}
95+
}

0 commit comments

Comments
 (0)