🐛(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:
committed by
Marie
parent
0df8f53037
commit
63b8984eb2
@@ -14,7 +14,11 @@ and this project adheres to
|
|||||||
- ✨(front) add show invitations mails domains access #1040
|
- ✨(front) add show invitations mails domains access #1040
|
||||||
- ✨(invitations) can delete domain invitations
|
- ✨(invitations) can delete domain invitations
|
||||||
|
|
||||||
## Changed
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛(domains) fix attemps to send invitations to existing users #953
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
- 🏗️(core) migrate from pip to uv
|
- 🏗️(core) migrate from pip to uv
|
||||||
- ✨(front) add show invitations mails domains access #1040
|
- ✨(front) add show invitations mails domains access #1040
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ from core.plugins.registry import registry as plugin_hooks_registry
|
|||||||
from core.utils.webhooks import webhooks_synchronizer
|
from core.utils.webhooks import webhooks_synchronizer
|
||||||
from core.validators import get_field_validators_from_setting
|
from core.validators import get_field_validators_from_setting
|
||||||
|
|
||||||
|
from mailbox_manager.exceptions import EmailAlreadyKnownException
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
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
|
# Check if a user already exists for the provided email
|
||||||
if User.objects.filter(email=self.email).exists():
|
if User.objects.filter(email=self.email).exists():
|
||||||
raise exceptions.ValidationError(
|
raise EmailAlreadyKnownException
|
||||||
{"email": _("This email is already associated to a registered user.")}
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
|
|||||||
@@ -171,10 +171,11 @@ def test_api_team_invitations__create__cannot_invite_existing_users():
|
|||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
assert response.json()["email"] == [
|
assert (
|
||||||
"This email is already associated to a registered user."
|
response.json()["detail"]
|
||||||
]
|
== "Email already known. Invitation not sent but access created instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_api_team_invitations__list__anonymous_user():
|
def test_api_team_invitations__list__anonymous_user():
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from core.api.client.serializers import UserSerializer
|
|||||||
from mailbox_manager import enums, models
|
from mailbox_manager import enums, models
|
||||||
from mailbox_manager.api import permissions
|
from mailbox_manager.api import permissions
|
||||||
from mailbox_manager.api.client import serializers
|
from mailbox_manager.api.client import serializers
|
||||||
|
from mailbox_manager.exceptions import EmailAlreadyKnownException
|
||||||
from mailbox_manager.utils.dimail import DimailAPIClient
|
from mailbox_manager.utils.dimail import DimailAPIClient
|
||||||
|
|
||||||
|
|
||||||
@@ -363,6 +364,7 @@ class MailDomainInvitationViewset(
|
|||||||
- issuer : User, automatically added from user making query, if allowed
|
- issuer : User, automatically added from user making query, if allowed
|
||||||
- domain : Domain, automatically added from requested URI
|
- domain : Domain, automatically added from requested URI
|
||||||
Return a newly created invitation
|
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,
|
PUT / PATCH : Not permitted. Instead of updating your invitation,
|
||||||
delete and create a new one.
|
delete and create a new one.
|
||||||
@@ -410,6 +412,34 @@ class MailDomainInvitationViewset(
|
|||||||
|
|
||||||
return queryset
|
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(
|
class AliasViewSet(
|
||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
|
|||||||
18
src/backend/mailbox_manager/exceptions.py
Normal file
18
src/backend/mailbox_manager/exceptions.py
Normal 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"
|
||||||
@@ -11,7 +11,7 @@ from rest_framework.test import APIClient
|
|||||||
|
|
||||||
from core import factories as core_factories
|
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
|
from mailbox_manager.api.client import serializers
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
@@ -141,9 +141,10 @@ def test_api_domain_invitations__should_not_create_duplicate_invitations():
|
|||||||
assert response.json()["__all__"] == [
|
assert response.json()["__all__"] == [
|
||||||
"Mail domain invitation with this Email address and Domain already exists."
|
"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."""
|
"""Already existing users should not be invited but given access directly."""
|
||||||
existing_user = core_factories.UserFactory()
|
existing_user = core_factories.UserFactory()
|
||||||
|
|
||||||
@@ -152,15 +153,19 @@ def test_api_domain_invitations__should_not_invite_when_user_already_exists():
|
|||||||
|
|
||||||
client = APIClient()
|
client = APIClient()
|
||||||
client.force_login(access.user)
|
client.force_login(access.user)
|
||||||
invitation_values = serializers.MailDomainInvitationSerializer(
|
|
||||||
factories.MailDomainInvitationFactory.build(email=existing_user.email)
|
|
||||||
).data
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
|
f"/api/v1.0/mail-domains/{access.domain.slug}/invitations/",
|
||||||
invitation_values,
|
{
|
||||||
|
"email": existing_user.email,
|
||||||
|
"role": "owner",
|
||||||
|
},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
assert response.json()["email"] == [
|
assert (
|
||||||
"This email is already associated to a registered user."
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user