Software /
code /
prosody-modules
Comparison
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 |
parent | 2069:cf9cd666ba00 |
child | 2758:82109d8eca41 |
comparison
equal
deleted
inserted
replaced
2710:956b75b0e9d9 | 2752:d0e75bf21d30 |
---|---|
13 local st = require("util.stanza") | 13 local st = require("util.stanza") |
14 local set = require("util.set") | 14 local set = require("util.set") |
15 | 15 |
16 local delegation_session = module:shared("/*/delegation/session") | 16 local delegation_session = module:shared("/*/delegation/session") |
17 | 17 |
18 -- FIXME: temporarily needed for disco_items_hook, to be removed when clean implementation is done | |
19 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; | |
20 local jid_split = require "util.jid".split; | |
21 local jid_bare = require "util.jid".bare; | |
22 | |
18 if delegation_session.connected_cb == nil then | 23 if delegation_session.connected_cb == nil then |
19 -- set used to have connected event listeners | 24 -- set used to have connected event listeners |
20 -- which allow a host to react on events from | 25 -- which allow a host to react on events from |
21 -- other hosts | 26 -- other hosts |
22 delegation_session.connected_cb = set.new() | 27 delegation_session.connected_cb = set.new() |
23 end | 28 end |
24 local connected_cb = delegation_session.connected_cb | 29 local connected_cb = delegation_session.connected_cb |
25 | 30 |
26 local _DELEGATION_NS = 'urn:xmpp:delegation:1' | 31 local _DELEGATION_NS = 'urn:xmpp:delegation:1' |
27 local _FORWARDED_NS = 'urn:xmpp:forward:0' | 32 local _FORWARDED_NS = 'urn:xmpp:forward:0' |
28 local _DISCO_NS = 'http://jabber.org/protocol/disco#info' | 33 local _DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info' |
34 local _DISCO_ITEMS_NS = 'http://jabber.org/protocol/disco#items' | |
29 local _DATA_NS = 'jabber:x:data' | 35 local _DATA_NS = 'jabber:x:data' |
30 | 36 |
31 local _MAIN_SEP = '::' | 37 local _MAIN_SEP = '::' |
32 local _BARE_SEP = ':bare:' | 38 local _BARE_SEP = ':bare:' |
39 local _REMAINING = ':*' | |
33 local _MAIN_PREFIX = _DELEGATION_NS.._MAIN_SEP | 40 local _MAIN_PREFIX = _DELEGATION_NS.._MAIN_SEP |
34 local _BARE_PREFIX = _DELEGATION_NS.._BARE_SEP | 41 local _BARE_PREFIX = _DELEGATION_NS.._BARE_SEP |
42 local _DISCO_REMAINING = _DISCO_ITEMS_NS.._REMAINING | |
35 local _PREFIXES = {_MAIN_PREFIX, _BARE_PREFIX} | 43 local _PREFIXES = {_MAIN_PREFIX, _BARE_PREFIX} |
36 | 44 |
37 local disco_nest | 45 local disco_nest |
38 | 46 |
39 module:log("debug", "Loading namespace delegation module "); | 47 module:log("debug", "Loading namespace delegation module ") |
40 | 48 |
41 --> Configuration management <-- | 49 --> Configuration management <-- |
42 | 50 |
43 local ns_delegations = module:get_option("delegations", {}) | 51 local ns_delegations = module:get_option("delegations", {}) |
44 | 52 |
93 -- if the namespace has already a connected entity, ignore the new one | 101 -- if the namespace has already a connected entity, ignore the new one |
94 local function set_config(jid_) | 102 local function set_config(jid_) |
95 for namespace, ns_data in pairs(jid2ns[jid_]) do | 103 for namespace, ns_data in pairs(jid2ns[jid_]) do |
96 if ns_data.connected == nil then | 104 if ns_data.connected == nil then |
97 ns_data.connected = entity_jid | 105 ns_data.connected = entity_jid |
98 disco_nest(namespace, entity_jid) | 106 -- disco remaining is a special namespace |
107 -- there is no disco nesting for it | |
108 if namespace ~= _DISCO_REMAINING then | |
109 disco_nest(namespace, entity_jid) | |
110 end | |
99 end | 111 end |
100 end | 112 end |
101 end | 113 end |
102 local bare_jid = jid.bare(entity_jid) | 114 local bare_jid = jid.bare(entity_jid) |
103 set_config(bare_jid) | 115 set_config(bare_jid) |
142 callback(event) | 154 callback(event) |
143 end | 155 end |
144 end | 156 end |
145 | 157 |
146 if module:get_host_type() ~= "component" then | 158 if module:get_host_type() ~= "component" then |
147 connected_cb:add(on_component_connected) | 159 connected_cb:add(on_component_connected) |
148 end | 160 end |
149 module:hook('component-authenticated', on_component_auth) | 161 module:hook('component-authenticated', on_component_auth) |
150 module:hook('presence/initial', on_presence) | 162 module:hook('presence/initial', on_presence) |
151 | 163 |
152 | 164 |
184 return true | 196 return true |
185 end | 197 end |
186 | 198 |
187 local iq = forwarded.tags[1] | 199 local iq = forwarded.tags[1] |
188 if #forwarded ~= 1 or iq.name ~= "iq" or | 200 if #forwarded ~= 1 or iq.name ~= "iq" or |
189 iq.attr.xmlns ~= 'jabber:client' or | 201 iq.attr.xmlns ~= 'jabber:client' or |
190 (iq.attr.type =='result' and #iq > 1) or | 202 (iq.attr.type =='result' and #iq > 1) or |
191 (iq.attr.type == 'error' and #iq > 2) then | 203 (iq.attr.type == 'error' and #iq > 2) then |
192 module:log("warn", "ignoring invalid iq result from managing entity %s", stanza.attr.from) | 204 module:log("warn", "ignoring invalid iq result from managing entity %s", stanza.attr.from) |
193 stanza_cache[stanza.attr.from][stanza.attr.id] = nil | 205 stanza_cache[stanza.attr.from][stanza.attr.id] = nil |
194 return true | 206 return true |
195 end | 207 end |
196 | 208 |
197 iq.attr.xmlns = nil | 209 iq.attr.xmlns = nil |
198 | 210 |
199 local original = stanza_cache[stanza.attr.from][stanza.attr.id] | 211 local original = stanza_cache[stanza.attr.from][stanza.attr.id] |
200 stanza_cache[stanza.attr.from][stanza.attr.id] = nil | 212 stanza_cache[stanza.attr.from][stanza.attr.id] = nil |
201 -- we get namespace from original and not iq | 213 -- we get namespace from original and not iq |
202 -- because the namespace can be lacking in case of error | 214 -- because the namespace can be lacking in case of error |
203 local namespace = original.tags[1].attr.xmlns | 215 local namespace = original.tags[1].attr.xmlns |
216 | |
217 -- small hack for disco remaining feat | |
218 if namespace == _DISCO_ITEMS_NS then | |
219 namespace = _DISCO_REMAINING | |
220 end | |
221 | |
204 local ns_data = ns_delegations[namespace] | 222 local ns_data = ns_delegations[namespace] |
205 | 223 |
206 if stanza.attr.from ~= ns_data.connected or (iq.attr.type ~= "result" and iq.attr.type ~= "error") or | 224 if stanza.attr.from ~= ns_data.connected or (iq.attr.type ~= "result" and iq.attr.type ~= "error") or |
207 iq.attr.id ~= original.attr.id or iq.attr.to ~= original.attr.from then | 225 iq.attr.id ~= original.attr.id or iq.attr.to ~= original.attr.from then |
208 module:log("warn", "ignoring forbidden iq result from managing entity %s, please check that the component is no trying to do something bad (stanza: %s)", stanza.attr.from, tostring(stanza)) | 226 module:log("warn", "ignoring forbidden iq result from managing entity %s, please check that the component is no trying to do something bad (stanza: %s)", stanza.attr.from, tostring(stanza)) |
211 end | 229 end |
212 | 230 |
213 -- at this point eveything is checked, | 231 -- at this point eveything is checked, |
214 -- and we (hopefully) can send the the result safely | 232 -- and we (hopefully) can send the the result safely |
215 module:send(iq) | 233 module:send(iq) |
216 return true | 234 return true |
217 end | 235 end |
218 | 236 |
219 function managing_ent_error(event) | 237 function managing_ent_error(event) |
220 local stanza = event.stanza | 238 local stanza = event.stanza |
221 if stanza.attr.to ~= module.host then | 239 if stanza.attr.to ~= module.host then |
226 module:unhook("iq-error/host/"..stanza.attr.id, managing_ent_error) | 244 module:unhook("iq-error/host/"..stanza.attr.id, managing_ent_error) |
227 local original = stanza_cache[stanza.attr.from][stanza.attr.id] | 245 local original = stanza_cache[stanza.attr.from][stanza.attr.id] |
228 stanza_cache[stanza.attr.from][stanza.attr.id] = nil | 246 stanza_cache[stanza.attr.from][stanza.attr.id] = nil |
229 module:log("warn", "Got an error after forwarding stanza to "..stanza.attr.from) | 247 module:log("warn", "Got an error after forwarding stanza to "..stanza.attr.from) |
230 module:send(st.error_reply(original, 'cancel', 'service-unavailable')) | 248 module:send(st.error_reply(original, 'cancel', 'service-unavailable')) |
231 return true | 249 return true |
232 end | 250 end |
233 | 251 |
234 local function forward_iq(stanza, ns_data) | 252 local function forward_iq(stanza, ns_data) |
235 local to_jid = ns_data.connected | 253 local to_jid = ns_data.connected |
236 stanza.attr.xmlns = 'jabber:client' | 254 stanza.attr.xmlns = 'jabber:client' |
268 for _, attribute in pairs(ns_data.filtering) do | 286 for _, attribute in pairs(ns_data.filtering) do |
269 -- if any filtered attribute if not present, | 287 -- if any filtered attribute if not present, |
270 -- we must continue the normal bahaviour | 288 -- we must continue the normal bahaviour |
271 if not first_child.attr[attribute] then | 289 if not first_child.attr[attribute] then |
272 -- Filtered attribute is not present, we do normal workflow | 290 -- Filtered attribute is not present, we do normal workflow |
273 return; | 291 return |
274 end | 292 end |
275 end | 293 end |
276 end | 294 end |
277 if not ns_data.connected then | 295 if not ns_data.connected then |
278 module:log("warn", "No connected entity to manage "..namespace) | 296 module:log("warn", "No connected entity to manage "..namespace) |
348 end | 366 end |
349 | 367 |
350 local function extension_added(event) | 368 local function extension_added(event) |
351 local source, stanza = event.source, event.item | 369 local source, stanza = event.source, event.item |
352 local form_type = find_form_type(stanza) | 370 local form_type = find_form_type(stanza) |
353 if not form_type then return; end | 371 if not form_type then return end |
354 | 372 |
355 for namespace, _ in pairs(ns_delegations) do | 373 for namespace, _ in pairs(ns_delegations) do |
356 if source ~= module and string.sub(form_type, 1, #namespace) == namespace then | 374 if source ~= module and string.sub(form_type, 1, #namespace) == namespace then |
357 module:log("debug", "Removing extension which is delegated: %s", tostring(stanza)) | 375 module:log("debug", "Removing extension which is delegated: %s", tostring(stanza)) |
358 source:remove_item("extension", stanza) | 376 source:remove_item("extension", stanza) |
359 end | 377 end |
360 end | 378 end |
361 end | 379 end |
362 | 380 |
363 -- for disco nesting (see § 7.2) we need to remove internal features | 381 -- for disco nesting (see § 7.2) we need to remove internal features |
364 -- we use handle_items as it allow to remove already added features | 382 -- we use handle_items as it allows to remove already added features |
365 -- and catch the ones which can come later | 383 -- and catch the ones which can come later |
366 module:handle_items("feature", feature_added, function(_) end) | 384 module:handle_items("feature", feature_added, function(_) end) |
367 module:handle_items("identity", identity_added, function(_) end, false) | 385 module:handle_items("identity", identity_added, function(_) end, false) |
368 module:handle_items("extension", extension_added, function(_) end) | 386 module:handle_items("extension", extension_added, function(_) end) |
369 | 387 |
383 module:log("warn", 'Stanza result has "to" attribute not addressed to current host, id conflict ?') | 401 module:log("warn", 'Stanza result has "to" attribute not addressed to current host, id conflict ?') |
384 return | 402 return |
385 end | 403 end |
386 module:unhook("iq-result/host/"..stanza.attr.id, disco_result) | 404 module:unhook("iq-result/host/"..stanza.attr.id, disco_result) |
387 module:unhook("iq-error/host/"..stanza.attr.id, disco_error) | 405 module:unhook("iq-error/host/"..stanza.attr.id, disco_error) |
388 local query = stanza:get_child("query", _DISCO_NS) | 406 local query = stanza:get_child("query", _DISCO_INFO_NS) |
389 if not query or not query.attr.node then | 407 if not query or not query.attr.node then |
390 session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) | 408 session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) |
391 return true | 409 return true |
392 end | 410 end |
393 | 411 |
456 | 474 |
457 for _, prefix in ipairs(_PREFIXES) do | 475 for _, prefix in ipairs(_PREFIXES) do |
458 local node = prefix..namespace | 476 local node = prefix..namespace |
459 | 477 |
460 local iq = st.iq({from=module.host, to=entity_jid, type='get'}) | 478 local iq = st.iq({from=module.host, to=entity_jid, type='get'}) |
461 :tag('query', {xmlns=_DISCO_NS, node=node}) | 479 :tag('query', {xmlns=_DISCO_INFO_NS, node=node}) |
462 | 480 |
463 local iq_id = iq.attr.id | 481 local iq_id = iq.attr.id |
464 | 482 |
465 module:hook("iq-result/host/"..iq_id, disco_result) | 483 module:hook("iq-result/host/"..iq_id, disco_result) |
466 module:hook("iq-error/host/"..iq_id, disco_error) | 484 module:hook("iq-error/host/"..iq_id, disco_error) |
467 module:send(iq) | 485 module:send(iq) |
468 end | 486 end |
469 end | 487 end |
470 | 488 |
471 -- disco to bare jids special case | 489 -- disco to bare jids special cases |
472 | 490 |
473 module:hook("account-disco-info", function(event) | 491 -- disco#info |
492 | |
493 local function disco_hook(event) | |
474 -- this event is called when a disco info request is done on a bare jid | 494 -- this event is called when a disco info request is done on a bare jid |
475 -- we get the final reply and filter delegated features/identities/extensions | 495 -- we get the final reply and filter delegated features/identities/extensions |
476 local reply = event.reply; | 496 local reply = event.reply |
477 reply.tags[1]:maptags(function(child) | 497 reply.tags[1]:maptags(function(child) |
478 if child.name == 'feature' then | 498 if child.name == 'feature' then |
479 local feature_ns = child.attr.var | 499 local feature_ns = child.attr.var |
480 for namespace, _ in pairs(ns_delegations) do | 500 for namespace, _ in pairs(ns_delegations) do |
481 if string.sub(feature_ns, 1, #namespace) == namespace then | 501 if string.sub(feature_ns, 1, #namespace) == namespace then |
516 end | 536 end |
517 for _, stanza in ipairs(bare_extensions) do | 537 for _, stanza in ipairs(bare_extensions) do |
518 reply:add_child(stanza) | 538 reply:add_child(stanza) |
519 end | 539 end |
520 | 540 |
521 end, -2^32); | 541 end |
542 | |
543 -- disco#items | |
544 | |
545 local function disco_items_node_hook(event) | |
546 -- check if node is not handled by server | |
547 -- and forward the disco request to suitable entity | |
548 if not event.exists then | |
549 -- this node is not handled by the server | |
550 local ns_data = ns_delegations[_DISCO_REMAINING] | |
551 if ns_data ~= nil then | |
552 -- remaining delegation is requested, we forward | |
553 forward_iq(event.stanza, ns_data) | |
554 -- and stop normal event handling | |
555 return true | |
556 end | |
557 end | |
558 end | |
559 module:hook("account-disco-items-node", disco_items_node_hook, -2^32) | |
560 | |
561 local function disco_items_hook(event) | |
562 -- FIXME: we forward all bare-jid disco-items requests (without node) which will replace any Prosody reply | |
563 -- for now it's OK because Prosody is not returning anything on request on bare jid | |
564 -- but to be properly done, any Prosody reply should be kept and managing entities items should be added (merged) to it. | |
565 -- account-disco-items can't be cancelled (return value of hooks are not checked in mod_disco), so corountine needs | |
566 -- to be used with util.async (to get the IQ result, merge items then return from the event) | |
567 local origin, stanza = event.origin, event.stanza; | |
568 if stanza.attr.type ~= "get" then return; end | |
569 local node = stanza.tags[1].attr.node; | |
570 local username = jid_split(stanza.attr.to) or origin.username; | |
571 if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then | |
572 if node == nil or node == "" then | |
573 local ns_data = ns_delegations[_DISCO_REMAINING] | |
574 if ns_data ~= nil then | |
575 forward_iq(event.stanza, ns_data) | |
576 return true | |
577 end | |
578 end | |
579 end | |
580 end | |
581 module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", disco_items_hook, 100) | |
582 | |
583 local function disco_items_raw_hook(event) | |
584 -- this method is called when account-disco-items-* event are not called | |
585 -- notably when a disco-item is done by an unsubscibed entity | |
586 -- (i.e. an entity doing a disco#item on an entity without having | |
587 -- presence subscription) | |
588 -- we forward the request to managing entity | |
589 -- it's the responsability of the managing entity to filter the items | |
590 local ns_data = ns_delegations[_DISCO_REMAINING] | |
591 if ns_data ~= nil then | |
592 forward_iq(event.stanza, ns_data) | |
593 return true | |
594 end | |
595 end | |
596 module:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", disco_items_raw_hook, -2^32) |