com.sap.cloud.security.ams.dcn.engine.Engine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta-ams Show documentation
Show all versions of jakarta-ams Show documentation
Client Library for integrating Jakarta EE applications with SAP Authorization Management Service (AMS)
The newest version!
/************************************************************************
* © 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cloud.security.ams.dcn.engine;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.AND;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.BETWEEN;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.EQ;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.GE;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.GT;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.IN;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.IS_NOT_NULL;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.IS_NULL;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.LE;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.LIKE;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.LT;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.NE;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.NOT_BETWEEN;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.NOT_IN;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.NOT_LIKE;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.NOT_RESTRICTED;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.OR;
import static com.sap.cloud.security.ams.dcl.client.el.Call.Names.RESTRICTED;
import static com.sap.cloud.security.ams.dcl.client.pdp.Attributes.Names.DCL_ACTION;
import static com.sap.cloud.security.ams.dcl.client.pdp.Attributes.Names.DCL_RESOURCE;
import static com.sap.cloud.security.ams.dcl.client.pdp.Attributes.Names.DCL_SCOPE_FILTER;
import static com.sap.cloud.security.ams.dcl.client.pdp.Attributes.Names.DCL_TENANT;
import com.sap.cloud.security.ams.dcl.client.dcn.DcnRepository;
import com.sap.cloud.security.ams.dcl.client.dcn.Function;
import com.sap.cloud.security.ams.dcl.client.dcn.Policy;
import com.sap.cloud.security.ams.dcl.client.dcn.Rule;
import com.sap.cloud.security.ams.dcl.client.el.AttributeName;
import com.sap.cloud.security.ams.dcl.client.el.Call;
import com.sap.cloud.security.ams.dcl.client.pdp.Attributes;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.ObjectUtils;
/** The DCN engine that is used to replace the open policy agent (OPA). */
public class Engine {
private static final RestrictFunction DEFAULT_RESTRICT_FUNCTION = (name, fallback) -> fallback;
private final DcnRepository repository;
private final EvaluationNode evaluationTree;
private final EvaluationNodeFactory factory;
public Engine(DcnRepository repository, EvaluationNodeFactory factory) {
this.repository = repository;
this.factory = factory;
this.evaluationTree = buildEvaluationTree().deepSimplify();
}
private EvaluationNode buildEvaluationTree() {
Collection policyNodes = new LinkedList<>();
Collection scopeFilterNodes = new LinkedList<>();
scopeFilterNodes.add(factory.isNullNode(factory.attributeNode(DCL_SCOPE_FILTER)));
repository
.getPolicies()
.forEach(
policy -> {
EvaluationNode policyContentNode =
policyContentToEvaluationNode(policy, DEFAULT_RESTRICT_FUNCTION);
String policyName = policy.qualifiedName().toEncodedString();
scopeFilterNodes.add(
factory.allOfNode(
factory.inNode(
factory.valueNode(policyName), factory.attributeNode(DCL_SCOPE_FILTER)),
policyContentNode));
EvaluationNode policyNode =
policy.defaultPolicy()
? policyContentNode
: factory.allOfNode(
factory.inNode(
factory.valueNode(policyName), factory.effectivePoliciesNode()),
policyContentNode);
String tenant = repository.getTenant(policy);
if (ObjectUtils.isEmpty(tenant)) {
policyNodes.add(policyNode);
} else {
policyNodes.add(
factory.allOfNode(
factory.equalsNode(
factory.valueNode(tenant), factory.attributeNode(DCL_TENANT)),
policyNode));
}
});
return factory.allOfNode(factory.anyOfNode(scopeFilterNodes), factory.anyOfNode(policyNodes));
}
private EvaluationNode policyContentToEvaluationNode(
Policy policy, RestrictFunction restrictFunction) {
Collection contentNodes = new LinkedList<>();
if (ObjectUtils.isNotEmpty(policy.rules())) {
policy
.rules()
.forEach(rule -> contentNodes.add(ruleToEvaluationNode(rule, restrictFunction)));
}
if (ObjectUtils.isNotEmpty(policy.uses())) {
policy
.uses()
.forEach(
use -> {
Policy usedPolicy = repository.getPolicy(use.qualifiedPolicyName());
if (ObjectUtils.isEmpty(use.restrictions())) {
contentNodes.add(
policyContentToEvaluationNode(usedPolicy, DEFAULT_RESTRICT_FUNCTION));
} else {
use.restrictions()
.forEach(
restrictCalls ->
contentNodes.add(
policyContentToEvaluationNode(
usedPolicy,
restrictFunctionFrom(restrictCalls, restrictFunction))));
}
});
}
return factory.anyOfNode(contentNodes);
}
private RestrictFunction restrictFunctionFrom(
List restrictCalls, RestrictFunction outerRestrictFunction) {
Map restrictionMap = createRestrictionMap(restrictCalls);
return (name, fallback) -> {
Call replacement = restrictionMap.get(name);
if (replacement != null) {
return conditionTree(replacement, outerRestrictFunction);
}
return outerRestrictFunction.apply(name, fallback);
};
}
private Map createRestrictionMap(List restrictCalls) {
Map restrictionMap = new HashMap<>();
restrictCalls.forEach(
call -> {
call.getArguments()
.forEach(
arg -> {
if (arg instanceof AttributeName) {
if (restrictionMap.put((AttributeName) arg, call) != null) {
throw new IllegalArgumentException(
"Duplicate restriction for attribute " + arg);
}
}
});
});
return restrictionMap;
}
private EvaluationNode ruleToEvaluationNode(Rule rule, RestrictFunction restrictFunction) {
Collection ruleNodes = new LinkedList<>();
Optional.ofNullable(rule.actions())
.ifPresent(
actions ->
ruleNodes.add(
factory.inNode(factory.attributeNode(DCL_ACTION), factory.valueNode(actions))));
Optional.ofNullable(rule.resources())
.ifPresent(
resources ->
ruleNodes.add(
factory.inNode(
factory.attributeNode(DCL_RESOURCE), factory.valueNode(resources))));
Optional.ofNullable(rule.condition())
.ifPresent(condition -> ruleNodes.add(conditionTree(condition, restrictFunction)));
return factory.allOfNode(ruleNodes);
}
private EvaluationNode conditionTree(Object condition, RestrictFunction restrictFunction) {
if (condition instanceof Call call) {
return switch (call.getName()) {
case AND ->
factory.allOfNode(
call.getArguments().stream()
.map(subCondition -> conditionTree(subCondition, restrictFunction)));
case OR ->
factory.anyOfNode(
call.getArguments().stream()
.map(subCondition -> conditionTree(subCondition, restrictFunction)));
case IS_NULL ->
factory.isNullNode(conditionTree(call.getArguments().get(0), restrictFunction));
case IS_NOT_NULL ->
factory.isNotNullNode(conditionTree(call.getArguments().get(0), restrictFunction));
case IN ->
factory.inNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case NOT_IN ->
factory.notInNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case LT ->
factory.lessThanNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case LE ->
factory.lessThanOrEqualsNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case EQ ->
factory.equalsNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case NE ->
factory.notEqualsNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case GE ->
factory.greaterThanOrEqualsNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case GT ->
factory.greaterThanNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction));
case BETWEEN ->
factory.betweenNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction),
conditionTree(call.getArguments().get(2), restrictFunction));
case NOT_BETWEEN ->
factory.notBetweenNode(
conditionTree(call.getArguments().get(0), restrictFunction),
conditionTree(call.getArguments().get(1), restrictFunction),
conditionTree(call.getArguments().get(2), restrictFunction));
case LIKE ->
switch (call.getArguments().size()) {
case 2 ->
factory.likeNode(
conditionTree(call.getArguments().get(0), restrictFunction),
call.getArguments().get(1).toString());
case 3 ->
factory.likeNode(
conditionTree(call.getArguments().get(0), restrictFunction),
call.getArguments().get(1).toString(),
call.getArguments().get(2).toString());
default ->
throw new IllegalArgumentException("LIKE operator requires 2 or 3 arguments");
};
case NOT_LIKE ->
switch (call.getArguments().size()) {
case 2 ->
factory.notLikeNode(
conditionTree(call.getArguments().get(0), restrictFunction),
call.getArguments().get(1).toString());
case 3 ->
factory.notLikeNode(
conditionTree(call.getArguments().get(0), restrictFunction),
call.getArguments().get(1).toString(),
call.getArguments().get(2).toString());
default ->
throw new IllegalArgumentException("NOT LIKE operator requires 2 or 3 arguments");
};
case RESTRICTED ->
restrictFunction.apply((AttributeName) call.getArguments().get(0), ValueNode.FALSE);
case NOT_RESTRICTED ->
restrictFunction.apply((AttributeName) call.getArguments().get(0), ValueNode.TRUE);
default -> {
Function dcnFunction = repository.getFunction(call.getQualifiedName());
if (dcnFunction != null && call.getArguments().isEmpty()) {
yield conditionTree(dcnFunction.result(), restrictFunction);
}
throw new UnsupportedOperationException("Operation " + call + " not implemented.");
}
};
}
if (condition instanceof AttributeName) {
return factory.attributeNode((AttributeName) condition);
}
return factory.valueNode(condition);
}
/**
* Evaluates this engines evaluation tree with the given attribute assignments. This method
* returns true iff the result is either TRUE or IGNORE. That is, if the evaluation result is
* false or depends on anything that is not ignored (including UNSET or any unresolved
* expressions), then this method returns false.
*
* @param valueAccessor the attribute assignments
* @return true iff the evaluation result is either TRUE or IGNORE
*/
public boolean evaluateToBool(ValueAccessor valueAccessor) {
EvaluationNode resultNode = evaluationTree.evaluate(valueAccessor);
return ValueNode.TRUE.equals(resultNode) || ValueNode.IGNORE.equals(resultNode);
}
/**
* Evaluates this engines evaluation tree with the given attribute assignments and transforms the
* result into a condition object, i.e. a boolean value or a call object hierarchy. This method
* maps IGNORE to true and UNSET to false. All other evaluation results have to be unresolved
* expressions and are transformed into a condition object before being returned.
*
* @param valueAccessor the attribute assignments
* @return the evaluation result as a condition object
*/
public Object evaluateToCondition(ValueAccessor valueAccessor) {
Object condition = evaluationTree.evaluate(valueAccessor).toCondition();
if (Attributes.SpecialValue.IGNORE.equals(condition)) {
return true;
} else if (Attributes.SpecialValue.UNSET.equals(condition)) {
return false;
}
return condition;
}
@FunctionalInterface
private interface RestrictFunction {
EvaluationNode apply(AttributeName name, EvaluationNode fallback);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy