Mercurial > prosody-modules
comparison mod_delegation/mod_delegation.lua @ 1710:b68eed25b880
mod_delegation: iq forwarding to managing entity
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 17 Apr 2015 21:05:08 +0200 |
parents | 59ba224bf145 |
children | 55b9ac807ac9 |
comparison
equal
deleted
inserted
replaced
1709:59ba224bf145 | 1710:b68eed25b880 |
---|---|
2 -- Copyright (C) 2015 Jérôme Poisson | 2 -- Copyright (C) 2015 Jérôme Poisson |
3 -- | 3 -- |
4 -- This module is MIT/X11 licensed. Please see the | 4 -- This module is MIT/X11 licensed. Please see the |
5 -- COPYING file in the source package for more information. | 5 -- COPYING file in the source package for more information. |
6 | 6 |
7 -- This module manage namespace delegation, a way to delegate server features | |
8 -- to an external entity/component. Only the admin mode is implemented so far | |
9 | |
10 -- TODO: client mode, managing entity error handling | |
7 | 11 |
8 local jid = require("util/jid") | 12 local jid = require("util/jid") |
9 local st = require("util/stanza") | 13 local st = require("util/stanza") |
10 local set = require("util/set") | 14 local set = require("util/set") |
11 | 15 |
18 delegation_session.connected_cb = set.new() | 22 delegation_session.connected_cb = set.new() |
19 end | 23 end |
20 local connected_cb = delegation_session.connected_cb | 24 local connected_cb = delegation_session.connected_cb |
21 | 25 |
22 local _DELEGATION_NS = 'urn:xmpp:delegation:1' | 26 local _DELEGATION_NS = 'urn:xmpp:delegation:1' |
23 -- local _FORWARDED_NS = 'urn:xmpp:forward:0' | 27 local _FORWARDED_NS = 'urn:xmpp:forward:0' |
24 | 28 local _ORI_ID_PREFIX = "IQ_RESULT_" |
25 | 29 |
26 module:log("debug", "Loading namespace delegation module "); | 30 module:log("debug", "Loading namespace delegation module "); |
27 | 31 |
28 | 32 |
29 --> Configuration management <-- | 33 --> Configuration management <-- |
30 | 34 |
31 local ns_delegations = module:get_option("delegations", {}) | 35 local ns_delegations = module:get_option("delegations", {}) |
32 | 36 |
33 local jid2ns = {} | 37 local jid2ns = {} |
34 for namespace, config in pairs(ns_delegations) do | 38 for namespace, ns_data in pairs(ns_delegations) do |
35 -- "connected" contain the full jid of connected managing entity | 39 -- "connected" contain the full jid of connected managing entity |
36 config.connected = nil | 40 ns_data.connected = nil |
37 if config.jid then | 41 if ns_data.jid then |
38 if jid2ns[config.jid] == nil then | 42 if jid2ns[ns_data.jid] == nil then |
39 jid2ns[config.jid] = {} | 43 jid2ns[ns_data.jid] = {} |
40 end | 44 end |
41 jid2ns[config.jid][namespace] = config | 45 jid2ns[ns_data.jid][namespace] = ns_data |
46 module:log("debug", "Namespace %s is delegated%s to %s", namespace, ns_data.filtering and " (with filtering)" or "", ns_data.jid) | |
42 else | 47 else |
43 module:log("warn", "Ignoring delegation for %s: no jid specified", tostring(namespace)) | 48 module:log("warn", "Ignoring delegation for %s: no jid specified", tostring(namespace)) |
44 ns_delegations[namespace] = nil | 49 ns_delegations[namespace] = nil |
45 end | 50 end |
46 end | 51 end |
55 -- we need to check if a delegation is granted because the configuration | 60 -- we need to check if a delegation is granted because the configuration |
56 -- can be complicated if some delegations are granted to bare jid | 61 -- can be complicated if some delegations are granted to bare jid |
57 -- and other to full jids, and several resources are connected. | 62 -- and other to full jids, and several resources are connected. |
58 local have_delegation = false | 63 local have_delegation = false |
59 | 64 |
60 for namespace, config in pairs(jid2ns[to_jid]) do | 65 for namespace, ns_data in pairs(jid2ns[to_jid]) do |
61 if config.connected == to_jid then | 66 if ns_data.connected == to_jid then |
62 have_delegation = true | 67 have_delegation = true |
63 message:tag("delegated", {namespace=namespace}) | 68 message:tag("delegated", {namespace=namespace}) |
64 if type(config.filtering) == "table" then | 69 if type(ns_data.filtering) == "table" then |
65 for _, attribute in pairs(config.filtering) do | 70 for _, attribute in pairs(ns_data.filtering) do |
66 message:tag("attribute", {name=attribute}):up() | 71 message:tag("attribute", {name=attribute}):up() |
67 end | 72 end |
68 message:up() | 73 message:up() |
69 end | 74 end |
70 end | 75 end |
77 | 82 |
78 local function set_connected(entity_jid) | 83 local function set_connected(entity_jid) |
79 -- set the "connected" key for all namespace managed by entity_jid | 84 -- set the "connected" key for all namespace managed by entity_jid |
80 -- if the namespace has already a connected entity, ignore the new one | 85 -- if the namespace has already a connected entity, ignore the new one |
81 local function set_config(jid_) | 86 local function set_config(jid_) |
82 for _, config in pairs(jid2ns[jid_]) do | 87 for _, ns_data in pairs(jid2ns[jid_]) do |
83 if config.connected == nil then | 88 if ns_data.connected == nil then |
84 config.connected = entity_jid | 89 ns_data.connected = entity_jid |
85 end | 90 end |
86 end | 91 end |
87 end | 92 end |
88 local bare_jid = jid.bare(entity_jid) | 93 local bare_jid = jid.bare(entity_jid) |
89 set_config(bare_jid) | 94 set_config(bare_jid) |
130 end | 135 end |
131 | 136 |
132 connected_cb:add(on_component_connected) | 137 connected_cb:add(on_component_connected) |
133 module:hook('component-authenticated', on_component_auth) | 138 module:hook('component-authenticated', on_component_auth) |
134 module:hook('presence/initial', on_presence) | 139 module:hook('presence/initial', on_presence) |
140 | |
141 | |
142 --> delegated namespaces hook <-- | |
143 | |
144 local function managing_ent_result(event) | |
145 -- this function manage iq results from the managing entity | |
146 -- it do a couple of security check before sending the | |
147 -- result to the managed entity | |
148 local session, stanza = event.origin, event.stanza | |
149 if stanza.attr.to ~= module.host then | |
150 module:log("warn", 'forwarded stanza result has "to" attribute not addressed to current host, id conflict ?') | |
151 return | |
152 end | |
153 module:unhook("iq-result/host/"..stanza.attr.id, managing_ent_result) | |
154 | |
155 -- lot of checks to do... | |
156 local delegation = stanza.tags[1] | |
157 if #stanza ~= 1 or delegation.name ~= "delegation" or | |
158 delegation.attr.xmlns ~= _DELEGATION_NS then | |
159 session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) | |
160 return true | |
161 end | |
162 | |
163 local forwarded = delegation.tags[1] | |
164 if #delegation ~= 1 or forwarded.name ~= "forwarded" or | |
165 forwarded.attr.xmlns ~= _FORWARDED_NS then | |
166 session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) | |
167 return true | |
168 end | |
169 | |
170 local iq = forwarded.tags[1] | |
171 if #forwarded ~= 1 or iq.name ~= "iq" or #iq ~= 1 then | |
172 session.send(st.error_reply(stanza, 'modify', 'not-acceptable')) | |
173 return true | |
174 end | |
175 | |
176 local namespace = iq.tags[1].xmlns | |
177 local ns_data = ns_delegations[namespace] | |
178 local original = ns_data[_ORI_ID_PREFIX..stanza.attr.id] | |
179 | |
180 if stanza.attr.from ~= ns_data.connected or iq.attr.type ~= "result" or | |
181 iq.attr.id ~= original.attr.id or iq.attr.to ~= original.attr.from then | |
182 session.send(st.error_reply(stanza, 'auth', 'forbidden')) | |
183 module:send(st.error_reply(original, 'cancel', 'service-unavailable')) | |
184 return true | |
185 end | |
186 | |
187 -- at this point eveything is checked, | |
188 -- and we (hopefully) can send the the result safely | |
189 module:send(iq) | |
190 end | |
191 | |
192 local function forward_iq(stanza, ns_data) | |
193 local to_jid = ns_data.connected | |
194 local iq_stanza = st.iq({ from=module.host, to=to_jid, type="set" }) | |
195 :tag("delegation", { xmlns=_DELEGATION_NS }) | |
196 :tag("forwarded", { xmlns=_FORWARDED_NS }) | |
197 :add_child(stanza) | |
198 local iq_id = iq_stanza.attr.id | |
199 -- we save the original stanza to check the managing entity result | |
200 ns_data[_ORI_ID_PREFIX..iq_id] = stanza | |
201 module:log("debug", "stanza forwarded to "..to_jid..": "..tostring(iq_stanza)) | |
202 module:hook("iq-result/host/"..iq_id, managing_ent_result) | |
203 module:send(iq_stanza) | |
204 end | |
205 | |
206 local function iq_hook(event) | |
207 -- general hook for all the iq which forward delegated ones | |
208 -- and continue normal behaviour else. If a namespace is | |
209 -- delegated but managing entity is offline, a service-unavailable | |
210 -- error will be sent, as requested by the XEP | |
211 local session, stanza = event.origin, event.stanza | |
212 if #stanza == 1 and stanza.attr.type == 'get' or stanza.attr.type == 'set' then | |
213 local namespace = stanza.tags[1].attr.xmlns | |
214 local ns_data = ns_delegations[namespace] | |
215 | |
216 if ns_data then | |
217 module:log("debug", "Namespace %s is delegated", namespace) | |
218 if ns_data.filtering then | |
219 local first_child = stanza.tags[1] | |
220 for _, attribute in ns_data.filtering do | |
221 -- if any filtered attribute if not present, | |
222 -- we must continue the normal bahaviour | |
223 if not first_child.attr[attribute] then | |
224 module:log("debug", "Filtered attribute %s not present, doing normal workflow", attribute) | |
225 return; | |
226 end | |
227 end | |
228 end | |
229 if not ns_data.connected then | |
230 module:log("warn", "No connected entity to manage "..namespace) | |
231 session.send(st.error_reply(stanza, 'cancel', 'service-unavailable')) | |
232 else | |
233 local managing_entity = ns_data.connected | |
234 module:log("debug", "Entity %s is managing %s", managing_entity, namespace) | |
235 forward_iq(stanza, ns_data) | |
236 end | |
237 return true | |
238 else | |
239 -- we have no delegation, we continue normal behaviour | |
240 return | |
241 end | |
242 end | |
243 end | |
244 | |
245 module:hook("iq/self", iq_hook, 2^32) | |
246 module:hook("iq/host", iq_hook, 2^32) |