view src/plugins/plugin_xep_0070.py @ 2014:0694a2611bad

plugin XEP-0070: change message popup to be more user-friendly
author Geoffrey POUZET <chteufleur@kingpenguin.tk>
date Tue, 19 Jul 2016 21:25:22 +0200
parents 2afd5bd781ef
children 438a49dbfe87
line wrap: on
line source

#!/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 <http://www.gnu.org/licenses/>.
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_("{} needs to validate your identity, do you agreeĀ ?".format(auth_url)))
        confirm_ui.addText(D_("Validation code : {}".format(auth_id)))
        confirm_ui.addText(D_("Please check that this code is the same as on {}".format(auth_url)))
        confirm_ui.addText("")
        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 []