Skip to content

Commit 99b53de

Browse files
committed
Add mail notification severity options
Signed-off-by: Erik Myhrberg <[email protected]>
1 parent 7b81bc2 commit 99b53de

File tree

7 files changed

+123
-2
lines changed

7 files changed

+123
-2
lines changed

docs/_docs/integrations/notifications.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ multiple levels, while others can only ever have a single level.
7373
| PORTFOLIO | POLICY_VIOLATION | Event | INFORMATIONAL | Notifications generated whenever a policy violation is identified |
7474
| PORTFOLIO | NEW_POLICY_VIOLATIONS_SUMMARY | Schedule | INFORMATIONAL | Summary of new policy violations identified in a set of projects |
7575

76+
## Configuring Severities
77+
78+
A **severity** is a Dependency-Track concept defining the seriousness of an event and controlling which notifications are sent based on the user’s selected levels.
79+
Only events matching one of the checked severities will trigger a notification.
80+
Only events which trigger `NEW_VULNERABILITY_IDENTIFIED` and **Mail** notifications will be triggered.
81+
Select the severity levels to be notified about. **The default is to notify about every severity levels**.
82+
83+
| Severity | Level | Description |
84+
|------------|:-----:|------------------------------------------|
85+
| CRITICAL | 5 | Issues that require immediate attention |
86+
| HIGH | 4 | High-severity issues |
87+
| MEDIUM | 3 | Moderate-severity issues |
88+
| LOW | 2 | Low-severity issues |
89+
| INFO | 1 | Informational messages |
90+
| UNASSIGNED | 0 | No severity assigned (treated as lowest) |
91+
7692
## Configuring Publishers
7793

7894
A notification publisher is a Dependency-Track concept allowing users to describe the structure of a notification (i.e. MIME type, template) and how to send a notification (i.e. publisher class).

src/main/java/org/dependencytrack/model/NotificationRule.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ public class NotificationRule implements Serializable {
138138
@Column(name = "NOTIFY_ON", length = 1024)
139139
private String notifyOn;
140140

141+
@Persistent
142+
@Column(name = "NOTIFY_SEVERITIES", length = 1024)
143+
private String notifySeverities;
144+
141145
@Persistent
142146
@Column(name = "MESSAGE", length = 1024)
143147
@Size(max = 1024)
@@ -325,6 +329,37 @@ public void setNotifyOn(Set<NotificationGroup> groups) {
325329
this.notifyOn = sb.toString();
326330
}
327331

332+
public List<Severity> getNotifySeverities() {
333+
List<Severity> result = new ArrayList<>();
334+
if (notifySeverities != null) {
335+
String[] severities = notifySeverities.split(",");
336+
for (String s: severities) {
337+
result.add(Severity.valueOf(s.trim()));
338+
}
339+
} else {
340+
// No severities set; return all severities (default behaviour)
341+
Collections.addAll(result, Severity.values());
342+
}
343+
return result;
344+
}
345+
346+
public void setNotifySeverities(List<Severity> notifySeverities){
347+
if (notifySeverities.isEmpty()){
348+
this.notifySeverities = null;
349+
return;
350+
}
351+
352+
// Sort the severities to ensure consistent ordering
353+
StringBuilder sb = new StringBuilder();
354+
for (int i=0; i < notifySeverities.size(); i++) {
355+
sb.append(notifySeverities.get(i));
356+
if (i+1 < notifySeverities.size()) {
357+
sb.append(",");
358+
}
359+
}
360+
this.notifySeverities = sb.toString();
361+
}
362+
328363
public NotificationPublisher getPublisher() {
329364
return publisher;
330365
}

src/main/java/org/dependencytrack/notification/NotificationRouter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.dependencytrack.model.Tag;
3030
import org.dependencytrack.notification.publisher.PublishContext;
3131
import org.dependencytrack.notification.publisher.Publisher;
32+
import org.dependencytrack.notification.publisher.SendMailPublisher;
3233
import org.dependencytrack.notification.vo.AnalysisDecisionChange;
3334
import org.dependencytrack.notification.vo.BomConsumedOrProcessed;
3435
import org.dependencytrack.notification.vo.BomProcessingFailed;
@@ -90,7 +91,22 @@ public void inform(final Notification notification) {
9091
.add(CONFIG_TEMPLATE_KEY, notificationPublisher.getTemplate())
9192
.addAll(Json.createObjectBuilder(config))
9293
.build();
93-
publisher.inform(ruleCtx, restrictNotificationToRuleProjects(notification, rule), notificationPublisherConfig);
94+
95+
if (publisherClass == SendMailPublisher.class
96+
&& rule.getTeams() != null
97+
&& !rule.getTeams().isEmpty()) {
98+
99+
((SendMailPublisher) publisher).inform(
100+
ruleCtx,
101+
restrictNotificationToRuleProjects(notification, rule),
102+
notificationPublisherConfig,
103+
rule.getNotifySeverities());
104+
} else {
105+
publisher.inform(
106+
ruleCtx,
107+
restrictNotificationToRuleProjects(notification, rule),
108+
notificationPublisherConfig);
109+
}
94110
} else {
95111
LOGGER.error("The defined notification publisher is not assignable from " + Publisher.class.getCanonicalName() + " (%s)".formatted(ruleCtx));
96112
}

src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import io.pebbletemplates.pebble.extension.core.DisallowExtensionCustomizerBuilder;
2727
import io.pebbletemplates.pebble.template.PebbleTemplate;
2828
import org.apache.commons.text.StringEscapeUtils;
29+
import org.dependencytrack.model.Severity;
30+
import org.dependencytrack.notification.vo.NewVulnerabilityIdentified;
2931
import org.dependencytrack.persistence.QueryManager;
3032
import org.dependencytrack.util.DebugDataEncryption;
3133

@@ -59,6 +61,10 @@ public class SendMailPublisher implements Publisher {
5961
.newLineTrimming(false)
6062
.build();
6163

64+
private static final List<Severity> DEFAULT_NOTIFY_SEVERITIES =
65+
Arrays.asList(Severity.LOW, Severity.MEDIUM, Severity.HIGH,
66+
Severity.UNASSIGNED, Severity.CRITICAL);
67+
6268
@Override
6369
public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) {
6470
if (config == null) {
@@ -69,6 +75,35 @@ public void inform(final PublishContext ctx, final Notification notification, fi
6975
sendNotification(ctx, notification, config, destinations);
7076
}
7177

78+
/**
79+
* Overload used when a rule has explicit teams/severity filtering.
80+
*/
81+
public void inform(final PublishContext ctx, final Notification notification, final JsonObject config, final List<Severity> notifySeverities) {
82+
83+
if (config == null) {
84+
LOGGER.warn("No configuration found; Skipping notification (%s)".formatted(ctx));
85+
return;
86+
}
87+
88+
// Severity filtering
89+
if (notification.getSubject() instanceof final NewVulnerabilityIdentified subject) {
90+
final List<Severity> effective = (notifySeverities == null || notifySeverities.isEmpty())
91+
? DEFAULT_NOTIFY_SEVERITIES
92+
: notifySeverities;
93+
94+
if (!effective.contains(subject.getVulnerability().getSeverity())) {
95+
LOGGER.debug("Severity %s filtered by rule – skipping (%s)"
96+
.formatted(subject.getVulnerability().getSeverity(), ctx));
97+
return;
98+
}
99+
}
100+
101+
// Team filtering
102+
final String[] destinations = getDestinations(config, ctx.ruleId());
103+
104+
sendNotification(ctx, notification, config, destinations);
105+
}
106+
72107
private void sendNotification(final PublishContext ctx, Notification notification, JsonObject config, String[] destinations) {
73108
if (config == null) {
74109
LOGGER.warn("No publisher configuration found; Skipping notification (%s)".formatted(ctx));

src/main/java/org/dependencytrack/persistence/NotificationQueryManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public NotificationRule updateNotificationRule(NotificationRule transientRule) {
159159
rule.setNotificationLevel(transientRule.getNotificationLevel());
160160
rule.setPublisherConfig(transientRule.getPublisherConfig());
161161
rule.setNotifyOn(transientRule.getNotifyOn());
162+
rule.setNotifySeverities(transientRule.getNotifySeverities());
162163
bind(rule, resolveTags(transientRule.getTags()));
163164
return rule;
164165
});

src/test/java/org/dependencytrack/model/NotificationRuleTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ public void testNotifyOn() {
9999
Assert.assertEquals(2, rule.getNotifyOn().size());
100100
}
101101

102+
@Test
103+
public void testNotifySeverities() {
104+
List<Severity> severities = new ArrayList<>();
105+
severities.add(Severity.LOW);
106+
severities.add(Severity.CRITICAL);
107+
NotificationRule rule = new NotificationRule();
108+
rule.setNotifySeverities(severities);
109+
Assert.assertEquals(2, rule.getNotifySeverities().size());
110+
}
111+
102112
@Test
103113
public void testPublisher() {
104114
NotificationPublisher publisher = new NotificationPublisher();

src/test/java/org/dependencytrack/resources/v1/NotificationRuleResourceTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ public void createScheduledNotificationRuleTest() {
173173
"tags": [],
174174
"teams": [],
175175
"notifyOn": [],
176+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
176177
"publisher": {
177178
"name": "Slack",
178179
"description": "${json-unit.any-string}",
@@ -300,6 +301,7 @@ public void updateNotificationRuleWithTagsTest() {
300301
],
301302
"teams": [],
302303
"notifyOn": [],
304+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
303305
"publisher": {
304306
"name": "${json-unit.any-string}",
305307
"description": "${json-unit.any-string}",
@@ -348,6 +350,7 @@ public void updateNotificationRuleWithTagsTest() {
348350
],
349351
"teams": [],
350352
"notifyOn": [],
353+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
351354
"publisher": {
352355
"name": "${json-unit.any-string}",
353356
"description": "${json-unit.any-string}",
@@ -404,7 +407,8 @@ public void updateNotificationRuleWithGroupUnsupportedForScheduleTest() {
404407
"name": "Rule 1",
405408
"scope": "PORTFOLIO",
406409
"notificationLevel": "INFORMATIONAL",
407-
"notifyOn": ["BOM_PROCESSED", "NEW_VULNERABILITIES_SUMMARY"]
410+
"notifyOn": ["BOM_PROCESSED", "NEW_VULNERABILITIES_SUMMARY"],
411+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"]
408412
}
409413
""".formatted(rule.getUuid())));
410414
assertThat(response.getStatus()).isEqualTo(400);
@@ -433,6 +437,7 @@ public void updateNotificationRuleWithNewCronExpressionTest() {
433437
"scope": "PORTFOLIO",
434438
"notificationLevel": "INFORMATIONAL",
435439
"notifyOn": ["NEW_VULNERABILITIES_SUMMARY"],
440+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
436441
"triggerType": "SCHEDULE",
437442
"scheduleCron": "6 6 6 6 6"
438443
}
@@ -454,6 +459,7 @@ public void updateNotificationRuleWithNewCronExpressionTest() {
454459
"tags": [],
455460
"teams": [],
456461
"notifyOn": ["NEW_VULNERABILITIES_SUMMARY"],
462+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
457463
"triggerType": "SCHEDULE",
458464
"scheduleLastTriggeredAt": "${json-unit.matches:unmodifiedScheduleLastTriggeredAt}",
459465
"scheduleNextTriggerAt": "${json-unit.matches:modifiedScheduleNextTriggerAt}",
@@ -479,6 +485,7 @@ public void updateNotificationRuleWithInvalidCronExpressionTest() {
479485
"scope": "PORTFOLIO",
480486
"notificationLevel": "INFORMATIONAL",
481487
"notifyOn": ["NEW_VULNERABILITIES_SUMMARY"],
488+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
482489
"triggerType": "SCHEDULE",
483490
"scheduleCron": "not valid at all"
484491
}
@@ -755,6 +762,7 @@ public void addTeamToRuleWithCustomEmailPublisherTest() {
755762
}
756763
],
757764
"notifyOn": [],
765+
"notifySeverities": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "UNASSIGNED"],
758766
"publisher": {
759767
"name": "foo",
760768
"description": "description",

0 commit comments

Comments
 (0)