view sat_pubsub/delegation.py @ 288:073161f6f143

namespace delegation: disco nesting management
author Goffi <goffi@goffi.org>
date Fri, 17 Apr 2015 21:09:37 +0200
parents 61f92273fb69
children f08f8536cab8
line wrap: on
line source

#!/usr/bin/python
#-*- coding: utf-8 -*-
#
"""
Copyright (c) 2015 Jérôme Poisson


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/>.

---

This module implements XEP-0355 (Namespace delegation) to use SàT Pubsub as PEP service
"""

from wokkel.subprotocols import XMPPHandler
from wokkel import pubsub
from wokkel import disco, iwokkel
from twisted.python import log
from twisted.words.protocols.jabber import error
from zope.interface import implements

DELEGATION_NS = 'urn:xmpp:delegation:1'
FORWARDED_NS = 'urn:xmpp:forward:0'
DELEGATION_ADV_XPATH = '/message/delegation[@xmlns="{}"]'.format(DELEGATION_NS)

DELEGATION_MAIN_SEP = "::"
DELEGATION_BARE_SEP = ":bare:"

class InvalidStanza(Exception):
    pass

class DelegationsHandler(XMPPHandler):
    implements(iwokkel.IDisco)

    def __init__(self):
        super(DelegationsHandler, self).__init__()

    def connectionInitialized(self):
        self.xmlstream.addObserver(DELEGATION_ADV_XPATH, self.onAdvertise)

    def onAdvertise(self, message):
        """Managage the <message/> advertising delegations"""
        delegation_elt = message.elements(DELEGATION_NS, 'delegation').next()
        delegated = {}
        for delegated_elt in delegation_elt.elements(DELEGATION_NS):
            try:
                if delegated_elt.name != 'delegated':
                    raise InvalidStanza(u'unexpected element {}'.format(delegated_elt.name))
                try:
                    namespace = delegated_elt['namespace']
                except KeyError:
                    raise InvalidStanza(u'was expecting a "namespace" attribute in delegated element')
                delegated[namespace] = []
                for attribute_elt in delegated_elt.elements(DELEGATION_NS, 'attribute'):
                    try:
                        delegated[namespace].append(attribute_elt["name"])
                    except KeyError:
                        raise InvalidStanza(u'was expecting a "name" attribute in attribute element')
            except InvalidStanza as e:
                log.msg("Invalid stanza received ({})".format(e))

        log.msg(u'delegations updated:\n{}'.format(
            u'\n'.join([u"    - namespace {}{}".format(ns,
            u"" if not attributes else u" with filtering on {} attribute(s)".format(
            u", ".join(attributes))) for ns, attributes in delegated.items()])))

        if not pubsub.NS_PUBSUB in delegated:
            log.msg(u"Didn't got pubsub delegation from server, can't act as a PEP service")

    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
        """Manage disco nesting

        This method looks for DiscoHandler in sibling handlers and use it to
        collect main disco infos. It then filters by delegated namespace and return it.
        An identity is added for PEP if pubsub namespace is requested.

        The same features/identities are returned for main and bare nodes
        """
        if not nodeIdentifier.startswith(DELEGATION_NS):
            return []

        try:
            _, namespace = nodeIdentifier.split(DELEGATION_MAIN_SEP, 1)
        except ValueError:
            try:
                _, namespace = nodeIdentifier.split(DELEGATION_BARE_SEP, 1)
            except ValueError:
                log.msg("Unexpected disco node: {}".format(nodeIdentifier))
                raise error.StanzaError('not-acceptable')

        if not namespace:
            log.msg("No namespace found in node {}".format(nodeIdentifier))
            return []

        def gotInfos(infos):
            ns_features = []
            for info in infos:
                if isinstance(info, disco.DiscoFeature) and info.startswith(namespace):
                    ns_features.append(info)

            if namespace == pubsub.NS_PUBSUB:
                ns_features.append(disco.DiscoIdentity('pubsub', 'pep'))

            return ns_features

        for handler in self.parent.handlers:
            if isinstance(handler, disco.DiscoHandler):
                break

        if not isinstance(handler, disco.DiscoHandler):
            log.err("Can't find DiscoHandler")
            return []

        d = handler.info(requestor, target, '')
        d.addCallback(gotInfos)
        return d

    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
        return []