🐛(domains) fix attemps to send invitations to existing users

Users can only search other users inside their own organization.
This leads to a bug where we try to invite an email already linked to an user
This commit is contained in:
Marie PUPO JEAMMET
2025-07-03 15:44:40 +02:00
committed by Marie
parent 0df8f53037
commit 63b8984eb2
6 changed files with 76 additions and 18 deletions

View File

@@ -14,7 +14,11 @@ and this project adheres to
- ✨(front) add show invitations mails domains access #1040
- ✨(invitations) can delete domain invitations
## Changed
### Fixed
- 🐛(domains) fix attemps to send invitations to existing users #953
### Changed
- 🏗️(core) migrate from pip to uv
- ✨(front) add show invitations mails domains access #1040

View File

@@ -36,6 +36,8 @@ from core.plugins.registry import registry as plugin_hooks_registry
from core.utils.webhooks import webhooks_synchronizer
from core.validators import get_field_validators_from_setting
from mailbox_manager.exceptions import EmailAlreadyKnownException
logger = getLogger(__name__)
current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -997,9 +999,7 @@ class BaseInvitation(BaseModel):
# Check if a user already exists for the provided email
if User.objects.filter(email=self.email).exists():
raise exceptions.ValidationError(
{"email": _("This email is already associated to a registered user.")}
)
raise EmailAlreadyKnownException
@property
def is_expired(self):

View File

@@ -171,10 +171,11 @@ def test_api_team_invitations__create__cannot_invite_existing_users():
format="json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["email"] == [
"This email is already associated to a registered user."
]
assert response.status_code == status.HTTP_201_CREATED
assert (
response.json()["detail"]
== "Email already known. Invitation not sent but access created instead."
)
def test_api_team_invitations__list__anonymous_user():

View File

@@ -13,6 +13,7 @@ from core.api.client.serializers import UserSerializer
from mailbox_manager import enums, models
from mailbox_manager.api import permissions
from mailbox_manager.api.client import serializers
from mailbox_manager.exceptions import EmailAlreadyKnownException
from mailbox_manager.utils.dimail import DimailAPIClient
@@ -363,6 +364,7 @@ class MailDomainInvitationViewset(
- issuer : User, automatically added from user making query, if allowed
- domain : Domain, automatically added from requested URI
Return a newly created invitation
or an access if email is already linked to an existing user
PUT / PATCH : Not permitted. Instead of updating your invitation,
delete and create a new one.
@@ -410,6 +412,34 @@ class MailDomainInvitationViewset(
return queryset
def perform_create(self, serializer):
"""Lookup for existing user before inviting."""
if email := serializer.validated_data["email"]:
existing_user = models.User.objects.filter(email=email)
if existing_user.exists():
return models.MailDomainAccess.objects.create(
user=existing_user[0],
domain=serializer.validated_data["domain"],
role=serializer.validated_data["role"],
)
return super().perform_create(serializer)
def create(self, request, *args, **kwargs):
"""Attempt to create invitation. If user is already registered,
they don't need an invitation but an access, which we create here."""
try:
return super().create(request, *args, **kwargs)
except EmailAlreadyKnownException as exc:
user = models.User.objects.get(email=email)
models.MailDomainAccess.objects.create(
user=user,
domain=models.MailDomain.objects.get(slug=kwargs["domain_slug"]),
role=request.data["role"],
)
raise exc
class AliasViewSet(
mixins.CreateModelMixin,

View File

@@ -0,0 +1,18 @@
"""
Custom exceptions for mailbox manager app
"""
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import APIException
class EmailAlreadyKnownException(APIException):
"""Exception raised when trying to create a user with an already existing email address."""
status_code = status.HTTP_201_CREATED
default_detail = _(
"Email already known. Invitation not sent but access created instead."
)
default_code = "email already known"

View File

@@ -11,7 +11,7 @@ from rest_framework.test import APIClient
from core import factories as core_factories
from mailbox_manager import enums, factories
from mailbox_manager import enums, factories, models
from mailbox_manager.api.client import serializers
pytestmark = pytest.mark.django_db
@@ -141,9 +141,10 @@ def test_api_domain_invitations__should_not_create_duplicate_invitations():
assert response.json()["__all__"] == [
"Mail domain invitation with this Email address and Domain already exists."
]
assert models.MailDomainInvitation.objects.count() == 1 # and specifically, not 2
def test_api_domain_invitations__should_not_invite_when_user_already_exists():
def test_api_domain_invitations__should_create_access_when_user_already_exists():
"""Already existing users should not be invited but given access directly."""
existing_user = core_factories.UserFactory()
@@ -152,15 +153,19 @@ def test_api_domain_invitations__should_not_invite_when_user_already_exists():
client = APIClient()
client.force_login(access.user)
invitation_values = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build(email=existing_user.email)
).data
response = client.post(
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
invitation_values,
{
"email": existing_user.email,
"role": "owner",
},
format="json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["email"] == [
"This email is already associated to a registered user."
]
assert response.status_code == status.HTTP_201_CREATED
assert (
response.json()["detail"]
== "Email already known. Invitation not sent but access created instead."
)
assert not models.MailDomainInvitation.objects.exists()
assert models.MailDomainAccess.objects.filter(user=existing_user).exists()