diff libervia/backend/plugins/plugin_misc_email_invitation.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents 4b842c1fb686
children
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_misc_email_invitation.py	Tue Jun 18 12:06:45 2024 +0200
+++ b/libervia/backend/plugins/plugin_misc_email_invitation.py	Wed Jun 19 18:44:57 2024 +0200
@@ -38,27 +38,37 @@
     C.PI_NAME: "Email Invitations",
     C.PI_IMPORT_NAME: "EMAIL_INVITATION",
     C.PI_TYPE: C.PLUG_TYPE_MISC,
-    C.PI_DEPENDENCIES: ['XEP-0077'],
+    C.PI_DEPENDENCIES: ["XEP-0077"],
     C.PI_RECOMMENDATIONS: ["IDENTITY"],
     C.PI_MAIN: "InvitationsPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""invitation of people without XMPP account""")
+    C.PI_DESCRIPTION: _("""invitation of people without XMPP account"""),
 }
 
 
 SUFFIX_MAX = 5
 INVITEE_PROFILE_TPL = "guest@@{uuid}"
-KEY_ID = 'id'
-KEY_JID = 'jid'
-KEY_CREATED = 'created'
-KEY_LAST_CONNECTION = 'last_connection'
-KEY_GUEST_PROFILE = 'guest_profile'
-KEY_PASSWORD = 'password'
-KEY_EMAILS_EXTRA = 'emails_extra'
-EXTRA_RESERVED = {KEY_ID, KEY_JID, KEY_CREATED, 'jid_', 'jid', KEY_LAST_CONNECTION,
-                  KEY_GUEST_PROFILE, KEY_PASSWORD, KEY_EMAILS_EXTRA}
+KEY_ID = "id"
+KEY_JID = "jid"
+KEY_CREATED = "created"
+KEY_LAST_CONNECTION = "last_connection"
+KEY_GUEST_PROFILE = "guest_profile"
+KEY_PASSWORD = "password"
+KEY_EMAILS_EXTRA = "emails_extra"
+EXTRA_RESERVED = {
+    KEY_ID,
+    KEY_JID,
+    KEY_CREATED,
+    "jid_",
+    "jid",
+    KEY_LAST_CONNECTION,
+    KEY_GUEST_PROFILE,
+    KEY_PASSWORD,
+    KEY_EMAILS_EXTRA,
+}
 DEFAULT_SUBJECT = D_("You have been invited by {host_name} to {app_name}")
-DEFAULT_BODY = D_("""Hello {name}!
+DEFAULT_BODY = D_(
+    """Hello {name}!
 
 You have received an invitation from {host_name} to participate to "{app_name}".
 To join, you just have to click on the following URL:
@@ -68,7 +78,8 @@
 If you want more details on {app_name}, you can check {app_url}.
 
 Welcome!
-""")
+"""
+)
 
 
 class InvitationsPlugin(object):
@@ -76,39 +87,79 @@
     def __init__(self, host):
         log.info(_("plugin Invitations initialization"))
         self.host = host
-        self.invitations = persistent.LazyPersistentBinaryDict('invitations')
-        host.bridge.add_method("invitation_create", ".plugin", in_sign='sasssssssssa{ss}s',
-                              out_sign='a{ss}',
-                              method=self._create,
-                              async_=True)
-        host.bridge.add_method("invitation_get", ".plugin", in_sign='s', out_sign='a{ss}',
-                              method=self.get,
-                              async_=True)
-        host.bridge.add_method("invitation_delete", ".plugin", in_sign='s', out_sign='',
-                              method=self._delete,
-                              async_=True)
-        host.bridge.add_method("invitation_modify", ".plugin", in_sign='sa{ss}b',
-                              out_sign='',
-                              method=self._modify,
-                              async_=True)
-        host.bridge.add_method("invitation_list", ".plugin", in_sign='s',
-                              out_sign='a{sa{ss}}',
-                              method=self._list,
-                              async_=True)
-        host.bridge.add_method("invitation_simple_create", ".plugin", in_sign='sssss',
-                              out_sign='a{ss}',
-                              method=self._simple_create,
-                              async_=True)
+        self.invitations = persistent.LazyPersistentBinaryDict("invitations")
+        host.bridge.add_method(
+            "invitation_create",
+            ".plugin",
+            in_sign="sasssssssssa{ss}s",
+            out_sign="a{ss}",
+            method=self._create,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "invitation_get",
+            ".plugin",
+            in_sign="s",
+            out_sign="a{ss}",
+            method=self.get,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "invitation_delete",
+            ".plugin",
+            in_sign="s",
+            out_sign="",
+            method=self._delete,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "invitation_modify",
+            ".plugin",
+            in_sign="sa{ss}b",
+            out_sign="",
+            method=self._modify,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "invitation_list",
+            ".plugin",
+            in_sign="s",
+            out_sign="a{sa{ss}}",
+            method=self._list,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "invitation_simple_create",
+            ".plugin",
+            in_sign="sssss",
+            out_sign="a{ss}",
+            method=self._simple_create,
+            async_=True,
+        )
 
     def check_extra(self, extra):
         if EXTRA_RESERVED.intersection(extra):
             raise ValueError(
-                _("You can't use following key(s) in extra, they are reserved: {}")
-                .format(', '.join(EXTRA_RESERVED.intersection(extra))))
+                _(
+                    "You can't use following key(s) in extra, they are reserved: {}"
+                ).format(", ".join(EXTRA_RESERVED.intersection(extra)))
+            )
 
-    def _create(self, email='', emails_extra=None, jid_='', password='', name='',
-                host_name='', language='', url_template='', message_subject='',
-                message_body='', extra=None, profile=''):
+    def _create(
+        self,
+        email="",
+        emails_extra=None,
+        jid_="",
+        password="",
+        name="",
+        host_name="",
+        language="",
+        url_template="",
+        message_subject="",
+        message_body="",
+        extra=None,
+        profile="",
+    ):
         # XXX: we don't use **kwargs here to keep arguments name for introspection with
         #      D-Bus bridge
         if emails_extra is None:
@@ -117,16 +168,24 @@
         if extra is None:
             extra = {}
         else:
-            extra = {str(k): str(v) for k,v in extra.items()}
+            extra = {str(k): str(v) for k, v in extra.items()}
 
-        kwargs = {"extra": extra,
-                  KEY_EMAILS_EXTRA: [str(e) for e in emails_extra]
-                  }
+        kwargs = {"extra": extra, KEY_EMAILS_EXTRA: [str(e) for e in emails_extra]}
 
         # we need to be sure that values are unicode, else they won't be pickled correctly
         # with D-Bus
-        for key in ("jid_", "password", "name", "host_name", "email", "language",
-                    "url_template", "message_subject", "message_body", "profile"):
+        for key in (
+            "jid_",
+            "password",
+            "name",
+            "host_name",
+            "email",
+            "language",
+            "url_template",
+            "message_subject",
+            "message_body",
+            "profile",
+        ):
             value = locals()[key]
             if value:
                 kwargs[key] = str(value)
@@ -152,16 +211,13 @@
                 return invitation
 
     async def _create_account_and_profile(
-        self,
-        id_: str,
-        kwargs: dict,
-        extra: dict
+        self, id_: str, kwargs: dict, extra: dict
     ) -> None:
         """Create XMPP account and Libervia profile for guest"""
         ## XMPP account creation
-        password = kwargs.pop('password', None)
+        password = kwargs.pop("password", None)
         if password is None:
-           password = utils.generate_password()
+            password = utils.generate_password()
         assert password
         # XXX: password is here saved in clear in database
         #      it is needed for invitation as the same password is used for profile
@@ -171,14 +227,15 @@
         #        not be saved and could be used to encrypt profile password.
         extra[KEY_PASSWORD] = password
 
-        jid_ = kwargs.pop('jid_', None)
+        jid_ = kwargs.pop("jid_", None)
         if not jid_:
-            domain = self.host.memory.config_get(None, 'xmpp_domain')
+            domain = self.host.memory.config_get(None, "xmpp_domain")
             if not domain:
                 # TODO: fallback to profile's domain
                 raise ValueError(_("You need to specify xmpp_domain in sat.conf"))
-            jid_ = "invitation-{uuid}@{domain}".format(uuid=shortuuid.uuid(),
-                                                        domain=domain)
+            jid_ = "invitation-{uuid}@{domain}".format(
+                uuid=shortuuid.uuid(), domain=domain
+            )
         jid_ = jid.JID(jid_)
         extra[KEY_JID] = jid_.full()
 
@@ -186,42 +243,46 @@
             # we don't register account if there is no user as anonymous login is then
             # used
             try:
-                await self.host.plugins['XEP-0077'].register_new_account(jid_, password)
+                await self.host.plugins["XEP-0077"].register_new_account(jid_, password)
             except error.StanzaError as e:
                 prefix = jid_.user
                 idx = 0
-                while e.condition == 'conflict':
+                while e.condition == "conflict":
                     if idx >= SUFFIX_MAX:
                         raise exceptions.ConflictError(_("Can't create XMPP account"))
-                    jid_.user = prefix + '_' + str(idx)
-                    log.info(_("requested jid already exists, trying with {}".format(
-                        jid_.full())))
+                    jid_.user = prefix + "_" + str(idx)
+                    log.info(
+                        _(
+                            "requested jid already exists, trying with {}".format(
+                                jid_.full()
+                            )
+                        )
+                    )
                     try:
-                        await self.host.plugins['XEP-0077'].register_new_account(
-                            jid_,
-                            password
+                        await self.host.plugins["XEP-0077"].register_new_account(
+                            jid_, password
                         )
                     except error.StanzaError:
                         idx += 1
                     else:
                         break
-                if e.condition != 'conflict':
+                if e.condition != "conflict":
                     raise e
 
             log.info(_("account {jid_} created").format(jid_=jid_.full()))
 
         ## profile creation
 
-        extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(
-            uuid=id_
-        )
+        extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(uuid=id_)
         # profile creation should not fail as we generate unique name ourselves
         await self.host.memory.create_profile(guest_profile, password)
         await self.host.memory.start_session(password, guest_profile)
-        await self.host.memory.param_set("JabberID", jid_.full(), "Connection",
-                                        profile_key=guest_profile)
-        await self.host.memory.param_set("Password", password, "Connection",
-                                        profile_key=guest_profile)
+        await self.host.memory.param_set(
+            "JabberID", jid_.full(), "Connection", profile_key=guest_profile
+        )
+        await self.host.memory.param_set(
+            "Password", password, "Connection", profile_key=guest_profile
+        )
 
     async def create(self, **kwargs):
         r"""Create an invitation
@@ -284,15 +345,17 @@
             - filled extra dictionary, as saved in the databae
         """
         ## initial checks
-        extra = kwargs.pop('extra', {})
+        extra = kwargs.pop("extra", {})
         if set(kwargs).intersection(extra):
             raise ValueError(
                 _("You can't use following key(s) in both args and extra: {}").format(
-                ', '.join(set(kwargs).intersection(extra))))
+                    ", ".join(set(kwargs).intersection(extra))
+                )
+            )
 
         self.check_extra(extra)
 
-        email = kwargs.pop('email', None)
+        email = kwargs.pop("email", None)
 
         existing = await self.get_existing_invitation(email)
         if existing is not None:
@@ -300,16 +363,20 @@
             extra.update(existing)
             del extra[KEY_ID]
 
-        emails_extra = kwargs.pop('emails_extra', [])
+        emails_extra = kwargs.pop("emails_extra", [])
         if not email and emails_extra:
             raise ValueError(
-                _('You need to provide a main email address before using emails_extra'))
+                _("You need to provide a main email address before using emails_extra")
+            )
 
-        if (email is not None
-            and not 'url_template' in kwargs
-            and not 'message_body' in kwargs):
+        if (
+            email is not None
+            and not "url_template" in kwargs
+            and not "message_body" in kwargs
+        ):
             raise ValueError(
-                _("You need to provide url_template if you use default message body"))
+                _("You need to provide url_template if you use default message body")
+            )
 
         ## uuid
         log.info(_("creating an invitation"))
@@ -318,64 +385,62 @@
         if existing is None:
             await self._create_account_and_profile(id_, kwargs, extra)
 
-        profile = kwargs.pop('profile', None)
+        profile = kwargs.pop("profile", None)
         guest_profile = extra[KEY_GUEST_PROFILE]
         jid_ = jid.JID(extra[KEY_JID])
 
         ## identity
-        name = kwargs.pop('name', None)
+        name = kwargs.pop("name", None)
         password = extra[KEY_PASSWORD]
         if name is not None:
-            extra['name'] = name
+            extra["name"] = name
             try:
-                id_plugin = self.host.plugins['IDENTITY']
+                id_plugin = self.host.plugins["IDENTITY"]
             except KeyError:
                 pass
             else:
                 await self.host.connect(guest_profile, password)
                 guest_client = self.host.get_client(guest_profile)
-                await id_plugin.set_identity(guest_client, {'nicknames': [name]})
+                await id_plugin.set_identity(guest_client, {"nicknames": [name]})
                 await self.host.disconnect(guest_profile)
 
         ## email
-        language = kwargs.pop('language', None)
+        language = kwargs.pop("language", None)
         if language is not None:
-            extra['language'] = language.strip()
+            extra["language"] = language.strip()
 
         if email is not None:
-            extra['email'] = email
+            extra["email"] = email
             data_format.iter2dict(KEY_EMAILS_EXTRA, extra)
-            url_template = kwargs.pop('url_template', '')
-            format_args = {
-                'uuid': id_,
-                'app_name': C.APP_NAME,
-                'app_url': C.APP_URL}
+            url_template = kwargs.pop("url_template", "")
+            format_args = {"uuid": id_, "app_name": C.APP_NAME, "app_url": C.APP_URL}
 
             if name is None:
-                format_args['name'] = email
+                format_args["name"] = email
             else:
-                format_args['name'] = name
+                format_args["name"] = name
 
             if profile is None:
-                format_args['profile'] = ''
+                format_args["profile"] = ""
             else:
-                format_args['profile'] = extra['profile'] = profile
+                format_args["profile"] = extra["profile"] = profile
 
-            host_name = kwargs.pop('host_name', None)
+            host_name = kwargs.pop("host_name", None)
             if host_name is None:
-                format_args['host_name'] = profile or _("somebody")
+                format_args["host_name"] = profile or _("somebody")
             else:
-                format_args['host_name'] = extra['host_name'] = host_name
+                format_args["host_name"] = extra["host_name"] = host_name
 
             invite_url = url_template.format(**format_args)
-            format_args['url'] = invite_url
+            format_args["url"] = invite_url
 
             await sat_email.send_email(
                 self.host.memory.config,
                 [email] + emails_extra,
-                (kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format(
-                    **format_args),
-                (kwargs.pop('message_body', None) or DEFAULT_BODY).format(**format_args),
+                (kwargs.pop("message_subject", None) or DEFAULT_SUBJECT).format(
+                    **format_args
+                ),
+                (kwargs.pop("message_body", None) or DEFAULT_BODY).format(**format_args),
             )
 
         ## roster
@@ -388,7 +453,7 @@
             except Exception as e:
                 log.error(f"Can't get host profile: {profile}: {e}")
             else:
-                await self.host.contact_update(client, jid_, name, ['guests'])
+                await self.host.contact_update(client, jid_, name, ["guests"])
 
         if kwargs:
             log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
@@ -410,11 +475,12 @@
         d = defer.ensureDeferred(
             self.simple_create(client, invitee_email, invitee_name, url_template, extra)
         )
-        d.addCallback(lambda data: {k: str(v) for k,v in data.items()})
+        d.addCallback(lambda data: {k: str(v) for k, v in data.items()})
         return d
 
     async def simple_create(
-        self, client, invitee_email, invitee_name, url_template, extra):
+        self, client, invitee_email, invitee_name, url_template, extra
+    ):
         """Simplified method to invite somebody by email"""
         return await self.create(
             name=invitee_name,
@@ -439,19 +505,20 @@
         """Delete an invitation data and associated XMPP account"""
         log.info(f"deleting invitation {id_}")
         data = await self.get(id_)
-        guest_profile = data['guest_profile']
-        password = data['password']
+        guest_profile = data["guest_profile"]
+        password = data["password"]
         try:
             await self.host.connect(guest_profile, password)
             guest_client = self.host.get_client(guest_profile)
             # XXX: be extra careful to use guest_client and not client below, as this will
             #   delete the associated XMPP account
             log.debug("deleting XMPP account")
-            await self.host.plugins['XEP-0077'].unregister(guest_client, None)
+            await self.host.plugins["XEP-0077"].unregister(guest_client, None)
         except (error.StanzaError, sasl.SASLAuthError) as e:
             log.warning(
                 f"Can't delete {guest_profile}'s XMPP account, maybe it as already been "
-                f"deleted: {e}")
+                f"deleted: {e}"
+            )
         try:
             await self.host.memory.profile_delete_async(guest_profile, True)
         except Exception as e:
@@ -461,8 +528,7 @@
         log.info(f"{id_} invitation has been deleted")
 
     def _modify(self, id_, new_extra, replace):
-        return self.modify(id_, {str(k): str(v) for k,v in new_extra.items()},
-                           replace)
+        return self.modify(id_, {str(k): str(v) for k, v in new_extra.items()}, replace)
 
     def modify(self, id_, new_extra, replace=False):
         """Modify invitation data
@@ -475,6 +541,7 @@
         @raise KeyError: there is not invitation with this id_
         """
         self.check_extra(new_extra)
+
         def got_current_data(current_data):
             if replace:
                 new_data = new_extra
@@ -485,7 +552,7 @@
                         continue
             else:
                 new_data = current_data
-                for k,v in new_extra.items():
+                for k, v in new_extra.items():
                     if k in EXTRA_RESERVED:
                         log.warning(_("Skipping reserved key {key}").format(key=k))
                         continue
@@ -515,7 +582,10 @@
         """
         invitations = await self.invitations.all()
         if profile != C.PROF_KEY_NONE:
-            invitations = {id_:data for id_, data in invitations.items()
-                           if data.get('profile') == profile}
+            invitations = {
+                id_: data
+                for id_, data in invitations.items()
+                if data.get("profile") == profile
+            }
 
         return invitations