Added way to allow/block alias target at creation/modification.

see #2013
This commit is contained in:
Antoine Nguyen
2025-09-26 15:09:43 +02:00
parent c44d7da534
commit dfe741698d
5 changed files with 134 additions and 1 deletions
@@ -169,7 +169,7 @@ function display(element) {
element.display.split('&').forEach((rule) => {
let [field, value] = rule.split('=')
if (value === 'true' || value === 'false') {
value = Boolean(value)
value = value === 'true'
}
result &= parameters.value[field] === value
})
+9
View File
@@ -244,6 +244,15 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
auto_create_domain_and_mailbox = serializers.BooleanField(default=True)
create_alias_on_mbox_rename = serializers.BooleanField(default=False)
# Aliases settings
alias_can_target_any_domain = serializers.BooleanField(default=True)
alias_target_block_list = serializers.ListField(
child=serializers.CharField(), required=False
)
alias_target_allow_list = serializers.ListField(
child=serializers.CharField(), required=False
)
def validate_default_domain_quota(self, value):
"""Ensure quota is a positive integer."""
if value < 0:
+39
View File
@@ -239,6 +239,45 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
),
},
),
(
"aliases",
{
"label": gettext_lazy("Aliases"),
"params": collections.OrderedDict(
[
(
"alias_can_target_any_domain",
{
"label": gettext_lazy("Alias can target any domain"),
"help_text": gettext_lazy(
"Allow aliases to target any domain, not just the local domains."
),
},
),
(
"alias_target_block_list",
{
"label": gettext_lazy("Alias target block list"),
"help_text": gettext_lazy(
"A list of domains that aliases cannot target (comma separated)."
),
"display": "alias_can_target_any_domain=true",
},
),
(
"alias_target_allow_list",
{
"label": gettext_lazy("Alias target allow list"),
"help_text": gettext_lazy(
"A list of domains that aliases can target (comma separated)."
),
"display": "alias_can_target_any_domain=false",
},
),
]
),
},
),
]
)
+17
View File
@@ -21,6 +21,7 @@ from modoboa.lib.exceptions import (
ModoboaException,
AliasExists,
)
from modoboa.parameters import tools as param_tools
from .. import signals
from .base import AdminObject
from .domain import Domain
@@ -90,6 +91,20 @@ class AliasManager(models.Manager):
)
def is_alias_target_allowed(domain: str) -> bool:
"""Check if alias target is allowed or not."""
params = dict(param_tools.get_global_parameters(app="admin"))
if params.get("alias_can_target_any_domain"):
blocked_domains = params.get("alias_target_block_list", "").split(",")
if domain in blocked_domains:
return False
else:
allowed_domains = params.get("alias_target_allow_list", "").split(",")
if domain not in allowed_domains:
return False
return True
class Alias(AdminObject):
"""Mailbox alias."""
@@ -182,6 +197,8 @@ class Alias(AdminObject):
if domname is None:
raise BadRequest("{} {}".format(_("Invalid address"), address))
domain = Domain.objects.filter(name=domname).first()
if not domain and not is_alias_target_allowed(domname):
raise BadRequest(f"{_('Target domain not allowed')}: {domname}")
kwargs = {"address": address, "alias": self}
if (domain is not None) and (
any(
+68
View File
@@ -0,0 +1,68 @@
"""Tests for alias target allow/block settings."""
from django.urls import reverse
from modoboa.admin import factories, models
from modoboa.lib.tests import ModoAPITestCase
class AliasTargetSettingsTestCase(ModoAPITestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
super().setUpTestData()
factories.populate_database()
def test_block_external_domain_when_any_allowed_with_block_list(self):
# alias_can_target_any_domain = True and block external domain
self.set_global_parameter("alias_can_target_any_domain", True, app="admin")
self.set_global_parameter("alias_target_block_list", "bad.com", app="admin")
values = {
"address": "list@test.com",
"recipients": ["user@bad.com"],
"enabled": True,
}
resp = self.client.post(reverse("v2:alias-list"), values, format="json")
# Creation should fail
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.json()["error"], "Target domain not allowed: bad.com")
def test_allow_only_list_when_any_disallowed(self):
# alias_can_target_any_domain = False and allow list contains domain
self.set_global_parameter("alias_can_target_any_domain", False, app="admin")
self.set_global_parameter("alias_target_allow_list", "ok.com", app="admin")
# Check if local domain is still allowed
values = {
"address": "list2@test.com",
"recipients": ["user@test.com"],
"enabled": True,
}
resp = self.client.post(reverse("v2:alias-list"), values, format="json")
self.assertEqual(resp.status_code, 201)
values = {
"address": "list3@test.com",
"recipients": ["user@ok.com"],
"enabled": True,
}
resp = self.client.post(reverse("v2:alias-list"), values, format="json")
self.assertEqual(resp.status_code, 201)
alias = models.Alias.objects.get(address="list3@test.com", internal=False)
# Allowed external domain should remain external
self.assertTrue(
alias.aliasrecipient_set.filter(
address="user@ok.com", r_mailbox__isnull=True, r_alias__isnull=True
).exists()
)
# Now try a domain not in allow list, should stay external as well
values = {
"address": "list4@test.com",
"recipients": ["user@bad.com"],
"enabled": True,
}
resp = self.client.post(reverse("v2:alias-list"), values, format="json")
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.json()["error"], "Target domain not allowed: bad.com")