From b8e0ae489a6c01dca506c876db5bc356e0552bc3 Mon Sep 17 00:00:00 2001 From: Suresh Srinivas Date: Tue, 21 Mar 2023 07:24:41 -0700 Subject: [PATCH] Fixes #10679 - Add policy functions inAnyTeam and hasAnyRole (#10680) --- .../jdbi3/EventSubscriptionRepository.java | 8 - .../service/jdbi3/TeamRepository.java | 3 +- .../service/jdbi3/UserRepository.java | 3 +- .../service/resources/CollectionRegistry.java | 10 -- .../policyevaluator/CompiledRule.java | 10 -- .../security/policyevaluator/PolicyCache.java | 2 +- .../policyevaluator/PolicyEvaluator.java | 69 ++------ .../policyevaluator/RuleEvaluator.java | 49 ++++-- .../policyevaluator/SubjectCache.java | 36 +++- .../policyevaluator/SubjectContext.java | 62 +++---- .../policyevaluator/RuleEvaluatorTest.java | 156 ++++++++++++++++- .../policyevaluator/SubjectContextTest.java | 164 +++++++++--------- .../org/openmetadata/schema/Function.java | 5 - 13 files changed, 350 insertions(+), 227 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EventSubscriptionRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EventSubscriptionRepository.java index f5b6f3c87bf..ea5af40eafe 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EventSubscriptionRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EventSubscriptionRepository.java @@ -24,7 +24,6 @@ import lombok.extern.slf4j.Slf4j; import org.openmetadata.schema.entity.events.EventFilterRule; import org.openmetadata.schema.entity.events.EventSubscription; import org.openmetadata.schema.entity.events.SubscriptionStatus; -import org.openmetadata.schema.type.EntityReference; import org.openmetadata.service.Entity; import org.openmetadata.service.events.EventPubSub; import org.openmetadata.service.events.subscription.AlertUtil; @@ -75,18 +74,11 @@ public class EventSubscriptionRepository extends EntityRepository { continue; // Parent is being created by CSV import } // Else the parent should already exist - if (!SubjectCache.getInstance().isInTeam(team.getName(), listOf(parentRef))) { + if (!SubjectCache.getInstance().isInTeam(team.getName(), parentRef)) { importFailure(printer, invalidTeam(4, team.getName(), importedTeam.getName(), parentRef.getName()), record); processRecord = false; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java index a850906827b..600d82ccfd3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/UserRepository.java @@ -13,7 +13,6 @@ package org.openmetadata.service.jdbi3; -import static org.openmetadata.common.utils.CommonUtil.listOf; import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; import static org.openmetadata.csv.CsvUtil.addEntityReferences; @@ -401,7 +400,7 @@ public class UserRepository extends EntityRepository { continue; // Team is same as the team to which CSV is being imported, then it is in the same hierarchy } // Else the parent should already exist - if (!SubjectCache.getInstance().isInTeam(team.getName(), listOf(teamRef))) { + if (!SubjectCache.getInstance().isInTeam(team.getName(), teamRef)) { importFailure(printer, invalidTeam(6, team.getName(), user, teamRef.getName()), record); processRecord = false; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java index c01303c9744..93275557141 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/CollectionRegistry.java @@ -61,12 +61,6 @@ public final class CollectionRegistry { /** Map of class name to list of functions exposed for writing conditions */ private final Map, List> functionMap = new ConcurrentHashMap<>(); - /** - * Some functions are used for capturing resource based rules where policies are applied based on resource being - * accessed and team hierarchy the resource belongs to instead of the subject. - */ - @Getter private final List resourceBasedFunctions = new ArrayList<>(); - /** Resources used only for testing */ @VisibleForTesting private final List testResources = new ArrayList<>(); @@ -137,10 +131,6 @@ public final class CollectionRegistry { .withParameterInputType(annotation.paramInputType()); functionList.add(function); functionList.sort(Comparator.comparing(org.openmetadata.schema.type.Function::getName)); - - if (annotation.resourceBased()) { - resourceBasedFunctions.add(annotation.name()); - } LOG.info("Initialized for {} function {}\n", method.getDeclaringClass().getSimpleName(), function); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/CompiledRule.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/CompiledRule.java index cf3bc6cf747..e472277cb38 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/CompiledRule.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/CompiledRule.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Iterator; import java.util.List; import java.util.Map; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.openmetadata.schema.entity.policies.accessControl.Rule; import org.openmetadata.schema.type.MetadataOperation; @@ -15,7 +14,6 @@ import org.openmetadata.schema.type.Permission; import org.openmetadata.schema.type.Permission.Access; import org.openmetadata.schema.type.ResourcePermission; import org.openmetadata.service.exception.CatalogExceptionMessage; -import org.openmetadata.service.resources.CollectionRegistry; import org.openmetadata.service.security.AuthorizationException; import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext; import org.springframework.expression.Expression; @@ -27,7 +25,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; public class CompiledRule extends Rule { private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); @JsonIgnore private Expression expression; - @JsonIgnore @Getter private boolean resourceBased = false; public CompiledRule(Rule rule) { super(); @@ -72,13 +69,6 @@ public class CompiledRule extends Rule { } if (expression == null) { expression = parseExpression(getCondition()); - List resourceBasedFunctions = CollectionRegistry.getInstance().getResourceBasedFunctions(); - for (String function : resourceBasedFunctions) { - if (getCondition().contains(function)) { - resourceBased = true; - break; - } - } } return expression; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyCache.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyCache.java index f5bd6545d37..d70c76171dd 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyCache.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyCache.java @@ -50,7 +50,7 @@ public class PolicyCache { public static void initialize() { if (!INITIALIZED) { POLICY_CACHE = - CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(3, TimeUnit.MINUTES).build(new PolicyLoader()); + CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(3, TimeUnit.MINUTES).build(new PolicyLoader()); POLICY_REPOSITORY = (PolicyRepository) Entity.getEntityRepository(Entity.POLICY); FIELDS = POLICY_REPOSITORY.getFields("rules"); INITIALIZED = true; diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyEvaluator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyEvaluator.java index 18780129725..f1bfeb255a7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyEvaluator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/PolicyEvaluator.java @@ -63,18 +63,12 @@ public class PolicyEvaluator { @NonNull ResourceContextInterface resourceContext, @NonNull OperationContext operationContext) throws IOException { - // First run through all the DENY policies based on the user + // First run through all the DENY policies evaluateDenySubjectPolicies(subjectContext, resourceContext, operationContext); - // Next run through all the DENY policies based on the resource - evaluateDenyResourcePolicies(subjectContext, resourceContext, operationContext); - // Next run through all the ALLOW policies based on the user evaluateAllowSubjectPolicies(subjectContext, resourceContext, operationContext); - // Next run through all the ALLOW policies based on the resource - evaluateAllowResourcePolicies(subjectContext, resourceContext, operationContext); - if (!operationContext.getOperations().isEmpty()) { // Some operations have not been allowed throw new AuthorizationException( CatalogExceptionMessage.permissionNotAllowed( @@ -83,33 +77,17 @@ public class PolicyEvaluator { } private static void evaluateDenySubjectPolicies( - SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) { - evaluatePolicies(subjectContext.getPolicies(), subjectContext, resourceContext, operationContext, true, false); + SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) + throws IOException { + Iterator policyIterator = subjectContext.getPolicies(resourceContext.getOwner()); + evaluatePolicies(policyIterator, subjectContext, resourceContext, operationContext, true); } private static void evaluateAllowSubjectPolicies( - SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) { - evaluatePolicies(subjectContext.getPolicies(), subjectContext, resourceContext, operationContext, false, false); - } - - private static void evaluateDenyResourcePolicies( SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) throws IOException { - if (resourceContext == null || resourceContext.getOwner() == null) { - return; // No owner for a resource. No need to walk the hierarchy of user and teams that are resource owners - } - Iterator resourcePolicies = subjectContext.getResourcePolicies(resourceContext.getOwner()); - evaluatePolicies(resourcePolicies, subjectContext, resourceContext, operationContext, true, true); - } - - private static void evaluateAllowResourcePolicies( - SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) - throws IOException { - if (resourceContext == null || resourceContext.getOwner() == null) { - return; // No owner for a resource. No need to walk the hierarchy of user and teams that are resource owners - } - Iterator resourcePolicies = subjectContext.getResourcePolicies(resourceContext.getOwner()); - evaluatePolicies(resourcePolicies, subjectContext, resourceContext, operationContext, false, true); + Iterator policyIterator = subjectContext.getPolicies(resourceContext.getOwner()); + evaluatePolicies(policyIterator, subjectContext, resourceContext, operationContext, false); } private static void evaluatePolicies( @@ -117,16 +95,12 @@ public class PolicyEvaluator { SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext, - boolean evaluateDeny, - boolean evaluateResourcePolicies) { + boolean evaluateDeny) { // When an operation is allowed by a rule, it is removed from operation context // When list of operations is empty in the operation context, all operations have been allowed while (policies.hasNext() && !operationContext.getOperations().isEmpty()) { PolicyContext context = policies.next(); for (CompiledRule rule : context.getRules()) { - if (evaluateResourcePolicies && !rule.isResourceBased()) { - continue; // Only evaluate resource based rules - } LOG.debug( "evaluating policy for {} {}:{}:{}", evaluateDeny ? "deny" : "allow", @@ -146,7 +120,7 @@ public class PolicyEvaluator { public static List listPermission(@NonNull SubjectContext subjectContext) { Map resourcePermissionMap = initResourcePermissions(); - Iterator policies = subjectContext.getPolicies(); + Iterator policies = subjectContext.getPolicies(null); while (policies.hasNext()) { PolicyContext policyContext = policies.next(); for (CompiledRule rule : policyContext.getRules()) { @@ -177,7 +151,7 @@ public class PolicyEvaluator { ResourcePermission resourcePermission = getResourcePermission(resourceType, Access.NOT_ALLOW); // Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW - Iterator policies = subjectContext.getPolicies(); + Iterator policies = subjectContext.getPolicies(null); while (policies.hasNext()) { PolicyContext policyContext = policies.next(); for (CompiledRule rule : policyContext.getRules()) { @@ -194,7 +168,7 @@ public class PolicyEvaluator { ResourcePermission resourcePermission = getResourcePermission(resourceContext.getResource(), Access.NOT_ALLOW); // Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW - Iterator policies = subjectContext.getPolicies(); + Iterator policies = subjectContext.getPolicies(resourceContext.getOwner()); while (policies.hasNext()) { PolicyContext policyContext = policies.next(); for (CompiledRule rule : policyContext.getRules()) { @@ -202,27 +176,6 @@ public class PolicyEvaluator { rule.evaluatePermission(subjectContext, resourceContext, resourcePermission, policyContext); } } - - // Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW - if (resourceContext == null || resourceContext.getOwner() == null) { - return resourcePermission; // No owner - No need to walk the hierarchy of user and teams that are resource owners - } - Iterator resourcePolicies = subjectContext.getResourcePolicies(resourceContext.getOwner()); - while (resourcePolicies.hasNext()) { - PolicyContext policyContext = resourcePolicies.next(); - for (CompiledRule rule : policyContext.getRules()) { - rule.getExpression(); - if (rule.isResourceBased() == false) { - continue; - } - LOG.debug( - "evaluating resource policies {}:{}:{}\n", - policyContext.getRoleName(), - policyContext.getPolicyName(), - rule.getName()); - rule.evaluatePermission(subjectContext, resourceContext, resourcePermission, policyContext); - } - } return resourcePermission; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java index 704ac6a2f40..c56e46f3489 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/RuleEvaluator.java @@ -39,8 +39,7 @@ public class RuleEvaluator { name = "isOwner", input = "none", description = "Returns true if the logged in user is the owner of the entity being accessed", - examples = {"isOwner()", "!isOwner", "noOwner() || isOwner()"}, - resourceBased = true) + examples = {"isOwner()", "!isOwner", "noOwner() || isOwner()"}) public boolean isOwner() throws IOException { return subjectContext != null && subjectContext.isOwner(resourceContext.getOwner()); } @@ -49,8 +48,7 @@ public class RuleEvaluator { name = "matchAllTags", input = "List of comma separated tag or glossary fully qualified names", description = "Returns true if the entity being accessed has all the tags given as input", - examples = {"matchAllTags('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"}, - resourceBased = true) + examples = {"matchAllTags('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"}) public boolean matchAllTags(String... tagFQNs) throws IOException { if (resourceContext == null) { return false; @@ -70,8 +68,7 @@ public class RuleEvaluator { name = "matchAnyTag", input = "List of comma separated tag or glossary fully qualified names", description = "Returns true if the entity being accessed has at least one of the tags given as input", - examples = {"matchAnyTag('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"}, - resourceBased = true) + examples = {"matchAnyTag('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"}) public boolean matchAnyTag(String... tagFQNs) throws IOException { if (resourceContext == null) { return false; @@ -93,16 +90,48 @@ public class RuleEvaluator { description = "Returns true if the user and the resource belongs to the team hierarchy where this policy is" + "attached. This allows restricting permissions to a resource to the members of the team hierarchy.", - examples = {"matchTeam()"}, - resourceBased = true) + examples = {"matchTeam()"}) public boolean matchTeam() throws IOException { if (resourceContext == null || resourceContext.getOwner() == null) { - return true; // No ownership information + return false; // No ownership information } if (policyContext == null || !policyContext.getEntityType().equals(Entity.TEAM)) { - return true; // Policy must be attached to a team for this function to work + return false; // Policy must be attached to a team for this function to work } return subjectContext.isTeamAsset(policyContext.getEntityName(), resourceContext.getOwner()) && subjectContext.isUserUnderTeam(policyContext.getEntityName()); } + + @Function( + name = "inAnyTeam", + input = "List of comma separated team names", + description = "Returns true if the user belongs under the hierarchy of any of the teams in the given team list.", + examples = {"inAnyTeam('marketing')"}) + public boolean inAnyTeam(String... teams) { + for (String team : teams) { + if (subjectContext.isUserUnderTeam(team)) { + LOG.debug("inAnyTeam - User {} is under the team {}", subjectContext.getUser().getName(), team); + return true; + } + LOG.debug("inAnyTeam - User {} is not under the team {}", subjectContext.getUser().getName(), team); + } + return false; + } + + @Function( + name = "hasAnyRole", + input = "List of comma separated roles", + description = + "Returns true if the user (either direct or inherited from the parent teams) has one or more roles " + + "from the list.", + examples = {"hasAnyRole('DataSteward', 'DataEngineer')"}) + public boolean hasAnyRole(String... roles) { + for (String role : roles) { + if (subjectContext.hasAnyRole(role)) { + LOG.debug("hasAnyRole - User {} has the role {}", subjectContext.getUser().getName(), role); + return true; + } + } + return false; + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectCache.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectCache.java index ac523c13891..374fbe20e40 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectCache.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectCache.java @@ -96,6 +96,14 @@ public class SubjectCache { } } + public User getUser(String userName) throws EntityNotFoundException { + try { + return USER_CACHE.get(userName).getUser(); + } catch (ExecutionException | UncheckedExecutionException ex) { + return null; + } + } + public Team getTeam(UUID teamId) throws EntityNotFoundException { try { return TEAM_CACHE.get(teamId); @@ -105,19 +113,41 @@ public class SubjectCache { } /** Return true if given list of teams is part of the hierarchy of parentTeam */ - public boolean isInTeam(String parentTeam, List teams) { + public boolean isInTeam(String parentTeam, EntityReference team) { Stack stack = new Stack<>(); - listOrEmpty(teams).forEach(stack::push); + stack.push(team); // Start with team and see if the parent matches while (!stack.empty()) { Team parent = getTeam(stack.pop().getId()); if (parent.getName().equals(parentTeam)) { return true; } - listOrEmpty(parent.getParents()).forEach(stack::push); + listOrEmpty(parent.getParents()).forEach(stack::push); // Continue to go up the chain of parents } return false; } + /** Return true if the given user has any roles the list of roles */ + public boolean hasRole(User user, String role) { + Stack stack = new Stack<>(); + // If user has one of the roles directly assigned then return true + if (hasRole(user.getRoles(), role)) { + return true; + } + listOrEmpty(user.getTeams()).forEach(stack::push); // Continue to go up the chain of parents + while (!stack.empty()) { + Team parent = getTeam(stack.pop().getId()); + if (hasRole(parent.getDefaultRoles(), role)) { + return true; + } + listOrEmpty(parent.getParents()).forEach(stack::push); // Continue to go up the chain of parents + } + return false; + } + + private static boolean hasRole(List userRoles, String expectedRole) { + return listOrEmpty(userRoles).stream().anyMatch(userRole -> userRole.getName().equals(expectedRole)); + } + public static void cleanUp() { LOG.info("Subject cache is cleaned up"); USER_CACHE.invalidateAll(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectContext.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectContext.java index fe171e3bc38..377c0c6b325 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectContext.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/SubjectContext.java @@ -16,7 +16,6 @@ package org.openmetadata.service.security.policyevaluator; import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -64,7 +63,12 @@ public class SubjectContext { /** Returns true if the user of this SubjectContext is under the team hierarchy of parentTeam */ public boolean isUserUnderTeam(String parentTeam) { - return isInTeam(parentTeam, user.getTeams()); + for (EntityReference userTeam : user.getTeams()) { + if (isInTeam(parentTeam, userTeam)) { + return true; + } + } + return false; } /** Returns true if the given resource owner is under the team hierarchy of parentTeam */ @@ -74,38 +78,30 @@ public class SubjectContext { return subjectContext.isUserUnderTeam(parentTeam); } else if (owner.getType().equals(Entity.TEAM)) { Team team = SubjectCache.getInstance().getTeam(owner.getId()); - return isInTeam(parentTeam, List.of(team.getEntityReference())); + return isInTeam(parentTeam, team.getEntityReference()); } return false; } - /** Return true if given list of teams is part of the hierarchy of parentTeam */ - private boolean isInTeam(String parentTeam, List teams) { - return SubjectCache.getInstance().isInTeam(parentTeam, teams); + /** Return true if the team is part of the hierarchy of parentTeam */ + private boolean isInTeam(String parentTeam, EntityReference team) { + return SubjectCache.getInstance().isInTeam(parentTeam, team); } // Iterate over all the policies of the team hierarchy the user belongs to - public Iterator getPolicies() { - return new UserPolicyIterator(user, new ArrayList<>()); - } - - // Iterate over all the policies of the team hierarchy the resource belongs to - public Iterator getResourcePolicies(EntityReference owner) { - if (owner.getType().equals(Entity.USER)) { - SubjectContext subjectContext = SubjectCache.getInstance().getSubjectContext(owner.getName()); - return subjectContext.getPolicies(); - } else if (owner.getType().equals(Entity.TEAM)) { - Team team = SubjectCache.getInstance().getTeam(owner.getId()); - List teamsVisited = new ArrayList<>(); - return new TeamPolicyIterator(team.getId(), teamsVisited); - } - return Collections.emptyIterator(); + public Iterator getPolicies(EntityReference resourceOwner) { + return new UserPolicyIterator(user, resourceOwner, new ArrayList<>()); } public List getTeams() { return user.getTeams(); } + /** Returns true if the user has any of the roles (either direct or inherited roles) */ + public boolean hasAnyRole(String roles) { + return SubjectCache.getInstance().hasRole(getUser(), roles); + } + @Getter static class PolicyContext { private final String entityType; @@ -123,7 +119,7 @@ public class SubjectContext { } } - /** PolicyIterator goes over policies in a set of policies one by one. */ + /** PolicyIterator goes over policies from a set of policies one by one. */ static class PolicyIterator implements Iterator { // When executing roles from a policy, entity type User or Team to which the Role is attached to. @@ -223,7 +219,7 @@ public class SubjectContext { private final List> iterators = new ArrayList<>(); /** Policy iterator for a user */ - UserPolicyIterator(User user, List teamsVisited) { + UserPolicyIterator(User user, EntityReference resourceOwner, List teamsVisited) { this.user = user; // Iterate over policies in user role @@ -231,13 +227,19 @@ public class SubjectContext { iterators.add(new RolePolicyIterator(Entity.USER, user.getName(), user.getRoles())); } + // Next, iterate over policies of teams to which the user belongs to + // Note that ** Bots don't inherit policies or default roles from teams ** if (!Boolean.TRUE.equals(user.getIsBot())) { - // Finally, iterate over policies of teams to which the user belongs to - // Note that ** Bots don't inherit policies or default roles from teams ** for (EntityReference team : user.getTeams()) { - iterators.add(new TeamPolicyIterator(team.getId(), teamsVisited)); + iterators.add(new TeamPolicyIterator(team.getId(), teamsVisited, false)); } } + + // Finally, iterate over policies of teams that own the resource + if (resourceOwner != null && resourceOwner.getType().equals(Entity.TEAM)) { + Team team = SubjectCache.getInstance().getTeam(resourceOwner.getId()); + iterators.add(new TeamPolicyIterator(team.getId(), teamsVisited, true)); + } } @Override @@ -248,7 +250,7 @@ public class SubjectContext { } iteratorIndex++; } - LOG.debug("Subject {} policy iteration done" + user.getName()); + LOG.debug("Subject {} policy iteration done", user.getName()); return false; } @@ -270,21 +272,21 @@ public class SubjectContext { private final List> iterators = new ArrayList<>(); /** Policy iterator for a team */ - TeamPolicyIterator(UUID teamId, List teamsVisited) { + TeamPolicyIterator(UUID teamId, List teamsVisited, boolean skipRoles) { Team team = SubjectCache.getInstance().getTeam(teamId); // If a team is already visited (because user can belong to multiple teams // and a team can belong to multiple teams) then don't visit the roles/policies of that team if (!teamsVisited.contains(teamId)) { teamsVisited.add(teamId); - if (team.getDefaultRoles() != null) { + if (!skipRoles && team.getDefaultRoles() != null) { iterators.add(new RolePolicyIterator(Entity.TEAM, team.getName(), team.getDefaultRoles())); } if (team.getPolicies() != null) { iterators.add(new PolicyIterator(Entity.TEAM, team.getName(), null, team.getPolicies())); } for (EntityReference parentTeam : listOrEmpty(team.getParents())) { - iterators.add(new TeamPolicyIterator(parentTeam.getId(), teamsVisited)); + iterators.add(new TeamPolicyIterator(parentTeam.getId(), teamsVisited, skipRoles)); } } } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java index 449840989cb..e661c7c5d99 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/RuleEvaluatorTest.java @@ -3,39 +3,52 @@ package org.openmetadata.service.security.policyevaluator; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.openmetadata.common.utils.CommonUtil.listOf; +import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.service.security.policyevaluator.CompiledRule.parseExpression; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.openmetadata.schema.entity.data.Table; +import org.openmetadata.schema.entity.teams.Role; import org.openmetadata.schema.entity.teams.Team; import org.openmetadata.schema.entity.teams.User; import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.TagLabel; import org.openmetadata.service.Entity; +import org.openmetadata.service.jdbi3.CollectionDAO.RoleDAO; import org.openmetadata.service.jdbi3.CollectionDAO.TableDAO; import org.openmetadata.service.jdbi3.CollectionDAO.TeamDAO; import org.openmetadata.service.jdbi3.CollectionDAO.UserDAO; +import org.openmetadata.service.jdbi3.RoleRepository; import org.openmetadata.service.jdbi3.TableRepository; import org.openmetadata.service.jdbi3.TeamRepository; import org.openmetadata.service.jdbi3.UserRepository; +import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; class RuleEvaluatorTest { - private static Table table = new Table().withName("table"); + private static final Table table = new Table().withName("table"); private static User user; private static EvaluationContext evaluationContext; + private static SubjectContext subjectContext; + private static ResourceContext resourceContext; @BeforeAll public static void setup() { Entity.registerEntity(User.class, Entity.USER, Mockito.mock(UserDAO.class), Mockito.mock(UserRepository.class)); Entity.registerEntity(Team.class, Entity.TEAM, Mockito.mock(TeamDAO.class), Mockito.mock(TeamRepository.class)); + Entity.registerEntity(Role.class, Entity.ROLE, Mockito.mock(RoleDAO.class), Mockito.mock(RoleRepository.class)); + SubjectCache.initialize(); + RoleCache.initialize(); TableRepository tableRepository = Mockito.mock(TableRepository.class); Mockito.when(tableRepository.getAllTags(any())) @@ -43,18 +56,24 @@ class RuleEvaluatorTest { Entity.registerEntity(Table.class, Entity.TABLE, Mockito.mock(TableDAO.class), tableRepository); user = new User().withId(UUID.randomUUID()).withName("user"); - ResourceContext resourceContext = + resourceContext = ResourceContext.builder() .resource("table") .entity(table) .entityRepository(Mockito.mock(TableRepository.class)) .build(); - SubjectContext subjectContext = new SubjectContext(user); + subjectContext = new SubjectContext(user); RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext); evaluationContext = new StandardEvaluationContext(ruleEvaluator); } + @AfterAll + public static void cleanup() { + SubjectCache.cleanUp(); + RoleCache.cleanUp(); + } + @Test void test_noOwner() { // Set no owner to the entity and test noOwner method @@ -133,6 +152,99 @@ class RuleEvaluatorTest { assertTrue(evaluateExpression("!matchAnyTag('tag4')")); } + @Test + void test_matchTeam() { + // Create a team hierarchy + Team team1 = createTeam("team1", null); + Team team11 = createTeam("team11", "team1"); + Team team12 = createTeam("team12", "team1"); + Team team111 = createTeam("team111", "team11"); + + // Resource belongs to team111 and the Policy executed is coming from team111 + table.setOwner(team111.getEntityReference()); + updatePolicyContext("team111"); + for (Team team : listOf(team111)) { // For users in team111 hierarchy matchTeam is true + user.setTeams(listOf(team.getEntityReference())); + assertTrue(evaluateExpression("matchTeam()")); + } + for (Team team : listOf(team1, team12, team11)) { // For users not in team111 hierarchy matchTeam is false + user.setTeams(listOf(team.getEntityReference())); + assertFalse(evaluateExpression("matchTeam()"), "Failed for team " + team.getName()); + } + + // Resource belongs to team111 and the Policy executed is coming from team11 + updatePolicyContext("team11"); + for (Team team : listOf(team11, team111)) { // For users in team11 hierarchy matchTeam is true + user.setTeams(listOf(team.getEntityReference())); + assertTrue(evaluateExpression("matchTeam()")); + } + for (Team team : listOf(team1, team12)) { // For users not in team11 hierarchy matchTeam is false + user.setTeams(listOf(team.getEntityReference())); + assertFalse(evaluateExpression("matchTeam()"), "Failed for team " + team.getName()); + } + + // Resource belongs to team111 and the Policy executed is coming from team1 + updatePolicyContext("team1"); + for (Team team : listOf(team1, team11, team111, team12)) { // For users in team1 hierarchy matchTeam is true + user.setTeams(listOf(team.getEntityReference())); + assertTrue(evaluateExpression("matchTeam()")); + } + } + + @Test + void test_inAnyTeam() { + // Create a team hierarchy + Team team1 = createTeam("team1", null); + createTeam("team11", "team1"); + Team team12 = createTeam("team12", "team1"); + Team team111 = createTeam("team111", "team11"); + + // User in team111 - that means user is also in parent teams team11 and team1 + user.setTeams(listOf(team111.getEntityReference())); + assertTrue(evaluateExpression("inAnyTeam('team1')")); + assertTrue(evaluateExpression("inAnyTeam('team11')")); + assertTrue(evaluateExpression("inAnyTeam('team111')")); + assertFalse(evaluateExpression("inAnyTeam('team12')")); + + // User in team12 - that means user is also in parent team team1 + user.setTeams(listOf(team12.getEntityReference())); + assertTrue(evaluateExpression("inAnyTeam('team1')")); + assertTrue(evaluateExpression("inAnyTeam('team12')")); + assertFalse(evaluateExpression("inAnyTeam('team111', 'team11')")); + + // User in team1 with no parents + user.setTeams(listOf(team1.getEntityReference())); + assertTrue(evaluateExpression("inAnyTeam('team1')")); + assertFalse(evaluateExpression("inAnyTeam('team12', 'team11', 'team111')")); + } + + @Test + void test_hasAnyRole() { + // Create a team hierarchy + Team team1 = createTeamWithRole("team1", null); + Team team11 = createTeamWithRole("team11", "team1"); + Team team111 = createTeamWithRole("team111", "team11"); + user.setRoles(listOf(createRole("user").getEntityReference())); + + // User in team111 inherits all roles + user.setTeams(listOf(team111.getEntityReference())); + for (String role : listOf("user", "team111", "team11", "team1")) { + assertTrue(evaluateExpression(String.format("hasAnyRole('%s')", role))); + } + + // User in team11 inherits all roles except team111 + user.setTeams(listOf(team11.getEntityReference())); + for (String role : listOf("user", "team11", "team1")) { + assertTrue(evaluateExpression(String.format("hasAnyRole('%s')", role))); + } + + // User in team1 does not have parent team to inherit from + user.setTeams(listOf(team1.getEntityReference())); + for (String role : listOf("user", "team1")) { + assertTrue(evaluateExpression(String.format("hasAnyRole('%s')", role))); + } + } + private Boolean evaluateExpression(String condition) { return parseExpression(condition).getValue(evaluationContext, Boolean.class); } @@ -144,4 +256,42 @@ class RuleEvaluatorTest { } return tagLabels; } + + private Team createTeam(String teamName, String parentName) { + UUID teamId = UUID.nameUUIDFromBytes(teamName.getBytes(StandardCharsets.UTF_8)); + Team team = new Team().withName(teamName).withId(teamId); + if (parentName != null) { + UUID parentId = UUID.nameUUIDFromBytes(parentName.getBytes(StandardCharsets.UTF_8)); + Team parentTeam = SubjectCache.getInstance().getTeam(parentId); + team.setParents(listOf(parentTeam.getEntityReference())); + } + SubjectCache.TEAM_CACHE.put(team.getId(), team); + return team; + } + + private Team createTeamWithRole(String teamName, String parentName) { + Team team = createTeam(teamName, parentName); + Role role = createRole(teamName); // Create a role with same name as the teamName + team.setDefaultRoles(listOf(role.getEntityReference())); + team.setInheritedRoles(new ArrayList<>()); + for (EntityReference parent : listOrEmpty(team.getParents())) { + Team parentTeam = SubjectCache.getInstance().getTeam(parent.getId()); + team.getInheritedRoles().addAll(listOrEmpty(parentTeam.getDefaultRoles())); + team.getInheritedRoles().addAll(listOrEmpty(parentTeam.getInheritedRoles())); + } + return team; + } + + private Role createRole(String roleName) { + UUID roleId = UUID.nameUUIDFromBytes(roleName.getBytes(StandardCharsets.UTF_8)); + Role role = new Role().withName(roleName).withId(roleId); + RoleCache.ROLE_CACHE.put(role.getId(), role); + return role; + } + + private void updatePolicyContext(String team) { + PolicyContext policyContext = new PolicyContext(Entity.TEAM, team, null, null, null); + RuleEvaluator ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContext); + evaluationContext = new StandardEvaluationContext(ruleEvaluator); + } } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/SubjectContextTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/SubjectContextTest.java index ec32084e207..c9c990f8cd4 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/SubjectContextTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/SubjectContextTest.java @@ -15,6 +15,7 @@ package org.openmetadata.service.security.policyevaluator; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import java.util.ArrayList; import java.util.Iterator; @@ -45,19 +46,25 @@ import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyCo public class SubjectContextTest { private static List team1Roles; private static List team1Policies; - private static Team team1; private static List team11Roles; private static List team11Policies; - private static Team team11; private static List team12Roles; private static List team12Policies; + private static List team13Roles; + private static List team13Policies; + private static Team team13; + private static List team111Roles; private static List team111Policies; private static Team team111; + private static List team131Roles; + private static List team131Policies; + private static Team team131; + private static List userRoles; private static User user; @@ -73,32 +80,40 @@ public class SubjectContextTest { SubjectCache.initialize(); // Create team hierarchy: - // team1, has team11, team12, team13 as children - // team11 has team111 as children - // team111 has user as children - // Each with 3 roles and 3 policies - team1Roles = getRoles("team1", 3); - team1Policies = getPolicies("team1", 3); - team1 = createTeam("team1", team1Roles, team1Policies, null); + // team1 + // / | \ + // team11 team12 team13 + // / / \ + // team111 / team131 + // \ / + // user + // Each team has 3 roles and 3 policies + team1Roles = getRoles("team1"); + team1Policies = getPolicies("team1"); + Team team1 = createTeam("team1", team1Roles, team1Policies, null); - team11Roles = getRoles("team11", 3); - team11Policies = getPolicies("team11", 3); - team11 = createTeam("team11", team11Roles, team11Policies, List.of(team1)); + team11Roles = getRoles("team11"); + team11Policies = getPolicies("team11"); + Team team11 = createTeam("team11", team11Roles, team11Policies, List.of(team1)); - team12Roles = getRoles("team12", 3); - team12Policies = getPolicies("team12", 3); + team12Roles = getRoles("team12"); + team12Policies = getPolicies("team12"); Team team12 = createTeam("team12", team12Roles, team12Policies, List.of(team1)); - List team13Roles = getRoles("team13", 3); - List team13Policies = getPolicies("team13", 3); - createTeam("team13", team13Roles, team13Policies, List.of(team1)); + team13Roles = getRoles("team13"); + team13Policies = getPolicies("team13"); + team13 = createTeam("team13", team13Roles, team13Policies, List.of(team1)); - team111Roles = getRoles("team111", 3); - team111Policies = getPolicies("team111", 3); + team111Roles = getRoles("team111"); + team111Policies = getPolicies("team111"); team111 = createTeam("team111", team111Roles, team111Policies, List.of(team11, team12)); + team131Roles = getRoles("team131"); + team131Policies = getPolicies("team131"); + team131 = createTeam("team131", team131Roles, team131Policies, List.of(team13)); + // Add user to team111 - userRoles = getRoles("user", 3); + userRoles = getRoles("user"); List userRolesRef = toEntityReferences(userRoles); user = new User().withName("user").withRoles(userRolesRef).withTeams(List.of(team111.getEntityReference())); SubjectCache.USER_CACHE.put("user", new SubjectContext(user)); @@ -113,12 +128,36 @@ public class SubjectContextTest { @Test void testPolicyIterator() { - // - // Check iteration order of the policies - // + // Check iteration order of the policies without resourceOwner SubjectContext subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName()); - Iterator policyContextIterator = subjectContext.getPolicies(); - assertUserPolicyIterator(policyContextIterator); + Iterator policyContextIterator = subjectContext.getPolicies(null); + List expectedUserPolicyOrder = new ArrayList<>(); + expectedUserPolicyOrder.addAll(getPolicyListFromRoles(userRoles)); // First polices associated with user roles + expectedUserPolicyOrder.addAll(getAllTeamPolicies(team111Roles, team111Policies)); // Next parent team111 policies + expectedUserPolicyOrder.addAll( + getAllTeamPolicies(team11Roles, team11Policies)); // Next team111 parent team11 policies + expectedUserPolicyOrder.addAll(getAllTeamPolicies(team1Roles, team1Policies)); // Next team11 parent team1 policies + expectedUserPolicyOrder.addAll( + getAllTeamPolicies(team12Roles, team12Policies)); // Next team111 parent team12 policies + assertPolicyIterator(expectedUserPolicyOrder, policyContextIterator); + + // Check iteration order of policies with team13 as the resource owner + subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName()); + policyContextIterator = subjectContext.getPolicies(team13.getEntityReference()); + List expectedUserAndTeam13PolicyOrder = new ArrayList<>(); + expectedUserAndTeam13PolicyOrder.addAll(expectedUserPolicyOrder); + expectedUserAndTeam13PolicyOrder.addAll(getAllTeamPolicies(null, team13Policies)); + assertPolicyIterator(expectedUserAndTeam13PolicyOrder, policyContextIterator); + + // Check iteration order of policies with team131 as the resource owner + subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName()); + policyContextIterator = subjectContext.getPolicies(team131.getEntityReference()); + // Roles & policies are inherited from resource owner team131 + List expectedUserAndTeam131PolicyOrder = new ArrayList<>(); + expectedUserAndTeam131PolicyOrder.addAll(expectedUserPolicyOrder); + expectedUserAndTeam131PolicyOrder.addAll(getAllTeamPolicies(null, team131Policies)); + expectedUserAndTeam131PolicyOrder.addAll(getAllTeamPolicies(null, team13Policies)); + assertPolicyIterator(expectedUserAndTeam131PolicyOrder, policyContextIterator); } @Test @@ -160,44 +199,12 @@ public class SubjectContextTest { } @Test - void testResourcePolicyIterator() { - // A resource with user as owner and make sure all policies from user's hierarchy is in the iterator - EntityReference userOwner = user.getEntityReference(); - SubjectContext subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName()); - Iterator actualPolicyIterator = subjectContext.getResourcePolicies(userOwner); - assertUserPolicyIterator(actualPolicyIterator); - - // A resource with team1 as owner and make sure all policies from user's hierarchy is in the iterator - EntityReference team1Owner = team1.getEntityReference(); - actualPolicyIterator = subjectContext.getResourcePolicies(team1Owner); - // add policies from team1 - List expectedPolicyOrder = new ArrayList<>(getAllTeamPolicies(team1Roles, team1Policies)); - assertPolicyIterator(expectedPolicyOrder, actualPolicyIterator); - - // A resource with team11 as owner and make sure all policies from user's hierarchy is in the iterator - EntityReference team11Owner = team11.getEntityReference(); - actualPolicyIterator = subjectContext.getResourcePolicies(team11Owner); - List list = new ArrayList<>(getAllTeamPolicies(team11Roles, team11Policies)); // add policies from team11 - list.addAll(expectedPolicyOrder); // Add all policies from parent team1 previously setup - expectedPolicyOrder = list; - assertPolicyIterator(expectedPolicyOrder, actualPolicyIterator); - - // A resource with team11 as owner and make sure all policies from user's hierarchy is in the iterator - EntityReference team111Owner = team111.getEntityReference(); - actualPolicyIterator = subjectContext.getResourcePolicies(team111Owner); - list = new ArrayList<>(getAllTeamPolicies(team111Roles, team111Policies)); // add policies from team111 - list.addAll(expectedPolicyOrder); // Add all policies form team11 and team1 previously setup - list.addAll(getPolicyListFromRoles(team12Roles)); // add policies from team12 roles - list.addAll(getPolicyList(team12Policies)); // add team12 policies - assertPolicyIterator(list, actualPolicyIterator); - } - - private static List getRoles(String prefix, int count) { + private static List getRoles(String prefix) { // Create roles with 3 policies each and each policy with 3 rules - List roles = new ArrayList<>(count); - for (int i = 1; i <= count; i++) { + List roles = new ArrayList<>(3); + for (int i = 1; i <= 3; i++) { String name = prefix + "_role_" + i; - List policies = toEntityReferences(getPolicies(name, 3)); + List policies = toEntityReferences(getPolicies(name)); Role role = new Role().withName(name).withId(UUID.randomUUID()).withPolicies(policies); RoleCache.ROLE_CACHE.put(role.getId(), role); roles.add(role); @@ -205,21 +212,21 @@ public class SubjectContextTest { return roles; } - private static List getPolicies(String prefix, int count) { - List policies = new ArrayList<>(count); - for (int i = 1; i <= count; i++) { + private static List getPolicies(String prefix) { + List policies = new ArrayList<>(3); + for (int i = 1; i <= 3; i++) { String name = prefix + "_policy_" + i; - Policy policy = new Policy().withName(name).withId(UUID.randomUUID()).withRules(getRules(name, 3)); + Policy policy = new Policy().withName(name).withId(UUID.randomUUID()).withRules(getRules(name)); policies.add(policy); PolicyCache.POLICY_CACHE.put(policy.getId(), PolicyCache.getInstance().getRules(policy)); } return policies; } - private static List getRules(String prefix, int count) { - List rules = new ArrayList<>(count); - for (int i = 1; i <= count; i++) { - rules.add(new Rule().withName(prefix + "rule" + count)); + private static List getRules(String prefix) { + List rules = new ArrayList<>(3); + for (int i = 1; i <= 3; i++) { + rules.add(new Rule().withName(prefix + "rule" + 3)); } return rules; } @@ -234,14 +241,14 @@ public class SubjectContextTest { private static List getAllTeamPolicies(List roles, List policies) { List list = new ArrayList<>(); - list.addAll(getPolicyListFromRoles(roles)); - list.addAll(getPolicyList(policies)); + listOrEmpty(list).addAll(getPolicyListFromRoles(roles)); + listOrEmpty(list).addAll(getPolicyList(policies)); return list; } private static List getPolicyListFromRoles(List roles) { List list = new ArrayList<>(); - roles.forEach(r -> list.addAll(getPolicyRefList(r.getPolicies()))); + listOrEmpty(roles).forEach(r -> list.addAll(getPolicyRefList(r.getPolicies()))); return list; } @@ -270,19 +277,6 @@ public class SubjectContextTest { return team; } - void assertUserPolicyIterator(Iterator actualPolicyIterator) { - // - // Check iteration order of the policies - // - List expectedPolicyOrder = new ArrayList<>(); - expectedPolicyOrder.addAll(getPolicyListFromRoles(userRoles)); // First polices associated with user roles - expectedPolicyOrder.addAll(getAllTeamPolicies(team111Roles, team111Policies)); // Next parent team111 policies - expectedPolicyOrder.addAll(getAllTeamPolicies(team11Roles, team11Policies)); // Next team111 parent team11 policies - expectedPolicyOrder.addAll(getAllTeamPolicies(team1Roles, team1Policies)); // Next team11 parent team1 policies - expectedPolicyOrder.addAll(getAllTeamPolicies(team12Roles, team12Policies)); // Next team111 parent team12 policies - assertPolicyIterator(expectedPolicyOrder, actualPolicyIterator); - } - void assertPolicyIterator(List expectedPolicyOrder, Iterator actualPolicyIterator) { int count = 0; while (actualPolicyIterator.hasNext()) { diff --git a/openmetadata-spec/src/main/java/org/openmetadata/schema/Function.java b/openmetadata-spec/src/main/java/org/openmetadata/schema/Function.java index 366a1ad5831..cc3292e5f0b 100644 --- a/openmetadata-spec/src/main/java/org/openmetadata/schema/Function.java +++ b/openmetadata-spec/src/main/java/org/openmetadata/schema/Function.java @@ -18,9 +18,4 @@ public @interface Function { String[] examples(); ParameterType paramInputType() default ParameterType.NOT_REQUIRED; - /** - * Some functions are used for capturing resource based rules where policies are applied based on resource being - * accessed and team hierarchy the resource belongs to instead of the subject. - */ - boolean resourceBased() default false; }