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) |
