comparison sat/plugins/plugin_xep_0166.py @ 3969:8e7d5796fb23

plugin XEP-0391: implement XEP-0391 (Jingle Encrypted Transports) + XEP-0396 (JET-OMEMO): rel 378
author Goffi <goffi@goffi.org>
date Mon, 31 Oct 2022 04:09:34 +0100
parents bbf92ef05f38
children 524856bd7b19
comparison
equal deleted inserted replaced
3968:0dd79c6cc1d2 3969:8e7d5796fb23
15 15
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 19
20 from collections import namedtuple
21 import time
22 from typing import Any, Dict, Tuple
20 import uuid 23 import uuid
21 import time 24
22 from typing import Tuple
23 from collections import namedtuple
24 from zope.interface import implementer
25 from twisted.words.protocols.jabber import jid
26 from twisted.internet import defer 25 from twisted.internet import defer
27 from twisted.internet import reactor 26 from twisted.internet import reactor
27 from twisted.python import failure
28 from twisted.words.protocols.jabber import jid
28 from twisted.words.protocols.jabber import error 29 from twisted.words.protocols.jabber import error
29 from twisted.words.protocols.jabber import xmlstream 30 from twisted.words.protocols.jabber import xmlstream
30 from twisted.python import failure 31 from twisted.words.xish import domish
31 from wokkel import disco, iwokkel 32 from wokkel import disco, iwokkel
33 from zope.interface import implementer
34
32 from sat.core import exceptions 35 from sat.core import exceptions
33 from sat.core.i18n import _, D_
34 from sat.core.constants import Const as C 36 from sat.core.constants import Const as C
37 from sat.core.core_types import SatXMPPEntity
38 from sat.core.i18n import D_, _
35 from sat.core.log import getLogger 39 from sat.core.log import getLogger
36 from sat.tools import xml_tools 40 from sat.tools import xml_tools
37 from sat.tools import utils 41 from sat.tools import utils
38 42
39 43
63 67
64 ApplicationData = namedtuple("ApplicationData", ("namespace", "handler")) 68 ApplicationData = namedtuple("ApplicationData", ("namespace", "handler"))
65 TransportData = namedtuple("TransportData", ("namespace", "handler", "priority")) 69 TransportData = namedtuple("TransportData", ("namespace", "handler", "priority"))
66 70
67 71
68 class XEP_0166(object): 72 class XEP_0166:
73 namespace = NS_JINGLE
69 ROLE_INITIATOR = "initiator" 74 ROLE_INITIATOR = "initiator"
70 ROLE_RESPONDER = "responder" 75 ROLE_RESPONDER = "responder"
71 TRANSPORT_DATAGRAM = "UDP" 76 TRANSPORT_DATAGRAM = "UDP"
72 TRANSPORT_STREAMING = "TCP" 77 TRANSPORT_STREAMING = "TCP"
73 REASON_SUCCESS = "success" 78 REASON_SUCCESS = "success"
370 content_name = content["name"] 375 content_name = content["name"]
371 except KeyError: 376 except KeyError:
372 content_name = content["name"] = str(uuid.uuid4()) 377 content_name = content["name"] = str(uuid.uuid4())
373 return application, app_args, app_kwargs, content_name 378 return application, app_args, app_kwargs, content_name
374 379
375 async def initiate(self, client, peer_jid, contents): 380 async def initiate(self, client, peer_jid, contents, encrypted=False):
376 """Send a session initiation request 381 """Send a session initiation request
377 382
378 @param peer_jid(jid.JID): jid to establith session with 383 @param peer_jid(jid.JID): jid to establith session with
379 @param contents(list[dict]): list of contents to use: 384 @param contents(list[dict]): list of contents to use:
380 The dict must have the following keys: 385 The dict must have the following keys:
385 - name(unicode): name of the content 390 - name(unicode): name of the content
386 - senders(unicode): One of XEP_0166.ROLE_INITIATOR, XEP_0166.ROLE_RESPONDER, both or none 391 - senders(unicode): One of XEP_0166.ROLE_INITIATOR, XEP_0166.ROLE_RESPONDER, both or none
387 default to BOTH (see XEP-0166 §7.3) 392 default to BOTH (see XEP-0166 §7.3)
388 - app_args(list): args to pass to the application plugin 393 - app_args(list): args to pass to the application plugin
389 - app_kwargs(dict): keyword args to pass to the application plugin 394 - app_kwargs(dict): keyword args to pass to the application plugin
395 @param encrypted: if True, session must be encrypted and "encryption" must be set
396 to all content data of session
390 @return (unicode): jingle session id 397 @return (unicode): jingle session id
391 """ 398 """
392 assert contents # there must be at least one content 399 assert contents # there must be at least one content
393 if (peer_jid == client.jid 400 if (peer_jid == client.jid
394 or client.is_component and peer_jid.host == client.jid.host): 401 or client.is_component and peer_jid.host == client.jid.host):
469 transport.handler.jingleSessionInit, 476 transport.handler.jingleSessionInit,
470 client, session, content_name 477 client, session, content_name
471 ) 478 )
472 content_elt.addChild(transport_elt) 479 content_elt.addChild(transport_elt)
473 480
481 if not await self.host.trigger.asyncPoint(
482 "XEP-0166_initiate_elt_built",
483 client, session, iq_elt, jingle_elt
484 ):
485 return
486 if encrypted:
487 for content in session["contents"].values():
488 if "encryption" not in content:
489 raise exceptions.EncryptionError(
490 "Encryption is requested, but no encryption has been set"
491 )
492
474 try: 493 try:
475 await iq_elt.send() 494 await iq_elt.send()
476 except Exception as e: 495 except Exception as e:
477 failure_ = failure.Failure(e) 496 failure_ = failure.Failure(e)
478 self._iqError(failure_, sid, client) 497 self._iqError(failure_, sid, client)
512 profile=client.profile, 531 profile=client.profile,
513 ) 532 )
514 533
515 ## jingle events ## 534 ## jingle events ##
516 535
517 def _onJingleRequest(self, request, client): 536 def _on_jingle_request(self, request: domish.Element, client: SatXMPPEntity) -> None:
537 defer.ensureDeferred(self.on_jingle_request(client, request))
538
539 async def on_jingle_request(
540 self,
541 client: SatXMPPEntity,
542 request: domish.Element
543 ) -> None:
518 """Called when any jingle request is received 544 """Called when any jingle request is received
519 545
520 The request will then be dispatched to appropriate method 546 The request will then be dispatched to appropriate method
521 according to current state 547 according to current state
522 @param request(domish.Element): received IQ request 548 @param request(domish.Element): received IQ request
592 log.error("session id doesn't match") 618 log.error("session id doesn't match")
593 self.sendError(client, "service-unavailable", sid, request) 619 self.sendError(client, "service-unavailable", sid, request)
594 raise exceptions.InternalError 620 raise exceptions.InternalError
595 621
596 if action == XEP_0166.A_SESSION_INITIATE: 622 if action == XEP_0166.A_SESSION_INITIATE:
597 self.onSessionInitiate(client, request, jingle_elt, session) 623 await self.onSessionInitiate(client, request, jingle_elt, session)
598 elif action == XEP_0166.A_SESSION_TERMINATE: 624 elif action == XEP_0166.A_SESSION_TERMINATE:
599 self.onSessionTerminate(client, request, jingle_elt, session) 625 self.onSessionTerminate(client, request, jingle_elt, session)
600 elif action == XEP_0166.A_SESSION_ACCEPT: 626 elif action == XEP_0166.A_SESSION_ACCEPT:
601 self.onSessionAccept(client, request, jingle_elt, session) 627 self.onSessionAccept(client, request, jingle_elt, session)
602 elif action == XEP_0166.A_SESSION_INFO: 628 elif action == XEP_0166.A_SESSION_INFO:
794 ) 820 )
795 defers_list.append(d) 821 defers_list.append(d)
796 822
797 return defers_list 823 return defers_list
798 824
799 def onSessionInitiate(self, client, request, jingle_elt, session): 825 async def onSessionInitiate(
826 self,
827 client: SatXMPPEntity,
828 request: domish.Element,
829 jingle_elt: domish.Element,
830 session: Dict[str, Any]
831 ) -> None:
800 """Called on session-initiate action 832 """Called on session-initiate action
801 833
802 The "jingleRequestConfirmation" method of each application will be called 834 The "jingleRequestConfirmation" method of each application will be called
803 (or self.jingleRequestConfirmationDefault if the former doesn't exist). 835 (or self.jingleRequestConfirmationDefault if the former doesn't exist).
804 The session is only accepted if all application are confirmed. 836 The session is only accepted if all application are confirmed.
826 self.sendError(client, "bad-request", session["id"], request) 858 self.sendError(client, "bad-request", session["id"], request)
827 return 859 return
828 860
829 # at this point we can send the <iq/> result to confirm reception of the request 861 # at this point we can send the <iq/> result to confirm reception of the request
830 client.send(xmlstream.toResponse(request, "result")) 862 client.send(xmlstream.toResponse(request, "result"))
863
864
865 if not await self.host.trigger.asyncPoint(
866 "XEP-0166_on_session_initiate",
867 client, session, request, jingle_elt
868 ):
869 return
831 870
832 # we now request each application plugin confirmation 871 # we now request each application plugin confirmation
833 # and if all are accepted, we can accept the session 872 # and if all are accepted, we can accept the session
834 confirm_defers = self._callPlugins( 873 confirm_defers = self._callPlugins(
835 client, 874 client,
1197 def __init__(self, plugin_parent): 1236 def __init__(self, plugin_parent):
1198 self.plugin_parent = plugin_parent 1237 self.plugin_parent = plugin_parent
1199 1238
1200 def connectionInitialized(self): 1239 def connectionInitialized(self):
1201 self.xmlstream.addObserver( 1240 self.xmlstream.addObserver(
1202 JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent 1241 JINGLE_REQUEST, self.plugin_parent._on_jingle_request, client=self.parent
1203 ) 1242 )
1204 1243
1205 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 1244 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
1206 return [disco.DiscoFeature(NS_JINGLE)] 1245 return [disco.DiscoFeature(NS_JINGLE)]
1207 1246