diff src/iidavoll.py @ 369:dabee42494ac

config file + cleaning: - SàT Pubsub can now be configured using the same config file as SàT itself (i.e. sat.conf or .sat.conf), in the same locations (/etc, local dir, xdg dir). Its options must be in the "pubsub" section - options on command line override config options - removed tap and http files which are not used anymore - changed directory structure to put source in src, to be coherent with SàT and Libervia - changed options name, db* become db_*, secret become xmpp_pwd - an exception is raised if jid or xmpp_pwd is are not configured
author Goffi <goffi@goffi.org>
date Fri, 02 Mar 2018 12:59:38 +0100
parents sat_pubsub/iidavoll.py@618a92080812
children aa3a464df605
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iidavoll.py	Fri Mar 02 12:59:38 2018 +0100
@@ -0,0 +1,665 @@
+#!/usr/bin/python
+#-*- coding: utf-8 -*-
+
+# Copyright (c) 2003-2011 Ralph Meijer
+# Copyright (c) 2012-2018 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 program is based on Idavoll (http://idavoll.ik.nu/),
+# originaly written by Ralph Meijer (http://ralphm.net/blog/)
+# It is sublicensed under AGPL v3 (or any later version) as allowed by the original
+# license.
+
+# --
+
+# Here is a copy of the original license:
+
+# Copyright (c) 2003-2011 Ralph Meijer
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+"""
+Interfaces for idavoll.
+"""
+
+from zope.interface import Attribute, Interface
+
+class IBackendService(Interface):
+    """ Interface to a backend service of a pubsub service. """
+
+
+    def __init__(storage):
+        """
+        @param storage: Object providing L{IStorage}.
+        """
+
+
+    def supportsPublisherAffiliation():
+        """ Reports if the backend supports the publisher affiliation.
+
+        @rtype: C{bool}
+        """
+
+
+    def supportsOutcastAffiliation():
+        """ Reports if the backend supports the publisher affiliation.
+
+        @rtype: C{bool}
+        """
+
+
+    def supportsPersistentItems():
+        """ Reports if the backend supports persistent items.
+
+        @rtype: C{bool}
+        """
+
+
+    def getNodeType(nodeIdentifier):
+        """ Return type of a node.
+
+        @return: a deferred that returns either 'leaf' or 'collection'
+        """
+
+
+    def getNodes():
+        """ Returns list of all nodes.
+
+        @return: a deferred that returns a C{list} of node ids.
+        """
+
+
+    def getNodeMetaData(nodeIdentifier):
+        """ Return meta data for a node.
+
+        @return: a deferred that returns a C{list} of C{dict}s with the
+                 metadata.
+        """
+
+
+    def createNode(nodeIdentifier, requestor):
+        """ Create a node.
+
+        @return: a deferred that fires when the node has been created.
+        """
+
+
+    def registerPreDelete(preDeleteFn):
+        """ Register a callback that is called just before a node deletion.
+
+        The function C{preDeletedFn} is added to a list of functions to be
+        called just before deletion of a node. The callback C{preDeleteFn} is
+        called with the C{nodeIdentifier} that is about to be deleted and
+        should return a deferred that returns a list of deferreds that are to
+        be fired after deletion. The backend collects the lists from all these
+        callbacks before actually deleting the node in question.  After
+        deletion all collected deferreds are fired to do post-processing.
+
+        The idea is that you want to be able to collect data from the node
+        before deleting it, for example to get a list of subscribers that have
+        to be notified after the node has been deleted. To do this,
+        C{preDeleteFn} fetches the subscriber list and passes this list to a
+        callback attached to a deferred that it sets up. This deferred is
+        returned in the list of deferreds.
+        """
+
+
+    def deleteNode(nodeIdentifier, requestor):
+        """ Delete a node.
+
+        @return: a deferred that fires when the node has been deleted.
+        """
+
+
+    def purgeNode(nodeIdentifier, requestor):
+        """ Removes all items in node from persistent storage """
+
+
+    def subscribe(nodeIdentifier, subscriber, requestor):
+        """ Request the subscription of an entity to a pubsub node.
+
+        Depending on the node's configuration and possible business rules, the
+        C{subscriber} is added to the list of subscriptions of the node with id
+        C{nodeIdentifier}. The C{subscriber} might be different from the
+        C{requestor}, and if the C{requestor} is not allowed to subscribe this
+        entity an exception should be raised.
+
+        @return: a deferred that returns the subscription state
+        """
+
+
+    def unsubscribe(nodeIdentifier, subscriber, requestor):
+        """ Cancel the subscription of an entity to a pubsub node.
+
+        The subscription of C{subscriber} is removed from the list of
+        subscriptions of the node with id C{nodeIdentifier}. If the
+        C{requestor} is not allowed to unsubscribe C{subscriber}, an an
+        exception should be raised.
+
+        @return: a deferred that fires when unsubscription is complete.
+        """
+
+
+    def getSubscribers(nodeIdentifier):
+        """ Get node subscriber list.
+
+        @return: a deferred that fires with the list of subscribers.
+        """
+
+
+    def getSubscriptions(entity):
+        """ Report the list of current subscriptions with this pubsub service.
+
+        Report the list of the current subscriptions with all nodes within this
+        pubsub service, for the C{entity}.
+
+        @return: a deferred that returns the list of all current subscriptions
+                 as tuples C{(nodeIdentifier, subscriber, subscription)}.
+        """
+
+
+    def getAffiliations(entity):
+        """ Report the list of current affiliations with this pubsub service.
+
+        Report the list of the current affiliations with all nodes within this
+        pubsub service, for the C{entity}.
+
+        @return: a deferred that returns the list of all current affiliations
+                 as tuples C{(nodeIdentifier, affiliation)}.
+        """
+
+
+    def publish(nodeIdentifier, items, requestor):
+        """ Publish items to a pubsub node.
+
+        @return: a deferred that fires when the items have been published.
+        @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+
+
+    def registerNotifier(observerfn, *args, **kwargs):
+        """ Register callback which is called for notification. """
+
+
+    def getNotifications(nodeIdentifier, items):
+        """
+        Get notification list.
+
+        This method is called to discover which entities should receive
+        notifications for the given items that have just been published to the
+        given node.
+
+        The notification list contains tuples (subscriber, subscriptions,
+        items) to result in one notification per tuple: the given subscriptions
+        yielded the given items to be notified to this subscriber.  This
+        structure is needed allow for letting the subscriber know which
+        subscriptions yielded which notifications, while catering for
+        collection nodes and content-based subscriptions.
+
+        To minimize the amount of notifications per entity, implementers
+        should take care that if all items in C{items} were yielded
+        by the same set of subscriptions, exactly one tuple is for this
+        subscriber is returned, so that the subscriber would get exactly one
+        notification. Alternatively, one tuple per subscription combination.
+
+        @param nodeIdentifier: The identifier of the node the items were
+                               published to.
+        @type nodeIdentifier: C{unicode}.
+        @param items: The list of published items as
+                      L{Element<twisted.words.xish.domish.Element>}s.
+        @type items: C{list}
+        @return: The notification list as tuples of
+                 (L{JID<twisted.words.protocols.jabber.jid.JID>},
+                  C{list} of L{Subscription<wokkel.pubsub.Subscription>},
+                  C{list} of L{Element<twisted.words.xish.domish.Element>}.
+        @rtype: C{list}
+        """
+
+
+    def getItems(nodeIdentifier, requestor, maxItems=None, itemIdentifiers=[]):
+        """ Retrieve items from persistent storage
+
+        If C{maxItems} is given, return the C{maxItems} last published
+        items, else if C{itemIdentifiers} is not empty, return the items
+        requested.  If neither is given, return all items.
+
+        @return: a deferred that returns the requested items
+        """
+
+
+    def retractItem(nodeIdentifier, itemIdentifier, requestor):
+        """ Removes item in node from persistent storage """
+
+
+
+class IStorage(Interface):
+    """
+    Storage interface.
+    """
+
+
+    def getNode(nodeIdentifier):
+        """
+        Get Node.
+
+        @param nodeIdentifier: NodeID of the desired node.
+        @type nodeIdentifier: C{str}
+        @return: deferred that returns a L{INode} providing object.
+        """
+
+
+    def getNodeIds():
+        """
+        Return all NodeIDs.
+
+        @return: deferred that returns a list of NodeIDs (C{unicode}).
+        """
+
+
+    def createNode(nodeIdentifier, owner, config):
+        """
+        Create new node.
+
+        The implementation should make sure, the passed owner JID is stripped
+        of the resource (e.g. using C{owner.userhostJID()}). The passed config
+        is expected to have values for the fields returned by
+        L{getDefaultConfiguration}, as well as a value for
+        C{'pubsub#node_type'}.
+
+        @param nodeIdentifier: NodeID of the new node.
+        @type nodeIdentifier: C{unicode}
+        @param owner: JID of the new nodes's owner.
+        @type owner: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param config: Node configuration.
+        @type config: C{dict}
+        @return: deferred that fires on creation.
+        """
+
+
+    def deleteNode(nodeIdentifier):
+        """
+        Delete a node.
+
+        @param nodeIdentifier: NodeID of the new node.
+        @type nodeIdentifier: C{unicode}
+        @return: deferred that fires on deletion.
+        """
+
+
+    def getAffiliations(entity):
+        """
+        Get all affiliations for entity.
+
+        The implementation should make sure, the passed owner JID is stripped
+        of the resource (e.g. using C{owner.userhostJID()}).
+
+        @param entity: JID of the entity.
+        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that returns a C{list} of tuples of the form
+                 C{(nodeIdentifier, affiliation)}, where C{nodeIdentifier} is
+                 of the type L{unicode} and C{affiliation} is one of
+                 C{'owner'}, C{'publisher'} and C{'outcast'}.
+        """
+
+
+    def getSubscriptions(entity):
+        """
+        Get all subscriptions for an entity.
+
+        The implementation should make sure, the passed owner JID is stripped
+        of the resource (e.g. using C{owner.userhostJID()}).
+
+        @param entity: JID of the entity.
+        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that returns a C{list} of tuples of the form
+                 C{(nodeIdentifier, subscriber, state)}, where
+                 C{nodeIdentifier} is of the type C{unicode}, C{subscriber} of
+                 the type J{JID<twisted.words.protocols.jabber.jid.JID>}, and
+                 C{state} is C{'subscribed'}, C{'pending'} or
+                 C{'unconfigured'}.
+        """
+
+
+    def getDefaultConfiguration(nodeType):
+        """
+        Get the default configuration for the given node type.
+
+        @param nodeType: Either C{'leaf'} or C{'collection'}.
+        @type nodeType: C{str}
+        @return: The default configuration.
+        @rtype: C{dict}.
+        @raises: L{idavoll.error.NoCollections} if collections are not
+                 supported.
+        """
+
+
+
+class INode(Interface):
+    """
+    Interface to the class of objects that represent nodes.
+    """
+
+    nodeType = Attribute("""The type of this node. One of {'leaf'},
+                           {'collection'}.""")
+    nodeIdentifier = Attribute("""The node identifer of this node""")
+
+
+    def getType():
+        """
+        Get node's type.
+
+        @return: C{'leaf'} or C{'collection'}.
+        """
+
+
+    def getConfiguration():
+        """
+        Get node's configuration.
+
+        The configuration must at least have two options:
+        C{pubsub#persist_items}, and C{pubsub#deliver_payloads}.
+
+        @return: C{dict} of configuration options.
+        """
+
+
+    def getMetaData():
+        """
+        Get node's meta data.
+
+        The meta data must be a superset of the configuration options, and
+        also at least should have a C{pubsub#node_type} entry.
+
+        @return: C{dict} of meta data.
+        """
+
+
+    def setConfiguration(options):
+        """
+        Set node's configuration.
+
+        The elements of {options} will set the new values for those
+        configuration items. This means that only changing items have to
+        be given.
+
+        @param options: a dictionary of configuration options.
+        @returns: a deferred that fires upon success.
+        """
+
+
+    def getAffiliation(entity):
+        """
+        Get affiliation of entity with this node.
+
+        @param entity: JID of entity.
+        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that returns C{'owner'}, C{'publisher'}, C{'outcast'}
+                 or C{None}.
+        """
+
+
+    def getSubscription(subscriber):
+        """
+        Get subscription to this node of subscriber.
+
+        @param subscriber: JID of the new subscriptions' entity.
+        @type subscriber: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that returns the subscription state (C{'subscribed'},
+                 C{'pending'} or C{None}).
+        """
+
+
+    def getSubscriptions(state=None):
+        """
+        Get list of subscriptions to this node.
+
+        The optional C{state} argument filters the subscriptions to their
+        state.
+
+        @param state: Subscription state filter. One of C{'subscribed'},
+                      C{'pending'}, C{'unconfigured'}.
+        @type state: C{str}
+        @return: a deferred that returns a C{list} of
+                 L{wokkel.pubsub.Subscription}s.
+        """
+
+
+    def addSubscription(subscriber, state, config):
+        """
+        Add new subscription to this node with given state.
+
+        @param subscriber: JID of the new subscriptions' entity.
+        @type subscriber: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param state: C{'subscribed'} or C{'pending'}
+        @type state: C{str}
+        @param config: Subscription configuration.
+        @param config: C{dict}
+        @return: deferred that fires on subscription.
+        """
+
+
+    def removeSubscription(subscriber):
+        """
+        Remove subscription to this node.
+
+        @param subscriber: JID of the subscriptions' entity.
+        @type subscriber: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that fires on removal.
+        """
+
+
+    def isSubscribed(entity):
+        """
+        Returns whether entity has any subscription to this node.
+
+        Only returns C{True} when the subscription state (if present) is
+        C{'subscribed'} for any subscription that matches the bare JID.
+
+        @param subscriber: bare JID of the subscriptions' entity.
+        @type subscriber: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that returns a C{bool}.
+        """
+
+
+    def getAffiliations():
+        """
+        Get affiliations of entities with this node.
+
+        @return: deferred that returns a C{list} of tuples (jid, affiliation),
+                 where jid is a L(JID<twisted.words.protocols.jabber.jid.JID>)
+                 and affiliation is one of C{'owner'},
+        C{'publisher'}, C{'outcast'}.
+        """
+
+
+
+class ILeafNode(Interface):
+    """
+    Interface to the class of objects that represent leaf nodes.
+    """
+
+    def storeItems(items, publisher):
+        """
+        Store items in persistent storage for later retrieval.
+
+        @param items: The list of items to be stored. Each item is the
+                      L{domish} representation of the XML fragment as defined
+                      for C{<item/>} in the
+                      C{http://jabber.org/protocol/pubsub} namespace.
+        @type items: C{list} of {domish.Element}
+        @param publisher: JID of the publishing entity.
+        @type publisher: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that fires upon success.
+        """
+
+
+    def removeItems(itemIdentifiers):
+        """
+        Remove items by id.
+
+        @param itemIdentifiers: C{list} of item ids.
+        @return: deferred that fires with a C{list} of ids of the items that
+                 were deleted
+        """
+
+
+    def getItems(authorized_groups, unrestricted, maxItems=None):
+        """ Get all authorised items
+        If C{maxItems} is not given, all authorised items in the node are returned,
+        just like C{getItemsById}. Otherwise, C{maxItems} limits
+        the returned items to a maximum of that number of most recently
+        published and authorised items.
+
+        @param authorized_groups: we want to get items that these groups can access
+        @param unrestricted: if true, don't check permissions (i.e.: get all items)
+        @param maxItems: if given, a natural number (>0) that limits the
+                          returned number of items.
+        @return: deferred that fires a C{list} of (item, access_model, id)
+        if unrestricted is True, else a C{list} of items.
+        """
+
+
+    def countItems(authorized_groups, unrestricted):
+        """ Count the accessible items.
+
+        @param authorized_groups: we want to get items that these groups can access.
+        @param unrestricted: if true, don't check permissions (i.e.: get all items).
+        @return: deferred that fires a C{int}.
+        """
+
+
+    def getIndex(authorized_groups, unrestricted, item):
+        """ Retrieve the index of the given item within the accessible window.
+
+        @param authorized_groups: we want to get items that these groups can access.
+        @param unrestricted: if true, don't check permissions (i.e.: get all items).
+        @param item: item identifier.
+        @return: deferred that fires a C{int}.
+        """
+
+    def getItemsById(authorized_groups, unrestricted, itemIdentifiers):
+        """
+        Get items by item id.
+
+        Each item in the returned list is a unicode string that
+        represent the XML of the item as it was published, including the
+        item wrapper with item id.
+
+        @param authorized_groups: we want to get items that these groups can access
+        @param unrestricted: if true, don't check permissions
+        @param itemIdentifiers: C{list} of item ids.
+        @return: deferred that fires a C{list} of (item, access_model, id)
+        if unrestricted is True, else a C{list} of items.
+        """
+
+
+    def purge():
+        """
+        Purge node of all items in persistent storage.
+
+        @return: deferred that fires when the node has been purged.
+        """
+
+
+    def filterItemsWithPublisher(itemIdentifiers, requestor):
+        """
+        Filter the given items by checking the items publisher against the requestor.
+
+        @param itemIdentifiers: C{list} of item ids.
+        @param requestor: JID of the requestor.
+        @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @return: deferred that fires with a C{list} of item identifiers.
+        """
+
+class IGatewayStorage(Interface):
+
+    def addCallback(service, nodeIdentifier, callback):
+        """
+        Register a callback URI.
+
+        The registered HTTP callback URI will have an Atom Entry documented
+        POSTed to it upon receiving a notification for the given pubsub node.
+
+        @param service: The XMPP entity that holds the node.
+        @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param nodeIdentifier: The identifier of the publish-subscribe node.
+        @type nodeIdentifier: C{unicode}.
+        @param callback: The callback URI to be registered.
+        @type callback: C{str}.
+        @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+
+    def removeCallback(service, nodeIdentifier, callback):
+        """
+        Remove a registered callback URI.
+
+        The returned deferred will fire with a boolean that signals wether or
+        not this was the last callback unregistered for this node.
+
+        @param service: The XMPP entity that holds the node.
+        @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param nodeIdentifier: The identifier of the publish-subscribe node.
+        @type nodeIdentifier: C{unicode}.
+        @param callback: The callback URI to be unregistered.
+        @type callback: C{str}.
+        @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+
+    def getCallbacks(service, nodeIdentifier):
+        """
+        Get the callbacks registered for this node.
+
+        Returns a deferred that fires with the set of HTTP callback URIs
+        registered for this node.
+
+        @param service: The XMPP entity that holds the node.
+        @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param nodeIdentifier: The identifier of the publish-subscribe node.
+        @type nodeIdentifier: C{unicode}.
+        @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+        """
+
+
+    def hasCallbacks(service, nodeIdentifier):
+        """
+        Return wether there are callbacks registered for a node.
+
+        @param service: The XMPP entity that holds the node.
+        @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
+        @param nodeIdentifier: The identifier of the publish-subscribe node.
+        @type nodeIdentifier: C{unicode}.
+        @returns: Deferred that fires with a boolean.
+        @rtype: L{Deferred<twisted.internet.defer.Deferred>}
+        """