From 54df9af179f71c5d68a7135068497d7fc5707852 Mon Sep 17 00:00:00 2001 From: Sabrina Demagny Date: Tue, 4 Mar 2025 10:01:10 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(domains)=20convert=20domain=20invitat?= =?UTF-8?q?ions=20to=20access=20roles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use django signals to keep mailbox_manager logic separated from people core --- src/backend/core/models.py | 54 ++------------------- src/backend/mailbox_manager/apps.py | 17 +++++++ src/backend/mailbox_manager/signals.py | 67 ++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 51 deletions(-) create mode 100644 src/backend/mailbox_manager/signals.py diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 61c3d36..b0e812f 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -37,8 +37,6 @@ from core.plugins.loader import ( from core.utils.webhooks import scim_synchronizer from core.validators import get_field_validators_from_setting -from mailbox_manager import enums - logger = getLogger(__name__) current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -520,7 +518,8 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): if self._state.adding: self._convert_valid_team_invitations() - self._convert_valid_domain_invitations() + # a post_save signal to convert domain invitations to domain accesses + # is triggered by the mailbox_manager app super().save(*args, **kwargs) @@ -555,53 +554,6 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): ) valid_invitations.delete() - def _convert_valid_domain_invitations(self): - """ - Convert valid domain invitations to domain accesses. - Expired invitations are ignored. - """ - - from mailbox_manager.models import DomainInvitation, MailDomainAccess - from mailbox_manager.utils.dimail import DimailAPIClient - - valid_domain_invitations = DomainInvitation.objects.filter( - email=self.email, - created_at__gte=( - timezone.now() - - timedelta(seconds=settings.INVITATION_VALIDITY_DURATION) - ), - ) - - if not valid_domain_invitations.exists(): - return - - MailDomainAccess.objects.bulk_create( - [ - MailDomainAccess( - user=self, domain=invitation.domain, role=invitation.role - ) - for invitation in valid_domain_invitations - ] - ) - - management_role = set(valid_domain_invitations.values_list("role", flat="True")) - if ( - enums.MailDomainRoleChoices.OWNER in management_role - or enums.MailDomainRoleChoices.ADMIN in management_role - ): - # Sync with dimail - dimail = DimailAPIClient() - dimail.create_user(self.sub) - - for invitation in valid_domain_invitations: - if invitation.role in [ - enums.MailDomainRoleChoices.OWNER, - enums.MailDomainRoleChoices.ADMIN, - ]: - dimail.create_allow(self.sub, invitation.domain.name) - - valid_domain_invitations.delete() - def email_user(self, subject, message, from_email=None, **kwargs): """Email this user.""" if not self.email: @@ -725,7 +677,7 @@ class TeamManager(MP_NodeManager): return self.model.add_root(**kwargs) # Retrieve parent object, because django-treebeard uses raw queries for most - # write operations, and raw queries don’t update the django objects of the db + # write operations, and raw queries don't update the django objects of the db # entries they modify. See caveats in the django-treebeard documentation. # This might be changed later if we never do any operation on the parent object # before creating the child. diff --git a/src/backend/mailbox_manager/apps.py b/src/backend/mailbox_manager/apps.py index ce062d2..f5ececb 100644 --- a/src/backend/mailbox_manager/apps.py +++ b/src/backend/mailbox_manager/apps.py @@ -1 +1,18 @@ """People additionnal application, to manage email adresses.""" + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class MailboxManagerConfig(AppConfig): + """Configuration class for the Mailbox manager app.""" + + name = "mailbox_manager" + verbose_name = _("Mailbox manager") + app_label = "mailbox_manager" + + def ready(self): + """ + Import signals when the app is ready. + """ + import mailbox_manager.signals # pylint: disable=import-outside-toplevel, unused-import diff --git a/src/backend/mailbox_manager/signals.py b/src/backend/mailbox_manager/signals.py new file mode 100644 index 0000000..cefba00 --- /dev/null +++ b/src/backend/mailbox_manager/signals.py @@ -0,0 +1,67 @@ +""" +Signals module for the mailbox_manager app. +""" + +import logging +from datetime import timedelta + +from django.conf import settings +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils import timezone + +from core.models import User + +from mailbox_manager import enums +from mailbox_manager.models import DomainInvitation, MailDomainAccess +from mailbox_manager.utils.dimail import DimailAPIClient + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=User) +def convert_domain_invitations(sender, created, instance, **kwargs): # pylint: disable=unused-argument + """ + Convert valid domain invitations to domain accesses for a given user. + Expired invitations are ignored. + """ + logger.info("Convert domain invitations for user %s", instance) + if created: + valid_domain_invitations = DomainInvitation.objects.filter( + email=instance.email, + created_at__gte=( + timezone.now() + - timedelta(seconds=settings.INVITATION_VALIDITY_DURATION) + ), + ) + + if not valid_domain_invitations.exists(): + return + + MailDomainAccess.objects.bulk_create( + [ + MailDomainAccess( + user=instance, domain=invitation.domain, role=invitation.role + ) + for invitation in valid_domain_invitations + ] + ) + + management_role = set(valid_domain_invitations.values_list("role", flat="True")) + if ( + enums.MailDomainRoleChoices.OWNER in management_role + or enums.MailDomainRoleChoices.ADMIN in management_role + ): + # Sync with dimail + dimail = DimailAPIClient() + dimail.create_user(instance.sub) + + for invitation in valid_domain_invitations: + if invitation.role in [ + enums.MailDomainRoleChoices.OWNER, + enums.MailDomainRoleChoices.ADMIN, + ]: + dimail.create_allow(instance.sub, invitation.domain.name) + + valid_domain_invitations.delete() + logger.info("Invitations converted to domain accesses for user %s", instance)