Repository: modoboa/modoboa (language: python)

Issue:
Alias API : Validation should make the address lowercase
Current API allow the creation of two identical alias with different casing : `TEST@test.local` and `test@test.local` are considered different.

The serializer should apply a lower casing to the characters and perhaps lower case the query to be sure there are not duplicates



Discussion / hints:

Fix patch:
diff --git a/modoboa/admin/api/v1/serializers.py b/modoboa/admin/api/v1/serializers.py
index c40d89c06..0ba2c6b3c 100644
--- a/modoboa/admin/api/v1/serializers.py
+++ b/modoboa/admin/api/v1/serializers.py
@@ -17,6 +17,7 @@
 from modoboa.core import (
     constants as core_constants, models as core_models, signals as core_signals
 )
+from modoboa.lib.exceptions import AliasExists
 from modoboa.lib import (
     email_utils, exceptions as lib_exceptions, fields as lib_fields,
     permissions, validators, web_utils
@@ -467,9 +468,14 @@ class Meta:
 
     def validate_address(self, value):
         """Check domain."""
-        local_part, self.domain = admin_models.validate_alias_address(
-            value, self.context["request"].user, instance=self.instance)
-        return value
+        try:
+            local_part, self.domain = admin_models.validate_alias_address(
+                value, self.context["request"].user, instance=self.instance)
+        except ValidationError as err:
+            raise serializers.ValidationError(err)
+        except AliasExists:
+            raise serializers.ValidationError(_("This alias already exists"))
+        return value.lower()
 
     def create(self, validated_data):
         """Create appropriate objects."""
@@ -498,13 +504,13 @@ class Meta:
 
     def validate_address(self, value):
         """Check domain."""
-        local_part, domain = email_utils.split_mailbox(value)
+        local_part, domain = email_utils.split_mailbox(value.lower())
         domain = admin_models.Domain.objects.filter(name=domain).first()
         user = self.context["request"].user
         if domain and not user.can_access(domain):
             raise serializers.ValidationError(
                 _("You don't have access to this domain."))
-        return value
+        return value.lower()
 
     def validate_mailbox(self, value):
         """Check permission."""
diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py
index 2e7f32683..28f48eef8 100644
--- a/modoboa/admin/models/alias.py
+++ b/modoboa/admin/models/alias.py
@@ -28,7 +28,7 @@ def validate_alias_address(
         address, creator, internal=False, instance=None, ignore_existing=False
         ):
     """Check if the given alias address can be created by creator."""
-    local_part, domain = split_mailbox(address)
+    local_part, domain = split_mailbox(address.lower())
     domain = Domain.objects.filter(name=domain).first()
     if domain is None:
         raise ValidationError(_("Domain not found."))


Test patch:
diff --git a/modoboa/admin/api/v1/tests.py b/modoboa/admin/api/v1/tests.py
index fd48783c5..7922e8c29 100644
--- a/modoboa/admin/api/v1/tests.py
+++ b/modoboa/admin/api/v1/tests.py
@@ -678,6 +678,17 @@ def test_create_alias(self):
         response = self.client.post(url, data, format="json")
         self.assertEqual(response.status_code, 201)
 
+    def test_create_duplicate_alias(self):
+        url = reverse("v1:alias-list")
+        response = self.client.post(url, self.ALIAS_DATA, format="json")
+        self.assertEqual(response.status_code, 201)
+
+        # Try with same address but different case
+        data = copy.deepcopy(self.ALIAS_DATA)
+        data["address"] = "ALIAS_fromapi@test.com"
+        response = self.client.post(url, data, format="json")
+        self.assertEqual(response.status_code, 400)
+
     def test_create_alias_as_domadmin(self):
         """As DomainAdmin, try to create a new alias."""
         self.client.credentials(
diff --git a/modoboa/admin/api/v2/tests.py b/modoboa/admin/api/v2/tests.py
index 0f09ae935..472c87e33 100644
--- a/modoboa/admin/api/v2/tests.py
+++ b/modoboa/admin/api/v2/tests.py
@@ -535,10 +535,11 @@ def test_validate(self):
 
         data = {"address": "alias@test.com"}
         resp = self.client.post(url, data, format="json")
-        self.assertEqual(resp.status_code, 409)
-
-        al_id = models.Alias.objects.get(address="alias@test.com").pk
-        self.assertEqual(resp.json()["id"], al_id)
+        self.assertEqual(resp.status_code, 400)
+        self.assertEqual(
+            resp.json()["address"][0],
+            "This alias already exists"
+        )
 
         data = {"address": "alias2@test.com"}
         resp = self.client.post(url, data, format="json")