# HG changeset patch # User Goffi # Date 1503859564 -7200 # Node ID d0e75bf21d3028920618830bcd73f2857433ce17 # Parent 956b75b0e9d9718ffe64521a91f5f530abe7c094 mod_delegation: added disco#items support disco#items are forwarded to managing entity when suitable. This feature is not yet in XEP-0355, but it should be added soon. "http://jabber.org/protocol/disco#items:*" is used as a pseudo-namespace to activate this delegation. Also changed spaces to tabs to follow Prosody coding style. diff -r 956b75b0e9d9 -r d0e75bf21d30 mod_delegation/mod_delegation.lua --- a/mod_delegation/mod_delegation.lua Thu Apr 27 19:55:18 2017 +0200 +++ b/mod_delegation/mod_delegation.lua Sun Aug 27 20:46:04 2017 +0200 @@ -15,6 +15,11 @@ local delegation_session = module:shared("/*/delegation/session") +-- FIXME: temporarily needed for disco_items_hook, to be removed when clean implementation is done +local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; + if delegation_session.connected_cb == nil then -- set used to have connected event listeners -- which allow a host to react on events from @@ -25,18 +30,21 @@ local _DELEGATION_NS = 'urn:xmpp:delegation:1' local _FORWARDED_NS = 'urn:xmpp:forward:0' -local _DISCO_NS = 'http://jabber.org/protocol/disco#info' +local _DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info' +local _DISCO_ITEMS_NS = 'http://jabber.org/protocol/disco#items' local _DATA_NS = 'jabber:x:data' local _MAIN_SEP = '::' local _BARE_SEP = ':bare:' +local _REMAINING = ':*' local _MAIN_PREFIX = _DELEGATION_NS.._MAIN_SEP local _BARE_PREFIX = _DELEGATION_NS.._BARE_SEP +local _DISCO_REMAINING = _DISCO_ITEMS_NS.._REMAINING local _PREFIXES = {_MAIN_PREFIX, _BARE_PREFIX} local disco_nest -module:log("debug", "Loading namespace delegation module "); +module:log("debug", "Loading namespace delegation module ") --> Configuration management <-- @@ -95,7 +103,11 @@ for namespace, ns_data in pairs(jid2ns[jid_]) do if ns_data.connected == nil then ns_data.connected = entity_jid - disco_nest(namespace, entity_jid) + -- disco remaining is a special namespace + -- there is no disco nesting for it + if namespace ~= _DISCO_REMAINING then + disco_nest(namespace, entity_jid) + end end end end @@ -144,7 +156,7 @@ end if module:get_host_type() ~= "component" then - connected_cb:add(on_component_connected) + connected_cb:add(on_component_connected) end module:hook('component-authenticated', on_component_auth) module:hook('presence/initial', on_presence) @@ -186,7 +198,7 @@ local iq = forwarded.tags[1] if #forwarded ~= 1 or iq.name ~= "iq" or - iq.attr.xmlns ~= 'jabber:client' or + iq.attr.xmlns ~= 'jabber:client' or (iq.attr.type =='result' and #iq > 1) or (iq.attr.type == 'error' and #iq > 2) then module:log("warn", "ignoring invalid iq result from managing entity %s", stanza.attr.from) @@ -194,13 +206,19 @@ return true end - iq.attr.xmlns = nil + iq.attr.xmlns = nil local original = stanza_cache[stanza.attr.from][stanza.attr.id] stanza_cache[stanza.attr.from][stanza.attr.id] = nil -- we get namespace from original and not iq -- because the namespace can be lacking in case of error local namespace = original.tags[1].attr.xmlns + + -- small hack for disco remaining feat + if namespace == _DISCO_ITEMS_NS then + namespace = _DISCO_REMAINING + end + local ns_data = ns_delegations[namespace] if stanza.attr.from ~= ns_data.connected or (iq.attr.type ~= "result" and iq.attr.type ~= "error") or @@ -213,7 +231,7 @@ -- at this point eveything is checked, -- and we (hopefully) can send the the result safely module:send(iq) - return true + return true end function managing_ent_error(event) @@ -228,7 +246,7 @@ stanza_cache[stanza.attr.from][stanza.attr.id] = nil module:log("warn", "Got an error after forwarding stanza to "..stanza.attr.from) module:send(st.error_reply(original, 'cancel', 'service-unavailable')) - return true + return true end local function forward_iq(stanza, ns_data) @@ -270,7 +288,7 @@ -- we must continue the normal bahaviour if not first_child.attr[attribute] then -- Filtered attribute is not present, we do normal workflow - return; + return end end end @@ -350,7 +368,7 @@ local function extension_added(event) local source, stanza = event.source, event.item local form_type = find_form_type(stanza) - if not form_type then return; end + if not form_type then return end for namespace, _ in pairs(ns_delegations) do if source ~= module and string.sub(form_type, 1, #namespace) == namespace then @@ -361,7 +379,7 @@ end -- for disco nesting (see ยง 7.2) we need to remove internal features --- we use handle_items as it allow to remove already added features +-- we use handle_items as it allows to remove already added features -- and catch the ones which can come later module:handle_items("feature", feature_added, function(_) end) module:handle_items("identity", identity_added, function(_) end, false) @@ -385,7 +403,7 @@ end module:unhook("iq-result/host/"..stanza.attr.id, disco_result) module:unhook("iq-error/host/"..stanza.attr.id, disco_error) - local query = stanza:get_child("query", _DISCO_NS) + local query = stanza:get_child("query", _DISCO_INFO_NS) if not query or not query.attr.node then session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) return true @@ -458,7 +476,7 @@ local node = prefix..namespace local iq = st.iq({from=module.host, to=entity_jid, type='get'}) - :tag('query', {xmlns=_DISCO_NS, node=node}) + :tag('query', {xmlns=_DISCO_INFO_NS, node=node}) local iq_id = iq.attr.id @@ -468,12 +486,14 @@ end end --- disco to bare jids special case +-- disco to bare jids special cases -module:hook("account-disco-info", function(event) +-- disco#info + +local function disco_hook(event) -- this event is called when a disco info request is done on a bare jid -- we get the final reply and filter delegated features/identities/extensions - local reply = event.reply; + local reply = event.reply reply.tags[1]:maptags(function(child) if child.name == 'feature' then local feature_ns = child.attr.var @@ -518,4 +538,59 @@ reply:add_child(stanza) end -end, -2^32); +end + +-- disco#items + +local function disco_items_node_hook(event) + -- check if node is not handled by server + -- and forward the disco request to suitable entity + if not event.exists then + -- this node is not handled by the server + local ns_data = ns_delegations[_DISCO_REMAINING] + if ns_data ~= nil then + -- remaining delegation is requested, we forward + forward_iq(event.stanza, ns_data) + -- and stop normal event handling + return true + end + end +end +module:hook("account-disco-items-node", disco_items_node_hook, -2^32) + +local function disco_items_hook(event) + -- FIXME: we forward all bare-jid disco-items requests (without node) which will replace any Prosody reply + -- for now it's OK because Prosody is not returning anything on request on bare jid + -- but to be properly done, any Prosody reply should be kept and managing entities items should be added (merged) to it. + -- account-disco-items can't be cancelled (return value of hooks are not checked in mod_disco), so corountine needs + -- to be used with util.async (to get the IQ result, merge items then return from the event) + local origin, stanza = event.origin, event.stanza; + if stanza.attr.type ~= "get" then return; end + local node = stanza.tags[1].attr.node; + local username = jid_split(stanza.attr.to) or origin.username; + if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then + if node == nil or node == "" then + local ns_data = ns_delegations[_DISCO_REMAINING] + if ns_data ~= nil then + forward_iq(event.stanza, ns_data) + return true + end + end + end +end +module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", disco_items_hook, 100) + +local function disco_items_raw_hook(event) + -- this method is called when account-disco-items-* event are not called + -- notably when a disco-item is done by an unsubscibed entity + -- (i.e. an entity doing a disco#item on an entity without having + -- presence subscription) + -- we forward the request to managing entity + -- it's the responsability of the managing entity to filter the items + local ns_data = ns_delegations[_DISCO_REMAINING] + if ns_data ~= nil then + forward_iq(event.stanza, ns_data) + return true + end +end +module:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", disco_items_raw_hook, -2^32)