diff mod_delegation/mod_delegation.lua @ 2752:d0e75bf21d30

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.
author Goffi <goffi@goffi.org>
date Sun, 27 Aug 2017 20:46:04 +0200
parents cf9cd666ba00
children 82109d8eca41
line wrap: on
line diff
--- 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)