Skip to content

Commit b8cca80

Browse files
feat(policy): expose customProperties in SpEL policy conditions (#27033)
1 parent 6a1be03 commit b8cca80

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/ResourceContext.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.openmetadata.service.security.policyevaluator;
22

33
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
4+
import static org.openmetadata.service.Entity.FIELD_EXTENSION;
45
import static org.openmetadata.service.Entity.FIELD_OWNERS;
56

67
import java.util.ArrayList;
78
import java.util.Collections;
89
import java.util.HashSet;
910
import java.util.List;
11+
import java.util.Map;
1012
import java.util.UUID;
1113
import lombok.Getter;
1214
import lombok.NonNull;
@@ -17,6 +19,7 @@
1719
import org.openmetadata.schema.type.EntityReference;
1820
import org.openmetadata.schema.type.Include;
1921
import org.openmetadata.schema.type.TagLabel;
22+
import org.openmetadata.schema.utils.JsonUtils;
2023
import org.openmetadata.service.Entity;
2124
import org.openmetadata.service.exception.EntityNotFoundException;
2225
import org.openmetadata.service.jdbi3.EntityRepository;
@@ -186,6 +189,20 @@ public List<EntityReference> getDomains() {
186189
return entity.getDomains();
187190
}
188191

192+
@Override
193+
public Map<String, Object> getCustomProperties() {
194+
resolveEntity();
195+
if (entity == null || entity.getExtension() == null) {
196+
return Collections.emptyMap();
197+
}
198+
try {
199+
return JsonUtils.getMap(entity.getExtension());
200+
} catch (Exception e) {
201+
LOG.warn("Failed to get custom properties: {}", e.getMessage());
202+
return Collections.emptyMap();
203+
}
204+
}
205+
189206
private EntityInterface resolveEntity() {
190207
if (entity == null) {
191208
Fields fieldList;
@@ -210,6 +227,9 @@ private EntityInterface resolveEntity() {
210227
if (entityRepository.isSupportsReviewers()) {
211228
fields = EntityUtil.addField(fields, Entity.FIELD_REVIEWERS);
212229
}
230+
if (supportsExtension()) {
231+
fields = EntityUtil.addField(fields, FIELD_EXTENSION);
232+
}
213233
fieldList = entityRepository.getFields(fields);
214234
}
215235

@@ -251,4 +271,13 @@ private boolean useRepositoryCache() {
251271
return operation != ResourceContextInterface.Operation.PATCH
252272
&& operation != ResourceContextInterface.Operation.PUT;
253273
}
274+
275+
private boolean supportsExtension() {
276+
try {
277+
entityRepository.getFields(FIELD_EXTENSION);
278+
return true;
279+
} catch (Exception e) {
280+
return false;
281+
}
282+
}
254283
}

openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/ResourceContextInterface.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.openmetadata.service.security.policyevaluator;
22

3+
import java.util.Collections;
34
import java.util.List;
5+
import java.util.Map;
46
import org.openmetadata.schema.EntityInterface;
57
import org.openmetadata.schema.type.EntityReference;
68
import org.openmetadata.schema.type.TagLabel;
@@ -25,4 +27,10 @@ enum Operation {
2527
EntityInterface getEntity();
2628

2729
List<EntityReference> getDomains();
30+
31+
// Get custom properties of a resource as a map for use in SpEL policy conditions.
32+
// Returns an empty map if the resource has no custom properties.
33+
default Map<String, Object> getCustomProperties() {
34+
return Collections.emptyMap();
35+
}
2836
}

openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import static org.openmetadata.schema.type.Include.NON_DELETED;
55

66
import java.util.Arrays;
7+
import java.util.Collections;
78
import java.util.List;
9+
import java.util.Map;
810
import java.util.Optional;
911
import java.util.stream.Collectors;
1012
import lombok.extern.slf4j.Slf4j;
@@ -414,4 +416,12 @@ private void validateEntityByName(String entityType, String name) {
414416
name);
415417
}
416418
}
419+
420+
@SuppressWarnings("unused")
421+
public Map<String, Object> getCustomProperties() {
422+
if (resourceContext == null) {
423+
return Collections.emptyMap();
424+
}
425+
return resourceContext.getCustomProperties();
426+
}
417427
}

openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.UUID;
2324
import lombok.extern.slf4j.Slf4j;
2425
import org.apache.commons.lang3.tuple.ImmutablePair;
@@ -1079,6 +1080,28 @@ void test_noDomain() {
10791080
assertTrue(negatedNoDomainInherited != null && negatedNoDomainInherited);
10801081
}
10811082

1083+
@Test
1084+
void test_customPropertiesExposedInSpEL() {
1085+
ResourceContextInterface customPropertiesContext = mock(ResourceContextInterface.class);
1086+
Mockito.when(customPropertiesContext.getCustomProperties())
1087+
.thenReturn(Map.of("sensitivity", "PII"));
1088+
1089+
SubjectContext localSubjectContext = new SubjectContext(user, null);
1090+
RuleEvaluator evaluator = new RuleEvaluator(null, localSubjectContext, customPropertiesContext);
1091+
StandardEvaluationContext context = new StandardEvaluationContext(evaluator);
1092+
Boolean result =
1093+
parseExpression("customProperties.get('sensitivity') == 'PII'")
1094+
.getValue(context, Boolean.class);
1095+
assertTrue(result != null && result);
1096+
1097+
RuleEvaluator validationEvaluator = new RuleEvaluator(false);
1098+
StandardEvaluationContext validationContext = new StandardEvaluationContext(validationEvaluator);
1099+
Boolean validationResult =
1100+
parseExpression("customProperties.get('sensitivity') == 'PII'")
1101+
.getValue(validationContext, Boolean.class);
1102+
assertTrue(validationResult != null && !validationResult);
1103+
}
1104+
10821105
@AfterEach
10831106
void resetContext() {
10841107
subjectContext = new SubjectContext(user, null);

0 commit comments

Comments
 (0)