Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ private void handleMethodReference(SpelNode node, ConditionCollector collector)
}
case "isOwner" -> isOwner((User) spelContext.lookupVariable("user"), collector);
case "noOwner" -> noOwner(collector);
case "isReviewer" -> isReviewer((User) spelContext.lookupVariable("user"), collector);
case "hasAnyRole" -> {
List<String> roles = extractMethodArguments(methodRef);
hasAnyRole(roles, collector);
Expand Down Expand Up @@ -306,6 +307,28 @@ public void noOwner(ConditionCollector collector) {
collector.addMustNot(existsQuery); // Wrap existsQuery in a List
}

public void isReviewer(User user, ConditionCollector collector) {
List<OMQueryBuilder> reviewerQueries = new ArrayList<>();
// Reviewer is the user
reviewerQueries.add(queryBuilderFactory.termQuery("reviewers.id", user.getId().toString()));

// Reviewer could also be any of the user's teams
if (user.getTeams() != null) {
for (EntityReference team : user.getTeams()) {
reviewerQueries.add(queryBuilderFactory.termQuery("reviewers.id", team.getId().toString()));
}
}

OMQueryBuilder reviewerQuery;
if (reviewerQueries.size() == 1) {
reviewerQuery = reviewerQueries.get(0);
} else {
reviewerQuery = queryBuilderFactory.boolQuery().should(reviewerQueries);
}

collector.addMust(reviewerQuery);
}

public void hasAnyRole(List<String> roles, ConditionCollector collector) {
User user = (User) spelContext.lookupVariable("user");
if (user.getRoles() == null || user.getRoles().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ public boolean isOwner() {
return subjectContext.isOwner(resourceContext.getOwners());
}

@Function(
name = "isReviewer",
input = "none",
description = "Returns true if the logged in user is a reviewer of the entity being accessed",
examples = {"isReviewer()", "!isReviewer"})
public boolean isReviewer() {
if (expressionValidation) {
return false;
}
if (subjectContext == null || resourceContext == null || resourceContext.getEntity() == null) {
return false;
}
return subjectContext.isReviewer(resourceContext.getEntity().getReviewers());
}

@Function(
name = "hasDomain",
input = "none",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@ public boolean isOwner(List<EntityReference> owners) {
return false;
}

public boolean isReviewer(List<EntityReference> reviewers) {
if (nullOrEmpty(reviewers)) {
return false;
}
for (EntityReference reviewer : reviewers) {
// Reviewer is the same user
if (reviewer.getType().equals(Entity.USER) && reviewer.getName().equals(user.getName())) {
return true;
}

// Reviewer is a team and user is a member of that team
if (reviewer.getType().equals(Entity.TEAM)) {
for (EntityReference userTeam : listOrEmpty(user.getTeams())) {
if (userTeam.getName().equals(reviewer.getName())) {
return true;
}
}
}
}
return false;
}

public boolean hasDomains(List<EntityReference> domains) {
return checkDomainHierarchyAccess(user.getDomains(), domains);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,32 @@ void testNegationWithIsOwner() {
"The negation should contain the user's ID.");
}

@Test
void testIsReviewer() {
// Setup a mock policy with the 'isReviewer()' condition
setupMockPolicies("isReviewer()", "ALLOW");

// Create a mock user and reviewer reference
UUID reviewerId = UUID.randomUUID();
when(mockUser.getId()).thenReturn(reviewerId);

EntityReference reviewerRef = new EntityReference();
reviewerRef.setId(reviewerId);
reviewerRef.setType(Entity.USER);
when(mockUser.getEntityReference()).thenReturn(reviewerRef);

// Evaluate the condition through RBAC evaluator
OMQueryBuilder finalQuery = evaluator.evaluateConditions(mockSubjectContext);
QueryBuilder elasticQuery = ((ElasticQueryBuilder) finalQuery).build();
String generatedQuery = elasticQuery.toString();

// Verify that the generated query correctly contains reviewer information
assertTrue(generatedQuery.contains("reviewers.id"), "The query should contain 'reviewers.id'.");
assertTrue(
generatedQuery.contains(reviewerId.toString()),
"The query should contain the user's reviewer ID.");
}

@Test
void testMatchAnyTag() {
setupMockPolicies("matchAnyTag('PII.Sensitive', 'PersonalData.Personal')", "ALLOW");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.mockito.stubbing.Answer;
import org.openmetadata.schema.entity.data.Database;
import org.openmetadata.schema.entity.data.DatabaseSchema;
import org.openmetadata.schema.entity.data.Glossary;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.entity.domains.DataProduct;
import org.openmetadata.schema.entity.domains.Domain;
Expand All @@ -48,6 +49,7 @@
import org.openmetadata.service.jdbi3.DatabaseSchemaRepository;
import org.openmetadata.service.jdbi3.DomainRepository;
import org.openmetadata.service.jdbi3.EntityRepository;
import org.openmetadata.service.jdbi3.GlossaryRepository;
import org.openmetadata.service.jdbi3.TableRepository;
import org.openmetadata.service.jdbi3.TeamRepository;
import org.openmetadata.service.security.policyevaluator.SubjectContext.PolicyContext;
Expand Down Expand Up @@ -325,6 +327,48 @@ void test_isOwner() {
assertFalse(evaluateExpression("isOwner()"));
}

@Test
void test_isReviewer() {
GlossaryRepository glossaryRepository = mock(GlossaryRepository.class);
Entity.registerEntity(Glossary.class, Entity.GLOSSARY, glossaryRepository);

User reviewer = new User().withId(UUID.randomUUID()).withName("reviewerUser");
EntityReference reviewerRef =
new EntityReference()
.withId(reviewer.getId())
.withType(Entity.USER)
.withName("reviewerUser");

Glossary glossary =
new Glossary()
.withId(UUID.randomUUID())
.withName("testGlossary")
.withReviewers(List.of(reviewerRef));

EntityRepository.CACHE_WITH_ID.put(
new ImmutablePair<>(Entity.GLOSSARY, glossary.getId()), glossary);

SubjectContext subjectContext = new SubjectContext(reviewer, null);

ResourceContext<Glossary> glossaryResourceContext =
new ResourceContext<>(Entity.GLOSSARY, glossary, glossaryRepository);

RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, glossaryResourceContext);
EvaluationContext evaluationContext = new StandardEvaluationContext(ruleEvaluator);

assertTrue(
parseExpression("isReviewer()").getValue(evaluationContext, Boolean.class),
"Reviewer user should return true for isReviewer()");

User otherUser = new User().withId(UUID.randomUUID()).withName("otherUser");
SubjectContext otherSubjectContext = new SubjectContext(otherUser, null);
ruleEvaluator = new RuleEvaluator(null, otherSubjectContext, glossaryResourceContext);
evaluationContext = new StandardEvaluationContext(ruleEvaluator);
assertFalse(
parseExpression("isReviewer()").getValue(evaluationContext, Boolean.class),
"Non-reviewer user should return false for isReviewer()");
}

@Test
void test_matchAllTags() {
table.withTags(getTags("tag1", "tag2", "tag3"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,33 @@ void assertPolicyIterator(
}
assertEquals(expectedPolicyOrder.size(), count);
}

@Test
void testIsReviewer() {
SubjectContext subjectContext = SubjectContext.getSubjectContext(user.getName());

// Case 1: reviewers list is null or empty
assertFalse(subjectContext.isReviewer(null), "Expected false when reviewers is null");
assertFalse(
subjectContext.isReviewer(new ArrayList<>()), "Expected false when reviewers is empty");

// Case 2: reviewer is same user
List<EntityReference> reviewers =
List.of(new EntityReference().withType(Entity.USER).withName("user"));
assertTrue(subjectContext.isReviewer(reviewers), "User should be reviewer if listed as USER");

// Case 3: reviewer is one of the user's teams
reviewers = List.of(new EntityReference().withType(Entity.TEAM).withName("team111"));
assertTrue(
subjectContext.isReviewer(reviewers),
"User should be reviewer if their team is in reviewers list");

// Case 4: reviewer list does not match user or their team
reviewers =
List.of(
new EntityReference().withType(Entity.USER).withName("someone_else"),
new EntityReference().withType(Entity.TEAM).withName("team13"));
assertFalse(
subjectContext.isReviewer(reviewers), "User should not be reviewer if no match found");
}
}
Loading