mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Merge pull request #2884 from modoboa/better-dkim
Added an alarm on dkim write error, reworked alarms
This commit is contained in:
+40
-1
@@ -35,6 +35,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import alarms from '@/api/alarms'
|
||||
import { bus } from '@/main'
|
||||
import Navbar from '@/components/layout/Navbar'
|
||||
|
||||
@@ -51,12 +52,23 @@ export default {
|
||||
created () {
|
||||
// this.$store.dispatch('auth/initialize')
|
||||
bus.$on('notification', this.showNotification)
|
||||
bus.$on('loggedIn', this.checkAlarms)
|
||||
},
|
||||
mounted () {
|
||||
this.checkAlarms()
|
||||
},
|
||||
beforeDestroy () {
|
||||
clearInterval(this.alarmChecker)
|
||||
},
|
||||
data: () => ({
|
||||
snackbar: false,
|
||||
notification: '',
|
||||
notificationColor: 'success',
|
||||
notificationTimeout: 2000
|
||||
notificationTimeout: 2000,
|
||||
alarmNotified: [],
|
||||
alarmChecker: null,
|
||||
lastAlarmDate: '',
|
||||
alarmSleepTime: 10000
|
||||
}),
|
||||
methods: {
|
||||
showNotification (options) {
|
||||
@@ -65,6 +77,33 @@ export default {
|
||||
this.notificationColor = (options.type) ? options.type : 'success'
|
||||
this.snackbar = true
|
||||
}
|
||||
},
|
||||
checkAlarms () {
|
||||
if (this.isAuthenticated) {
|
||||
this.alarmChecker = window.setInterval(() => {
|
||||
const params = {}
|
||||
if (this.lastAlarmDate instanceof Date) {
|
||||
params.min_date = this.lastAlarmDate
|
||||
}
|
||||
alarms.getAll(params).then(resp => {
|
||||
let count = 0
|
||||
for (const alarm of resp.data) {
|
||||
if (alarm.status === 1 && !this.alarmNotified.includes(alarm.id)) {
|
||||
if (this.lastAlarmDate instanceof Date && new Date(alarm.created) > this.lastAlarmDate) {
|
||||
this.lastAlarmDate = new Date(alarm.created)
|
||||
} else if (!(this.lastAlarmDate instanceof Date)) {
|
||||
this.lastAlarmDate = new Date(alarm.created)
|
||||
}
|
||||
count++
|
||||
this.alarmNotified.push(alarm.id)
|
||||
}
|
||||
}
|
||||
if (count !== 0) {
|
||||
bus.$emit('notification', { msg: this.$gettext('You have one or more opened alarms'), type: 'error' })
|
||||
}
|
||||
})
|
||||
}, this.alarmSleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,14 @@ const resource = 'alarms'
|
||||
export default {
|
||||
getAll (params) {
|
||||
return repository.get(`${resource}/`, { params })
|
||||
},
|
||||
switchStatus (alarmId, data) {
|
||||
return repository.patch(`${resource}/${alarmId}/switch/`, data)
|
||||
},
|
||||
delete (alarmId) {
|
||||
return repository.delete(`${resource}/${alarmId}/`)
|
||||
},
|
||||
bulkDelete (alarmsIds) {
|
||||
return repository.delete(`${resource}/bulk_delete/`, { params: { ids: alarmsIds } })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<menu-items :items="getActionMenuItems()"/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-spacer></v-spacer>
|
||||
@@ -26,7 +27,7 @@
|
||||
:headers="headers"
|
||||
:items="alarms"
|
||||
:search="search"
|
||||
item-key="pk"
|
||||
item-key="id"
|
||||
:options.sync="options"
|
||||
:server-items-length="totalAlarms"
|
||||
:loading="loading"
|
||||
@@ -53,18 +54,40 @@
|
||||
<template v-if="item.mailbox">
|
||||
{{ item.mailbox.address }}@{{ item.domain.name }}
|
||||
</template>
|
||||
<template v-else>
|
||||
/
|
||||
</template>
|
||||
</template>
|
||||
<template v-slot:item.created="{ item }">
|
||||
{{ item.created|date }}
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<div class="text-right">
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn icon v-bind="attrs" v-on="on">
|
||||
<v-icon>mdi-dots-horizontal</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<menu-items :items="getMenuItems(item)" :object="item" />
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { bus } from '@/main'
|
||||
import alarms from '@/api/alarms'
|
||||
import debounce from 'debounce'
|
||||
import MenuItems from '@/components/tools/MenuItems'
|
||||
import constants from '@/constants.json'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MenuItems
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
alarms: [],
|
||||
@@ -73,7 +96,8 @@ export default {
|
||||
{ text: this.$gettext('Status'), value: 'status' },
|
||||
{ text: this.$gettext('Domain'), value: 'domain.name', sortable: false },
|
||||
{ text: this.$gettext('Mailbox'), value: 'mailbox', sortable: false },
|
||||
{ text: this.$gettext('Message'), value: 'title' }
|
||||
{ text: this.$gettext('Message'), value: 'title' },
|
||||
{ text: this.$gettext('Actions'), value: 'actions', sortable: false, align: 'right' }
|
||||
],
|
||||
loading: true,
|
||||
options: {},
|
||||
@@ -99,6 +123,66 @@ export default {
|
||||
this.totalAlarms = resp.data.count
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
async deleteAlarm (alarm, single = true) {
|
||||
alarms.delete(alarm.id).then(() => {
|
||||
if (single) {
|
||||
bus.$emit('notification', { msg: this.$gettext('Alarm deleted') })
|
||||
this.fetchAlarms()
|
||||
}
|
||||
})
|
||||
},
|
||||
async closeAlarm (alarm) {
|
||||
alarms.switchStatus(alarm.id, { status: constants.ALARM_CLOSED }).then(() => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Alarm closed') })
|
||||
this.fetchAlarms()
|
||||
})
|
||||
},
|
||||
async openAlarm (alarm) {
|
||||
alarms.switchStatus(alarm.id, { status: constants.ALARM_OPENED }).then(() => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Alarm re-opened') })
|
||||
this.fetchAlarms()
|
||||
})
|
||||
},
|
||||
async deleteAlarms () {
|
||||
await alarms.bulkDelete(this.selected.map(alarm => alarm.id))
|
||||
bus.$emit('notification', { msg: this.$gettext('Alarms deleted') })
|
||||
this.selected = []
|
||||
this.fetchAlarms()
|
||||
},
|
||||
getMenuItems (item) {
|
||||
const result = []
|
||||
result.push({
|
||||
label: this.$gettext('Delete'),
|
||||
icon: 'mdi-delete-outline',
|
||||
onClick: this.deleteAlarm,
|
||||
color: 'red'
|
||||
})
|
||||
if (item.status === constants.ALARM_OPENED) {
|
||||
result.push({
|
||||
label: this.$gettext('Close alarm'),
|
||||
icon: 'mdi-check',
|
||||
onClick: this.closeAlarm,
|
||||
color: 'green'
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
label: this.$gettext('Reopen alarm'),
|
||||
icon: 'mdi-alert-circle-outline',
|
||||
onClick:
|
||||
this.openAlarm,
|
||||
color: 'orange'
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
getActionMenuItems () {
|
||||
const result = []
|
||||
if (this.selected.length > 0) {
|
||||
result.push({ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteAlarms, color: 'red' })
|
||||
}
|
||||
result.push({ label: this.$gettext('Reload'), icon: 'mdi-reload', onClick: this.fetchAlarms })
|
||||
return result
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ALARM_OPENED": 1,
|
||||
"ALARM_CLOSED": 2
|
||||
}
|
||||
@@ -58,6 +58,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { bus } from '@/main'
|
||||
import Cookies from 'js-cookie'
|
||||
import auth from '@/api/auth'
|
||||
|
||||
@@ -86,6 +87,7 @@ export default {
|
||||
Cookies.set('token', resp.data.access, { sameSite: 'strict' })
|
||||
Cookies.set('refreshToken', resp.data.refresh, { sameSite: 'strict' })
|
||||
this.$store.dispatch('auth/initialize').then(() => {
|
||||
bus.$emit('loggedIn')
|
||||
this.$router.push({ name: 'DomainList' })
|
||||
})
|
||||
}).catch(err => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -35,6 +35,8 @@ DKIM_KEY_LENGTHS = [
|
||||
(4096, "4096"),
|
||||
]
|
||||
|
||||
DKIM_WRITE_ERROR = "DKIM path error"
|
||||
|
||||
ALARM_OPENED = 1
|
||||
ALARM_CLOSED = 2
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import os
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.lib import sysutils
|
||||
from modoboa.parameters import tools as param_tools
|
||||
|
||||
from .... import models
|
||||
from .... import signals
|
||||
from ....constants import DKIM_WRITE_ERROR, ALARM_OPENED
|
||||
|
||||
|
||||
class ManageDKIMKeys(BaseCommand):
|
||||
@@ -19,6 +21,19 @@ class ManageDKIMKeys(BaseCommand):
|
||||
"""Create a new DKIM key."""
|
||||
storage_dir = param_tools.get_global_parameter("dkim_keys_storage_dir")
|
||||
pkey_path = os.path.join(storage_dir, "{}.pem".format(domain.name))
|
||||
alarm_qset = domain.alarms.filter(internal_name=DKIM_WRITE_ERROR)
|
||||
if not os.access(storage_dir, os.W_OK):
|
||||
if not alarm_qset.exists():
|
||||
domain.alarms.create(
|
||||
title=_("DKIM path non-writable"),
|
||||
internal_name=DKIM_WRITE_ERROR)
|
||||
else:
|
||||
alarm = alarm_qset.first()
|
||||
if alarm.status != ALARM_OPENED:
|
||||
alarm.reopen()
|
||||
return
|
||||
elif alarm_qset.exists():
|
||||
alarm_qset.first().close()
|
||||
key_size = (
|
||||
domain.dkim_key_length if domain.dkim_key_length
|
||||
else self.default_key_length)
|
||||
|
||||
@@ -51,5 +51,11 @@ class Alarm(models.Model):
|
||||
self.closed = timezone.now()
|
||||
self.save()
|
||||
|
||||
def reopen(self):
|
||||
"""Reopen this alarm."""
|
||||
self.status = constants.ALARM_OPENED
|
||||
self.closed = None
|
||||
self.save()
|
||||
|
||||
|
||||
reversion.register(Alarm)
|
||||
|
||||
@@ -60,7 +60,7 @@ class MXRecord(models.Model):
|
||||
managed = models.BooleanField(default=False)
|
||||
updated = models.DateTimeField()
|
||||
|
||||
objects = MXRecordManager.from_queryset(MXRecordQuerySet)()
|
||||
objects: MXRecordManager = MXRecordManager.from_queryset(MXRecordQuerySet)()
|
||||
|
||||
def is_managed(self):
|
||||
if not param_tools.get_global_parameter("enable_mx_checks"):
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Alarm related tests."""
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from ..factories import populate_database
|
||||
from ..models import Alarm, Domain
|
||||
|
||||
|
||||
class AlarmsTestCase(ModoTestCase):
|
||||
"""Alarm open/close test cases."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
populate_database()
|
||||
|
||||
def test_set_forward(self):
|
||||
domain = Domain.objects.all().first()
|
||||
alarm = Alarm.objects.create(
|
||||
domain=domain, mailbox=None, title="Test alarm 3")
|
||||
alarm.save()
|
||||
self.assertEqual(alarm.status, 1)
|
||||
alarm.close()
|
||||
self.assertEqual(alarm.status, 2)
|
||||
alarm.reopen()
|
||||
self.assertEqual(alarm.status, 1)
|
||||
@@ -20,9 +20,10 @@ from modoboa.core.tests.test_views import SETTINGS_SAMPLE
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.maillog import factories as ml_factories
|
||||
|
||||
from ..import constants
|
||||
from . import utils
|
||||
from .. import factories
|
||||
from ..models import Alias, Domain
|
||||
from ..models import Alarm, Alias, Domain
|
||||
|
||||
|
||||
class DomainTestCase(ModoTestCase):
|
||||
@@ -445,10 +446,30 @@ class DKIMTestCase(ModoTestCase):
|
||||
values["dkim_key_selector"] = "default"
|
||||
self.ajax_post(reverse("admin:domain_add"), values)
|
||||
|
||||
# Try generating a key to a protected directory
|
||||
self.set_global_parameter("dkim_keys_storage_dir", "/I_DONT_EXIST")
|
||||
call_command("modo", "manage_dkim_keys")
|
||||
domain = Domain.objects.get(name=values["name"])
|
||||
|
||||
# Closing the alarm and retry generating dkim keys
|
||||
Alarm.objects.get(domain=domain,
|
||||
internal_name=constants.DKIM_WRITE_ERROR
|
||||
).close()
|
||||
call_command("modo", "manage_dkim_keys")
|
||||
self.assertEqual(
|
||||
Alarm.objects.get(domain=domain,
|
||||
internal_name=constants.DKIM_WRITE_ERROR).status,
|
||||
constants.ALARM_OPENED)
|
||||
|
||||
# Generate normally
|
||||
self.set_global_parameter("dkim_keys_storage_dir", self.workdir)
|
||||
call_command("modo", "manage_dkim_keys")
|
||||
self.assertEqual(
|
||||
Alarm.objects.get(domain=domain,
|
||||
internal_name=constants.DKIM_WRITE_ERROR).status,
|
||||
constants.ALARM_CLOSED)
|
||||
key_path = os.path.join(self.workdir, "{}.pem".format(values["name"]))
|
||||
self.assertTrue(os.path.exists(key_path))
|
||||
|
||||
domain = Domain.objects.get(name=values["name"])
|
||||
url = reverse("admin:domain_detail", args=[domain.pk])
|
||||
response = self.client.get(url)
|
||||
|
||||
@@ -75,6 +75,18 @@ class ParametersAPITestCase(ModoAPITestCase):
|
||||
errors["dkim_keys_storage_dir"], ["Directory not found."]
|
||||
)
|
||||
|
||||
# Try to set dkim storage dir to a non-writable directory.
|
||||
data = {
|
||||
"dkim_keys_storage_dir": "/root"
|
||||
}
|
||||
url = reverse("v2:parameter-detail", args=["admin"])
|
||||
resp = self.client.put(url, data, format="json")
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
errors = resp.json()
|
||||
self.assertEqual(
|
||||
errors["dkim_keys_storage_dir"], ["Directory non-writable"]
|
||||
)
|
||||
|
||||
data = {
|
||||
"enable_mx_checks": True,
|
||||
"domains_must_have_authorized_mx": True,
|
||||
|
||||
Reference in New Issue
Block a user