Merge pull request #2884 from modoboa/better-dkim

Added an alarm on dkim write error, reworked alarms
This commit is contained in:
Antoine Nguyen
2023-03-26 13:26:43 +02:00
committed by GitHub
15 changed files with 366 additions and 9 deletions
+15
View File
@@ -226,6 +226,10 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
raise serializers.ValidationError(
_("Directory not found.")
)
if not os.access(value, os.W_OK):
raise serializers.ValidationError(
_("Directory non-writable")
)
code, output = exec_cmd("which openssl")
if code:
raise serializers.ValidationError(
@@ -621,3 +625,14 @@ class AlarmSerializer(serializers.ModelSerializer):
depth = 1
fields = "__all__"
model = models.Alarm
class AlarmSwitchStatusSerializer(serializers.Serializer):
"""Serializer to switch the status of an Alarm."""
status = serializers.IntegerField()
def validate(self, data):
if data["status"] not in [constants.ALARM_OPENED, constants.ALARM_CLOSED]:
raise ValidationError(_("Unknown status"))
return data
+76 -1
View File
@@ -6,7 +6,7 @@ from django.utils.encoding import force_text
from rest_framework.authtoken.models import Token
from modoboa.admin import factories, models
from modoboa.admin import factories, models, constants
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoAPITestCase
@@ -479,9 +479,84 @@ class AlarmViewSetTestCase(ModoAPITestCase):
factories.populate_database()
factories.AlarmFactory(
domain__name="test.com", mailbox=None, title="Test alarm")
cls.da_token = Token.objects.create(
user=core_models.User.objects.get(username="admin@test.com"))
def test_list(self):
url = reverse("v2:alarm-list")
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertEqual(len(resp.json()), 1)
def test_update_alarm(self):
"""Try updating alarm status and delete it afterward."""
domain = models.Domain.objects.get(name="test.com")
# Try performing action on restricted domains
self.client.credentials(
HTTP_AUTHORIZATION="Token " + self.da_token.key)
domain = models.Domain.objects.get(name="test2.com")
alarm_restricted = models.Alarm.objects.create(
domain=domain, mailbox=None, title="Test alarm 2")
alarm_restricted.save()
url = reverse("v2:alarm-switch", args=[alarm_restricted.pk])
resp = self.client.post(url)
self.assertEqual(resp.status_code, 405)
url = reverse("v2:alarm-detail", args=[alarm_restricted.pk])
resp = self.client.delete(url)
self.assertEqual(resp.status_code, 404)
# Perform actions as SuperAdmin
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.token.key)
alarm = models.Alarm.objects.create(
domain=domain, mailbox=None, title="Test alarm 3")
alarm.save()
# Switch status of the alarm to close
url = reverse("v2:alarm-switch", args=[alarm.pk])
resp = self.client.patch(url, {"status": constants.ALARM_CLOSED})
self.assertEqual(resp.status_code, 204)
# Check actual status
url = reverse("v2:alarm-detail", args=[alarm.pk])
resp = self.client.get(url)
self.assertEqual(resp.json()["status"], constants.ALARM_CLOSED)
# Switch status back to open
url = reverse("v2:alarm-switch", args=[alarm.pk])
resp = self.client.patch(url, {"status": constants.ALARM_OPENED})
self.assertEqual(resp.status_code, 204)
# Check actual status
url = reverse("v2:alarm-detail", args=[alarm.pk])
resp = self.client.get(url)
self.assertEqual(resp.json()["status"], constants.ALARM_OPENED)
# Try to set an non-existant status
url = reverse("v2:alarm-switch", args=[alarm.pk])
resp = self.client.patch(url, {"status": 10})
self.assertEqual(resp.status_code, 400)
# Delete the alarm
url = reverse("v2:alarm-detail", args=[alarm.pk])
resp = self.client.delete(url)
self.assertEqual(resp.status_code, 204)
def test_bulk_delete(self):
url = reverse("v2:alarm-bulk-delete")
resp = self.client.delete(url)
self.assertEqual(resp.status_code, 400)
resp = self.client.delete(f"{url}?ids[]=toto")
self.assertEqual(resp.status_code, 400)
alarm1 = factories.AlarmFactory(
domain__name="test.com", mailbox=None, title="Test alarm")
alarm2 = factories.AlarmFactory(
domain__name="test.com", mailbox=None, title="Test alarm")
resp = self.client.delete(f"{url}?ids[]={alarm1.pk}&ids[]={alarm2.pk}")
self.assertEqual(resp.status_code, 204)
with self.assertRaises(models.Alarm.DoesNotExist):
alarm1.refresh_from_db()
with self.assertRaises(models.Alarm.DoesNotExist):
alarm2.refresh_from_db()
+49 -2
View File
@@ -20,6 +20,7 @@ from modoboa.lib.throttle import GetThrottleViewsetMixin
from ... import lib
from ... import models
from ... import constants
from . import serializers
@@ -325,10 +326,30 @@ class UserAccountViewSet(GetThrottleViewsetMixin, viewsets.ViewSet):
return response.Response(serializer.validated_data)
class AlarmViewSet(GetThrottleViewsetMixin, viewsets.ReadOnlyModelViewSet):
class AlarmFilterSet(dj_filters.FilterSet):
"""Custom FilterSet for Alarms."""
min_date = dj_filters.DateTimeFilter(method="filter_date")
class Meta:
model = models.Alarm
fields = ["created"]
def filter_date(self, queryset, name, value):
return queryset.filter(created__gt=value)
class AlarmViewSet(GetThrottleViewsetMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
"""Viewset for Alarm."""
filter_backends = (filters.OrderingFilter, filters.SearchFilter, )
filter_backends = (filters.OrderingFilter,
filters.SearchFilter,
dj_filters.DjangoFilterBackend)
filterset_class = AlarmFilterSet
ordering_fields = ["created", "status", "title"]
pagination_class = pagination.PageNumberPagination
permission_classes = (
@@ -342,3 +363,29 @@ class AlarmViewSet(GetThrottleViewsetMixin, viewsets.ReadOnlyModelViewSet):
domain__in=models.Domain.objects.get_for_admin(
self.request.user)
).order_by("-created")
@action(methods=['patch'], detail=True)
def switch(self, request, **kwargs):
"""Custom update method that switch status of an alarm."""
alarm = self.get_object()
serializer = serializers.AlarmSwitchStatusSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if serializer.data["status"] == constants.ALARM_CLOSED:
alarm.close()
else:
alarm.reopen()
return response.Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=['delete'], detail=False)
def bulk_delete(self, request, **kwargs):
"""Delete multiple alarms at the same time."""
ids = request.query_params.getlist("ids[]")
if not ids:
return response.Response(_("No alarm ID provided"), status=400)
try:
ids = [int(alarm_id) for alarm_id in ids]
except ValueError:
return response.Response(
_("Received invalid alarm id(s)"), status=400)
models.Alarm.objects.filter(pk__in=ids).delete()
return response.Response(status=204)