Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0050.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | a8c2d8b3453f |
children | 9d0df638c8b4 |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 # SAT plugin for Ad-Hoc Commands (XEP-0050) | 4 # SAT plugin for Ad-Hoc Commands (XEP-0050) |
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) | 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
6 | 6 |
30 from sat.core import exceptions | 30 from sat.core import exceptions |
31 from sat.memory.memory import Sessions | 31 from sat.memory.memory import Sessions |
32 from uuid import uuid4 | 32 from uuid import uuid4 |
33 from sat.tools import xml_tools | 33 from sat.tools import xml_tools |
34 | 34 |
35 from zope.interface import implements | 35 from zope.interface import implementer |
36 | 36 |
37 try: | 37 try: |
38 from twisted.words.protocols.xmlstream import XMPPHandler | 38 from twisted.words.protocols.xmlstream import XMPPHandler |
39 except ImportError: | 39 except ImportError: |
40 from wokkel.subprotocols import XMPPHandler | 40 from wokkel.subprotocols import XMPPHandler |
69 C.PI_MODES: C.PLUG_MODE_BOTH, | 69 C.PI_MODES: C.PLUG_MODE_BOTH, |
70 C.PI_TYPE: "XEP", | 70 C.PI_TYPE: "XEP", |
71 C.PI_PROTOCOLS: ["XEP-0050"], | 71 C.PI_PROTOCOLS: ["XEP-0050"], |
72 C.PI_MAIN: "XEP_0050", | 72 C.PI_MAIN: "XEP_0050", |
73 C.PI_HANDLER: "yes", | 73 C.PI_HANDLER: "yes", |
74 C.PI_DESCRIPTION: _(u"""Implementation of Ad-Hoc Commands"""), | 74 C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands"""), |
75 } | 75 } |
76 | 76 |
77 | 77 |
78 class AdHocError(Exception): | 78 class AdHocError(Exception): |
79 def __init__(self, error_const): | 79 def __init__(self, error_const): |
82 """ | 82 """ |
83 assert error_const in XEP_0050.ERROR | 83 assert error_const in XEP_0050.ERROR |
84 self.callback_error = error_const | 84 self.callback_error = error_const |
85 | 85 |
86 | 86 |
87 @implementer(iwokkel.IDisco) | |
87 class AdHocCommand(XMPPHandler): | 88 class AdHocCommand(XMPPHandler): |
88 implements(iwokkel.IDisco) | |
89 | 89 |
90 def __init__(self, callback, label, node, features, timeout, | 90 def __init__(self, callback, label, node, features, timeout, |
91 allowed_jids, allowed_groups, allowed_magics, forbidden_jids, | 91 allowed_jids, allowed_groups, allowed_magics, forbidden_jids, |
92 forbidden_groups): | 92 forbidden_groups): |
93 XMPPHandler.__init__(self) | 93 XMPPHandler.__init__(self) |
128 allowed = set(self.allowed_jids) | 128 allowed = set(self.allowed_jids) |
129 for group in self.allowed_groups: | 129 for group in self.allowed_groups: |
130 try: | 130 try: |
131 allowed.update(self.client.roster.getJidsFromGroup(group)) | 131 allowed.update(self.client.roster.getJidsFromGroup(group)) |
132 except exceptions.UnknownGroupError: | 132 except exceptions.UnknownGroupError: |
133 log.warning(_(u"The groups [{group}] is unknown for profile [{profile}])") | 133 log.warning(_("The groups [{group}] is unknown for profile [{profile}])") |
134 .format(group=group, profile=self.client.profile)) | 134 .format(group=group, profile=self.client.profile)) |
135 if requestor.userhostJID() in allowed: | 135 if requestor.userhostJID() in allowed: |
136 return True | 136 return True |
137 return False | 137 return False |
138 | 138 |
202 @param request: original request (domish.Element) | 202 @param request: original request (domish.Element) |
203 """ | 203 """ |
204 xmpp_condition, cmd_condition = error_constant | 204 xmpp_condition, cmd_condition = error_constant |
205 iq_elt = jabber.error.StanzaError(xmpp_condition).toResponse(request) | 205 iq_elt = jabber.error.StanzaError(xmpp_condition).toResponse(request) |
206 if cmd_condition: | 206 if cmd_condition: |
207 error_elt = iq_elt.elements(None, "error").next() | 207 error_elt = next(iq_elt.elements(None, "error")) |
208 error_elt.addElement(cmd_condition, NS_COMMANDS) | 208 error_elt.addElement(cmd_condition, NS_COMMANDS) |
209 self.client.send(iq_elt) | 209 self.client.send(iq_elt) |
210 del self.sessions[session_id] | 210 del self.sessions[session_id] |
211 | 211 |
212 def onRequest(self, command_elt, requestor, action, session_id): | 212 def onRequest(self, command_elt, requestor, action, session_id): |
291 "adHocRun", | 291 "adHocRun", |
292 ".plugin", | 292 ".plugin", |
293 in_sign="sss", | 293 in_sign="sss", |
294 out_sign="s", | 294 out_sign="s", |
295 method=self._run, | 295 method=self._run, |
296 async=True, | 296 async_=True, |
297 ) | 297 ) |
298 host.bridge.addMethod( | 298 host.bridge.addMethod( |
299 "adHocList", | 299 "adHocList", |
300 ".plugin", | 300 ".plugin", |
301 in_sign="ss", | 301 in_sign="ss", |
302 out_sign="s", | 302 out_sign="s", |
303 method=self._listUI, | 303 method=self._listUI, |
304 async=True, | 304 async_=True, |
305 ) | 305 ) |
306 self.__requesting_id = host.registerCallback( | 306 self.__requesting_id = host.registerCallback( |
307 self._requestingEntity, with_data=True | 307 self._requestingEntity, with_data=True |
308 ) | 308 ) |
309 host.importMenu( | 309 host.importMenu( |
310 (D_("Service"), D_("Commands")), | 310 (D_("Service"), D_("Commands")), |
311 self._commandsMenu, | 311 self._commandsMenu, |
312 security_limit=2, | 312 security_limit=2, |
313 help_string=D_("Execute ad-hoc commands"), | 313 help_string=D_("Execute ad-hoc commands"), |
314 ) | 314 ) |
315 host.registerNamespace(u'commands', NS_COMMANDS) | 315 host.registerNamespace('commands', NS_COMMANDS) |
316 | 316 |
317 def getHandler(self, client): | 317 def getHandler(self, client): |
318 return XEP_0050_handler(self) | 318 return XEP_0050_handler(self) |
319 | 319 |
320 def profileConnected(self, client): | 320 def profileConnected(self, client): |
352 d = iq_elt.send() | 352 d = iq_elt.send() |
353 return d | 353 return d |
354 | 354 |
355 def getCommandElt(self, iq_elt): | 355 def getCommandElt(self, iq_elt): |
356 try: | 356 try: |
357 return iq_elt.elements(NS_COMMANDS, "command").next() | 357 return next(iq_elt.elements(NS_COMMANDS, "command")) |
358 except StopIteration: | 358 except StopIteration: |
359 raise exceptions.NotFound(_(u"Missing command element")) | 359 raise exceptions.NotFound(_("Missing command element")) |
360 | 360 |
361 def adHocError(self, error_type): | 361 def adHocError(self, error_type): |
362 """Shortcut to raise an AdHocError | 362 """Shortcut to raise an AdHocError |
363 | 363 |
364 @param error_type(unicode): one of XEP_0050.ERROR | 364 @param error_type(unicode): one of XEP_0050.ERROR |
387 return C.XMLUI_DATA_LVL_ERROR | 387 return C.XMLUI_DATA_LVL_ERROR |
388 elif type_ == "warn": | 388 elif type_ == "warn": |
389 return C.XMLUI_DATA_LVL_WARNING | 389 return C.XMLUI_DATA_LVL_WARNING |
390 else: | 390 else: |
391 if type_ != "info": | 391 if type_ != "info": |
392 log.warning(_(u"Invalid note type [%s], using info") % type_) | 392 log.warning(_("Invalid note type [%s], using info") % type_) |
393 return C.XMLUI_DATA_LVL_INFO | 393 return C.XMLUI_DATA_LVL_INFO |
394 | 394 |
395 def _mergeNotes(self, notes): | 395 def _mergeNotes(self, notes): |
396 """Merge notes with level prefix (e.g. "ERROR: the message") | 396 """Merge notes with level prefix (e.g. "ERROR: the message") |
397 | 397 |
401 lvl_map = { | 401 lvl_map = { |
402 C.XMLUI_DATA_LVL_INFO: "", | 402 C.XMLUI_DATA_LVL_INFO: "", |
403 C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), | 403 C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), |
404 C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR"), | 404 C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR"), |
405 } | 405 } |
406 return [u"%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes] | 406 return ["%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes] |
407 | 407 |
408 def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data): | 408 def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data): |
409 """Convert command answer to an ui for frontend | 409 """Convert command answer to an ui for frontend |
410 | 410 |
411 @param iq_elt: command result | 411 @param iq_elt: command result |
427 notes = [] | 427 notes = [] |
428 for note_elt in command_elt.elements(NS_COMMANDS, "note"): | 428 for note_elt in command_elt.elements(NS_COMMANDS, "note"): |
429 notes.append( | 429 notes.append( |
430 ( | 430 ( |
431 self._getDataLvl(note_elt.getAttribute("type", "info")), | 431 self._getDataLvl(note_elt.getAttribute("type", "info")), |
432 unicode(note_elt), | 432 str(note_elt), |
433 ) | 433 ) |
434 ) | 434 ) |
435 for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"): | 435 for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"): |
436 if data_elt["type"] in ("form", "result"): | 436 if data_elt["type"] in ("form", "result"): |
437 break | 437 break |
459 dlg_level = notes[0][0] if len(notes) == 1 else C.XMLUI_DATA_LVL_INFO | 459 dlg_level = notes[0][0] if len(notes) == 1 else C.XMLUI_DATA_LVL_INFO |
460 return xml_tools.XMLUI( | 460 return xml_tools.XMLUI( |
461 C.XMLUI_DIALOG, | 461 C.XMLUI_DIALOG, |
462 dialog_opt={ | 462 dialog_opt={ |
463 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, | 463 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, |
464 C.XMLUI_DATA_MESS: u"\n".join(self._mergeNotes(notes)), | 464 C.XMLUI_DATA_MESS: "\n".join(self._mergeNotes(notes)), |
465 C.XMLUI_DATA_LVL: dlg_level, | 465 C.XMLUI_DATA_LVL: dlg_level, |
466 }, | 466 }, |
467 session_id=session_id, | 467 session_id=session_id, |
468 ) | 468 ) |
469 | 469 |
563 if len(actions) == 1: | 563 if len(actions) == 1: |
564 # it's our first request, we ask the desired new status | 564 # it's our first request, we ask the desired new status |
565 status = XEP_0050.STATUS.EXECUTING | 565 status = XEP_0050.STATUS.EXECUTING |
566 form = data_form.Form("form", title=_("status selection")) | 566 form = data_form.Form("form", title=_("status selection")) |
567 show_options = [ | 567 show_options = [ |
568 data_form.Option(name, label) for name, label in SHOWS.items() | 568 data_form.Option(name, label) for name, label in list(SHOWS.items()) |
569 ] | 569 ] |
570 field = data_form.Field( | 570 field = data_form.Field( |
571 "list-single", "show", options=show_options, required=True | 571 "list-single", "show", options=show_options, required=True |
572 ) | 572 ) |
573 form.addField(field) | 573 form.addField(field) |
576 note = None | 576 note = None |
577 | 577 |
578 elif len(actions) == 2: | 578 elif len(actions) == 2: |
579 # we should have the answer here | 579 # we should have the answer here |
580 try: | 580 try: |
581 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() | 581 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) |
582 answer_form = data_form.Form.fromElement(x_elt) | 582 answer_form = data_form.Form.fromElement(x_elt) |
583 show = answer_form["show"] | 583 show = answer_form["show"] |
584 except (KeyError, StopIteration): | 584 except (KeyError, StopIteration): |
585 self.adHocError(XEP_0050.ERROR.BAD_PAYLOAD) | 585 self.adHocError(XEP_0050.ERROR.BAD_PAYLOAD) |
586 if show not in SHOWS: | 586 if show not in SHOWS: |
591 self.host.setPresence(show=show, profile_key=client.profile) | 591 self.host.setPresence(show=show, profile_key=client.profile) |
592 | 592 |
593 # job done, we can end the session | 593 # job done, we can end the session |
594 status = XEP_0050.STATUS.COMPLETED | 594 status = XEP_0050.STATUS.COMPLETED |
595 payload = None | 595 payload = None |
596 note = (self.NOTE.INFO, _(u"Status updated")) | 596 note = (self.NOTE.INFO, _("Status updated")) |
597 else: | 597 else: |
598 self.adHocError(XEP_0050.ERROR.INTERNAL) | 598 self.adHocError(XEP_0050.ERROR.INTERNAL) |
599 | 599 |
600 return (payload, status, None, note) | 600 return (payload, status, None, note) |
601 | 601 |
728 commands[node] = ad_hoc_command | 728 commands[node] = ad_hoc_command |
729 | 729 |
730 def onCmdRequest(self, request, client): | 730 def onCmdRequest(self, request, client): |
731 request.handled = True | 731 request.handled = True |
732 requestor = jid.JID(request["from"]) | 732 requestor = jid.JID(request["from"]) |
733 command_elt = request.elements(NS_COMMANDS, "command").next() | 733 command_elt = next(request.elements(NS_COMMANDS, "command")) |
734 action = command_elt.getAttribute("action", self.ACTION.EXECUTE) | 734 action = command_elt.getAttribute("action", self.ACTION.EXECUTE) |
735 node = command_elt.getAttribute("node") | 735 node = command_elt.getAttribute("node") |
736 if not node: | 736 if not node: |
737 client.sendError(request, u"bad-request") | 737 client.sendError(request, "bad-request") |
738 return | 738 return |
739 sessionid = command_elt.getAttribute("sessionid") | 739 sessionid = command_elt.getAttribute("sessionid") |
740 commands = client._XEP_0050_commands | 740 commands = client._XEP_0050_commands |
741 try: | 741 try: |
742 command = commands[node] | 742 command = commands[node] |
743 except KeyError: | 743 except KeyError: |
744 client.sendError(request, u"item-not-found") | 744 client.sendError(request, "item-not-found") |
745 return | 745 return |
746 command.onRequest(command_elt, requestor, action, sessionid) | 746 command.onRequest(command_elt, requestor, action, sessionid) |
747 | 747 |
748 | 748 |
749 @implementer(iwokkel.IDisco) | |
749 class XEP_0050_handler(XMPPHandler): | 750 class XEP_0050_handler(XMPPHandler): |
750 implements(iwokkel.IDisco) | |
751 | 751 |
752 def __init__(self, plugin_parent): | 752 def __init__(self, plugin_parent): |
753 self.plugin_parent = plugin_parent | 753 self.plugin_parent = plugin_parent |
754 | 754 |
755 @property | 755 @property |
770 | 770 |
771 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | 771 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
772 ret = [] | 772 ret = [] |
773 if nodeIdentifier == NS_COMMANDS: | 773 if nodeIdentifier == NS_COMMANDS: |
774 commands = self.client._XEP_0050_commands | 774 commands = self.client._XEP_0050_commands |
775 for command in commands.values(): | 775 for command in list(commands.values()): |
776 if command.isAuthorised(requestor): | 776 if command.isAuthorised(requestor): |
777 ret.append( | 777 ret.append( |
778 disco.DiscoItem(self.parent.jid, command.node, command.getName()) | 778 disco.DiscoItem(self.parent.jid, command.node, command.getName()) |
779 ) # TODO: manage name language | 779 ) # TODO: manage name language |
780 return ret | 780 return ret |