🐛(mailbox) fix case-sensitive duplicate display names

uniqueness on first name + last name was case-sensitive, which allowed
duplicates
This commit is contained in:
Marie PUPO JEAMMET
2025-10-21 17:24:38 +02:00
committed by Marie
parent 302671bc69
commit 71a7bf688f
5 changed files with 34 additions and 5 deletions

View File

@@ -8,6 +8,7 @@ and this project adheres to
## [Unreleased]
- 🐛(mailbox) fix case-sensitive duplicates on display names
- ✨(mailbox) synchronize password of newly created mailbox with Dimail's
## [1.19.1] - 2025-09-19

View File

@@ -206,7 +206,7 @@ class MailboxAdmin(UserAdmin):
list_display = ("__str__", "domain", "status", "updated_at")
list_filter = ("status",)
search_fields = ("local_part", "domain__name")
search_fields = ("local_part", "domain__name", "first_name", "last_name")
readonly_fields = ["updated_at"]
fieldsets = None

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.7 on 2025-10-21 15:49
import django.db.models.functions.text
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mailbox_manager', '0026_alter_mailbox_unique_together_and_more'),
]
operations = [
migrations.RemoveConstraint(
model_name='mailbox',
name='unique_ox_display_name',
),
migrations.AddConstraint(
model_name='mailbox',
constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('first_name'), django.db.models.functions.text.Lower('last_name'), models.F('domain'), name='unique_ox_display_name', violation_error_message='Mailbox with this First name, Last name and Domain already exists.'),
),
]

View File

@@ -10,6 +10,7 @@ from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.sites.models import Site
from django.core import exceptions, mail, validators
from django.db import models
from django.db.models.functions import Lower
from django.template.loader import render_to_string
from django.utils.text import slugify
from django.utils.translation import get_language, gettext, override
@@ -308,12 +309,17 @@ class Mailbox(AbstractBaseUser, BaseModel):
fields=["local_part", "domain"], name="unique_username"
),
models.UniqueConstraint(
fields=["first_name", "last_name", "domain"],
Lower("first_name"),
Lower("last_name"),
"domain",
name="unique_ox_display_name",
violation_error_message="Mailbox with this First name, \
Last name and Domain already exists.",
),
# Display name in OpenXChange must be unique
# To avoid sending failing requests to dimail,
# we impose uniqueness here too
# And compare to lowercase to enforce case-insensitive uniqueness
]
ordering = ["-created_at"]

View File

@@ -77,7 +77,7 @@ def test_api_mailboxes__create_viewer_failure(mailbox_data):
def test_api_mailboxes__create_display_name_must_be_unique():
"""Primary id on OpenXchange is display name (first_name + last_name).
It needs to be unique on each context. We don't track context info for now
but can impose uniqueness by domain."""
but can impose case-insensitive uniqueness by domain."""
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
existing_mailbox = factories.MailboxFactory(domain=access.domain)
@@ -86,8 +86,8 @@ def test_api_mailboxes__create_display_name_must_be_unique():
new_mailbox_data = {
"local_part": "something_else",
"first_name": existing_mailbox.first_name,
"last_name": existing_mailbox.last_name,
"first_name": existing_mailbox.first_name.upper(), # ensure case-insensitivity
"last_name": existing_mailbox.last_name.lower(),
}
response = client.post(
f"/api/v1.0/mail-domains/{access.domain.slug}/mailboxes/",