Mercurial > libervia-backend
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