# HG changeset patch # User Geoffrey POUZET # Date 1468260065 -7200 # Node ID 2afd5bd781effb2ac6d5b678fe4117077ee0c8d0 # Parent 99ec7b66daa91262016c4ff36a98a5432635c975 plugin XEP-0070: implementation of XEP-0070 (verifying HTTP request via XMPP) diff -r 99ec7b66daa9 -r 2afd5bd781ef src/plugins/plugin_xep_0070.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_xep_0070.py Mon Jul 11 20:01:05 2016 +0200 @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# SAT plugin for managing xep-0070 +# Copyright (C) 2009-2016 Geoffrey POUZET (chteufleur@kingpenguin.tk) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from sat.core.i18n import _, D_ +from sat.core.constants import Const as C +from sat.core.i18n import _ +from sat.core.log import getLogger +from twisted.words.protocols.jabber import xmlstream +from twisted.words.protocols import jabber +log = getLogger(__name__) +from sat.tools import xml_tools + +from wokkel import disco, iwokkel +from zope.interface import implements +try: + from twisted.words.protocols.xmlstream import XMPPHandler +except ImportError: + from wokkel.subprotocols import XMPPHandler + + +NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth' + +IQ = 'iq' +IQ_GET = '/'+IQ+'[@type="get"]' +IQ_HTTP_AUTH_REQUEST = IQ_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' + +MSG = 'message' +MSG_GET = '/'+MSG+'[@type="normal"]' +MSG_HTTP_AUTH_REQUEST = MSG_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' + + +PLUGIN_INFO = { + "name": "XEP-0070 Plugin", + "import_name": "XEP-0070", + "type": "XEP", + "protocols": ["XEP-0070"], + "dependencies": [], + "main": "XEP_0070", + "handler": "yes", + "description": _("""Implementation of HTTP Requests via XMPP""") +} + + +class XEP_0070(object): + """ + Implementation for XEP 0070. + """ + + def __init__(self, host): + log.info(_("Plugin XEP_0070 initialization")) + self.host = host + self._dictRequest = dict() + + def getHandler(self, profile): + return XEP_0070_handler(self, profile) + + def onHttpAuthRequestIQ(self, iq_elt, client): + """This method is called on confirmation request received (XEP-0070 #4.5) + @param iq_elt: IQ element + @param client: %(doc_client)s""" + log.info(_("XEP-0070 Verifying HTTP Requests via XMPP (iq)")) + self._treatHttpAuthRequest(iq_elt, IQ, client) + + def onHttpAuthRequestMsg(self, msg_elt, client): + """This method is called on confirmation request received (XEP-0070 #4.5) + @param msg_elt: message element + @param client: %(doc_client)s""" + log.info(_("XEP-0070 Verifying HTTP Requests via XMPP (message)")) + self._treatHttpAuthRequest(msg_elt, MSG, client) + + def _treatHttpAuthRequest(self, elt, stanzaType, client): + elt.handled = True + auth_elt = elt.elements(NS_HTTP_AUTH, 'confirm').next() + auth_id = auth_elt['id'] + auth_method = auth_elt['method'] + auth_url = auth_elt['url'] + self._dictRequest[client] = (auth_id, auth_method, auth_url, stanzaType, elt) + + confirm_ui = xml_tools.XMLUI("form", title=D_("Auth confirmation"), submit_id='') + confirm_ui.addText(D_("HTTP ({}) Authorization for {} (id: {}).".format(auth_method, auth_url, auth_id))) + confirm_ui.addText(D_("Submit to authorize, cancel otherwise.")) + d = xml_tools.deferredUI(self.host, confirm_ui, chained=False) + d.addCallback(self._authRequestCallback, client.profile) + self.host.actionNew({"xmlui": confirm_ui.toXml()}, profile=client.profile) + + def _authRequestCallback(self, result, profile): + client = self.host.getClient(profile) + try: + cancelled = result['cancelled'] + except KeyError: + cancelled = False + + authorized = False + + if cancelled: + auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[client] + del self._dictRequest[client] + authorized = False + else: + try: + auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[client] + del self._dictRequest[client] + authorized = True + except KeyError: + authorized = False + + if authorized: + if (stanzaType == IQ): + # iq + log.debug(_("XEP-0070 reply iq")) + iq_result_elt = xmlstream.toResponse(elt, 'result') + client.xmlstream.send(iq_result_elt) + elif (stanzaType == MSG): + # message + log.debug(_("XEP-0070 reply message")) + msg_result_elt = xmlstream.toResponse(elt, 'result') + msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, 'confirm').next()) + client.xmlstream.send(msg_result_elt) + else: + log.debug(_("XEP-0070 reply error")) + result_elt = jabber.error.StanzaError("not-authorized").toResponse(elt) + client.xmlstream.send(result_elt) + + +class XEP_0070_handler(XMPPHandler): + implements(iwokkel.IDisco) + + def __init__(self, plugin_parent, profile): + self.plugin_parent = plugin_parent + self.host = plugin_parent.host + self.profile = profile + + def connectionInitialized(self): + self.xmlstream.addObserver(IQ_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestIQ, client=self.parent) + self.xmlstream.addObserver(MSG_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestMsg, client=self.parent) + + def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + return [disco.DiscoFeature(NS_HTTP_AUTH)] + + def getDiscoItems(self, requestor, target, nodeIdentifier=''): + return []