Skip to content

Commit

Permalink
Add NOTIFICATIONS_SYSTEM_LEVEL_TRUMP (#9699)
Browse files Browse the repository at this point in the history
* Add NOTIFICATIONS_SYSTEM_LEVEL_TRUMP

* Update docs/content/en/integrations/notifications.md

Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com>

* Update unittests/test_notifications.py

Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com>

---------

Co-authored-by: Charles Neill <1749665+cneill@users.noreply.github.com>
  • Loading branch information
kiblik and cneill committed Apr 3, 2024
1 parent e2d5540 commit 0dc0b22
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 7 deletions.
10 changes: 10 additions & 0 deletions docs/content/en/integrations/notifications.md
Expand Up @@ -115,3 +115,13 @@ To activate notifications to Microsoft Teams, you have to:
- Configure an Incoming Webhook in a Teams channel and copy the URL of the webhook to the clipboard
- Activate `Enable Microsoft Teams notifications` in the System Settings
- Paste the URL of the Incoming Webhook into the field `Msteams url`

## Specific overrides

System notification settings (scope: system) describe the sending of notifications to superadmins. User notification settings (scope: personal) describe sending notifications to the specific user.

However, there is a specific use-case when the user decides to disable notifications (to decrease noise) but the system setting is used to override this behavior. These overrides apply only to `user_mentioned` and `review_requested` by default.

The scope of this setting is customizable (see environmental variable `DD_NOTIFICATIONS_SYSTEM_LEVEL_TRUMP`).

For more information about this behavior see the [related pull request #9699](https://github.com/DefectDojo/django-DefectDojo/pull/9699/)
19 changes: 12 additions & 7 deletions dojo/notifications/helper.py
@@ -1,6 +1,7 @@
import logging
import requests

from django.conf import settings
from django.core.mail import EmailMessage
from django.db.models import Q, Count, Prefetch
from django.template import TemplateDoesNotExist
Expand Down Expand Up @@ -30,12 +31,17 @@ def create_notification(event=None, **kwargs):
# mimic existing code so that when recipients is specified, no other system or personal notifications are sent.
logger.debug('creating notifications for recipients: %s', kwargs['recipients'])
for recipient_notifications in Notifications.objects.filter(user__username__in=kwargs['recipients'], user__is_active=True, product=None):
# merge the system level notifications with the personal level
# this allows for system to trump the personal
merged_notifications = Notifications.merge_notifications_list([system_notifications, recipient_notifications])
merged_notifications.user = recipient_notifications.user
logger.debug('Sent notification to %s', merged_notifications.user)
process_notifications(event, merged_notifications, **kwargs)
if event in settings.NOTIFICATIONS_SYSTEM_LEVEL_TRUMP:
# merge the system level notifications with the personal level
# this allows for system to trump the personal
merged_notifications = Notifications.merge_notifications_list([system_notifications, recipient_notifications])
merged_notifications.user = recipient_notifications.user
logger.debug('Sent notification to %s', merged_notifications.user)
process_notifications(event, merged_notifications, **kwargs)
else:
# Do not trump user preferences and send notifications as usual
logger.debug('Sent notification to %s', recipient_notifications.user)
process_notifications(event, recipient_notifications, **kwargs)

else:
logger.debug('creating system notifications for event: %s', event)
Expand Down Expand Up @@ -322,7 +328,6 @@ def send_alert_notification(event, user=None, *args, **kwargs):
except Exception as e:
logger.exception(e)
log_alert(e, "Alert Notification", title=kwargs['title'], description=str(e), url=kwargs['url'])
pass


def get_slack_user_id(user_email):
Expand Down
6 changes: 6 additions & 0 deletions dojo/settings/settings.dist.py
Expand Up @@ -285,6 +285,8 @@
# When set to True, use the older version of the qualys parser that is a more heavy handed in setting severity
# with the use of CVSS scores to potentially override the severity found in the report produced by the tool
DD_QUALYS_LEGACY_SEVERITY_PARSING=(bool, True),
# Use System notification settings to override user's notification settings
DD_NOTIFICATIONS_SYSTEM_LEVEL_TRUMP=(list, ["user_mentioned", "review_requested"]),
)


Expand Down Expand Up @@ -1704,6 +1706,10 @@ def saml2_attrib_map_format(dict):
USE_FIRST_SEEN = env('DD_USE_FIRST_SEEN')
USE_QUALYS_LEGACY_SEVERITY_PARSING = env('DD_QUALYS_LEGACY_SEVERITY_PARSING')

# ------------------------------------------------------------------------------
# Notifications
# ------------------------------------------------------------------------------
NOTIFICATIONS_SYSTEM_LEVEL_TRUMP = env('DD_NOTIFICATIONS_SYSTEM_LEVEL_TRUMP')

# ------------------------------------------------------------------------------
# Ignored Warnings
Expand Down
46 changes: 46 additions & 0 deletions unittests/test_notifications.py
@@ -1,5 +1,8 @@
from .dojo_test_case import DojoTestCase
from dojo.models import Product, User, Notifications
from dojo.models import DEFAULT_NOTIFICATION
from dojo.notifications.helper import create_notification, send_alert_notification
from unittest.mock import patch


class TestNotifications(DojoTestCase):
Expand Down Expand Up @@ -55,3 +58,46 @@ def test_merge_notifications_list(self):
self.assertEqual('slack' in merged_notifications.other, True) # default alert from global
self.assertEqual(len(merged_notifications.other), 3)
self.assertEqual(merged_notifications.other, {'alert', 'mail', 'slack'})

@patch('dojo.notifications.helper.send_alert_notification', wraps=send_alert_notification)
def test_notifications_system_level_trump(self, mock):
notif_user, _ = Notifications.objects.get_or_create(user=User.objects.get(username='admin'))
notif_system, _ = Notifications.objects.get_or_create(user=None, template=False)

last_count = 0
with self.subTest('user off, system off'):
notif_user.user_mentioned = () # no alert
notif_user.save()
notif_system.user_mentioned = () # no alert
notif_system.save()
create_notification(event="user_mentioned", title="user_mentioned", recipients=['admin'])
self.assertEqual(mock.call_count, last_count)

last_count = mock.call_count
with self.subTest('user off, system on'):
notif_user.user_mentioned = () # no alert
notif_user.save()
notif_system.user_mentioned = DEFAULT_NOTIFICATION # alert only
notif_system.save()
create_notification(event="user_mentioned", title="user_mentioned", recipients=['admin'])
self.assertEqual(mock.call_count, last_count + 1)

# Small note for this test-cast: Trump works only in positive direction - system is not able to disable some kind of notification if user enabled it
last_count = mock.call_count
with self.subTest('user on, system off'):
notif_user.user_mentioned = DEFAULT_NOTIFICATION # alert only
notif_user.save()
notif_system.user_mentioned = () # no alert
notif_system.save()
create_notification(event="user_mentioned", title="user_mentioned", recipients=['admin'])
self.assertEqual(mock.call_count, last_count + 1)

last_count = mock.call_count
with self.subTest('user on, system on'):
notif_user.user_mentioned = DEFAULT_NOTIFICATION # alert only
notif_user.save()
notif_system.user_mentioned = DEFAULT_NOTIFICATION # alert only
notif_system.save()
create_notification(event="user_mentioned", title="user_mentioned", recipients=['admin'])
self.assertEqual(mock.call_count, last_count + 1)
last_count = mock.call_count

0 comments on commit 0dc0b22

Please sign in to comment.