-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Fixes #26774: expose custom properties in SpEL policy rule conditions #27218
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
base: main
Are you sure you want to change the base?
Changes from all commits
d1164f2
c9b69aa
2e365ad
c6d5ee2
ffc9755
7a24084
19bae7d
ebf3951
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,14 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package org.openmetadata.service.security.policyevaluator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.openmetadata.service.Entity.FIELD_EXTENSION; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import static org.openmetadata.service.Entity.FIELD_OWNERS; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.HashSet; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NonNull; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -17,6 +19,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.schema.type.EntityReference; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.schema.type.Include; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.schema.type.TagLabel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.schema.utils.JsonUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.service.Entity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.service.exception.EntityNotFoundException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.openmetadata.service.jdbi3.EntityRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -37,6 +40,7 @@ public class ResourceContext<T extends EntityInterface> implements ResourceConte | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final UUID id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final String name; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private T entity; // Will be lazily initialized | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Map<String, Object> cachedCustomProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
gitar-bot[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private ResourceContextInterface.Operation operation = ResourceContextInterface.Operation.NONE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Include include; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Fields requestedFields; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -186,6 +190,31 @@ public List<EntityReference> getDomains() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return entity.getDomains(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Map<String, Object> getCustomProperties() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cachedCustomProperties != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cachedCustomProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolveEntity(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (entity == null || entity.getExtension() == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cachedCustomProperties = Collections.emptyMap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cachedCustomProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @SuppressWarnings("unchecked") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<String, Object> props = JsonUtils.convertValue(entity.getExtension(), Map.class); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+199
to
+205
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (entity == null || entity.getExtension() == null) { | |
| cachedCustomProperties = Collections.emptyMap(); | |
| return cachedCustomProperties; | |
| } | |
| try { | |
| @SuppressWarnings("unchecked") | |
| Map<String, Object> props = JsonUtils.convertValue(entity.getExtension(), Map.class); | |
| if (entity == null) { | |
| cachedCustomProperties = Collections.emptyMap(); | |
| return cachedCustomProperties; | |
| } | |
| Object extension = entity.getExtension(); | |
| if (extension == null) { | |
| try { | |
| extension = entityRepository.getExtension(entity); | |
| } catch (Exception e) { | |
| LOG.warn( | |
| "Failed to fetch custom properties for entity {}: {}", | |
| entity.getId(), | |
| e.getMessage()); | |
| } | |
| } | |
| if (extension == null) { | |
| cachedCustomProperties = Collections.emptyMap(); | |
| return cachedCustomProperties; | |
| } | |
| try { | |
| @SuppressWarnings("unchecked") | |
| Map<String, Object> props = JsonUtils.convertValue(extension, Map.class); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,9 @@ | |||||
| import static org.openmetadata.schema.type.Include.NON_DELETED; | ||||||
|
|
||||||
| import java.util.Arrays; | ||||||
| import java.util.Collections; | ||||||
| import java.util.List; | ||||||
| import java.util.Map; | ||||||
| import java.util.Optional; | ||||||
| import java.util.stream.Collectors; | ||||||
| import lombok.extern.slf4j.Slf4j; | ||||||
|
|
@@ -414,4 +416,61 @@ private void validateEntityByName(String entityType, String name) { | |||||
| name); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| @Function( | ||||||
| name = "getCustomProperties", | ||||||
| input = "None", | ||||||
| description = "Returns a map of custom properties for the resource being evaluated.", | ||||||
| examples = {"getCustomProperties()"}) | ||||||
| @SuppressWarnings("unused") | ||||||
| public Map<String, Object> getCustomProperties() { | ||||||
| if (resourceContext == null) { | ||||||
| return Collections.emptyMap(); | ||||||
| } | ||||||
| return resourceContext.getCustomProperties(); | ||||||
|
mohitjeswani01 marked this conversation as resolved.
|
||||||
| } | ||||||
|
mohitjeswani01 marked this conversation as resolved.
|
||||||
|
|
||||||
| /** | ||||||
| * SpEL helper: returns true if the resource has a custom property with the given name whose value | ||||||
| * equals the given expected value. | ||||||
| * | ||||||
| * <p>Usage in policy rule condition: | ||||||
| * | ||||||
| * <p>matchCustomProperty('dataSensitivity', 'PII') matchCustomProperty('department', 'Finance') | ||||||
| * | ||||||
| * <p>Returns false (not an error) when: | ||||||
| * | ||||||
| * <p>- the entity has no custom properties - the property does not exist on this entity - the | ||||||
| * property value is null | ||||||
| */ | ||||||
| @Function( | ||||||
| name = "matchCustomProperty", | ||||||
| input = "propertyName (String), expectedValue (String)", | ||||||
| description = | ||||||
| "Returns true if the resource has a custom property with the given name and its value matches the expected value.", | ||||||
| examples = {"matchCustomProperty('propertyName', 'expectedValue')"}) | ||||||
| @SuppressWarnings("unused") | ||||||
| public boolean matchCustomProperty(String propertyName, String expectedValue) { | ||||||
| if (expressionValidation) { | ||||||
| // During validation mode — just confirm syntax is valid, return false | ||||||
| return false; | ||||||
| } | ||||||
| if (resourceContext == null || propertyName == null || expectedValue == null) { | ||||||
| return false; | ||||||
|
mohitjeswani01 marked this conversation as resolved.
|
||||||
| } | ||||||
| Map<String, Object> props = resourceContext.getCustomProperties(); | ||||||
| if (props == null || props.isEmpty()) { | ||||||
| return false; | ||||||
| } | ||||||
| Object actual = props.get(propertyName); | ||||||
| if (actual == null) { | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| if (actual instanceof List<?> list) { | ||||||
| return list.stream().anyMatch(item -> expectedValue.equals(item.toString())); | ||||||
|
||||||
| return list.stream().anyMatch(item -> expectedValue.equals(item.toString())); | |
| return list.stream().anyMatch(item -> item != null && expectedValue.equals(item.toString())); |
Uh oh!
There was an error while loading. Please reload this page.