🐛(aliases) alias destination can be devnull@devnull
devnull@devnull is not considered a valid email address by django's EmailFieldValidator but it's a special address in dimail's config. Make "destination" a CharField instead of an EmailField to replace validator and add devnull to allowlist.
This commit is contained in:
committed by
Marie
parent
5ebc88bcff
commit
99433a6722
@@ -8,6 +8,10 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛(aliases) authorize special domain devnull in alias destinations #1029
|
||||||
|
|
||||||
## [1.22.1] - 2026-01-21
|
## [1.22.1] - 2026-01-21
|
||||||
|
|
||||||
- 🔒️(organization) the first user is not admin #776
|
- 🔒️(organization) the first user is not admin #776
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
|||||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created at')),
|
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created at')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated at')),
|
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated at')),
|
||||||
('local_part', models.CharField(max_length=100)),
|
('local_part', models.CharField(max_length=100)),
|
||||||
('destination', models.EmailField(max_length=254, verbose_name='destination address')),
|
('destination', models.CharField(max_length=254, validators=[django.core.validators.EmailValidator(allowlist=['localhost', 'devnull'])], verbose_name='destination address')),
|
||||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='mailbox_manager.maildomain')),
|
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='mailbox_manager.maildomain')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.base_user import AbstractBaseUser
|
from django.contrib.auth.base_user import AbstractBaseUser
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import exceptions, mail, validators
|
from django.core import exceptions, mail, validators
|
||||||
|
from django.core.validators import EmailValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@@ -460,7 +461,15 @@ class Alias(BaseModel):
|
|||||||
"""Model for aliases."""
|
"""Model for aliases."""
|
||||||
|
|
||||||
local_part = models.CharField(max_length=100, blank=False)
|
local_part = models.CharField(max_length=100, blank=False)
|
||||||
destination = models.EmailField(_("destination address"), null=False, blank=False)
|
destination = models.CharField(
|
||||||
|
_("destination address"),
|
||||||
|
max_length=254,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
validators=[
|
||||||
|
EmailValidator(allowlist=["localhost", "devnull"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
domain = models.ForeignKey(
|
domain = models.ForeignKey(
|
||||||
MailDomain,
|
MailDomain,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|||||||
@@ -182,3 +182,33 @@ def test_api_aliases_create__existing_mailbox_ok(dimail_token_ok):
|
|||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
assert models.Alias.objects.exists()
|
assert models.Alias.objects.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_api_aliases_create__devnull_destination_ok(dimail_token_ok):
|
||||||
|
"""Can create alias where destination is devnull@devnull."""
|
||||||
|
access = factories.MailDomainAccessFactory(
|
||||||
|
role="owner", domain=factories.MailDomainEnabledFactory()
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(access.user)
|
||||||
|
|
||||||
|
responses.post(
|
||||||
|
re.compile(rf".*/domains/{access.domain.name}/aliases/"),
|
||||||
|
json={
|
||||||
|
"username": "spammy-address",
|
||||||
|
"domain": access.domain.name,
|
||||||
|
"destination": "devnull@devnull",
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{access.domain.slug}/aliases/",
|
||||||
|
{"local_part": "spammy-address", "destination": "devnull@devnull"},
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
assert models.Alias.objects.exists()
|
||||||
|
|||||||
19
src/backend/mailbox_manager/tests/models/test_aliases.py
Normal file
19
src/backend/mailbox_manager/tests/models/test_aliases.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for the Alias model
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mailbox_manager import factories, models
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_models_aliases__devnull_destination_ok():
|
||||||
|
"""Can create alias where destination is devnull@devnull."""
|
||||||
|
|
||||||
|
models.Alias.objects.create(
|
||||||
|
local_part="spam",
|
||||||
|
domain=factories.MailDomainEnabledFactory(),
|
||||||
|
destination="devnull@devnull",
|
||||||
|
)
|
||||||
@@ -199,6 +199,12 @@ def test_dimail_synchronization__synchronize_aliases(dimail_token_ok): # pylint
|
|||||||
"destination": existing_mailbox.secondary_email,
|
"destination": existing_mailbox.secondary_email,
|
||||||
"allow_to_send": False,
|
"allow_to_send": False,
|
||||||
},
|
},
|
||||||
|
{ # alias to devnull@devnull
|
||||||
|
"username": "spam",
|
||||||
|
"domain": alias.domain.name,
|
||||||
|
"destination": "devnull@devnull",
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
responses.get(
|
responses.get(
|
||||||
re.compile(rf".*/domains/{alias.domain.name}/aliases/"),
|
re.compile(rf".*/domains/{alias.domain.name}/aliases/"),
|
||||||
@@ -209,8 +215,8 @@ def test_dimail_synchronization__synchronize_aliases(dimail_token_ok): # pylint
|
|||||||
|
|
||||||
imported_aliases = dimail_client.import_aliases(alias.domain)
|
imported_aliases = dimail_client.import_aliases(alias.domain)
|
||||||
|
|
||||||
assert len(imported_aliases) == 3
|
assert len(imported_aliases) == 4
|
||||||
assert models.Alias.objects.count() == 4
|
assert models.Alias.objects.count() == 5
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
Reference in New Issue
Block a user