Mercurial > prosody-modules
comparison mod_privilege/mod_privilege.lua @ 1725:d85d5b0bf977
Merge with Goffi
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Thu, 07 May 2015 23:39:54 +0200 |
| parents | ad7afcf86131 |
| children | 0d78bb31348e |
comparison
equal
deleted
inserted
replaced
| 1706:e4867211cddb | 1725:d85d5b0bf977 |
|---|---|
| 16 local hosts = prosody.hosts | 16 local hosts = prosody.hosts |
| 17 local full_sessions = prosody.full_sessions; | 17 local full_sessions = prosody.full_sessions; |
| 18 | 18 |
| 19 local priv_session = module:shared("/*/privilege/session") | 19 local priv_session = module:shared("/*/privilege/session") |
| 20 | 20 |
| 21 if priv_session.connected_cb == nil then | |
| 22 -- set used to have connected event listeners | |
| 23 -- which allows a host to react on events from | |
| 24 -- other hosts | |
| 25 priv_session.connected_cb = set.new() | |
| 26 end | |
| 27 local connected_cb = priv_session.connected_cb | |
| 28 | |
| 21 -- the folowing sets are used to forward presence stanza | 29 -- the folowing sets are used to forward presence stanza |
| 22 if not priv_session.presence_man_ent then | 30 -- the folowing sets are used to forward presence stanza |
| 23 priv_session.presence_man_ent = set.new() | 31 local presence_man_ent = set.new() |
| 24 end | 32 local presence_roster = set.new() |
| 25 local presence_man_ent = priv_session.presence_man_ent | |
| 26 if not priv_session.presence_roster then | |
| 27 priv_session.presence_roster = set.new() | |
| 28 end | |
| 29 local presence_roster = priv_session.presence_roster | |
| 30 | 33 |
| 31 local _ALLOWED_ROSTER = set.new({'none', 'get', 'set', 'both'}) | 34 local _ALLOWED_ROSTER = set.new({'none', 'get', 'set', 'both'}) |
| 32 local _ROSTER_GET_PERM = set.new({'get', 'both'}) | 35 local _ROSTER_GET_PERM = set.new({'get', 'both'}) |
| 33 local _ROSTER_SET_PERM = set.new({'set', 'both'}) | 36 local _ROSTER_SET_PERM = set.new({'set', 'both'}) |
| 34 local _ALLOWED_MESSAGE = set.new({'none', 'outgoing'}) | 37 local _ALLOWED_MESSAGE = set.new({'none', 'outgoing'}) |
| 42 module:log("debug", "Loading privileged entity module "); | 45 module:log("debug", "Loading privileged entity module "); |
| 43 | 46 |
| 44 | 47 |
| 45 --> Permissions management <-- | 48 --> Permissions management <-- |
| 46 | 49 |
| 47 privileges = module:get_option("privileged_entities", {}) | 50 local privileges = module:get_option("privileged_entities", {}) |
| 48 | 51 |
| 49 function advertise_perm(session, to_jid, perms) | 52 local function advertise_perm(session, to_jid, perms) |
| 50 -- send <message/> stanza to advertise permissions | 53 -- send <message/> stanza to advertise permissions |
| 51 -- as expained in § 4.2 | 54 -- as expained in § 4.2 |
| 52 local message = st.message({to=to_jid}) | 55 local message = st.message({from=module.host, to=to_jid}) |
| 53 :tag("privilege", {xmlns=_PRIV_ENT_NS}) | 56 :tag("privilege", {xmlns=_PRIV_ENT_NS}) |
| 54 | 57 |
| 55 for _, perm in pairs({'roster', 'message', 'presence'}) do | 58 for _, perm in pairs({'roster', 'message', 'presence'}) do |
| 56 if perms[perm] then | 59 if perms[perm] then |
| 57 message:tag("perm", {access=perm, type=perms[perm]}):up() | 60 message:tag("perm", {access=perm, type=perms[perm]}):up() |
| 58 end | 61 end |
| 59 end | 62 end |
| 60 session.send(message) | 63 session.send(message) |
| 61 end | 64 end |
| 62 | 65 |
| 63 function set_presence_perm_set(to_jid, perms) | 66 local function set_presence_perm_set(to_jid, perms) |
| 64 -- fill the global presence sets according to perms | 67 -- fill the presence sets according to perms |
| 65 if _PRESENCE_MANAGED:contains(perms.presence) then | 68 if _PRESENCE_MANAGED:contains(perms.presence) then |
| 66 presence_man_ent:add(to_jid) | 69 presence_man_ent:add(to_jid) |
| 67 end | 70 end |
| 68 if perms.presence == 'roster' then | 71 if perms.presence == 'roster' then |
| 69 presence_roster:add(to_jid) | 72 presence_roster:add(to_jid) |
| 70 end | 73 end |
| 71 end | 74 end |
| 72 | 75 |
| 73 function advertise_presences(session, to_jid, perms) | 76 local function advertise_presences(session, to_jid, perms) |
| 74 -- send presence status for already conencted entities | 77 -- send presence status for already conencted entities |
| 75 -- as explained in § 7.1 | 78 -- as explained in § 7.1 |
| 76 -- people in roster are probed only for active sessions | 79 -- people in roster are probed only for active sessions |
| 77 -- TODO: manage roster load for inactive sessions | 80 -- TODO: manage roster load for inactive sessions |
| 78 if not perms.presence then return; end | 81 if not perms.presence then return; end |
| 90 | 93 |
| 91 if user_session.roster then | 94 if user_session.roster then |
| 92 local bare_jid = jid.bare(user_session.full_jid) | 95 local bare_jid = jid.bare(user_session.full_jid) |
| 93 for entity, item in pairs(user_session.roster) do | 96 for entity, item in pairs(user_session.roster) do |
| 94 if entity~=false and entity~="pending" and (item.subscription=="both" or item.subscription=="to") then | 97 if entity~=false and entity~="pending" and (item.subscription=="both" or item.subscription=="to") then |
| 95 _, host = jid.split(entity) | 98 local _, host = jid.split(entity) |
| 96 if not hosts[host] then -- we don't probe jid from hosts we manage | 99 if not hosts[host] then -- we don't probe jid from hosts we manage |
| 97 -- using a table with entity as key avoid probing several time the same one | 100 -- using a table with entity as key avoid probing several time the same one |
| 98 to_probe[entity] = bare_jid | 101 to_probe[entity] = bare_jid |
| 99 end | 102 end |
| 100 end | 103 end |
| 102 end | 105 end |
| 103 end | 106 end |
| 104 end | 107 end |
| 105 | 108 |
| 106 -- now we probe peoples for "roster" presence permission | 109 -- now we probe peoples for "roster" presence permission |
| 107 for to_jid, from_jid in pairs(to_probe) do | 110 for probe_to, probe_from in pairs(to_probe) do |
| 108 module:log("debug", "probing presence for %s (on behalf of %s)", tostring(to_jid), tostring(from_jid)) | 111 module:log("debug", "probing presence for %s (on behalf of %s)", tostring(probe_to), tostring(probe_from)) |
| 109 local probe = st.presence({from=from_jid, to=to_jid, type="probe"}) | 112 local probe = st.presence({from=probe_from, to=probe_to, type="probe"}) |
| 110 prosody.core_route_stanza(nil, probe) | 113 prosody.core_route_stanza(nil, probe) |
| 111 end | 114 end |
| 112 end | 115 end |
| 113 | 116 |
| 114 function on_auth(event) | 117 local function on_auth(event) |
| 115 -- Check if entity is privileged according to configuration, | 118 -- Check if entity is privileged according to configuration, |
| 116 -- and set session.privileges accordingly | 119 -- and set session.privileges accordingly |
| 117 | 120 |
| 118 local session = event.session | 121 local session = event.session |
| 119 local bare_jid = jid.join(session.username, session.host) | 122 local bare_jid = jid.join(session.username, session.host) |
| 136 end | 139 end |
| 137 -- extra checks for presence permission | 140 -- extra checks for presence permission |
| 138 if ent_priv.permission == 'roster' and not _ROSTER_GET_PERM:contains(session.privileges.roster) then | 141 if ent_priv.permission == 'roster' and not _ROSTER_GET_PERM:contains(session.privileges.roster) then |
| 139 module:log("warn", "Can't allow roster presence privilege without roster \"get\" privilege") | 142 module:log("warn", "Can't allow roster presence privilege without roster \"get\" privilege") |
| 140 module:log("warn", "Setting presence permission to none") | 143 module:log("warn", "Setting presence permission to none") |
| 141 end_priv.permission = nil | 144 ent_priv.permission = nil |
| 142 end | 145 end |
| 143 | 146 |
| 144 if session.type == "component" then | 147 if session.type == "component" then |
| 145 -- we send the message stanza only for component | 148 -- we send the message stanza only for component |
| 146 -- it will be sent at first <presence/> for other entities | 149 -- it will be sent at first <presence/> for other entities |
| 151 end | 154 end |
| 152 | 155 |
| 153 session.privileges = ent_priv | 156 session.privileges = ent_priv |
| 154 end | 157 end |
| 155 | 158 |
| 156 function on_presence(event) | 159 local function on_presence(event) |
| 157 -- Permission are already checked at this point, | 160 -- Permission are already checked at this point, |
| 158 -- we only advertise them to the entity | 161 -- we only advertise them to the entity |
| 159 local session, stanza = event.origin, event.stanza; | 162 local session = event.origin |
| 160 if session.privileges then | 163 if session.privileges then |
| 161 advertise_perm(session, session.full_jid, session.privileges) | 164 advertise_perm(session, session.full_jid, session.privileges) |
| 162 set_presence_perm_set(session.full_jid, session.privileges) | 165 set_presence_perm_set(session.full_jid, session.privileges) |
| 163 advertise_presences(session, session.full_jid, session.privileges) | 166 advertise_presences(session, session.full_jid, session.privileges) |
| 164 end | 167 end |
| 165 end | 168 end |
| 166 | 169 |
| 170 local function on_component_auth(event) | |
| 171 -- react to component-authenticated event from this host | |
| 172 -- and call the on_auth methods from all other hosts | |
| 173 -- needed for the component to get delegations advertising | |
| 174 for callback in connected_cb:items() do | |
| 175 callback(event) | |
| 176 end | |
| 177 end | |
| 178 | |
| 179 connected_cb:add(on_auth) | |
| 167 module:hook('authentication-success', on_auth) | 180 module:hook('authentication-success', on_auth) |
| 168 module:hook('component-authenticated', on_auth) | 181 module:hook('component-authenticated', on_component_auth) |
| 169 module:hook('presence/initial', on_presence) | 182 module:hook('presence/initial', on_presence) |
| 170 | 183 |
| 171 | 184 |
| 172 --> roster permission <-- | 185 --> roster permission <-- |
| 173 | 186 |
| 186 local roster = roster_manager.load_roster(node, host); | 199 local roster = roster_manager.load_roster(node, host); |
| 187 | 200 |
| 188 local reply = st.reply(stanza):query("jabber:iq:roster"); | 201 local reply = st.reply(stanza):query("jabber:iq:roster"); |
| 189 for entity_jid, item in pairs(roster) do | 202 for entity_jid, item in pairs(roster) do |
| 190 if entity_jid and entity_jid ~= "pending" then | 203 if entity_jid and entity_jid ~= "pending" then |
| 191 local node, host = jid.split(entity_jid); | 204 reply:tag("item", { |
| 192 reply:tag("item", { | 205 jid = entity_jid, |
| 193 jid = entity_jid, | 206 subscription = item.subscription, |
| 194 subscription = item.subscription, | 207 ask = item.ask, |
| 195 ask = item.ask, | 208 name = item.name, |
| 196 name = item.name, | 209 }); |
| 197 }); | 210 for group in pairs(item.groups) do |
| 198 for group in pairs(item.groups) do | 211 reply:tag("group"):text(group):up(); |
| 199 reply:tag("group"):text(group):up(); | 212 end |
| 200 end | 213 reply:up(); -- move out from item |
| 201 reply:up(); -- move out from item | |
| 202 end | 214 end |
| 203 end | 215 end |
| 204 -- end of code adapted from mod_remote_roster | 216 -- end of code adapted from mod_remote_roster |
| 205 session.send(reply); | 217 session.send(reply); |
| 206 else | 218 else |
| 226 if not(user_manager.user_exists(from_node, from_host)) then return; end | 238 if not(user_manager.user_exists(from_node, from_host)) then return; end |
| 227 local roster = roster_manager.load_roster(from_node, from_host); | 239 local roster = roster_manager.load_roster(from_node, from_host); |
| 228 if not(roster) then return; end | 240 if not(roster) then return; end |
| 229 | 241 |
| 230 local query = stanza.tags[1]; | 242 local query = stanza.tags[1]; |
| 231 for i_, item in ipairs(query.tags) do | 243 for _, item in ipairs(query.tags) do |
| 232 if item.name == "item" | 244 if item.name == "item" |
| 233 and item.attr.xmlns == "jabber:iq:roster" and item.attr.jid | 245 and item.attr.xmlns == "jabber:iq:roster" and item.attr.jid |
| 234 -- Protection against overwriting roster.pending, until we move it | 246 -- Protection against overwriting roster.pending, until we move it |
| 235 and item.attr.jid ~= "pending" then | 247 and item.attr.jid ~= "pending" then |
| 236 | 248 |
| 237 local item_jid = jid.prep(item.attr.jid); | 249 local item_jid = jid.prep(item.attr.jid); |
| 238 local node, host, resource = jid.split(item_jid); | 250 local _, host, resource = jid.split(item_jid); |
| 239 if not resource then | 251 if not resource then |
| 240 if item_jid ~= stanza.attr.to then -- not self-item_jid | 252 if item_jid ~= stanza.attr.to then -- not self-item_jid |
| 241 if item.attr.subscription == "remove" then | 253 if item.attr.subscription == "remove" then |
| 242 local r_item = roster[item_jid]; | 254 local r_item = roster[item_jid]; |
| 243 if r_item then | 255 if r_item then |
| 244 local to_bare = node and (node.."@"..host) or host; -- bare jid | |
| 245 roster[item_jid] = nil; | 256 roster[item_jid] = nil; |
| 246 if roster_manager.save_roster(from_node, from_host, roster) then | 257 if roster_manager.save_roster(from_node, from_host, roster) then |
| 247 session.send(st.reply(stanza)); | 258 session.send(st.reply(stanza)); |
| 248 roster_manager.roster_push(from_node, from_host, item_jid); | 259 roster_manager.roster_push(from_node, from_host, item_jid); |
| 249 else | 260 else |
| 313 if session.privileges and session.privileges.message=="outgoing" then | 324 if session.privileges and session.privileges.message=="outgoing" then |
| 314 if #privilege_elt.tags==1 and privilege_elt.tags[1].name == "forwarded" | 325 if #privilege_elt.tags==1 and privilege_elt.tags[1].name == "forwarded" |
| 315 and privilege_elt.tags[1].attr.xmlns==_FORWARDED_NS then | 326 and privilege_elt.tags[1].attr.xmlns==_FORWARDED_NS then |
| 316 local message_elt = privilege_elt.tags[1]:get_child('message', 'jabber:client') | 327 local message_elt = privilege_elt.tags[1]:get_child('message', 'jabber:client') |
| 317 if message_elt ~= nil then | 328 if message_elt ~= nil then |
| 318 local from_node, from_host, from_resource = jid.split(message_elt.attr.from) | 329 local _, from_host, from_resource = jid.split(message_elt.attr.from) |
| 319 if from_resource == nil and hosts[from_host] then -- we only accept bare jids from one of the server hosts | 330 if from_resource == nil and hosts[from_host] then -- we only accept bare jids from one of the server hosts |
| 320 -- at this point everything should be alright, we can send the message | 331 -- at this point everything should be alright, we can send the message |
| 321 prosody.core_route_stanza(nil, message_elt) | 332 prosody.core_route_stanza(nil, message_elt) |
| 322 else -- trying to send a message from a forbidden entity | 333 else -- trying to send a message from a forbidden entity |
| 323 module:log("warn", "Entity "..tostring(session.full_jid).." try to send a message from "..tostring(message_elt.attr.from)) | 334 module:log("warn", "Entity "..tostring(session.full_jid).." try to send a message from "..tostring(message_elt.attr.from)) |
| 338 end); | 349 end); |
| 339 | 350 |
| 340 | 351 |
| 341 --> presence permission <-- | 352 --> presence permission <-- |
| 342 | 353 |
| 343 function same_tags(tag1, tag2) | 354 local function same_tags(tag1, tag2) |
| 344 -- check if two tags are equivalent | 355 -- check if two tags are equivalent |
| 345 | 356 |
| 346 if tag1.name ~= tag2.name then return false; end | 357 if tag1.name ~= tag2.name then return false; end |
| 347 | 358 |
| 348 if #tag1 ~= #tag2 then return false; end | 359 if #tag1 ~= #tag2 then return false; end |
| 360 end | 371 end |
| 361 | 372 |
| 362 return true | 373 return true |
| 363 end | 374 end |
| 364 | 375 |
| 365 function same_presences(presence1, presence2) | 376 local function same_presences(presence1, presence2) |
| 366 -- check that 2 <presence/> stanzas are equivalent (except for "to" attribute) | 377 -- check that 2 <presence/> stanzas are equivalent (except for "to" attribute) |
| 367 -- /!\ if the id change but everything else is equivalent, this method return false | 378 -- /!\ if the id change but everything else is equivalent, this method return false |
| 368 -- this behaviour may change in the future | 379 -- this behaviour may change in the future |
| 369 if presence1.attr.from ~= presence2.attr.from or presence1.attr.id ~= presence2.attr.id | 380 if presence1.attr.from ~= presence2.attr.from or presence1.attr.id ~= presence2.attr.id |
| 370 or presence1.attr.type ~= presence2.attr.type then | 381 or presence1.attr.type ~= presence2.attr.type then |
| 384 end | 395 end |
| 385 | 396 |
| 386 return true | 397 return true |
| 387 end | 398 end |
| 388 | 399 |
| 389 function forward_presence(presence, to_jid) | 400 local function forward_presence(presence, to_jid) |
| 390 presence_fwd = st.clone(presence) | 401 local presence_fwd = st.clone(presence) |
| 391 presence_fwd.attr.to = to_jid | 402 presence_fwd.attr.to = to_jid |
| 392 module:log("debug", "presence forwarded to "..to_jid..": "..tostring(presence_fwd)) | 403 module:log("debug", "presence forwarded to "..to_jid..": "..tostring(presence_fwd)) |
| 393 module:send(presence_fwd) | 404 module:send(presence_fwd) |
| 394 -- cache used to avoid to send several times the same stanza | 405 -- cache used to avoid to send several times the same stanza |
| 395 priv_session.last_presence = presence | 406 priv_session.last_presence = presence |
| 396 end | 407 end |
| 397 | 408 |
| 398 module:hook("presence/bare", function(event) | 409 module:hook("presence/bare", function(event) |
| 399 if presence_man_ent:empty() and presence_roster:empty() then return; end | 410 if presence_man_ent:empty() and presence_roster:empty() then return; end |
| 400 | 411 |
| 401 local session, stanza = event.origin, event.stanza; | 412 local stanza = event.stanza |
| 402 if stanza.attr.type == nil or stanza.attr.type == "unavailable" then | 413 if stanza.attr.type == nil or stanza.attr.type == "unavailable" then |
| 403 if not stanza.attr.to then | 414 if not stanza.attr.to then |
| 404 for entity in presence_man_ent:items() do | 415 for entity in presence_man_ent:items() do |
| 405 if stanza.attr.from ~= entity then forward_presence(stanza, entity); end | 416 if stanza.attr.from ~= entity then forward_presence(stanza, entity); end |
| 406 end | 417 end |
| 407 else -- directed presence | 418 else -- directed presence |
| 408 -- we ignore directed presences from our own host, as we already have them | 419 -- we ignore directed presences from our own host, as we already have them |
| 409 _, from_host = jid.split(stanza.attr.from) | 420 local _, from_host = jid.split(stanza.attr.from) |
| 410 if hosts[from_host] then return; end | 421 if hosts[from_host] then return; end |
| 411 | 422 |
| 412 -- we don't send several time the same presence, as recommended in §7 #2 | 423 -- we don't send several time the same presence, as recommended in §7 #2 |
| 413 if priv_session.last_presence and same_presences(priv_session.last_presence, stanza) then | 424 if priv_session.last_presence and same_presences(priv_session.last_presence, stanza) then |
| 414 return | 425 return |
