comparison sat/plugins/plugin_xep_0384.py @ 2662:0bef44f8e8ca

plugin XEP-0384: PEP handling + mark as encrypted: devices are gotten using PEP and +notify, and bundles are then retrieved, and kept in cache. This allow to remove the test code which was always requesting devices and bundles before sending a message. For now OMEMO plugin will only work with people in roster, this will be fixed in the future.
author Goffi <goffi@goffi.org>
date Sat, 11 Aug 2018 18:24:55 +0200
parents e7bfbded652a
children eb58f26ed236
comparison
equal deleted inserted replaced
2661:661f66d41215 2662:0bef44f8e8ca
45 PLUGIN_INFO = { 45 PLUGIN_INFO = {
46 C.PI_NAME: u"OMEMO", 46 C.PI_NAME: u"OMEMO",
47 C.PI_IMPORT_NAME: u"OMEMO", 47 C.PI_IMPORT_NAME: u"OMEMO",
48 C.PI_TYPE: u"SEC", 48 C.PI_TYPE: u"SEC",
49 C.PI_PROTOCOLS: [u"XEP-0384"], 49 C.PI_PROTOCOLS: [u"XEP-0384"],
50 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334", u"XEP-0060"], 50 C.PI_DEPENDENCIES: [u"XEP-0163", u"XEP-0280", u"XEP-0334", u"XEP-0060"],
51 C.PI_MAIN: u"OMEMO", 51 C.PI_MAIN: u"OMEMO",
52 C.PI_HANDLER: u"no", 52 C.PI_HANDLER: u"no",
53 C.PI_DESCRIPTION: _(u"""Implementation of OMEMO"""), 53 C.PI_DESCRIPTION: _(u"""Implementation of OMEMO"""),
54 } 54 }
55 55
264 self._p_carbons = host.plugins[u"XEP-0280"] 264 self._p_carbons = host.plugins[u"XEP-0280"]
265 self._p = host.plugins[u"XEP-0060"] 265 self._p = host.plugins[u"XEP-0060"]
266 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) 266 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050)
267 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) 267 host.trigger.add("sendMessageData", self._sendMessageDataTrigger)
268 self.host.registerEncryptionPlugin(self, u"OMEMO", NS_OMEMO, 100) 268 self.host.registerEncryptionPlugin(self, u"OMEMO", NS_OMEMO, 100)
269 pep = host.plugins['XEP-0163']
270 pep.addPEPEvent("OMEMO_DEVICES", NS_OMEMO_DEVICES, self.onNewDevices)
269 271
270 @defer.inlineCallbacks 272 @defer.inlineCallbacks
271 def profileConnected(self, client): 273 def profileConnected(self, client):
272 # we first need to get devices ids (including our own) 274 # we first need to get devices ids (including our own)
273 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) 275 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile)
288 devices.add(device_id) 290 devices.add(device_id)
289 yield self.setDevices(client, devices) 291 yield self.setDevices(client, devices)
290 292
291 omemo_storage = OmemoStorage(client, device_id, persistent_dict) 293 omemo_storage = OmemoStorage(client, device_id, persistent_dict)
292 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) 294 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id)
295 client._xep_0384_cache = {}
293 client._xep_0384_session = omemo_session 296 client._xep_0384_session = omemo_session
294 client._xep_0384_device_id = device_id 297 client._xep_0384_device_id = device_id
295 yield omemo_session.newDeviceList(devices, client.jid) 298 yield omemo_session.newDeviceList(devices, client.jid)
296 if omemo_session.state.changed: 299 if omemo_session.state.changed:
297 log.info(_(u"Saving public bundle for this device ({device_id})").format( 300 log.info(_(u"Saving public bundle for this device ({device_id})").format(
301 304
302 ## XMPP PEP nodes manipulation 305 ## XMPP PEP nodes manipulation
303 306
304 # devices 307 # devices
305 308
306 @defer.inlineCallbacks 309 def parseDevices(self, items):
307 def getDevices(self, client, entity_jid=None): 310 """Parse devices found in items
308 """Retrieve list of registered OMEMO devices 311
309 312 @param items(iterable[domish.Element]): items as retrieved by getItems
310 @param entity_jid(jid.JID, None): get devices from this entity 313 @return set[int]: parsed devices
311 None to get our own devices 314 """
312 @return (set(int)): list of devices
313 """
314 if entity_jid is not None:
315 assert not entity_jid.resource
316 devices = set() 315 devices = set()
317 try:
318 items, metadata = yield self._p.getItems(client, entity_jid, NS_OMEMO_DEVICES)
319 except error.StanzaError as e:
320 if e.condition == 'item-not-found':
321 log.info(_(u"there is no node to handle OMEMO devices"))
322 defer.returnValue(devices)
323
324 if len(items) > 1: 316 if len(items) > 1:
325 log.warning(_(u"OMEMO devices list is stored in more that one items, " 317 log.warning(_(u"OMEMO devices list is stored in more that one items, "
326 u"this is not expected")) 318 u"this is not expected"))
327 if items: 319 if items:
328 try: 320 try:
339 except ValueError: 331 except ValueError:
340 log.warning(_(u'invalid device id: {device_id}').format( 332 log.warning(_(u'invalid device id: {device_id}').format(
341 device_id=device_elt['id'])) 333 device_id=device_elt['id']))
342 else: 334 else:
343 devices.add(device_id) 335 devices.add(device_id)
336 return devices
337
338 @defer.inlineCallbacks
339 def getDevices(self, client, entity_jid=None):
340 """Retrieve list of registered OMEMO devices
341
342 @param entity_jid(jid.JID, None): get devices from this entity
343 None to get our own devices
344 @return (set(int)): list of devices
345 """
346 if entity_jid is not None:
347 assert not entity_jid.resource
348 try:
349 items, metadata = yield self._p.getItems(client, entity_jid, NS_OMEMO_DEVICES)
350 except error.StanzaError as e:
351 if e.condition == 'item-not-found':
352 log.info(_(u"there is no node to handle OMEMO devices"))
353 defer.returnValue(set())
354
355 devices = self.parseDevices(items)
344 defer.returnValue(devices) 356 defer.returnValue(devices)
345 357
346 def setDevicesEb(self, failure_): 358 def setDevicesEb(self, failure_):
347 log.warning(_(u"Can't set devices: {reason}").format(reason=failure_)) 359 log.warning(_(u"Can't set devices: {reason}").format(reason=failure_))
348 360
464 node = NS_OMEMO_BUNDLE.format(device_id=device_id) 476 node = NS_OMEMO_BUNDLE.format(device_id=device_id)
465 d = self._p.sendItem(client, None, node, bundle_elt, item_id=self._p.ID_SINGLETON) 477 d = self._p.sendItem(client, None, node, bundle_elt, item_id=self._p.ID_SINGLETON)
466 d.addErrback(self.setBundleEb) 478 d.addErrback(self.setBundleEb)
467 return d 479 return d
468 480
481 ## PEP node events callbacks
482
483 @defer.inlineCallbacks
484 def onNewDevices(self, itemsEvent, profile):
485 client = self.host.getClient(profile)
486 cache = client._xep_0384_cache
487 omemo_session = client._xep_0384_session
488 entity = itemsEvent.sender
489 entity_cache = cache.setdefault(entity, {})
490 devices = self.parseDevices(itemsEvent.items)
491 omemo_session.newDeviceList(devices, entity)
492 missing_devices = devices.difference(entity_cache.keys())
493 if missing_devices:
494 missing_bundles = yield self.getBundles(client, entity, missing_devices)
495 entity_cache.update(missing_bundles)
496
469 ## triggers 497 ## triggers
470 498
471 @defer.inlineCallbacks 499 @defer.inlineCallbacks
472 def encryptMessage(self, client, entity_bare_jid, message): 500 def encryptMessage(self, client, entity_bare_jid, message):
473 omemo_session = client._xep_0384_session 501 omemo_session = client._xep_0384_session
474 devices = yield self.getDevices(client, entity_bare_jid) 502 try:
475 omemo_session.newDeviceList(devices, entity_bare_jid) 503 bundles = client._xep_0384_cache[entity_bare_jid]
476 bundles = yield self.getBundles(client, entity_bare_jid, devices) 504 except KeyError:
505 raise exceptions.NotFound(_(u"No OMEMO encryption information found for this"
506 u"contact ({entity})".format(
507 entity=entity_bare_jid.full())))
477 encrypted = yield omemo_session.encryptMessage( 508 encrypted = yield omemo_session.encryptMessage(
478 entity_bare_jid, 509 entity_bare_jid,
479 message, 510 message,
480 {entity_bare_jid: bundles}) 511 {entity_bare_jid: bundles})
481 defer.returnValue(encrypted) 512 defer.returnValue(encrypted)
545 self.setBundle(client, bundle, device_id) 576 self.setBundle(client, bundle, device_id)
546 577
547 message_elt.children.remove(encrypted_elt) 578 message_elt.children.remove(encrypted_elt)
548 if plaintext: 579 if plaintext:
549 message_elt.addElement("body", content=plaintext.decode('utf-8')) 580 message_elt.addElement("body", content=plaintext.decode('utf-8'))
581 post_treat.addCallback(client.encryption.markAsEncrypted)
550 defer.returnValue(True) 582 defer.returnValue(True)
551 583
552 @defer.inlineCallbacks 584 @defer.inlineCallbacks
553 def _sendMessageDataTrigger(self, client, mess_data): 585 def _sendMessageDataTrigger(self, client, mess_data):
554 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) 586 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION)