Skip to content

Commit

Permalink
Merge pull request #930 from nscuro/version-policy-condition
Browse files Browse the repository at this point in the history
Add support for version policy conditions
  • Loading branch information
stevespringett authored Feb 10, 2021
2 parents 126d50f + b283ebd commit dd99309
Show file tree
Hide file tree
Showing 6 changed files with 548 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/main/java/org/dependencytrack/model/PolicyCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public enum Subject {
LICENSE_GROUP,
PACKAGE_URL,
SEVERITY,
SWID_TAGID
SWID_TAGID,
VERSION
}

@PrimaryKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@
import org.dependencytrack.model.Coordinates;
import org.dependencytrack.model.Policy;
import org.dependencytrack.model.PolicyCondition;
import org.dependencytrack.util.ComponentVersion;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Evaluates a components group + name + version against a policy.
Expand All @@ -37,6 +41,7 @@
public class CoordinatesPolicyEvaluator extends AbstractPolicyEvaluator {

private static final Logger LOGGER = Logger.getLogger(CoordinatesPolicyEvaluator.class);
private static final Pattern VERSION_OPERATOR_PATTERN = Pattern.compile("^(?<operator>[<>]=?|[!=]=)\\s*");

/**
* {@inheritDoc}
Expand All @@ -52,12 +57,12 @@ public PolicyCondition.Subject supportedSubject() {
@Override
public List<PolicyConditionViolation> evaluate(final Policy policy, final Component component) {
final List<PolicyConditionViolation> violations = new ArrayList<>();
for (final PolicyCondition condition: super.extractSupportedConditions(policy)) {
for (final PolicyCondition condition : super.extractSupportedConditions(policy)) {
LOGGER.debug("Evaluating component (" + component.getUuid() + ") against policy condition (" + condition.getUuid() + ")");
final Coordinates coordinates = parseCoordinatesDefinition(condition);
if (matches(condition.getOperator(), coordinates.getGroup(), component.getGroup())
&& matches(condition.getOperator(), coordinates.getName(), component.getName())
&& matches(condition.getOperator(), coordinates.getVersion(), component.getVersion())) {
&& versionMatches(condition.getOperator(), coordinates.getVersion(), component.getVersion())) {
violations.add(new PolicyConditionViolation(condition, component));
}
}
Expand Down Expand Up @@ -90,6 +95,53 @@ private boolean matches(final PolicyCondition.Operator operator, final String co
return false;
}

private boolean versionMatches(final PolicyCondition.Operator conditionOperator, final String conditionValue, final String part) {
final Matcher versionOperatorMatcher = VERSION_OPERATOR_PATTERN.matcher(conditionValue);
if (!versionOperatorMatcher.find()) {
// No operator provided, use default matching algorithm
return matches(conditionOperator, conditionValue, part);
}

final PolicyCondition.Operator versionOperator;
switch (versionOperatorMatcher.group(1)) {
case "==":
versionOperator = PolicyCondition.Operator.NUMERIC_EQUAL;
break;
case "!=":
versionOperator = PolicyCondition.Operator.NUMERIC_NOT_EQUAL;
break;
case "<":
versionOperator = PolicyCondition.Operator.NUMERIC_LESS_THAN;
break;
case "<=":
versionOperator = PolicyCondition.Operator.NUMERIC_LESSER_THAN_OR_EQUAL;
break;
case ">":
versionOperator = PolicyCondition.Operator.NUMERIC_GREATER_THAN;
break;
case ">=":
versionOperator = PolicyCondition.Operator.NUMERIC_GREATER_THAN_OR_EQUAL;
break;
default:
versionOperator = null;
break;
}
if (versionOperator == null) {
// Shouldn't ever happen because the regex won't match anything else
LOGGER.error("Failed to infer version operator from " + versionOperatorMatcher.group(1));
return false;
}

final var componentVersion = new ComponentVersion(part);
final var conditionVersion = new ComponentVersion(VERSION_OPERATOR_PATTERN.split(conditionValue)[1]);

final boolean versionMatches = VersionPolicyEvaluator.matches(componentVersion, conditionVersion, versionOperator);
if (PolicyCondition.Operator.NO_MATCH == conditionOperator) {
return !versionMatches;
}
return versionMatches;
}

/**
* Expects the format of condition.getValue() to be:
* <pre>
Expand All @@ -99,6 +151,7 @@ private boolean matches(final PolicyCondition.Operator operator, final String co
* 'version': '1.0.0'
* }
* </pre>
*
* @param condition teh condition to evaluate
* @return the Coordinates
*/
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/dependencytrack/policy/PolicyEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public PolicyEngine() {
evaluators.add(new PackageURLPolicyEvaluator());
evaluators.add(new CpePolicyEvaluator());
evaluators.add(new SwidTagIdPolicyEvaluator());
evaluators.add(new VersionPolicyEvaluator());
}

public void evaluate(final List<Component> components) {
Expand Down Expand Up @@ -118,6 +119,7 @@ private PolicyViolation.Type determineViolationType(final PolicyCondition.Subjec
case PACKAGE_URL:
case CPE:
case SWID_TAGID:
case VERSION:
return PolicyViolation.Type.OPERATIONAL;
case LICENSE:
case LICENSE_GROUP:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.dependencytrack.policy;

import alpine.logging.Logger;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Policy;
import org.dependencytrack.model.PolicyCondition;
import org.dependencytrack.util.ComponentVersion;

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

/**
* Evaluates a components version against a policy.
*
* @since 4.2.0
*/
public class VersionPolicyEvaluator extends AbstractPolicyEvaluator {

private static final Logger LOGGER = Logger.getLogger(VersionPolicyEvaluator.class);

@Override
public PolicyCondition.Subject supportedSubject() {
return PolicyCondition.Subject.VERSION;
}

@Override
public List<PolicyConditionViolation> evaluate(final Policy policy, final Component component) {
final var componentVersion = new ComponentVersion(component.getVersion());

final List<PolicyConditionViolation> violations = new ArrayList<>();

for (final PolicyCondition condition : super.extractSupportedConditions(policy)) {
LOGGER.debug("Evaluating component (" + component.getUuid() + ") against policy condition (" + condition.getUuid() + ")");

final var conditionVersion = new ComponentVersion(condition.getValue());
if (conditionVersion.getVersionParts().isEmpty()) {
LOGGER.warn("Unable to parse version (" + condition.getValue() + " provided by condition");
continue;
}

if (matches(componentVersion, conditionVersion, condition.getOperator())) {
violations.add(new PolicyConditionViolation(condition, component));
}
}

return violations;
}

static boolean matches(final ComponentVersion componentVersion,
final ComponentVersion conditionVersion,
final PolicyCondition.Operator operator) {
final int comparisonResult = componentVersion.compareTo(conditionVersion);
switch (operator) {
case NUMERIC_EQUAL:
return comparisonResult == 0;
case NUMERIC_NOT_EQUAL:
return comparisonResult != 0;
case NUMERIC_LESS_THAN:
return comparisonResult < 0;
case NUMERIC_LESSER_THAN_OR_EQUAL:
return comparisonResult <= 0;
case NUMERIC_GREATER_THAN:
return comparisonResult > 0;
case NUMERIC_GREATER_THAN_OR_EQUAL:
return comparisonResult >= 0;
default:
LOGGER.warn("Unsupported operation " + operator);
break;
}
return false;
}

}
Loading

0 comments on commit dd99309

Please sign in to comment.