Skip to content

Commit 734f179

Browse files
authored
Merge pull request #996 from DependencyTrack/backport-pr-4234
2 parents 3c83bc2 + 70d2d4a commit 734f179

File tree

3 files changed

+70
-23
lines changed

3 files changed

+70
-23
lines changed

apiserver/src/main/java/org/dependencytrack/model/PolicyViolation.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@
4949
@PersistenceCapable
5050
@JsonInclude(JsonInclude.Include.NON_NULL)
5151
@JsonIgnoreProperties(ignoreUnknown = true)
52-
// TODO: Add @Unique composite constraint on the fields component, policyCondition, and type.
53-
// The legacy PolicyEngine erroneously allows for duplicates on those fields, but CelPolicyEngine
54-
// will never produce such duplicates. Until we remove the legacy engine, we can't add this constraint.
52+
@Index(members = {"component", "project", "policyCondition"}, unique = "true")
5553
public class PolicyViolation implements Serializable {
5654

5755
public enum Type {
@@ -78,12 +76,12 @@ public enum Type {
7876
@Persistent(defaultFetchGroup = "true")
7977
@ForeignKey(name = "POLICYVIOLATION_COMPONENT_FK", updateAction = ForeignKeyAction.NONE, deleteAction = ForeignKeyAction.CASCADE, deferred = "true")
8078
@Column(name = "COMPONENT_ID", allowsNull = "false")
81-
@Index(name = "POLICYVIOLATION_COMPONENT_IDX")
8279
private Component component;
8380

8481
@Persistent(defaultFetchGroup = "true")
8582
@ForeignKey(name = "POLICYVIOLATION_POLICYCONDITION_FK", updateAction = ForeignKeyAction.NONE, deleteAction = ForeignKeyAction.CASCADE, deferred = "true")
8683
@Column(name = "POLICYCONDITION_ID", allowsNull = "false")
84+
@Index(name = "POLICYVIOLATION_POLICYCONDITION_ID_IDX")
8785
private PolicyCondition policyCondition;
8886

8987
@Persistent

apiserver/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,33 +121,49 @@ public void getViolationsByProjectTest() {
121121
component0.setVersion("1.0");
122122
component0 = qm.createComponent(component0, false);
123123

124-
var component1 = new Component();
125-
component1.setProject(project);
126-
component1.setName("Acme Component 1");
127-
component1.setVersion("1.0");
128-
component1 = qm.createComponent(component1, false);
129-
130124
final Policy policy0 = qm.createPolicy("Blacklisted Version 0", Policy.Operator.ALL, Policy.ViolationState.FAIL);
131125
final PolicyCondition condition0 = qm.createPolicyCondition(policy0, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0");
132126

133127
final Policy policy1 = qm.createPolicy("Blacklisted Version 1", Policy.Operator.ALL, Policy.ViolationState.FAIL);
134128
final PolicyCondition condition1 = qm.createPolicyCondition(policy1, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0");
135129

136-
ArrayList<PolicyViolation> filteredPolicyViolations = new ArrayList<>();
137-
for (int i=0; i<10; i++) {
138-
final boolean componentFilter = (i == 3);
139-
final boolean conditionFilter = (i == 7);
130+
final var filteredPolicyViolations = new ArrayList<PolicyViolation>();
131+
132+
var violationFiltered0 = new PolicyViolation();
133+
violationFiltered0.setType(PolicyViolation.Type.OPERATIONAL);
134+
violationFiltered0.setComponent(component0);
135+
violationFiltered0.setPolicyCondition(condition1);
136+
violationFiltered0.setTimestamp(new Date());
137+
violationFiltered0 = qm.persist(violationFiltered0);
138+
filteredPolicyViolations.add(violationFiltered0);
139+
140+
var componentForCondition0 = new Component();
141+
componentForCondition0.setProject(project);
142+
componentForCondition0.setName("Acme Component 1");
143+
componentForCondition0.setVersion("1.0");
144+
componentForCondition0 = qm.createComponent(componentForCondition0, false);
145+
146+
var violationFiltered1 = new PolicyViolation();
147+
violationFiltered1.setType(PolicyViolation.Type.OPERATIONAL);
148+
violationFiltered1.setComponent(componentForCondition0);
149+
violationFiltered1.setPolicyCondition(condition0);
150+
violationFiltered1.setTimestamp(new Date());
151+
violationFiltered1 = qm.persist(violationFiltered1);
152+
filteredPolicyViolations.add(violationFiltered1);
153+
154+
for (int i = 2; i < 6; i++) {
155+
var component = new Component();
156+
component.setProject(project);
157+
component.setName("Acme Component " + i);
158+
component.setVersion("1.0");
159+
component = qm.createComponent(component, false);
140160

141161
var violation = new PolicyViolation();
142162
violation.setType(PolicyViolation.Type.OPERATIONAL);
143-
violation.setComponent(componentFilter ? component0 : component1);
144-
violation.setPolicyCondition(conditionFilter ? condition0 : condition1);
163+
violation.setComponent(component);
164+
violation.setPolicyCondition(condition1);
145165
violation.setTimestamp(new Date());
146-
violation = qm.persist(violation);
147-
148-
if (conditionFilter || componentFilter) {
149-
filteredPolicyViolations.add(violation);
150-
}
166+
qm.persist(violation);
151167
}
152168

153169
final Response response = jersey.target(V1_POLICY_VIOLATION)
@@ -409,9 +425,9 @@ public void getViolationsWithAclEnabledTest() {
409425

410426
var componentD = new Component();
411427
componentD.setProject(projectA);
412-
componentD.setName("Acme Component");
428+
componentD.setName("Acme Component D");
413429
componentD.setVersion("1.0");
414-
componentD = qm.createComponent(componentA, false);
430+
componentD = qm.createComponent(componentD, false);
415431

416432
final Policy policy = qm.createPolicy("Blacklisted Version", Policy.Operator.ALL, Policy.ViolationState.FAIL);
417433
final PolicyCondition condition = qm.createPolicyCondition(policy, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0");

migration/src/main/resources/migration/changelog-v5.7.0.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,4 +1293,37 @@
12931293
);
12941294
</sql>
12951295
</changeSet>
1296+
1297+
<changeSet id="v5.7.0-60" author="nscuro">
1298+
<!--
1299+
Delete any duplicate policy violations.
1300+
Note that this doesn't do any fancy reconciliation, it just keeps the latest.
1301+
Users upgrading from v4 should already not have duplicates anymore, as of v4.12.1.
1302+
https://github.com/DependencyTrack/dependency-track/pull/4234
1303+
-->
1304+
<sql>
1305+
WITH duplicate AS (
1306+
SELECT "ID"
1307+
, ROW_NUMBER() OVER(
1308+
PARTITION BY "COMPONENT_ID", "PROJECT_ID", "POLICYCONDITION_ID"
1309+
ORDER BY "TIMESTAMP" DESC
1310+
) AS rn
1311+
FROM "POLICYVIOLATION"
1312+
)
1313+
DELETE FROM "POLICYVIOLATION"
1314+
USING duplicate
1315+
WHERE "POLICYVIOLATION"."ID" = duplicate."ID"
1316+
AND duplicate.rn > 1
1317+
</sql>
1318+
1319+
<!-- Create unique index to prevent new dupes from being created. -->
1320+
<createIndex tableName="POLICYVIOLATION" indexName="POLICYVIOLATION_IDENTITY_IDX" unique="true">
1321+
<column name="COMPONENT_ID"/>
1322+
<column name="PROJECT_ID"/>
1323+
<column name="POLICYCONDITION_ID"/>
1324+
</createIndex>
1325+
1326+
<!-- Drop redundant index that is covered by the new unique index. -->
1327+
<dropIndex tableName="POLICYVIOLATION" indexName="POLICYVIOLATION_COMPONENT_IDX"/>
1328+
</changeSet>
12961329
</databaseChangeLog>

0 commit comments

Comments
 (0)