Skip to content

Commit

Permalink
Add option to test notification publisher
Browse files Browse the repository at this point in the history
Co-Authored-By: Ross Murphy <[email protected]>
  • Loading branch information
sahibamittal and 2000rosser committed Sep 26, 2024
1 parent 7e51968 commit 28815c3
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import alpine.common.logging.Logger;
import alpine.model.ConfigProperty;
import alpine.notification.Notification;
import alpine.server.auth.PermissionRequired;
import alpine.server.resources.AlpineResource;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -44,9 +45,13 @@
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.event.kafka.KafkaEventDispatcher;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.NotificationPublisher;
import org.dependencytrack.model.NotificationRule;
import org.dependencytrack.model.validation.ValidUuid;
import org.dependencytrack.notification.NotificationConstants;
import org.dependencytrack.notification.NotificationGroup;
import org.dependencytrack.notification.publisher.PublisherClass;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.util.NotificationUtil;
Expand Down Expand Up @@ -267,4 +272,39 @@ public Response restoreDefaultTemplates() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Exception occured while restoring default notification publisher templates.").build();
}
}

@POST
@Path("/test/{uuid}")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Dispatches a rule notification test",
description = "<p>Requires permission <strong>SYSTEM_CONFIGURATION</strong></p>"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Test notification dispatched successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.SYSTEM_CONFIGURATION)
public Response testSlackPublisherConfig(
@Parameter(description = "The UUID of the rule to test", schema = @Schema(type = "string", format = "uuid"), required = true)
@PathParam("uuid") @ValidUuid String ruleUuid) {
try (QueryManager qm = new QueryManager()) {
NotificationRule rule = qm.getObjectByUuid(NotificationRule.class, ruleUuid);
final KafkaEventDispatcher eventDispatcher = new KafkaEventDispatcher();
for(NotificationGroup group : rule.getNotifyOn()){
eventDispatcher.dispatchNotification(new Notification()
.scope(rule.getScope())
.group(group.toString())
.level(rule.getNotificationLevel())
.title(NotificationConstants.Title.NOTIFICATION_TEST)
.subject(NotificationUtil.generateSubjectForTestRuleNotification(group))
.content("Rule configuration test"));
}
return Response.ok().build();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Exception occured while sending the notification.").build();
}
}
}
108 changes: 108 additions & 0 deletions src/main/java/org/dependencytrack/util/NotificationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,33 @@
import org.dependencytrack.event.kafka.KafkaEventDispatcher;
import org.dependencytrack.model.Analysis;
import org.dependencytrack.model.AnalysisState;
import org.dependencytrack.model.Bom;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.NotificationPublisher;
import org.dependencytrack.model.Policy;
import org.dependencytrack.model.PolicyCondition;
import org.dependencytrack.model.PolicyViolation;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Severity;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.Vex;
import org.dependencytrack.model.ViolationAnalysis;
import org.dependencytrack.model.ViolationAnalysisState;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerabilityAnalysisLevel;
import org.dependencytrack.notification.NotificationConstants;
import org.dependencytrack.notification.NotificationGroup;
import org.dependencytrack.notification.NotificationScope;
import org.dependencytrack.notification.publisher.DefaultNotificationPublishers;
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
import org.dependencytrack.notification.vo.BomProcessingFailed;
import org.dependencytrack.notification.vo.BomValidationFailed;
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
import org.dependencytrack.notification.vo.NewVulnerableDependency;
import org.dependencytrack.notification.vo.PolicyViolationIdentified;
import org.dependencytrack.notification.vo.VexConsumedOrProcessed;
import org.dependencytrack.notification.vo.ViolationAnalysisDecisionChange;
import org.dependencytrack.persistence.QueryManager;

Expand All @@ -54,8 +65,10 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import static java.nio.charset.StandardCharsets.UTF_8;
Expand Down Expand Up @@ -437,4 +450,99 @@ public static class PolicyViolationNotificationProjection {
public String analysisState;
}

public static Object generateSubjectForTestRuleNotification(NotificationGroup group) {
final Project project = createProjectForTestRuleNotification();
final Vulnerability vuln = createVulnerabilityForTestRuleNotification();
final Component component = createComponentForTestRuleNotification(project);
final Analysis analysis = createAnalysisForTestRuleNotification(component, vuln);
final PolicyViolation policyViolation = createPolicyViolationForTestRuleNotification(component, project);
final UUID token = UUID.randomUUID();
switch (group) {
case BOM_CONSUMED, BOM_PROCESSED:
return new BomConsumedOrProcessed(token, project, /* bom */ "(Omitted)", Bom.Format.CYCLONEDX, "1.5");
case BOM_PROCESSING_FAILED:
return new BomProcessingFailed(token, project, /* bom */ "(Omitted)", "cause", Bom.Format.CYCLONEDX, "1.5");
case BOM_VALIDATION_FAILED:
return new BomValidationFailed(project, /* bom */ "(Omitted)", List.of("TEST"));
case VEX_CONSUMED, VEX_PROCESSED:
return new VexConsumedOrProcessed(project, "", Vex.Format.CYCLONEDX, "");
case NEW_VULNERABILITY:
return new NewVulnerabilityIdentified(vuln, component, VulnerabilityAnalysisLevel.BOM_UPLOAD_ANALYSIS);
case NEW_VULNERABLE_DEPENDENCY:
return new NewVulnerableDependency(component, Set.of(vuln));
case POLICY_VIOLATION:
return new PolicyViolationIdentified(policyViolation, component, project);
case PROJECT_CREATED:
return project;
case PROJECT_AUDIT_CHANGE:
return new AnalysisDecisionChange(vuln, component, project, analysis);
default:
return null;
}
}

private static Project createProjectForTestRuleNotification() {
final Project project = new Project();
project.setUuid(UUID.fromString("c9c9539a-e381-4b36-ac52-6a7ab83b2c95"));
project.setName("projectName");
project.setVersion("projectVersion");
project.setPurl("pkg:maven/org.acme/projectName@projectVersion");
return project;
}

private static Vulnerability createVulnerabilityForTestRuleNotification() {
final Vulnerability vuln = new Vulnerability();
vuln.setUuid(UUID.fromString("bccec5d5-ec21-4958-b3e8-22a7a866a05a"));
vuln.setVulnId("INT-001");
vuln.setSource(Vulnerability.Source.INTERNAL);
vuln.setSeverity(Severity.MEDIUM);
return vuln;
}

private static Component createComponentForTestRuleNotification(Project project) {
final Component component = new Component();
component.setProject(project);
component.setUuid(UUID.fromString("94f87321-a5d1-4c2f-b2fe-95165debebc6"));
component.setName("componentName");
component.setVersion("componentVersion");
return component;
}

private static Analysis createAnalysisForTestRuleNotification(Component component, Vulnerability vuln) {
final Analysis analysis = new Analysis();
analysis.setComponent(component);
analysis.setVulnerability(vuln);
analysis.setAnalysisState(AnalysisState.FALSE_POSITIVE);
analysis.setSuppressed(true);
return analysis;
}

private static PolicyViolation createPolicyViolationForTestRuleNotification(Component component, Project project) {
final Policy policy = new Policy();
policy.setId(1);
policy.setName("test");
policy.setOperator(Policy.Operator.ALL);
policy.setProjects(List.of(project));
policy.setUuid(UUID.randomUUID());
policy.setViolationState(Policy.ViolationState.INFO);

final PolicyCondition condition = new PolicyCondition();
condition.setId(1);
condition.setUuid(UUID.randomUUID());
condition.setOperator(PolicyCondition.Operator.NUMERIC_EQUAL);
condition.setSubject(PolicyCondition.Subject.AGE);
condition.setValue("1");
condition.setPolicy(policy);

final PolicyViolation policyViolation = new PolicyViolation();
policyViolation.setId(1);
policyViolation.setPolicyCondition(condition);
policyViolation.setComponent(component);
policyViolation.setText("test");
policyViolation.setType(PolicyViolation.Type.SECURITY);
policyViolation.setAnalysis(new ViolationAnalysis());
policyViolation.setUuid(UUID.randomUUID());
policyViolation.setTimestamp(new Date(System.currentTimeMillis()));
return policyViolation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@
import alpine.notification.NotificationLevel;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.dependencytrack.JerseyTestRule;
import org.dependencytrack.ResourceTest;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.NotificationPublisher;
import org.dependencytrack.model.NotificationRule;
import org.dependencytrack.notification.NotificationGroup;
import org.dependencytrack.notification.NotificationScope;
import org.dependencytrack.notification.publisher.DefaultNotificationPublishers;
import org.dependencytrack.persistence.DefaultObjectGenerator;
Expand All @@ -36,13 +42,11 @@
import org.junit.ClassRule;
import org.junit.Test;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.dependencytrack.notification.publisher.PublisherClass.SendMailPublisher;

public class NotificationPublisherResourceTest extends ResourceTest {
Expand Down Expand Up @@ -316,4 +320,28 @@ public void restoreDefaultTemplatesTest() {
slackPublisher = qm.getDefaultNotificationPublisherByName(DefaultNotificationPublishers.SLACK.getPublisherName());
Assert.assertEquals(DefaultNotificationPublishers.SLACK.getPublisherName(), slackPublisher.getName());
}

@Test
public void testNotificationRuleTest() {
NotificationPublisher slackPublisher = qm.getDefaultNotificationPublisherByName(DefaultNotificationPublishers.SLACK.getPublisherName());
slackPublisher.setName(slackPublisher.getName()+" Test Rule");
qm.persist(slackPublisher);
qm.detach(NotificationPublisher.class, slackPublisher.getId());

NotificationRule rule = qm.createNotificationRule("Example Rule 1", NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, slackPublisher);

Set<NotificationGroup> groups = new HashSet<>(Set.of(NotificationGroup.BOM_CONSUMED, NotificationGroup.BOM_PROCESSED, NotificationGroup.BOM_PROCESSING_FAILED,
NotificationGroup.BOM_VALIDATION_FAILED, NotificationGroup.NEW_VULNERABILITY, NotificationGroup.NEW_VULNERABLE_DEPENDENCY,
NotificationGroup.POLICY_VIOLATION, NotificationGroup.PROJECT_CREATED, NotificationGroup.PROJECT_AUDIT_CHANGE,
NotificationGroup.VEX_CONSUMED, NotificationGroup.VEX_PROCESSED));
rule.setNotifyOn(groups);
rule.setPublisherConfig("{\"destination\":\"https://example.com/webhook\"}");

Response response = jersey.target(V1_NOTIFICATION_PUBLISHER + "/test/" + rule.getUuid()).request()
.header(X_API_KEY, apiKey)
.post(Entity.entity("", MediaType.APPLICATION_FORM_URLENCODED_TYPE));

Assert.assertEquals(200, response.getStatus());
assertThat(kafkaMockProducer.history().size()).isEqualTo(11);
}
}

0 comments on commit 28815c3

Please sign in to comment.