mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-16 21:27:03 +00:00
Add STARTS_WITH policy condition to allow for URN-wildcard-based policies (#11441)
Co-authored-by: Hendrik Richert <hendrik.richert@swisscom.com>
This commit is contained in:
parent
9eefedfdc0
commit
b607a66c05
@ -9157,6 +9157,10 @@ enum PolicyMatchCondition {
|
|||||||
Whether the field matches the value
|
Whether the field matches the value
|
||||||
"""
|
"""
|
||||||
EQUALS
|
EQUALS
|
||||||
|
"""
|
||||||
|
Whether the field value starts with the value
|
||||||
|
"""
|
||||||
|
STARTS_WITH
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -3,9 +3,14 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Button, Divider, Modal, Tag, Typography } from 'antd';
|
import { Button, Divider, Modal, Tag, Typography } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useEntityRegistry } from '../../useEntityRegistry';
|
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||||
import { Maybe, Policy, PolicyState, PolicyType } from '../../../types.generated';
|
import { Maybe, Policy, PolicyMatchCondition, PolicyState, PolicyType } from '../../../types.generated';
|
||||||
import { useAppConfig } from '../../useAppConfig';
|
import { useAppConfig } from '../../useAppConfig';
|
||||||
import { convertLegacyResourceFilter, getFieldValues, mapResourceTypeToDisplayName } from './policyUtils';
|
import {
|
||||||
|
convertLegacyResourceFilter,
|
||||||
|
getFieldValues,
|
||||||
|
getFieldCondition,
|
||||||
|
mapResourceTypeToDisplayName,
|
||||||
|
} from './policyUtils';
|
||||||
import AvatarsGroup from '../AvatarsGroup';
|
import AvatarsGroup from '../AvatarsGroup';
|
||||||
|
|
||||||
type PrivilegeOptionType = {
|
type PrivilegeOptionType = {
|
||||||
@ -70,6 +75,7 @@ export default function PolicyDetailsModal({ policy, open, onClose, privileges }
|
|||||||
const resourceTypes = getFieldValues(resources?.filter, 'TYPE') || [];
|
const resourceTypes = getFieldValues(resources?.filter, 'TYPE') || [];
|
||||||
const dataPlatformInstances = getFieldValues(resources?.filter, 'DATA_PLATFORM_INSTANCE') || [];
|
const dataPlatformInstances = getFieldValues(resources?.filter, 'DATA_PLATFORM_INSTANCE') || [];
|
||||||
const resourceEntities = getFieldValues(resources?.filter, 'URN') || [];
|
const resourceEntities = getFieldValues(resources?.filter, 'URN') || [];
|
||||||
|
const resourceFilterCondition = getFieldCondition(resources?.filter, 'URN') || PolicyMatchCondition.Equals;
|
||||||
const domains = getFieldValues(resources?.filter, 'DOMAIN') || [];
|
const domains = getFieldValues(resources?.filter, 'DOMAIN') || [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -104,6 +110,10 @@ export default function PolicyDetailsModal({ policy, open, onClose, privileges }
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getWildcardUrnTag = (criterionValue) => {
|
||||||
|
return <Typography.Text>{criterionValue.value}*</Typography.Text>;
|
||||||
|
};
|
||||||
|
|
||||||
const resourceOwnersField = (actors) => {
|
const resourceOwnersField = (actors) => {
|
||||||
if (!actors?.resourceOwners) {
|
if (!actors?.resourceOwners) {
|
||||||
return <PoliciesTag>No</PoliciesTag>;
|
return <PoliciesTag>No</PoliciesTag>;
|
||||||
@ -166,7 +176,10 @@ export default function PolicyDetailsModal({ policy, open, onClose, privileges }
|
|||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<PoliciesTag key={`resource-${value.value}-${key}`}>
|
<PoliciesTag key={`resource-${value.value}-${key}`}>
|
||||||
{getEntityTag(value)}
|
{resourceFilterCondition &&
|
||||||
|
resourceFilterCondition === PolicyMatchCondition.StartsWith
|
||||||
|
? getWildcardUrnTag(value)
|
||||||
|
: getEntityTag(value)}
|
||||||
</PoliciesTag>
|
</PoliciesTag>
|
||||||
);
|
);
|
||||||
})) || <PoliciesTag>All</PoliciesTag>}
|
})) || <PoliciesTag>All</PoliciesTag>}
|
||||||
|
@ -118,6 +118,10 @@ export const getFieldValues = (filter: Maybe<PolicyMatchFilter> | undefined, res
|
|||||||
return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.values || [];
|
return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.values || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFieldCondition = (filter: Maybe<PolicyMatchFilter> | undefined, resourceFieldType: string) => {
|
||||||
|
return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.condition || null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getFieldValuesOfTags = (filter: Maybe<PolicyMatchFilter> | undefined, resourceFieldType: string) => {
|
export const getFieldValuesOfTags = (filter: Maybe<PolicyMatchFilter> | undefined, resourceFieldType: string) => {
|
||||||
return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.values || [];
|
return filter?.criteria?.find((criterion) => criterion.field === resourceFieldType)?.values || [];
|
||||||
};
|
};
|
||||||
|
@ -8,4 +8,9 @@ enum PolicyMatchCondition {
|
|||||||
* Whether the field matches the value
|
* Whether the field matches the value
|
||||||
*/
|
*/
|
||||||
EQUALS
|
EQUALS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the field value starts with the value
|
||||||
|
*/
|
||||||
|
STARTS_WITH
|
||||||
}
|
}
|
@ -252,11 +252,15 @@ public class PolicyEngine {
|
|||||||
|
|
||||||
private boolean checkCondition(
|
private boolean checkCondition(
|
||||||
Set<String> fieldValues, String filterValue, PolicyMatchCondition condition) {
|
Set<String> fieldValues, String filterValue, PolicyMatchCondition condition) {
|
||||||
if (condition == PolicyMatchCondition.EQUALS) {
|
switch (condition) {
|
||||||
return fieldValues.contains(filterValue);
|
case EQUALS:
|
||||||
|
return fieldValues.contains(filterValue);
|
||||||
|
case STARTS_WITH:
|
||||||
|
return fieldValues.stream().anyMatch(v -> v.startsWith(filterValue));
|
||||||
|
default:
|
||||||
|
log.error("Unsupported condition {}", condition);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
log.error("Unsupported condition {}", condition);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,9 +23,7 @@ import com.linkedin.entity.EnvelopedAspectMap;
|
|||||||
import com.linkedin.entity.client.EntityClient;
|
import com.linkedin.entity.client.EntityClient;
|
||||||
import com.linkedin.identity.RoleMembership;
|
import com.linkedin.identity.RoleMembership;
|
||||||
import com.linkedin.metadata.Constants;
|
import com.linkedin.metadata.Constants;
|
||||||
import com.linkedin.policy.DataHubActorFilter;
|
import com.linkedin.policy.*;
|
||||||
import com.linkedin.policy.DataHubPolicyInfo;
|
|
||||||
import com.linkedin.policy.DataHubResourceFilter;
|
|
||||||
import io.datahubproject.metadata.context.OperationContext;
|
import io.datahubproject.metadata.context.OperationContext;
|
||||||
import io.datahubproject.test.metadata.context.TestOperationContexts;
|
import io.datahubproject.test.metadata.context.TestOperationContexts;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@ -1043,6 +1041,92 @@ public class PolicyEngineTest {
|
|||||||
verify(_entityClient, times(0)).batchGetV2(any(), any(), any(), any());
|
verify(_entityClient, times(0)).batchGetV2(any(), any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvaluatePolicyResourceFilterResourceUrnStartsWithMatch() throws Exception {
|
||||||
|
final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo();
|
||||||
|
dataHubPolicyInfo.setType(METADATA_POLICY_TYPE);
|
||||||
|
dataHubPolicyInfo.setState(ACTIVE_POLICY_STATE);
|
||||||
|
dataHubPolicyInfo.setPrivileges(new StringArray("EDIT_ENTITY_TAGS"));
|
||||||
|
dataHubPolicyInfo.setDisplayName("My Test Display");
|
||||||
|
dataHubPolicyInfo.setDescription("My test display!");
|
||||||
|
dataHubPolicyInfo.setEditable(true);
|
||||||
|
|
||||||
|
final DataHubActorFilter actorFilter = new DataHubActorFilter();
|
||||||
|
actorFilter.setResourceOwners(true);
|
||||||
|
actorFilter.setAllUsers(true);
|
||||||
|
actorFilter.setAllGroups(true);
|
||||||
|
dataHubPolicyInfo.setActors(actorFilter);
|
||||||
|
|
||||||
|
final DataHubResourceFilter resourceFilter = new DataHubResourceFilter();
|
||||||
|
PolicyMatchCriterion policyMatchCriterion =
|
||||||
|
FilterUtils.newCriterion(
|
||||||
|
EntityFieldType.URN,
|
||||||
|
Collections.singletonList("urn:li:dataset:te"),
|
||||||
|
PolicyMatchCondition.STARTS_WITH);
|
||||||
|
|
||||||
|
resourceFilter.setFilter(
|
||||||
|
new PolicyMatchFilter()
|
||||||
|
.setCriteria(
|
||||||
|
new PolicyMatchCriterionArray(Collections.singleton(policyMatchCriterion))));
|
||||||
|
dataHubPolicyInfo.setResources(resourceFilter);
|
||||||
|
|
||||||
|
ResolvedEntitySpec resourceSpec = buildEntityResolvers("dataset", RESOURCE_URN);
|
||||||
|
PolicyEngine.PolicyEvaluationResult result =
|
||||||
|
_policyEngine.evaluatePolicy(
|
||||||
|
systemOperationContext,
|
||||||
|
dataHubPolicyInfo,
|
||||||
|
resolvedAuthorizedUserSpec,
|
||||||
|
"EDIT_ENTITY_TAGS",
|
||||||
|
Optional.of(resourceSpec));
|
||||||
|
assertTrue(result.isGranted());
|
||||||
|
|
||||||
|
// Verify no network calls
|
||||||
|
verify(_entityClient, times(0)).batchGetV2(any(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvaluatePolicyResourceFilterResourceUrnStartsWithNoMatch() throws Exception {
|
||||||
|
final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo();
|
||||||
|
dataHubPolicyInfo.setType(METADATA_POLICY_TYPE);
|
||||||
|
dataHubPolicyInfo.setState(ACTIVE_POLICY_STATE);
|
||||||
|
dataHubPolicyInfo.setPrivileges(new StringArray("EDIT_ENTITY_TAGS"));
|
||||||
|
dataHubPolicyInfo.setDisplayName("My Test Display");
|
||||||
|
dataHubPolicyInfo.setDescription("My test display!");
|
||||||
|
dataHubPolicyInfo.setEditable(true);
|
||||||
|
|
||||||
|
final DataHubActorFilter actorFilter = new DataHubActorFilter();
|
||||||
|
actorFilter.setResourceOwners(true);
|
||||||
|
actorFilter.setAllUsers(true);
|
||||||
|
actorFilter.setAllGroups(true);
|
||||||
|
dataHubPolicyInfo.setActors(actorFilter);
|
||||||
|
|
||||||
|
final DataHubResourceFilter resourceFilter = new DataHubResourceFilter();
|
||||||
|
PolicyMatchCriterion policyMatchCriterion =
|
||||||
|
FilterUtils.newCriterion(
|
||||||
|
EntityFieldType.URN,
|
||||||
|
Collections.singletonList("urn:li:dataset:other"),
|
||||||
|
PolicyMatchCondition.STARTS_WITH);
|
||||||
|
|
||||||
|
resourceFilter.setFilter(
|
||||||
|
new PolicyMatchFilter()
|
||||||
|
.setCriteria(
|
||||||
|
new PolicyMatchCriterionArray(Collections.singleton(policyMatchCriterion))));
|
||||||
|
dataHubPolicyInfo.setResources(resourceFilter);
|
||||||
|
|
||||||
|
ResolvedEntitySpec resourceSpec = buildEntityResolvers("dataset", RESOURCE_URN);
|
||||||
|
PolicyEngine.PolicyEvaluationResult result =
|
||||||
|
_policyEngine.evaluatePolicy(
|
||||||
|
systemOperationContext,
|
||||||
|
dataHubPolicyInfo,
|
||||||
|
resolvedAuthorizedUserSpec,
|
||||||
|
"EDIT_ENTITY_TAGS",
|
||||||
|
Optional.of(resourceSpec));
|
||||||
|
assertFalse(result.isGranted());
|
||||||
|
|
||||||
|
// Verify no network calls
|
||||||
|
verify(_entityClient, times(0)).batchGetV2(any(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEvaluatePolicyResourceFilterSpecificResourceMatchDomain() throws Exception {
|
public void testEvaluatePolicyResourceFilterSpecificResourceMatchDomain() throws Exception {
|
||||||
final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo();
|
final DataHubPolicyInfo dataHubPolicyInfo = new DataHubPolicyInfo();
|
||||||
|
@ -5478,9 +5478,10 @@
|
|||||||
"type" : "enum",
|
"type" : "enum",
|
||||||
"name" : "PolicyMatchCondition",
|
"name" : "PolicyMatchCondition",
|
||||||
"doc" : "The matching condition in a filter criterion",
|
"doc" : "The matching condition in a filter criterion",
|
||||||
"symbols" : [ "EQUALS" ],
|
"symbols" : [ "EQUALS", "STARTS_WITH" ],
|
||||||
"symbolDocs" : {
|
"symbolDocs" : {
|
||||||
"EQUALS" : "Whether the field matches the value"
|
"EQUALS" : "Whether the field matches the value",
|
||||||
|
"STARTS_WITH" : "Whether the field value starts with the value"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doc" : "The condition for the criterion",
|
"doc" : "The condition for the criterion",
|
||||||
|
@ -5472,9 +5472,10 @@
|
|||||||
"type" : "enum",
|
"type" : "enum",
|
||||||
"name" : "PolicyMatchCondition",
|
"name" : "PolicyMatchCondition",
|
||||||
"doc" : "The matching condition in a filter criterion",
|
"doc" : "The matching condition in a filter criterion",
|
||||||
"symbols" : [ "EQUALS" ],
|
"symbols" : [ "EQUALS", "STARTS_WITH" ],
|
||||||
"symbolDocs" : {
|
"symbolDocs" : {
|
||||||
"EQUALS" : "Whether the field matches the value"
|
"EQUALS" : "Whether the field matches the value",
|
||||||
|
"STARTS_WITH" : "Whether the field value starts with the value"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doc" : "The condition for the criterion",
|
"doc" : "The condition for the criterion",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user