diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index ec543e7c8d..7316fff165 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -39,6 +39,7 @@ public enum ConfigPropertyConstants { EMAIL_SMTP_PASSWORD("email", "smtp.password", null, PropertyType.ENCRYPTEDSTRING, "The optional password for the username used for authentication"), EMAIL_SMTP_SSLTLS("email", "smtp.ssltls", "false", PropertyType.BOOLEAN, "Flag to enable/disable the use of SSL/TLS when connecting to the SMTP server"), EMAIL_SMTP_TRUSTCERT("email", "smtp.trustcert", "false", PropertyType.BOOLEAN, "Flag to enable/disable the trust of the certificate presented by the SMTP server"), + EMAIL_SUBJECT_SHOW_LEVEL("email", "subject.show.level", "false", PropertyType.BOOLEAN,"If true, email subjects include the notification level"), INTERNAL_COMPONENTS_GROUPS_REGEX("internal-components", "groups.regex", null, PropertyType.STRING, "Regex that matches groups of internal components"), INTERNAL_COMPONENTS_NAMES_REGEX("internal-components", "names.regex", null, PropertyType.STRING, "Regex that matches names of internal components"), INTERNAL_COMPONENTS_MATCH_MODE("internal-components", "match-mode", "OR", PropertyType.STRING, "Determines how internal component regexes are combined: OR (default) or AND"), diff --git a/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java b/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java index 07df5bb898..8269362f8b 100644 --- a/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java +++ b/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java @@ -48,6 +48,7 @@ import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_SSLTLS; import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_TRUSTCERT; import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_USERNAME; +import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SUBJECT_SHOW_LEVEL; public class SendMailPublisher implements Publisher { @@ -98,7 +99,9 @@ private void sendNotification(final PublishContext ctx, Notification notificatio final String encryptedSmtpPassword; final boolean smtpSslTls; final boolean smtpTrustCert; + final boolean showLevel; String emailSubjectPrefix; + String subject; try (QueryManager qm = new QueryManager()) { smtpEnabled = qm.isEnabled(EMAIL_SMTP_ENABLED); @@ -116,6 +119,11 @@ private void sendNotification(final PublishContext ctx, Notification notificatio encryptedSmtpPassword = qm.getConfigProperty(EMAIL_SMTP_PASSWORD.getGroupName(), EMAIL_SMTP_PASSWORD.getPropertyName()).getPropertyValue(); smtpSslTls = qm.isEnabled(EMAIL_SMTP_SSLTLS); smtpTrustCert = qm.isEnabled(EMAIL_SMTP_TRUSTCERT); + showLevel = qm.isEnabled(EMAIL_SUBJECT_SHOW_LEVEL); + subject = emailSubjectPrefix.trim() + + (showLevel ? " [" + ctx.notificationLevel() + "] " : " ") + + notification.getTitle(); + } catch (RuntimeException e) { LOGGER.error("Failed to load SMTP configuration from datastore (%s)".formatted(ctx), e); return; @@ -130,12 +138,12 @@ private void sendNotification(final PublishContext ctx, Notification notificatio return; } String unescapedContent = StringEscapeUtils.unescapeHtml4(content); - + try { final SendMail sendMail = new SendMail() .from(smtpFrom) .to(destinations) - .subject(emailSubjectPrefix + " " + notification.getTitle()) + .subject(subject) .body(MediaType.TEXT_HTML.equals(mimeType) ? StringEscapeUtils.escapeHtml4(unescapedContent) : unescapedContent) .bodyMimeType(mimeType) .host(smtpHostname) diff --git a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java index 6490ee08ab..fb9513a169 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java @@ -41,6 +41,7 @@ import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_SSLTLS; import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_TRUSTCERT; import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SMTP_USERNAME; +import static org.dependencytrack.model.ConfigPropertyConstants.EMAIL_SUBJECT_SHOW_LEVEL; class SendMailPublisherTest extends AbstractPublisherTest { @@ -121,6 +122,13 @@ public void setUp() throws Exception { EMAIL_SMTP_TRUSTCERT.getPropertyType(), EMAIL_SMTP_TRUSTCERT.getDescription() ); + qm.createConfigProperty( + EMAIL_SUBJECT_SHOW_LEVEL.getGroupName(), + EMAIL_SUBJECT_SHOW_LEVEL.getPropertyName(), + "true", + EMAIL_SUBJECT_SHOW_LEVEL.getPropertyType(), + EMAIL_SUBJECT_SHOW_LEVEL.getDescription() + ); } @Test @@ -129,7 +137,7 @@ public void testMailSubjectIsSetCorrectly() { assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> assertThat(message.getSubject()) - .isEqualTo("[Dependency-Track] New Vulnerability Identified on Project: [projectName : projectVersion]") + .isEqualTo("[Dependency-Track] [INFORMATIONAL] New Vulnerability Identified on Project: [projectName : projectVersion]") ); } @@ -138,7 +146,7 @@ public void testInformWithBomConsumedNotification() { super.baseTestInformWithBomConsumedNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Consumed"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] Bill of Materials Consumed"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -170,7 +178,7 @@ public void testInformWithBomProcessingFailedNotification() { super.baseTestInformWithBomProcessingFailedNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Processing Failed"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [ERROR] Bill of Materials Processing Failed"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -206,7 +214,7 @@ public void testInformWithBomValidationFailedNotification() { super.baseTestInformWithBomValidationFailedNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Validation Failed"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [ERROR] Bill of Materials Validation Failed"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -238,7 +246,7 @@ public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubje super.baseTestInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Processing Failed"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [ERROR] Bill of Materials Processing Failed"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -274,7 +282,7 @@ public void testInformWithDataSourceMirroringNotification() { super.baseTestInformWithDataSourceMirroringNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] GitHub Advisory Mirroring"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [ERROR] GitHub Advisory Mirroring"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -304,7 +312,7 @@ public void testInformWithNewVulnerabilityNotification() { super.baseTestInformWithNewVulnerabilityNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] New Vulnerability Identified on Project: [projectName : projectVersion]"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] New Vulnerability Identified on Project: [projectName : projectVersion]"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -341,7 +349,7 @@ public void testPublishWithScheduledNewVulnerabilitiesNotification() { super.baseTestPublishWithScheduledNewVulnerabilitiesNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] New Vulnerabilities Summary"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] New Vulnerabilities Summary"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -399,7 +407,7 @@ public void testPublishWithScheduledNewPolicyViolationsNotification() { super.baseTestPublishWithScheduledNewPolicyViolationsNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] New Policy Violations Summary"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] New Policy Violations Summary"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -458,7 +466,7 @@ public void testInformWithNewVulnerableDependencyNotification() { super.baseTestInformWithNewVulnerableDependencyNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Vulnerable Dependency Introduced"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] Vulnerable Dependency Introduced"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -500,7 +508,7 @@ public void testInformWithProjectAuditChangeNotification() { super.baseTestInformWithProjectAuditChangeNotification(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Analysis Decision: Finding Suppressed"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [INFORMATIONAL] Analysis Decision: Finding Suppressed"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -541,7 +549,7 @@ public void testInformWithEscapedData() { super.baseTestInformWithEscapedData(); assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Notification Test"); + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] [ERROR] Notification Test"); assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); final MimeMultipart content = (MimeMultipart) message.getContent(); assertThat(content.getCount()).isEqualTo(1); @@ -954,6 +962,21 @@ void testEmptyOidcUsersAsDestination() { "steve@jobs.org"); } + @Test + public void testMailSubjectWithoutLevelWhenDisabled() { + qm.getConfigProperty( + EMAIL_SUBJECT_SHOW_LEVEL.getGroupName(), + EMAIL_SUBJECT_SHOW_LEVEL.getPropertyName()) + .setPropertyValue("false"); + + super.baseTestInformWithNewVulnerabilityNotification(); + + assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> + assertThat(message.getSubject()) + .isEqualTo("[Dependency-Track] New Vulnerability Identified on Project: [projectName : projectVersion]") + ); + } + private NotificationRule createNotificationRule() { final NotificationPublisher publisher = qm.createNotificationPublisher( DefaultNotificationPublishers.EMAIL.getPublisherName(),