Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: Fix notification tests not working for Jira #4460

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
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 org.dependencytrack.auth.Permissions;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.NotificationPublisher;
Expand Down Expand Up @@ -340,38 +339,52 @@ public Response testSmtpPublisherConfig(@FormParam("destination") String destina
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.SYSTEM_CONFIGURATION)
public Response testSlackPublisherConfig(
public Response testNotificationRule(
@Parameter(description = "The UUID of the rule to test", schema = @Schema(type = "string", format = "uuid"), required = true)
@PathParam("uuid") @ValidUuid String ruleUuid) throws Exception {
try(QueryManager qm = new QueryManager()){
NotificationRule rule = qm.getObjectByUuid(NotificationRule.class, ruleUuid);
NotificationPublisher notificationPublisher = rule.getPublisher();
final Class<?> publisherClass = Class.forName(notificationPublisher.getPublisherClass());
Publisher publisher = (Publisher) publisherClass.getDeclaredConstructor().newInstance();
String publisherConfig = rule.getPublisherConfig();
JsonReader jsonReader = Json.createReader(new StringReader(publisherConfig));
JsonObject configObject = jsonReader.readObject();
jsonReader.close();
final JsonObject config = Json.createObjectBuilder()
.add(Publisher.CONFIG_DESTINATION, configObject.getString("destination"))
try (final var qm = new QueryManager()) {
final NotificationRule rule = qm.getObjectByUuid(NotificationRule.class, ruleUuid);
if (rule == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}

final JsonObject publisherConfig;
final String publisherConfigJson = rule.getPublisherConfig() != null ? rule.getPublisherConfig() : "{}";
try (final JsonReader jsonReader = Json.createReader(new StringReader(publisherConfigJson))) {
publisherConfig = jsonReader.readObject();
}

final JsonObject config = Json.createObjectBuilder(publisherConfig)
.add(Publisher.CONFIG_TEMPLATE_KEY, rule.getPublisher().getTemplate())
.add(Publisher.CONFIG_TEMPLATE_MIME_TYPE_KEY, rule.getPublisher().getTemplateMimeType())
.build();

for(NotificationGroup group : rule.getNotifyOn()){

final Class<?> publisherClass = Class.forName(rule.getPublisher().getPublisherClass());
final Publisher publisher = (Publisher) publisherClass.getDeclaredConstructor().newInstance();

for (NotificationGroup group : rule.getNotifyOn()) {
final Notification notification = new Notification()
.scope(rule.getScope())
.group(group.toString())
.title(group)
.content("Rule configuration test")
.level(rule.getNotificationLevel())
.subject(NotificationUtil.generateSubject(group.toString()));
.scope(rule.getScope())
.group(group.toString())
.title(group)
.content("Rule configuration test")
.level(rule.getNotificationLevel())
.subject(NotificationUtil.generateSubject(group.toString()));

publisher.inform(PublishContext.from(notification), notification, config);
}

return Response.ok().build();
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
} catch (
InvocationTargetException
| InstantiationException
| IllegalAccessException
| NoSuchMethodException e) {
LOGGER.error(e.getMessage(), e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Exception occured while sending the notification.").build();
return Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Exception occurred while sending the notification.")
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
*/
package org.dependencytrack.resources.v1;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import alpine.common.util.UuidUtil;
import alpine.notification.NotificationLevel;
import alpine.security.crypto.DataEncryption;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import org.dependencytrack.JerseyTestRule;
import org.dependencytrack.ResourceTest;
import org.dependencytrack.model.ConfigPropertyConstants;
Expand All @@ -40,16 +43,24 @@
import org.junit.ClassRule;
import org.junit.Test;

import alpine.common.util.UuidUtil;
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.Form;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class NotificationPublisherResourceTest extends ResourceTest {

Expand Down Expand Up @@ -139,7 +150,7 @@ public void createNotificationPublisherWithExistingNameTest() {
.put(Entity.entity(publisher, MediaType.APPLICATION_JSON));
Assert.assertEquals(409, response.getStatus(), 0);
String body = getPlainTextBody(response);
Assert.assertEquals("The notification with the name "+DefaultNotificationPublishers.SLACK.getPublisherName()+" already exist", body);
Assert.assertEquals("The notification with the name " + DefaultNotificationPublishers.SLACK.getPublisherName() + " already exist", body);
}

@Test
Expand All @@ -156,7 +167,7 @@ public void createNotificationPublisherWithClassNotImplementingPublisherInterfac
.put(Entity.entity(publisher, MediaType.APPLICATION_JSON));
Assert.assertEquals(400, response.getStatus(), 0);
String body = getPlainTextBody(response);
Assert.assertEquals("The class "+NotificationPublisherResource.class.getName()+" does not implement "+ Publisher.class.getName(), body);
Assert.assertEquals("The class " + NotificationPublisherResource.class.getName() + " does not implement " + Publisher.class.getName(), body);
}

@Test
Expand Down Expand Up @@ -245,7 +256,7 @@ public void updateNotificationPublisherWithNameOfAnotherNotificationPublisherTes
Assert.assertEquals(409, response.getStatus(), 0);
Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
String body = getPlainTextBody(response);
Assert.assertEquals("An existing publisher with the name '"+DefaultNotificationPublishers.MS_TEAMS.getPublisherName()+"' already exist", body);
Assert.assertEquals("An existing publisher with the name '" + DefaultNotificationPublishers.MS_TEAMS.getPublisherName() + "' already exist", body);
}

@Test
Expand Down Expand Up @@ -279,7 +290,7 @@ public void updateNotificationPublisherWithClassNotImplementingPublisherInterfac
Assert.assertEquals(400, response.getStatus(), 0);
Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
String body = getPlainTextBody(response);
Assert.assertEquals("The class "+NotificationPublisherResource.class.getName()+" does not implement "+ Publisher.class.getName(), body);
Assert.assertEquals("The class " + NotificationPublisherResource.class.getName() + " does not implement " + Publisher.class.getName(), body);
}

@Test
Expand Down Expand Up @@ -350,28 +361,106 @@ public void testNotificationRuleTest() {
"Example Publisher", "Publisher description",
SlackPublisher.class, "template", "text/html",
false);

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

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));
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 sendMailResponse = 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, sendMailResponse.getStatus());
}

@Test
public void testNotificationRuleJiraTest() throws Exception {
new DefaultObjectGenerator().loadDefaultNotificationPublishers();

final NotificationPublisher jiraPublisher = qm.getNotificationPublisher(
DefaultNotificationPublishers.JIRA.getPublisherName());
assertThat(jiraPublisher).isNotNull();

final var notificationRule = new NotificationRule();
notificationRule.setPublisher(jiraPublisher);
notificationRule.setPublisherConfig("""
{
"destination": "FOO",
"jiraTicketType": "Task"
}
""");
notificationRule.setName("Jira Test");
notificationRule.setNotifyOn(Set.of(NotificationGroup.NEW_VULNERABILITY));
notificationRule.setNotificationLevel(NotificationLevel.INFORMATIONAL);
notificationRule.setScope(NotificationScope.PORTFOLIO);
qm.persist(notificationRule);

final var wireMock = new WireMockServer(options().dynamicPort());
wireMock.start();

try {
qm.createConfigProperty(
ConfigPropertyConstants.JIRA_URL.getGroupName(),
ConfigPropertyConstants.JIRA_URL.getPropertyName(),
wireMock.baseUrl(),
ConfigPropertyConstants.JIRA_URL.getPropertyType(),
ConfigPropertyConstants.JIRA_URL.getDescription());
qm.createConfigProperty(
ConfigPropertyConstants.JIRA_PASSWORD.getGroupName(),
ConfigPropertyConstants.JIRA_PASSWORD.getPropertyName(),
DataEncryption.encryptAsString("authToken"),
ConfigPropertyConstants.JIRA_PASSWORD.getPropertyType(),
ConfigPropertyConstants.JIRA_PASSWORD.getDescription());

wireMock.stubFor(WireMock.post(WireMock.anyUrl())
.willReturn(aResponse()
.withStatus(200)));

final Response response = jersey.target(V1_NOTIFICATION_PUBLISHER + "/test/" + notificationRule.getUuid()).request()
.header(X_API_KEY, apiKey)
.post(null);
assertThat(response.getStatus()).isEqualTo(200);

await("Notification Delivery")
.atMost(Duration.ofSeconds(5))
.untilAsserted(() -> wireMock.verify(postRequestedFor(urlPathEqualTo("/rest/api/2/issue"))
.withRequestBody(equalToJson("""
{
"fields" : {
"project" : {
"key" : "FOO"
},
"issuetype" : {
"name" : "Task"
},
"summary" : "[Dependency-Track] [NEW_VULNERABILITY] [MEDIUM] New medium vulnerability identified: INT-001",
"description" : "A new vulnerability has been identified on your project(s).\\n\\\\\\\\\\n\\\\\\\\\\n*Vulnerability description*\\n{code:none|bgColor=white|borderStyle=none}{code}\\n\\n*VulnID*\\nINT-001\\n\\n*Severity*\\nMedium\\n\\n*Component*\\n[componentName : componentVersion|/components/94f87321-a5d1-4c2f-b2fe-95165debebc6]\\n\\n*Affected project(s)*\\n- [projectName (projectVersion)|/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95]\\n"
}
}
"""))));
} finally {
wireMock.stop();
}
}

@Test
public void testNotificationRuleNotFoundTest() {
final Response response = jersey.target(V1_NOTIFICATION_PUBLISHER + "/test/" + UUID.randomUUID()).request()
.header(X_API_KEY, apiKey)
.post(null);
assertThat(response.getStatus()).isEqualTo(404);
}

@Test
public void restoreDefaultTemplatesTest() {
NotificationPublisher slackPublisher = qm.getDefaultNotificationPublisher(DefaultNotificationPublishers.SLACK.getPublisherClass());
slackPublisher.setName(slackPublisher.getName()+" Updated");
slackPublisher.setName(slackPublisher.getName() + " Updated");
qm.persist(slackPublisher);
qm.detach(NotificationPublisher.class, slackPublisher.getId());
qm.createConfigProperty(
Expand Down
Loading