comparison sat/plugins/plugin_misc_email_invitation.py @ 3335:83bc9d46a417

plugin email invitation: fixed create/simpleCreate + invitee_name: - fixed invitationSimpleCreate signature - fixed nicknames settings in create - use async coroutines - guest jid is now stored (as a string), avoiding the need to check profile to retrieve it - invitee_name is now needed in invitationSimpleCreate
author Goffi <goffi@goffi.org>
date Thu, 13 Aug 2020 23:46:18 +0200
parents 44a9438d6608
children e38ddaf477bd
comparison
equal deleted inserted replaced
3334:2cd54c72fae4 3335:83bc9d46a417
31 31
32 log = getLogger(__name__) 32 log = getLogger(__name__)
33 33
34 34
35 PLUGIN_INFO = { 35 PLUGIN_INFO = {
36 C.PI_NAME: "Invitations", 36 C.PI_NAME: "Email Invitations",
37 C.PI_IMPORT_NAME: "EMAIL_INVITATION", 37 C.PI_IMPORT_NAME: "EMAIL_INVITATION",
38 C.PI_TYPE: C.PLUG_TYPE_MISC, 38 C.PI_TYPE: C.PLUG_TYPE_MISC,
39 C.PI_DEPENDENCIES: ['XEP-0077'], 39 C.PI_DEPENDENCIES: ['XEP-0077'],
40 C.PI_RECOMMENDATIONS: ["IDENTITY"], 40 C.PI_RECOMMENDATIONS: ["IDENTITY"],
41 C.PI_MAIN: "InvitationsPlugin", 41 C.PI_MAIN: "InvitationsPlugin",
88 async_=True) 88 async_=True)
89 host.bridge.addMethod("invitationList", ".plugin", in_sign='s', 89 host.bridge.addMethod("invitationList", ".plugin", in_sign='s',
90 out_sign='a{sa{ss}}', 90 out_sign='a{sa{ss}}',
91 method=self._list, 91 method=self._list,
92 async_=True) 92 async_=True)
93 host.bridge.addMethod("invitationSimpleCreate", ".plugin", in_sign='', 93 host.bridge.addMethod("invitationSimpleCreate", ".plugin", in_sign='sssss',
94 out_sign='a{ss}', 94 out_sign='a{ss}',
95 method=self._simpleCreate, 95 method=self._simpleCreate,
96 async_=True) 96 async_=True)
97 97
98 def checkExtra(self, extra): 98 def checkExtra(self, extra):
123 for key in ("jid_", "password", "name", "host_name", "email", "language", 123 for key in ("jid_", "password", "name", "host_name", "email", "language",
124 "url_template", "message_subject", "message_body", "profile"): 124 "url_template", "message_subject", "message_body", "profile"):
125 value = locals()[key] 125 value = locals()[key]
126 if value: 126 if value:
127 kwargs[key] = str(value) 127 kwargs[key] = str(value)
128 d = self.create(**kwargs) 128 return defer.ensureDeferred(self.create(**kwargs))
129 def serialize(data): 129
130 data[KEY_JID] = data[KEY_JID].full() 130 async def create(self, **kwargs):
131 return data
132 d.addCallback(serialize)
133 return d
134
135 @defer.inlineCallbacks
136 def create(self, **kwargs):
137 r"""Create an invitation 131 r"""Create an invitation
138 132
139 This will create an XMPP account and a profile, and use a UUID to retrieve them. 133 This will create an XMPP account and a profile, and use a UUID to retrieve them.
140 The profile is automatically generated in the form guest@@[UUID], this way they 134 The profile is automatically generated in the form guest@@[UUID], this way they
141 can be retrieved easily 135 can be retrieved easily
242 jid_ = jid.JID(jid_) 236 jid_ = jid.JID(jid_)
243 if jid_.user: 237 if jid_.user:
244 # we don't register account if there is no user as anonymous login is then 238 # we don't register account if there is no user as anonymous login is then
245 # used 239 # used
246 try: 240 try:
247 yield self.host.plugins['XEP-0077'].registerNewAccount(jid_, password) 241 await self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
248 except error.StanzaError as e: 242 except error.StanzaError as e:
249 prefix = jid_.user 243 prefix = jid_.user
250 idx = 0 244 idx = 0
251 while e.condition == 'conflict': 245 while e.condition == 'conflict':
252 if idx >= SUFFIX_MAX: 246 if idx >= SUFFIX_MAX:
253 raise exceptions.ConflictError(_("Can't create XMPP account")) 247 raise exceptions.ConflictError(_("Can't create XMPP account"))
254 jid_.user = prefix + '_' + str(idx) 248 jid_.user = prefix + '_' + str(idx)
255 log.info(_("requested jid already exists, trying with {}".format( 249 log.info(_("requested jid already exists, trying with {}".format(
256 jid_.full()))) 250 jid_.full())))
257 try: 251 try:
258 yield self.host.plugins['XEP-0077'].registerNewAccount(jid_, 252 await self.host.plugins['XEP-0077'].registerNewAccount(jid_,
259 password) 253 password)
260 except error.StanzaError: 254 except error.StanzaError:
261 idx += 1 255 idx += 1
262 else: 256 else:
263 break 257 break
268 262
269 ## profile creation 263 ## profile creation
270 264
271 extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(uuid=id_) 265 extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(uuid=id_)
272 # profile creation should not fail as we generate unique name ourselves 266 # profile creation should not fail as we generate unique name ourselves
273 yield self.host.memory.createProfile(guest_profile, password) 267 await self.host.memory.createProfile(guest_profile, password)
274 yield self.host.memory.startSession(password, guest_profile) 268 await self.host.memory.startSession(password, guest_profile)
275 yield self.host.memory.setParam("JabberID", jid_.full(), "Connection", 269 await self.host.memory.setParam("JabberID", jid_.full(), "Connection",
276 profile_key=guest_profile) 270 profile_key=guest_profile)
277 yield self.host.memory.setParam("Password", password, "Connection", 271 await self.host.memory.setParam("Password", password, "Connection",
278 profile_key=guest_profile) 272 profile_key=guest_profile)
279 name = kwargs.pop('name', None) 273 name = kwargs.pop('name', None)
280 if name is not None: 274 if name is not None:
281 extra['name'] = name 275 extra['name'] = name
282 try: 276 try:
283 id_plugin = self.host.plugins['IDENTITY'] 277 id_plugin = self.host.plugins['IDENTITY']
284 except KeyError: 278 except KeyError:
285 pass 279 pass
286 else: 280 else:
287 yield defer.ensureDeferred(self.host.connect(guest_profile, password)) 281 await self.host.connect(guest_profile, password)
288 guest_client = self.host.getClient(guest_profile) 282 guest_client = self.host.getClient(guest_profile)
289 yield id_plugin.setIdentity(guest_client, {'nick': name}) 283 await id_plugin.setIdentity(guest_client, {'nicknames': [name]})
290 yield self.host.disconnect(guest_profile) 284 await self.host.disconnect(guest_profile)
291 285
292 ## email 286 ## email
293 language = kwargs.pop('language', None) 287 language = kwargs.pop('language', None)
294 if language is not None: 288 if language is not None:
295 extra['language'] = language.strip() 289 extra['language'] = language.strip()
321 format_args['host_name'] = extra['host_name'] = host_name 315 format_args['host_name'] = extra['host_name'] = host_name
322 316
323 invite_url = url_template.format(**format_args) 317 invite_url = url_template.format(**format_args)
324 format_args['url'] = invite_url 318 format_args['url'] = invite_url
325 319
326 yield sat_email.sendEmail( 320 await sat_email.sendEmail(
327 self.host.memory.config, 321 self.host.memory.config,
328 [email] + emails_extra, 322 [email] + emails_extra,
329 (kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format( 323 (kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format(
330 **format_args), 324 **format_args),
331 (kwargs.pop('message_body', None) or DEFAULT_BODY).format(**format_args), 325 (kwargs.pop('message_body', None) or DEFAULT_BODY).format(**format_args),
332 ) 326 )
333 327
328
329 if kwargs:
330 log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
331
332 extra[KEY_JID] = jid_.full()
333
334 ## extra data saving 334 ## extra data saving
335 self.invitations[id_] = extra 335 self.invitations[id_] = extra
336 336
337 if kwargs:
338 log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
339
340 extra[KEY_ID] = id_ 337 extra[KEY_ID] = id_
341 extra[KEY_JID] = jid_ 338
342 defer.returnValue(extra) 339 return extra
343 340
344 def _simpleCreate(self, invitee_email, url_template, extra_s, profile): 341 def _simpleCreate(self, invitee_email, invitee_name, url_template, extra_s, profile):
345 client = self.host.getClient(profile) 342 client = self.host.getClient(profile)
346 # FIXME: needed because python-dbus use a specific string class 343 # FIXME: needed because python-dbus use a specific string class
347 invitee_email = str(invitee_email) 344 invitee_email = str(invitee_email)
345 invitee_name = str(invitee_name)
348 url_template = str(url_template) 346 url_template = str(url_template)
349 extra = data_format.deserialise(extra_s) 347 extra = data_format.deserialise(extra_s)
350 d = defer.ensureDeferred( 348 d = defer.ensureDeferred(
351 self.simpleCreate(client, invitee_email, url_template, extra) 349 self.simpleCreate(client, invitee_email, invitee_name, url_template, extra)
352 ) 350 )
353 d.addCallback(lambda data: {k: str(v) for k,v in data.items()}) 351 d.addCallback(lambda data: {k: str(v) for k,v in data.items()})
354 return d 352 return d
355 353
356 async def simpleCreate(self, client, invitee_email, url_template, extra): 354 async def simpleCreate(
355 self, client, invitee_email, invitee_name, url_template, extra):
357 """Simplified method to invite somebody by email""" 356 """Simplified method to invite somebody by email"""
358 return await self.create( 357 return await self.create(
358 name=invitee_name,
359 email=invitee_email, 359 email=invitee_email,
360 url_template=url_template, 360 url_template=url_template,
361 profile=client.profile, 361 profile=client.profile,
362 ) 362 )
363 363
412 d = self.invitations[id_] 412 d = self.invitations[id_]
413 d.addCallback(gotCurrentData) 413 d.addCallback(gotCurrentData)
414 return d 414 return d
415 415
416 def _list(self, profile=C.PROF_KEY_NONE): 416 def _list(self, profile=C.PROF_KEY_NONE):
417 return self.list(profile) 417 return defer.ensureDeferred(self.list(profile))
418 418
419 @defer.inlineCallbacks 419 async def list(self, profile=C.PROF_KEY_NONE):
420 def list(self, profile=C.PROF_KEY_NONE):
421 """List invitations 420 """List invitations
422 421
423 @param profile(unicode): return invitation linked to this profile only 422 @param profile(unicode): return invitation linked to this profile only
424 C.PROF_KEY_NONE: don't filter invitations 423 C.PROF_KEY_NONE: don't filter invitations
425 @return list(unicode): invitations uids 424 @return list(unicode): invitations uids
426 """ 425 """
427 invitations = yield self.invitations.all() 426 invitations = await self.invitations.all()
428 if profile != C.PROF_KEY_NONE: 427 if profile != C.PROF_KEY_NONE:
429 invitations = {id_:data for id_, data in invitations.items() 428 invitations = {id_:data for id_, data in invitations.items()
430 if data.get('profile') == profile} 429 if data.get('profile') == profile}
431 430
432 defer.returnValue(invitations) 431 return invitations