From cfef0507b164815f0f1b6e66b37c6face7dab140 Mon Sep 17 00:00:00 2001 From: Steffen Ohrendorf Date: Mon, 6 Apr 2026 15:44:12 +0200 Subject: [PATCH] log suppression when creating new violation analysis Signed-off-by: Steffen Ohrendorf --- .../v1/ViolationAnalysisResource.java | 23 ++++--- .../v1/ViolationAnalysisResourceTest.java | 60 +++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/dependencytrack/resources/v1/ViolationAnalysisResource.java b/src/main/java/org/dependencytrack/resources/v1/ViolationAnalysisResource.java index 300e1ab036..91cf93c97f 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ViolationAnalysisResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ViolationAnalysisResource.java @@ -35,6 +35,15 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirements; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Validator; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.dependencytrack.auth.Permissions; import org.dependencytrack.model.Component; @@ -46,15 +55,7 @@ import org.dependencytrack.resources.v1.vo.ViolationAnalysisRequest; import org.dependencytrack.util.NotificationUtil; -import jakarta.validation.Validator; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; +import java.util.Objects; /** * JAX-RS resources for processing violation analysis decisions. @@ -168,6 +169,10 @@ public Response updateAnalysis(ViolationAnalysisRequest request) { if (ViolationAnalysisState.NOT_SET != request.getAnalysisState()) { qm.makeViolationAnalysisComment(analysis, String.format("%s → %s", ViolationAnalysisState.NOT_SET, request.getAnalysisState()), commenter); } + if (Objects.equals(request.isSuppressed(), true)) { + suppressionChange = true; + qm.makeViolationAnalysisComment(analysis, "Suppressed", commenter); + } } final String comment = StringUtils.trimToNull(request.getComment()); diff --git a/src/test/java/org/dependencytrack/resources/v1/ViolationAnalysisResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ViolationAnalysisResourceTest.java index 933f55cc85..c7d231e4b5 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ViolationAnalysisResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ViolationAnalysisResourceTest.java @@ -257,6 +257,66 @@ void updateAnalysisCreateNewTest() throws Exception { assertThat(notification.getContent()).isEqualTo("An violation analysis decision was made to a policy violation affecting a project"); } + @Test + void updateAnalysisCreateNewLogsSuppressedTest() throws Exception { + initializeWithPermissions(Permissions.POLICY_VIOLATION_ANALYSIS); + + final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + + var component = new Component(); + component.setProject(project); + component.setName("Acme Component"); + component.setVersion("1.0"); + component = qm.createComponent(component, false); + + final Policy policy = qm.createPolicy("Blacklisted Version", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition condition = qm.createPolicyCondition(policy, Subject.VERSION, Operator.NUMERIC_EQUAL, "1.0"); + + var violation = new PolicyViolation(); + violation.setType(Type.OPERATIONAL); + violation.setComponent(component); + violation.setPolicyCondition(condition); + violation.setTimestamp(new Date()); + violation = qm.persist(violation); + + final var request = new ViolationAnalysisRequest(component.getUuid().toString(), + violation.getUuid().toString(), ViolationAnalysisState.APPROVED, "Some comment", true); + + final Response response = jersey.target(V1_VIOLATION_ANALYSIS) + .request() + .header(X_API_KEY, apiKey) + .put(Entity.entity(request, MediaType.APPLICATION_JSON)); + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isNull(); + + final JsonObject jsonObject = parseJsonObject(response); + assertThat(jsonObject).isNotNull(); + assertThat(jsonObject.getString("analysisState")).isEqualTo("APPROVED"); + assertThat(jsonObject.getBoolean("isSuppressed")).isTrue(); + + assertThat(jsonObject.getJsonArray("analysisComments")).hasSize(3); + assertThat(jsonObject.getJsonArray("analysisComments").getJsonObject(0)) + .hasFieldOrPropertyWithValue("comment", Json.createValue("NOT_SET → APPROVED")) + .doesNotContainKey("commenter"); // Not set when authenticating via API key + assertThat(jsonObject.getJsonArray("analysisComments").getJsonObject(1)) + .hasFieldOrPropertyWithValue("comment", Json.createValue("Suppressed")) + .doesNotContainKey("commenter"); // Not set when authenticating via API key + assertThat(jsonObject.getJsonArray("analysisComments").getJsonObject(2)) + .hasFieldOrPropertyWithValue("comment", Json.createValue("Some comment")) + .doesNotContainKey("commenter"); // Not set when authenticating via API key; + + assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5)); + final Notification projectNotification = NOTIFICATIONS.poll(); + assertThat(projectNotification).isNotNull(); + final Notification notification = NOTIFICATIONS.poll(); + assertThat(notification).isNotNull(); + assertThat(notification.getScope()).isEqualTo(NotificationScope.PORTFOLIO.name()); + assertThat(notification.getGroup()).isEqualTo(NotificationGroup.PROJECT_AUDIT_CHANGE.name()); + assertThat(notification.getLevel()).isEqualTo(NotificationLevel.INFORMATIONAL); + assertThat(notification.getTitle()).isEqualTo(NotificationUtil.generateNotificationTitle(NotificationConstants.Title.VIOLATIONANALYSIS_DECISION_APPROVED, project)); + assertThat(notification.getContent()).isEqualTo("An violation analysis decision was made to a policy violation affecting a project"); + } + @Test void updateAnalysisCreateNewWithEmptyRequestTest() throws Exception { initializeWithPermissions(Permissions.POLICY_VIOLATION_ANALYSIS);