Skip to content

Commit

Permalink
Merge branch 'hotfix/3.1.1' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
NavidMitchell committed Jan 16, 2025
2 parents 054a9c1 + cb44d94 commit f87a8b3
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 41 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
structuresVersion=3.1.0
structuresVersion=3.1.1

antlrVersion=4.13.1
apacheCommonsTextVersion=1.13.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

Expand Down Expand Up @@ -75,7 +76,7 @@ public class Structure implements Identifiable<String> {
private String itemIndex = null; // do not ever set, system managed

@Field(type = FieldType.Flattened)
private List<DecoratedProperty> decoratedProperties = null; // do not ever set, system managed
private List<DecoratedProperty> decoratedProperties = new ArrayList<>(); // do not ever set, system managed

@Override
public boolean equals(Object o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class StructurePolicyAuthorizationService implements AuthorizationService

private static final Logger log = LoggerFactory.getLogger(StructurePolicyAuthorizationService.class);
private final Map<EntityOperation, PolicyEvaluator> operationEvaluators = new HashMap<>();
private final PolicyEvaluatorWithoutOperation sharedEvaluator;
private final String structureId;

public StructurePolicyAuthorizationService(Structure structure,
Expand All @@ -50,7 +51,7 @@ public StructurePolicyAuthorizationService(Structure structure,

SharedPolicyManager sharedPolicyManager = new SharedPolicyManager(entityPolicies != null ? entityPolicies.getPolicies() : null,
fieldPolicies);
PolicyEvaluatorWithoutOperation sharedEvaluator = new PolicyEvaluatorWithoutOperation(policyAuthorizer, sharedPolicyManager);
sharedEvaluator = new PolicyEvaluatorWithoutOperation(policyAuthorizer, sharedPolicyManager);

// Check if we have any policy decorators to apply to operations
EntityServiceDecoratorsDecorator decorators = entityDefinition.findDecorator(EntityServiceDecoratorsDecorator.class);
Expand All @@ -63,8 +64,6 @@ public StructurePolicyAuthorizationService(Structure structure,
List<List<String>> operationPolicies = extractPolicies(entry.getValue());
if(!operationPolicies.isEmpty()){
operationEvaluators.put(entry.getKey(), new PolicyEvaluatorWithOperation(policyAuthorizer, sharedPolicyManager, operationPolicies));
}else{
operationEvaluators.put(entry.getKey(), sharedEvaluator);
}
}
}
Expand All @@ -74,40 +73,39 @@ public StructurePolicyAuthorizationService(Structure structure,
public CompletableFuture<Void> authorize(EntityOperation operation, SecurityContext securityContext) {
try {
PolicyEvaluator evaluator = operationEvaluators.get(operation);
if(evaluator != null){
// if no operation policy use the
if(evaluator == null) {
evaluator = sharedEvaluator;
}

return evaluator.evaluatePolicies(securityContext).thenCompose(result -> {
return evaluator.evaluatePolicies(securityContext).thenCompose(result -> {

// Check if the operation is allowed i.e. findAll, save
if(!result.operationAllowed()){
// Check if the operation is allowed i.e. findAll, save
if(!result.operationAllowed()){

return CompletableFuture.failedFuture(new AuthorizationException("Operation %s not allowed.".formatted(operation)));
return CompletableFuture.failedFuture(new AuthorizationException("Operation %s not allowed.".formatted(operation)));

} else if (!result.entityAllowed()) { // Check if access to the entity is allowed
} else if (!result.entityAllowed()) { // Check if access to the entity is allowed

return CompletableFuture.failedFuture(new AuthorizationException("Structure %s Entity access not allowed.".formatted(structureId)));
return CompletableFuture.failedFuture(new AuthorizationException("Structure %s Entity access not allowed.".formatted(structureId)));

} else { // Check if access to the individual fields are allowed
} else { // Check if access to the individual fields are allowed

List<String> deniedFields = new ArrayList<>();
for(Map.Entry<String, Boolean> fieldResult : result.fieldResults().entrySet()){
if(!fieldResult.getValue()){
deniedFields.add(fieldResult.getKey());
}
}
if(!deniedFields.isEmpty()){
return CompletableFuture.failedFuture(new AuthorizationException("Structure %s Fields %s access not allowed.".formatted(structureId, deniedFields)));
}else{
return CompletableFuture.completedFuture(null);
List<String> deniedFields = new ArrayList<>();
for(Map.Entry<String, Boolean> fieldResult : result.fieldResults().entrySet()){
if(!fieldResult.getValue()){
deniedFields.add(fieldResult.getKey());
}

}
});
}else{
// This should never happen as long as the EntityOperation is being used properly by the EntityService implementation
log.error("No policy evaluator found for operation: {}.", operation);
return CompletableFuture.failedFuture(new IllegalArgumentException("No policy evaluator found for operation: " + operation));
}
if(!deniedFields.isEmpty()){
return CompletableFuture.failedFuture(new AuthorizationException("Structure %s Fields %s access not allowed.".formatted(structureId, deniedFields)));
}else{
return CompletableFuture.completedFuture(null);
}

}
});

} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,30 @@ public CompletableFuture<AuthorizationResult> evaluatePolicies(SecurityContext s
Set<String> allPolicies = new HashSet<>(sharedPolicyManager.getSharedPolicies());
addOperationPolicies(allPolicies);

Map<String, PolicyAuthorizationRequest> policyRequests = allPolicies.stream()
.collect(Collectors.toMap(policy -> policy, DefaultPolicyAuthorizationRequest::new));
// no need to call authorizer if there are no policies to evaluate
if (allPolicies.isEmpty()) {

List<PolicyAuthorizationRequest> requests = new ArrayList<>(policyRequests.values());
return CompletableFuture.completedFuture(new AuthorizationResult(true, true, Collections.emptyMap()));

return authorizer.authorize(requests, securityContext)
.thenApply(ignored -> {
}else {
Map<String, PolicyAuthorizationRequest> policyRequests = allPolicies.stream()
.collect(Collectors.toMap(policy -> policy,
DefaultPolicyAuthorizationRequest::new));
List<PolicyAuthorizationRequest> requests = new ArrayList<>(policyRequests.values());

Map<String, Boolean> fieldResults = evaluateFieldPolicies(policyRequests);
return authorizer.authorize(requests, securityContext)
.thenApply(ignored -> {

boolean operationAllowed = isOperationAllowed(policyRequests);
Map<String, Boolean> fieldResults = evaluateFieldPolicies(policyRequests);

boolean entityAllowed = sharedPolicyManager.getEntityExpression() == null
|| sharedPolicyManager.getEntityExpression().evaluate(policyRequests);
boolean operationAllowed = isOperationAllowed(policyRequests);

return new AuthorizationResult(operationAllowed, entityAllowed, fieldResults);
});
boolean entityAllowed = sharedPolicyManager.getEntityExpression() == null
|| sharedPolicyManager.getEntityExpression().evaluate(policyRequests);

return new AuthorizationResult(operationAllowed, entityAllowed, fieldResults);
});
}
}

private Map<String, Boolean> evaluateFieldPolicies(Map<String, PolicyAuthorizationRequest> policyRequests) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,58 @@ public class PolicyAuthorizationServiceTest {

private final PolicyAuthorizer authorizer = new MockPolicyAuthorizer();

@Test
public void testAuthorizeWithNoPoliciesOnStructure() {
Structure structure = new Structure();
structure.setNamespace("testNamespace");
structure.setName("testName");

ObjectC3Type entityDefinition = new ObjectC3Type();
structure.setEntityDefinition(entityDefinition);

StructurePolicyAuthorizationService service = new StructurePolicyAuthorizationService(structure, authorizer);

CompletableFuture<Void> result = service.authorize(EntityOperation.FIND_ALL, null);

assertDoesNotThrow(result::join); // Should pass since there are no policies
}

@Test
public void testAuthorizeWithEntityOnlyPolicies(){
Structure structure = new Structure();
structure.setNamespace("testNamespace");
structure.setName("testName");

ObjectC3Type entityDefinition = new ObjectC3Type();
entityDefinition.addDecorator(new PolicyDecorator().setPolicies(List.of(List.of("policy1"))));
structure.setEntityDefinition(entityDefinition);

StructurePolicyAuthorizationService service = new StructurePolicyAuthorizationService(structure, authorizer);

CompletableFuture<Void> result = service.authorize(EntityOperation.FIND_ALL, null);

assertDoesNotThrow(result::join); // Should pass since the READ operation policy is allowed
}

@Test
public void testAuthorizeDeniedWithEntityOnlyPolicies(){
Structure structure = new Structure();
structure.setNamespace("testNamespace");
structure.setName("testName");

ObjectC3Type entityDefinition = new ObjectC3Type();
entityDefinition.addDecorator(new PolicyDecorator().setPolicies(List.of(List.of("policy2"))));
structure.setEntityDefinition(entityDefinition);

StructurePolicyAuthorizationService service = new StructurePolicyAuthorizationService(structure, authorizer);

CompletableFuture<Void> result = service.authorize(EntityOperation.FIND_ALL, null);

Throwable exception = assertThrows(CompletionException.class, result::join);
assertInstanceOf(AuthorizationException.class, exception.getCause());
assertTrue(exception.getCause().getMessage().endsWith("Entity access not allowed.")); // Fails due to policy2
}

@Test
public void testAuthorizeReadOperationWithNoFieldPolicies() {
Structure structure = createStructureWithNoFieldPolicies();
Expand Down

0 comments on commit f87a8b3

Please sign in to comment.