diff --git a/src/main/java/org/dependencytrack/model/Policy.java b/src/main/java/org/dependencytrack/model/Policy.java index 701f66017a..a172088b40 100644 --- a/src/main/java/org/dependencytrack/model/Policy.java +++ b/src/main/java/org/dependencytrack/model/Policy.java @@ -139,6 +139,13 @@ public enum FetchGroup { @Element(column = "TAG_ID") private Set tags; + /** + * If true, the policy will only match projects that do not have any of the specified tags. + */ + @Persistent + @Column(name = "INVERT_TAG_MATCH", defaultValue = "false") + private boolean invertTagMatch; + /** * The unique identifier of the object. */ @@ -226,6 +233,14 @@ public void setTags(Set tags) { this.tags = tags; } + public boolean isInvertTagMatch() { + return invertTagMatch; + } + + public void setInvertTagMatch(boolean invertTagMatch) { + this.invertTagMatch = invertTagMatch; + } + public UUID getUuid() { return uuid; } diff --git a/src/main/java/org/dependencytrack/policy/PolicyEngine.java b/src/main/java/org/dependencytrack/policy/PolicyEngine.java index b6ee0a2d20..5c38fd3feb 100644 --- a/src/main/java/org/dependencytrack/policy/PolicyEngine.java +++ b/src/main/java/org/dependencytrack/policy/PolicyEngine.java @@ -163,7 +163,8 @@ private boolean isPolicyAssignedToProjectTag(Policy policy, Project project) { break; } } - return flag; + + return policy.isInvertTagMatch() != flag; } diff --git a/src/main/java/org/dependencytrack/resources/v1/PolicyResource.java b/src/main/java/org/dependencytrack/resources/v1/PolicyResource.java index b04e996bf3..2bd3d4e8b2 100644 --- a/src/main/java/org/dependencytrack/resources/v1/PolicyResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/PolicyResource.java @@ -196,6 +196,7 @@ public Response updatePolicy(Policy jsonPolicy) { policy.setViolationState(jsonPolicy.getViolationState()); policy.setIncludeChildren(jsonPolicy.isIncludeChildren()); policy.setOnlyLatestProjectVersion(jsonPolicy.isOnlyLatestProjectVersion()); + policy.setInvertTagMatch(jsonPolicy.isInvertTagMatch()); policy = qm.persist(policy); return Response.ok(policy).build(); } else { diff --git a/src/test/java/org/dependencytrack/policy/PolicyEngineTest.java b/src/test/java/org/dependencytrack/policy/PolicyEngineTest.java index fa318f0ddb..89bc0823e5 100644 --- a/src/test/java/org/dependencytrack/policy/PolicyEngineTest.java +++ b/src/test/java/org/dependencytrack/policy/PolicyEngineTest.java @@ -144,6 +144,57 @@ void noTagMatchPolicyLimitedToTag() { Assertions.assertEquals(0, violations.size()); } + @Test + void hasTagMatchPolicyWithExcludedTag() { + Policy policy = qm.createPolicy("Test Policy", Operator.ANY, ViolationState.INFO); + policy.setInvertTagMatch(true); + qm.createPolicyCondition(policy, Subject.SEVERITY, PolicyCondition.Operator.IS, Severity.CRITICAL.name()); + Tag commonTag = qm.createTag("Tag 1"); + qm.bind(policy, List.of(commonTag)); + Project project = qm.createProject("My Project", null, "1", List.of(commonTag), null, null, true, false); + Component component = new Component(); + component.setName("Test Component"); + component.setVersion("1.0"); + component.setProject(project); + Vulnerability vulnerability = new Vulnerability(); + vulnerability.setVulnId("12345"); + vulnerability.setSource(Vulnerability.Source.INTERNAL); + vulnerability.setSeverity(Severity.CRITICAL); + qm.persist(project); + qm.persist(component); + qm.persist(vulnerability); + qm.persist(policy); + qm.addVulnerability(vulnerability, component, AnalyzerIdentity.INTERNAL_ANALYZER); + PolicyEngine policyEngine = new PolicyEngine(); + List violations = policyEngine.evaluate(List.of(component)); + Assertions.assertEquals(0, violations.size()); + } + + @Test + void noTagMatchPolicyWithExcludedTag() { + Policy policy = qm.createPolicy("Test Policy", Operator.ANY, ViolationState.INFO); + policy.setInvertTagMatch(true); + qm.createPolicyCondition(policy, Subject.SEVERITY, PolicyCondition.Operator.IS, Severity.CRITICAL.name()); + qm.bind(policy, List.of(qm.createTag("Tag 1"))); + Project project = qm.createProject("My Project", null, "1", List.of(qm.createTag("Tag 2")), null, null, true, false); + Component component = new Component(); + component.setName("Test Component"); + component.setVersion("1.0"); + component.setProject(project); + Vulnerability vulnerability = new Vulnerability(); + vulnerability.setVulnId("12345"); + vulnerability.setSource(Vulnerability.Source.INTERNAL); + vulnerability.setSeverity(Severity.CRITICAL); + qm.persist(project); + qm.persist(component); + qm.persist(vulnerability); + qm.addVulnerability(vulnerability, component, AnalyzerIdentity.INTERNAL_ANALYZER); + PolicyEngine policyEngine = new PolicyEngine(); + List violations = policyEngine.evaluate(List.of(component)); + Assertions.assertEquals(1, violations.size()); + } + + @Test void hasPolicyAssignedToParentProject() { Policy policy = qm.createPolicy("Test Policy", Operator.ANY, ViolationState.INFO);