plugin XEP-0033: implementation of the addressing feature:

- frontends pass the recipients in the extra parameter of sendMessage
    - backend checks if the target server supports the feature (this is not done yet by prosody plugin)
    - features and identities are cached per profile and server
    - messages are duplicated in history for now (TODO: redesign the database)
    - echos signals are also duplicated to the sender (FIXME)
This commit is contained in:
souliane 2013-12-11 17:16:53 +01:00
commit 718bb98300
4 changed files with 373 additions and 60 deletions

View file

@ -140,7 +140,7 @@ class GroupBlog(object):
#we don't have any pubsub server featuring item access yet
client.item_access_pubsub = None
client._item_access_pubsub_pending = defer.Deferred()
for entity in self.host.memory.getServerServiceEntities("pubsub", "service", profile):
for entity in self.host.memory.getServerServiceEntities("pubsub", "service", profile=profile):
_disco = yield client.disco.requestInfo(entity)
#if set([NS_PUBSUB_ITEM_ACCESS, NS_PUBSUB_AUTO_CREATE, NS_PUBSUB_CREATOR_JID_CHECK]).issubset(_disco.features):
if set([NS_PUBSUB_AUTO_CREATE, NS_PUBSUB_CREATOR_JID_CHECK]).issubset(_disco.features):

View file

@ -0,0 +1,174 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SAT plugin for Extended Stanza Addressing (xep-0033)
# Copyright (C) 2013 Adrien Cossa (souliane@mailoo.org)
# 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/>.
import logging
from sat.core import exceptions
from wokkel import disco, iwokkel
from zope.interface import implements
from twisted.words.protocols.jabber.jid import JID
import copy
try:
from twisted.words.protocols.xmlstream import XMPPHandler
except ImportError:
from wokkel.subprotocols import XMPPHandler
from threading import Timer
from twisted.words.xish import domish
from twisted.internet import defer
from sat.core.sat_main import MessageSentAndStored, AbortSendMessage
from sat.tools.misc import TriggerManager
# TODO: fix Prosody "addressing" plugin to leave the concerned bcc according to the spec:
#
# http://xmpp.org/extensions/xep-0033.html#addr-type-bcc
# "This means that the server MUST remove these addresses before the stanza is delivered to anyone other than the given bcc addressee or the multicast service of the bcc addressee."
#
# http://xmpp.org/extensions/xep-0033.html#multicast
# "Each 'bcc' recipient MUST receive only the <address type='bcc'/> associated with that addressee."
# TODO: fix Prosody "addressing" plugin to determine itself if remote servers supports this XEP
NS_XMPP_CLIENT = "jabber:client"
NS_ADDRESS = "http://jabber.org/protocol/address"
ATTRIBUTES = ["jid", "uri", "node", "desc", "delivered", "type"]
ADDRESS_TYPES = ["to", "cc", "bcc", "replyto", "replyroom", "noreply"]
PLUGIN_INFO = {
"name": "Extended Stanza Addressing Protocol Plugin",
"import_name": "XEP-0033",
"type": "XEP",
"protocols": ["XEP-0033"],
"dependencies": [],
"main": "XEP_0033",
"handler": "yes",
"description": _("""Implementation of Extended Stanza Addressing""")
}
class XEP_0033(object):
"""
Implementation for XEP 0033
"""
def __init__(self, host):
logging.info(_("Extended Stanza Addressing plugin initialization"))
self.host = host
host.trigger.add("sendMessage", self.sendMessageTrigger, TriggerManager.MIN_PRIORITY)
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
def sendMessageTrigger(self, mess_data, treatments, profile):
"""Process the XEP-0033 related data to be sent"""
def treatment(mess_data):
if not 'address' in mess_data['extra']:
return mess_data
def discoCallback(entity):
if entity is None:
raise AbortSendMessage(_("XEP-0033 is being used but the server doesn't support it!"))
to = JID(mess_data["to"].host)
if to != entity:
logging.warning(_("Stanzas using XEP-0033 should be addressed to %s, not %s!") % (entity, to))
logging.warning(_("TODO: addressing has be fixed by the backend... fix it in the frontend!"))
mess_data["to"] = entity
element = mess_data['xml'].addElement('addresses', NS_ADDRESS)
entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != '']
for type_, jid_ in entries:
element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_}))
# when the prosody plugin is completed, we can immediately return mess_data from here
return self.sendAndStoreMessage(mess_data, entries, profile)
d = self.host.requestServerDisco(NS_ADDRESS, profile_key=profile)
d.addCallbacks(discoCallback, lambda dummy: discoCallback(None))
return d
treatments.addCallback(treatment)
return True
def sendAndStoreMessage(self, mess_data, entries, profile):
"""Check if target servers support XEP-0033, send and store the messages
@raise: a friendly failure to let the core know that we sent the message already
Later we should be able to remove this method because:
# XXX: sending the messages should be done by the local server
# FIXME: for now we duplicate the messages in the history for each recipient, this should change
# FIXME: for now we duplicate the echoes to the sender, this should also change
Ideas:
- fix Prosody plugin to check if target server support the feature
- redesign the database to save only one entry to the database
- change the newMessage signal to eventually pass more than one recipient
"""
def discoCallback(entity, to_jid):
new_data = copy.deepcopy(mess_data)
new_data['to'] = JID(to_jid)
new_data['xml']['to'] = to_jid
if entity:
if 'address' in mess_data['extra']:
self.host.sendAndStoreMessage(mess_data, False, profile)
# just to remember that the message has been sent
del mess_data['extra']['address']
# we still need to fill the history and signal the echo...
self.host.sendAndStoreMessage(new_data, True, profile)
else:
# target server misses the addressing feature
self.host.sendAndStoreMessage(new_data, False, profile)
def errback(failure, to_jid):
discoCallback(None, to_jid)
for type_, jid_ in entries:
d = defer.Deferred()
d.addCallback(self.host.requestServerDisco, JID(JID(jid_).host), profile_key=profile)
d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_])
d.callback(NS_ADDRESS)
raise MessageSentAndStored("XEP-0033 took over")
def messageReceivedTrigger(self, message, post_treat, profile):
"""In order to save the addressing information in the history"""
def post_treat_addr(data, addresses):
data['extra']['addresses'] = ""
for address in addresses:
data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid'])
return data
try:
addresses = message.elements(NS_ADDRESS, 'addresses').next()
post_treat.addCallback(post_treat_addr, addresses.children)
except StopIteration:
pass # no addresses
return True
def getHandler(self, profile):
return XEP_0033_handler(self, profile)
class XEP_0033_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 getDiscoInfo(self, requestor, target, nodeIdentifier=''):
return [disco.DiscoFeature(NS_ADDRESS)]
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
return []

View file

@ -187,7 +187,7 @@ class XEP_0045(object):
def getMUCService(self, profile):
"""Return the MUC service or None"""
muc_service = None
for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
for service in self.host.memory.getServerServiceEntities("conference", "text", profile=profile):
if not ".irc." in service.userhost():
#FIXME:
#This awfull ugly hack is here to avoid an issue with openfire: the irc gateway

View file

@ -807,7 +807,7 @@ class XEP_0065_handler(XMPPHandler):
def connectionInitialized(self):
def after_init(ignore):
proxy_ent = self.host.memory.getServerServiceEntity("proxy", "bytestreams", self.parent.profile)
proxy_ent = self.host.memory.getServerServiceEntity("proxy", "bytestreams", profile=self.parent.profile)
if not proxy_ent:
debug(_("No proxy found on this server"))
return