Fixes #10679 - Add policy functions inAnyTeam and hasAnyRole (#10680)

This commit is contained in:
Suresh Srinivas 2023-03-21 07:24:41 -07:00 committed by GitHub
parent 55fde2d775
commit b8e0ae489a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 350 additions and 227 deletions

View File

@ -24,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.entity.events.EventFilterRule; import org.openmetadata.schema.entity.events.EventFilterRule;
import org.openmetadata.schema.entity.events.EventSubscription; import org.openmetadata.schema.entity.events.EventSubscription;
import org.openmetadata.schema.entity.events.SubscriptionStatus; import org.openmetadata.schema.entity.events.SubscriptionStatus;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.service.Entity; import org.openmetadata.service.Entity;
import org.openmetadata.service.events.EventPubSub; import org.openmetadata.service.events.EventPubSub;
import org.openmetadata.service.events.subscription.AlertUtil; import org.openmetadata.service.events.subscription.AlertUtil;
@ -75,18 +74,11 @@ public class EventSubscriptionRepository extends EntityRepository<EventSubscript
@Override @Override
public void storeEntity(EventSubscription entity, boolean update) throws IOException { public void storeEntity(EventSubscription entity, boolean update) throws IOException {
EntityReference owner = entity.getOwner();
// Don't store owner, database, href and tags as JSON. Build it on the fly based on relationships
entity.withOwner(null).withHref(null);
store(entity, update); store(entity, update);
// Restore the relationships
entity.withOwner(owner);
} }
@Override @Override
public void storeRelationships(EventSubscription entity) { public void storeRelationships(EventSubscription entity) {
// store owner
storeOwner(entity, entity.getOwner()); storeOwner(entity, entity.getOwner());
} }

View File

@ -13,7 +13,6 @@
package org.openmetadata.service.jdbi3; 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.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.csv.CsvUtil.addEntityReferences; import static org.openmetadata.csv.CsvUtil.addEntityReferences;
@ -601,7 +600,7 @@ public class TeamRepository extends EntityRepository<Team> {
continue; // Parent is being created by CSV import continue; // Parent is being created by CSV import
} }
// Else the parent should already exist // 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); importFailure(printer, invalidTeam(4, team.getName(), importedTeam.getName(), parentRef.getName()), record);
processRecord = false; processRecord = false;
} }

View File

@ -13,7 +13,6 @@
package org.openmetadata.service.jdbi3; 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.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.csv.CsvUtil.addEntityReferences; import static org.openmetadata.csv.CsvUtil.addEntityReferences;
@ -401,7 +400,7 @@ public class UserRepository extends EntityRepository<User> {
continue; // Team is same as the team to which CSV is being imported, then it is in the same hierarchy 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 // 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); importFailure(printer, invalidTeam(6, team.getName(), user, teamRef.getName()), record);
processRecord = false; processRecord = false;
} }

View File

@ -61,12 +61,6 @@ public final class CollectionRegistry {
/** Map of class name to list of functions exposed for writing conditions */ /** Map of class name to list of functions exposed for writing conditions */
private final Map<Class<?>, List<org.openmetadata.schema.type.Function>> functionMap = new ConcurrentHashMap<>(); private final Map<Class<?>, List<org.openmetadata.schema.type.Function>> 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<String> resourceBasedFunctions = new ArrayList<>();
/** Resources used only for testing */ /** Resources used only for testing */
@VisibleForTesting private final List<Object> testResources = new ArrayList<>(); @VisibleForTesting private final List<Object> testResources = new ArrayList<>();
@ -137,10 +131,6 @@ public final class CollectionRegistry {
.withParameterInputType(annotation.paramInputType()); .withParameterInputType(annotation.paramInputType());
functionList.add(function); functionList.add(function);
functionList.sort(Comparator.comparing(org.openmetadata.schema.type.Function::getName)); 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); LOG.info("Initialized for {} function {}\n", method.getDeclaringClass().getSimpleName(), function);
} }
} }

View File

@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.entity.policies.accessControl.Rule; import org.openmetadata.schema.entity.policies.accessControl.Rule;
import org.openmetadata.schema.type.MetadataOperation; 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.Permission.Access;
import org.openmetadata.schema.type.ResourcePermission; import org.openmetadata.schema.type.ResourcePermission;
import org.openmetadata.service.exception.CatalogExceptionMessage; import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.resources.CollectionRegistry;
import org.openmetadata.service.security.AuthorizationException; import org.openmetadata.service.security.AuthorizationException;
import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext; import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
@ -27,7 +25,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
public class CompiledRule extends Rule { public class CompiledRule extends Rule {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
@JsonIgnore private Expression expression; @JsonIgnore private Expression expression;
@JsonIgnore @Getter private boolean resourceBased = false;
public CompiledRule(Rule rule) { public CompiledRule(Rule rule) {
super(); super();
@ -72,13 +69,6 @@ public class CompiledRule extends Rule {
} }
if (expression == null) { if (expression == null) {
expression = parseExpression(getCondition()); expression = parseExpression(getCondition());
List<String> resourceBasedFunctions = CollectionRegistry.getInstance().getResourceBasedFunctions();
for (String function : resourceBasedFunctions) {
if (getCondition().contains(function)) {
resourceBased = true;
break;
}
}
} }
return expression; return expression;
} }

View File

@ -50,7 +50,7 @@ public class PolicyCache {
public static void initialize() { public static void initialize() {
if (!INITIALIZED) { if (!INITIALIZED) {
POLICY_CACHE = 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); POLICY_REPOSITORY = (PolicyRepository) Entity.getEntityRepository(Entity.POLICY);
FIELDS = POLICY_REPOSITORY.getFields("rules"); FIELDS = POLICY_REPOSITORY.getFields("rules");
INITIALIZED = true; INITIALIZED = true;

View File

@ -63,18 +63,12 @@ public class PolicyEvaluator {
@NonNull ResourceContextInterface resourceContext, @NonNull ResourceContextInterface resourceContext,
@NonNull OperationContext operationContext) @NonNull OperationContext operationContext)
throws IOException { throws IOException {
// First run through all the DENY policies based on the user // First run through all the DENY policies
evaluateDenySubjectPolicies(subjectContext, resourceContext, operationContext); 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 // Next run through all the ALLOW policies based on the user
evaluateAllowSubjectPolicies(subjectContext, resourceContext, operationContext); 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 if (!operationContext.getOperations().isEmpty()) { // Some operations have not been allowed
throw new AuthorizationException( throw new AuthorizationException(
CatalogExceptionMessage.permissionNotAllowed( CatalogExceptionMessage.permissionNotAllowed(
@ -83,33 +77,17 @@ public class PolicyEvaluator {
} }
private static void evaluateDenySubjectPolicies( private static void evaluateDenySubjectPolicies(
SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext) { SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext)
evaluatePolicies(subjectContext.getPolicies(), subjectContext, resourceContext, operationContext, true, false); throws IOException {
Iterator<PolicyContext> policyIterator = subjectContext.getPolicies(resourceContext.getOwner());
evaluatePolicies(policyIterator, subjectContext, resourceContext, operationContext, true);
} }
private static void evaluateAllowSubjectPolicies( 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) SubjectContext subjectContext, ResourceContextInterface resourceContext, OperationContext operationContext)
throws IOException { throws IOException {
if (resourceContext == null || resourceContext.getOwner() == null) { Iterator<PolicyContext> policyIterator = subjectContext.getPolicies(resourceContext.getOwner());
return; // No owner for a resource. No need to walk the hierarchy of user and teams that are resource owners evaluatePolicies(policyIterator, subjectContext, resourceContext, operationContext, false);
}
Iterator<PolicyContext> 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<PolicyContext> resourcePolicies = subjectContext.getResourcePolicies(resourceContext.getOwner());
evaluatePolicies(resourcePolicies, subjectContext, resourceContext, operationContext, false, true);
} }
private static void evaluatePolicies( private static void evaluatePolicies(
@ -117,16 +95,12 @@ public class PolicyEvaluator {
SubjectContext subjectContext, SubjectContext subjectContext,
ResourceContextInterface resourceContext, ResourceContextInterface resourceContext,
OperationContext operationContext, OperationContext operationContext,
boolean evaluateDeny, boolean evaluateDeny) {
boolean evaluateResourcePolicies) {
// When an operation is allowed by a rule, it is removed from operation context // 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 // When list of operations is empty in the operation context, all operations have been allowed
while (policies.hasNext() && !operationContext.getOperations().isEmpty()) { while (policies.hasNext() && !operationContext.getOperations().isEmpty()) {
PolicyContext context = policies.next(); PolicyContext context = policies.next();
for (CompiledRule rule : context.getRules()) { for (CompiledRule rule : context.getRules()) {
if (evaluateResourcePolicies && !rule.isResourceBased()) {
continue; // Only evaluate resource based rules
}
LOG.debug( LOG.debug(
"evaluating policy for {} {}:{}:{}", "evaluating policy for {} {}:{}:{}",
evaluateDeny ? "deny" : "allow", evaluateDeny ? "deny" : "allow",
@ -146,7 +120,7 @@ public class PolicyEvaluator {
public static List<ResourcePermission> listPermission(@NonNull SubjectContext subjectContext) { public static List<ResourcePermission> listPermission(@NonNull SubjectContext subjectContext) {
Map<String, ResourcePermission> resourcePermissionMap = initResourcePermissions(); Map<String, ResourcePermission> resourcePermissionMap = initResourcePermissions();
Iterator<PolicyContext> policies = subjectContext.getPolicies(); Iterator<PolicyContext> policies = subjectContext.getPolicies(null);
while (policies.hasNext()) { while (policies.hasNext()) {
PolicyContext policyContext = policies.next(); PolicyContext policyContext = policies.next();
for (CompiledRule rule : policyContext.getRules()) { for (CompiledRule rule : policyContext.getRules()) {
@ -177,7 +151,7 @@ public class PolicyEvaluator {
ResourcePermission resourcePermission = getResourcePermission(resourceType, Access.NOT_ALLOW); ResourcePermission resourcePermission = getResourcePermission(resourceType, Access.NOT_ALLOW);
// Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW // Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW
Iterator<PolicyContext> policies = subjectContext.getPolicies(); Iterator<PolicyContext> policies = subjectContext.getPolicies(null);
while (policies.hasNext()) { while (policies.hasNext()) {
PolicyContext policyContext = policies.next(); PolicyContext policyContext = policies.next();
for (CompiledRule rule : policyContext.getRules()) { for (CompiledRule rule : policyContext.getRules()) {
@ -194,7 +168,7 @@ public class PolicyEvaluator {
ResourcePermission resourcePermission = getResourcePermission(resourceContext.getResource(), Access.NOT_ALLOW); ResourcePermission resourcePermission = getResourcePermission(resourceContext.getResource(), Access.NOT_ALLOW);
// Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW // Iterate through policies and set the permissions to DENY, ALLOW, CONDITIONAL_DENY, or CONDITIONAL_ALLOW
Iterator<PolicyContext> policies = subjectContext.getPolicies(); Iterator<PolicyContext> policies = subjectContext.getPolicies(resourceContext.getOwner());
while (policies.hasNext()) { while (policies.hasNext()) {
PolicyContext policyContext = policies.next(); PolicyContext policyContext = policies.next();
for (CompiledRule rule : policyContext.getRules()) { for (CompiledRule rule : policyContext.getRules()) {
@ -202,27 +176,6 @@ public class PolicyEvaluator {
rule.evaluatePermission(subjectContext, resourceContext, resourcePermission, policyContext); 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<PolicyContext> 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; return resourcePermission;
} }

View File

@ -39,8 +39,7 @@ public class RuleEvaluator {
name = "isOwner", name = "isOwner",
input = "none", input = "none",
description = "Returns true if the logged in user is the owner of the entity being accessed", description = "Returns true if the logged in user is the owner of the entity being accessed",
examples = {"isOwner()", "!isOwner", "noOwner() || isOwner()"}, examples = {"isOwner()", "!isOwner", "noOwner() || isOwner()"})
resourceBased = true)
public boolean isOwner() throws IOException { public boolean isOwner() throws IOException {
return subjectContext != null && subjectContext.isOwner(resourceContext.getOwner()); return subjectContext != null && subjectContext.isOwner(resourceContext.getOwner());
} }
@ -49,8 +48,7 @@ public class RuleEvaluator {
name = "matchAllTags", name = "matchAllTags",
input = "List of comma separated tag or glossary fully qualified names", 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", description = "Returns true if the entity being accessed has all the tags given as input",
examples = {"matchAllTags('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"}, examples = {"matchAllTags('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"})
resourceBased = true)
public boolean matchAllTags(String... tagFQNs) throws IOException { public boolean matchAllTags(String... tagFQNs) throws IOException {
if (resourceContext == null) { if (resourceContext == null) {
return false; return false;
@ -70,8 +68,7 @@ public class RuleEvaluator {
name = "matchAnyTag", name = "matchAnyTag",
input = "List of comma separated tag or glossary fully qualified names", 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", 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')"}, examples = {"matchAnyTag('PersonalData.Personal', 'Tier.Tier1', 'Business Glossary.Clothing')"})
resourceBased = true)
public boolean matchAnyTag(String... tagFQNs) throws IOException { public boolean matchAnyTag(String... tagFQNs) throws IOException {
if (resourceContext == null) { if (resourceContext == null) {
return false; return false;
@ -93,16 +90,48 @@ public class RuleEvaluator {
description = description =
"Returns true if the user and the resource belongs to the team hierarchy where this policy is" "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.", + "attached. This allows restricting permissions to a resource to the members of the team hierarchy.",
examples = {"matchTeam()"}, examples = {"matchTeam()"})
resourceBased = true)
public boolean matchTeam() throws IOException { public boolean matchTeam() throws IOException {
if (resourceContext == null || resourceContext.getOwner() == null) { if (resourceContext == null || resourceContext.getOwner() == null) {
return true; // No ownership information return false; // No ownership information
} }
if (policyContext == null || !policyContext.getEntityType().equals(Entity.TEAM)) { 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()) return subjectContext.isTeamAsset(policyContext.getEntityName(), resourceContext.getOwner())
&& subjectContext.isUserUnderTeam(policyContext.getEntityName()); && 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;
}
} }

View File

@ -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 { public Team getTeam(UUID teamId) throws EntityNotFoundException {
try { try {
return TEAM_CACHE.get(teamId); 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 */ /** Return true if given list of teams is part of the hierarchy of parentTeam */
public boolean isInTeam(String parentTeam, List<EntityReference> teams) { public boolean isInTeam(String parentTeam, EntityReference team) {
Stack<EntityReference> stack = new Stack<>(); Stack<EntityReference> stack = new Stack<>();
listOrEmpty(teams).forEach(stack::push); stack.push(team); // Start with team and see if the parent matches
while (!stack.empty()) { while (!stack.empty()) {
Team parent = getTeam(stack.pop().getId()); Team parent = getTeam(stack.pop().getId());
if (parent.getName().equals(parentTeam)) { if (parent.getName().equals(parentTeam)) {
return true; 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 false;
} }
/** Return true if the given user has any roles the list of roles */
public boolean hasRole(User user, String role) {
Stack<EntityReference> 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<EntityReference> userRoles, String expectedRole) {
return listOrEmpty(userRoles).stream().anyMatch(userRole -> userRole.getName().equals(expectedRole));
}
public static void cleanUp() { public static void cleanUp() {
LOG.info("Subject cache is cleaned up"); LOG.info("Subject cache is cleaned up");
USER_CACHE.invalidateAll(); USER_CACHE.invalidateAll();

View File

@ -16,7 +16,6 @@ package org.openmetadata.service.security.policyevaluator;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; 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 */ /** Returns true if the user of this SubjectContext is under the team hierarchy of parentTeam */
public boolean isUserUnderTeam(String 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 */ /** 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); return subjectContext.isUserUnderTeam(parentTeam);
} else if (owner.getType().equals(Entity.TEAM)) { } else if (owner.getType().equals(Entity.TEAM)) {
Team team = SubjectCache.getInstance().getTeam(owner.getId()); Team team = SubjectCache.getInstance().getTeam(owner.getId());
return isInTeam(parentTeam, List.of(team.getEntityReference())); return isInTeam(parentTeam, team.getEntityReference());
} }
return false; return false;
} }
/** Return true if given list of teams is part of the hierarchy of parentTeam */ /** Return true if the team is part of the hierarchy of parentTeam */
private boolean isInTeam(String parentTeam, List<EntityReference> teams) { private boolean isInTeam(String parentTeam, EntityReference team) {
return SubjectCache.getInstance().isInTeam(parentTeam, teams); return SubjectCache.getInstance().isInTeam(parentTeam, team);
} }
// Iterate over all the policies of the team hierarchy the user belongs to // Iterate over all the policies of the team hierarchy the user belongs to
public Iterator<PolicyContext> getPolicies() { public Iterator<PolicyContext> getPolicies(EntityReference resourceOwner) {
return new UserPolicyIterator(user, new ArrayList<>()); return new UserPolicyIterator(user, resourceOwner, new ArrayList<>());
}
// Iterate over all the policies of the team hierarchy the resource belongs to
public Iterator<PolicyContext> 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<UUID> teamsVisited = new ArrayList<>();
return new TeamPolicyIterator(team.getId(), teamsVisited);
}
return Collections.emptyIterator();
} }
public List<EntityReference> getTeams() { public List<EntityReference> getTeams() {
return user.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 @Getter
static class PolicyContext { static class PolicyContext {
private final String entityType; 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<PolicyContext> { static class PolicyIterator implements Iterator<PolicyContext> {
// When executing roles from a policy, entity type User or Team to which the Role is attached to. // 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<Iterator<PolicyContext>> iterators = new ArrayList<>(); private final List<Iterator<PolicyContext>> iterators = new ArrayList<>();
/** Policy iterator for a user */ /** Policy iterator for a user */
UserPolicyIterator(User user, List<UUID> teamsVisited) { UserPolicyIterator(User user, EntityReference resourceOwner, List<UUID> teamsVisited) {
this.user = user; this.user = user;
// Iterate over policies in user role // Iterate over policies in user role
@ -231,13 +227,19 @@ public class SubjectContext {
iterators.add(new RolePolicyIterator(Entity.USER, user.getName(), user.getRoles())); 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())) { 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()) { 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 @Override
@ -248,7 +250,7 @@ public class SubjectContext {
} }
iteratorIndex++; iteratorIndex++;
} }
LOG.debug("Subject {} policy iteration done" + user.getName()); LOG.debug("Subject {} policy iteration done", user.getName());
return false; return false;
} }
@ -270,21 +272,21 @@ public class SubjectContext {
private final List<Iterator<PolicyContext>> iterators = new ArrayList<>(); private final List<Iterator<PolicyContext>> iterators = new ArrayList<>();
/** Policy iterator for a team */ /** Policy iterator for a team */
TeamPolicyIterator(UUID teamId, List<UUID> teamsVisited) { TeamPolicyIterator(UUID teamId, List<UUID> teamsVisited, boolean skipRoles) {
Team team = SubjectCache.getInstance().getTeam(teamId); Team team = SubjectCache.getInstance().getTeam(teamId);
// If a team is already visited (because user can belong to multiple teams // 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 // and a team can belong to multiple teams) then don't visit the roles/policies of that team
if (!teamsVisited.contains(teamId)) { if (!teamsVisited.contains(teamId)) {
teamsVisited.add(teamId); teamsVisited.add(teamId);
if (team.getDefaultRoles() != null) { if (!skipRoles && team.getDefaultRoles() != null) {
iterators.add(new RolePolicyIterator(Entity.TEAM, team.getName(), team.getDefaultRoles())); iterators.add(new RolePolicyIterator(Entity.TEAM, team.getName(), team.getDefaultRoles()));
} }
if (team.getPolicies() != null) { if (team.getPolicies() != null) {
iterators.add(new PolicyIterator(Entity.TEAM, team.getName(), null, team.getPolicies())); iterators.add(new PolicyIterator(Entity.TEAM, team.getName(), null, team.getPolicies()));
} }
for (EntityReference parentTeam : listOrEmpty(team.getParents())) { for (EntityReference parentTeam : listOrEmpty(team.getParents())) {
iterators.add(new TeamPolicyIterator(parentTeam.getId(), teamsVisited)); iterators.add(new TeamPolicyIterator(parentTeam.getId(), teamsVisited, skipRoles));
} }
} }
} }

View File

@ -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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; 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 static org.openmetadata.service.security.policyevaluator.CompiledRule.parseExpression;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.openmetadata.schema.entity.data.Table; 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.Team;
import org.openmetadata.schema.entity.teams.User; import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.TagLabel; import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.service.Entity; 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.TableDAO;
import org.openmetadata.service.jdbi3.CollectionDAO.TeamDAO; import org.openmetadata.service.jdbi3.CollectionDAO.TeamDAO;
import org.openmetadata.service.jdbi3.CollectionDAO.UserDAO; import org.openmetadata.service.jdbi3.CollectionDAO.UserDAO;
import org.openmetadata.service.jdbi3.RoleRepository;
import org.openmetadata.service.jdbi3.TableRepository; import org.openmetadata.service.jdbi3.TableRepository;
import org.openmetadata.service.jdbi3.TeamRepository; import org.openmetadata.service.jdbi3.TeamRepository;
import org.openmetadata.service.jdbi3.UserRepository; import org.openmetadata.service.jdbi3.UserRepository;
import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
class RuleEvaluatorTest { 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 User user;
private static EvaluationContext evaluationContext; private static EvaluationContext evaluationContext;
private static SubjectContext subjectContext;
private static ResourceContext resourceContext;
@BeforeAll @BeforeAll
public static void setup() { public static void setup() {
Entity.registerEntity(User.class, Entity.USER, Mockito.mock(UserDAO.class), Mockito.mock(UserRepository.class)); 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(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); TableRepository tableRepository = Mockito.mock(TableRepository.class);
Mockito.when(tableRepository.getAllTags(any())) Mockito.when(tableRepository.getAllTags(any()))
@ -43,18 +56,24 @@ class RuleEvaluatorTest {
Entity.registerEntity(Table.class, Entity.TABLE, Mockito.mock(TableDAO.class), tableRepository); Entity.registerEntity(Table.class, Entity.TABLE, Mockito.mock(TableDAO.class), tableRepository);
user = new User().withId(UUID.randomUUID()).withName("user"); user = new User().withId(UUID.randomUUID()).withName("user");
ResourceContext resourceContext = resourceContext =
ResourceContext.builder() ResourceContext.builder()
.resource("table") .resource("table")
.entity(table) .entity(table)
.entityRepository(Mockito.mock(TableRepository.class)) .entityRepository(Mockito.mock(TableRepository.class))
.build(); .build();
SubjectContext subjectContext = new SubjectContext(user); subjectContext = new SubjectContext(user);
RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext); RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext);
evaluationContext = new StandardEvaluationContext(ruleEvaluator); evaluationContext = new StandardEvaluationContext(ruleEvaluator);
} }
@AfterAll
public static void cleanup() {
SubjectCache.cleanUp();
RoleCache.cleanUp();
}
@Test @Test
void test_noOwner() { void test_noOwner() {
// Set no owner to the entity and test noOwner method // Set no owner to the entity and test noOwner method
@ -133,6 +152,99 @@ class RuleEvaluatorTest {
assertTrue(evaluateExpression("!matchAnyTag('tag4')")); 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) { private Boolean evaluateExpression(String condition) {
return parseExpression(condition).getValue(evaluationContext, Boolean.class); return parseExpression(condition).getValue(evaluationContext, Boolean.class);
} }
@ -144,4 +256,42 @@ class RuleEvaluatorTest {
} }
return tagLabels; 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);
}
} }

View File

@ -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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
@ -45,19 +46,25 @@ import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyCo
public class SubjectContextTest { public class SubjectContextTest {
private static List<Role> team1Roles; private static List<Role> team1Roles;
private static List<Policy> team1Policies; private static List<Policy> team1Policies;
private static Team team1;
private static List<Role> team11Roles; private static List<Role> team11Roles;
private static List<Policy> team11Policies; private static List<Policy> team11Policies;
private static Team team11;
private static List<Role> team12Roles; private static List<Role> team12Roles;
private static List<Policy> team12Policies; private static List<Policy> team12Policies;
private static List<Role> team13Roles;
private static List<Policy> team13Policies;
private static Team team13;
private static List<Role> team111Roles; private static List<Role> team111Roles;
private static List<Policy> team111Policies; private static List<Policy> team111Policies;
private static Team team111; private static Team team111;
private static List<Role> team131Roles;
private static List<Policy> team131Policies;
private static Team team131;
private static List<Role> userRoles; private static List<Role> userRoles;
private static User user; private static User user;
@ -73,32 +80,40 @@ public class SubjectContextTest {
SubjectCache.initialize(); SubjectCache.initialize();
// Create team hierarchy: // Create team hierarchy:
// team1, has team11, team12, team13 as children // team1
// team11 has team111 as children // / | \
// team111 has user as children // team11 team12 team13
// Each with 3 roles and 3 policies // / / \
team1Roles = getRoles("team1", 3); // team111 / team131
team1Policies = getPolicies("team1", 3); // \ /
team1 = createTeam("team1", team1Roles, team1Policies, null); // 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); team11Roles = getRoles("team11");
team11Policies = getPolicies("team11", 3); team11Policies = getPolicies("team11");
team11 = createTeam("team11", team11Roles, team11Policies, List.of(team1)); Team team11 = createTeam("team11", team11Roles, team11Policies, List.of(team1));
team12Roles = getRoles("team12", 3); team12Roles = getRoles("team12");
team12Policies = getPolicies("team12", 3); team12Policies = getPolicies("team12");
Team team12 = createTeam("team12", team12Roles, team12Policies, List.of(team1)); Team team12 = createTeam("team12", team12Roles, team12Policies, List.of(team1));
List<Role> team13Roles = getRoles("team13", 3); team13Roles = getRoles("team13");
List<Policy> team13Policies = getPolicies("team13", 3); team13Policies = getPolicies("team13");
createTeam("team13", team13Roles, team13Policies, List.of(team1)); team13 = createTeam("team13", team13Roles, team13Policies, List.of(team1));
team111Roles = getRoles("team111", 3); team111Roles = getRoles("team111");
team111Policies = getPolicies("team111", 3); team111Policies = getPolicies("team111");
team111 = createTeam("team111", team111Roles, team111Policies, List.of(team11, team12)); 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 // Add user to team111
userRoles = getRoles("user", 3); userRoles = getRoles("user");
List<EntityReference> userRolesRef = toEntityReferences(userRoles); List<EntityReference> userRolesRef = toEntityReferences(userRoles);
user = new User().withName("user").withRoles(userRolesRef).withTeams(List.of(team111.getEntityReference())); user = new User().withName("user").withRoles(userRolesRef).withTeams(List.of(team111.getEntityReference()));
SubjectCache.USER_CACHE.put("user", new SubjectContext(user)); SubjectCache.USER_CACHE.put("user", new SubjectContext(user));
@ -113,12 +128,36 @@ public class SubjectContextTest {
@Test @Test
void testPolicyIterator() { void testPolicyIterator() {
// // Check iteration order of the policies without resourceOwner
// Check iteration order of the policies
//
SubjectContext subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName()); SubjectContext subjectContext = SubjectCache.getInstance().getSubjectContext(user.getName());
Iterator<PolicyContext> policyContextIterator = subjectContext.getPolicies(); Iterator<PolicyContext> policyContextIterator = subjectContext.getPolicies(null);
assertUserPolicyIterator(policyContextIterator); List<String> 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<String> 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<String> expectedUserAndTeam131PolicyOrder = new ArrayList<>();
expectedUserAndTeam131PolicyOrder.addAll(expectedUserPolicyOrder);
expectedUserAndTeam131PolicyOrder.addAll(getAllTeamPolicies(null, team131Policies));
expectedUserAndTeam131PolicyOrder.addAll(getAllTeamPolicies(null, team13Policies));
assertPolicyIterator(expectedUserAndTeam131PolicyOrder, policyContextIterator);
} }
@Test @Test
@ -160,44 +199,12 @@ public class SubjectContextTest {
} }
@Test @Test
void testResourcePolicyIterator() { private static List<Role> getRoles(String prefix) {
// 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<PolicyContext> 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<String> 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<String> 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<Role> getRoles(String prefix, int count) {
// Create roles with 3 policies each and each policy with 3 rules // Create roles with 3 policies each and each policy with 3 rules
List<Role> roles = new ArrayList<>(count); List<Role> roles = new ArrayList<>(3);
for (int i = 1; i <= count; i++) { for (int i = 1; i <= 3; i++) {
String name = prefix + "_role_" + i; String name = prefix + "_role_" + i;
List<EntityReference> policies = toEntityReferences(getPolicies(name, 3)); List<EntityReference> policies = toEntityReferences(getPolicies(name));
Role role = new Role().withName(name).withId(UUID.randomUUID()).withPolicies(policies); Role role = new Role().withName(name).withId(UUID.randomUUID()).withPolicies(policies);
RoleCache.ROLE_CACHE.put(role.getId(), role); RoleCache.ROLE_CACHE.put(role.getId(), role);
roles.add(role); roles.add(role);
@ -205,21 +212,21 @@ public class SubjectContextTest {
return roles; return roles;
} }
private static List<Policy> getPolicies(String prefix, int count) { private static List<Policy> getPolicies(String prefix) {
List<Policy> policies = new ArrayList<>(count); List<Policy> policies = new ArrayList<>(3);
for (int i = 1; i <= count; i++) { for (int i = 1; i <= 3; i++) {
String name = prefix + "_policy_" + 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); policies.add(policy);
PolicyCache.POLICY_CACHE.put(policy.getId(), PolicyCache.getInstance().getRules(policy)); PolicyCache.POLICY_CACHE.put(policy.getId(), PolicyCache.getInstance().getRules(policy));
} }
return policies; return policies;
} }
private static List<Rule> getRules(String prefix, int count) { private static List<Rule> getRules(String prefix) {
List<Rule> rules = new ArrayList<>(count); List<Rule> rules = new ArrayList<>(3);
for (int i = 1; i <= count; i++) { for (int i = 1; i <= 3; i++) {
rules.add(new Rule().withName(prefix + "rule" + count)); rules.add(new Rule().withName(prefix + "rule" + 3));
} }
return rules; return rules;
} }
@ -234,14 +241,14 @@ public class SubjectContextTest {
private static List<String> getAllTeamPolicies(List<Role> roles, List<Policy> policies) { private static List<String> getAllTeamPolicies(List<Role> roles, List<Policy> policies) {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
list.addAll(getPolicyListFromRoles(roles)); listOrEmpty(list).addAll(getPolicyListFromRoles(roles));
list.addAll(getPolicyList(policies)); listOrEmpty(list).addAll(getPolicyList(policies));
return list; return list;
} }
private static List<String> getPolicyListFromRoles(List<Role> roles) { private static List<String> getPolicyListFromRoles(List<Role> roles) {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
roles.forEach(r -> list.addAll(getPolicyRefList(r.getPolicies()))); listOrEmpty(roles).forEach(r -> list.addAll(getPolicyRefList(r.getPolicies())));
return list; return list;
} }
@ -270,19 +277,6 @@ public class SubjectContextTest {
return team; return team;
} }
void assertUserPolicyIterator(Iterator<PolicyContext> actualPolicyIterator) {
//
// Check iteration order of the policies
//
List<String> 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<String> expectedPolicyOrder, Iterator<PolicyContext> actualPolicyIterator) { void assertPolicyIterator(List<String> expectedPolicyOrder, Iterator<PolicyContext> actualPolicyIterator) {
int count = 0; int count = 0;
while (actualPolicyIterator.hasNext()) { while (actualPolicyIterator.hasNext()) {

View File

@ -18,9 +18,4 @@ public @interface Function {
String[] examples(); String[] examples();
ParameterType paramInputType() default ParameterType.NOT_REQUIRED; 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;
} }