Mercurial > prosody-modules
comparison mod_pep_plus/util_pubsub.lib.lua @ 2801:cb2342cf3f3c
mod_pep_plus: Snapshot from Prosody trunk 910d3c3f60a6 including dependencies
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Wed, 18 Oct 2017 09:56:29 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 2800:8d9aed6d1f87 | 2801:cb2342cf3f3c |
|---|---|
| 1 local events = require "util.events"; | |
| 2 local cache = require "util.cache"; | |
| 3 | |
| 4 local service = {}; | |
| 5 local service_mt = { __index = service }; | |
| 6 | |
| 7 local default_config = { __index = { | |
| 8 itemstore = function (config, _) return cache.new(config["max_items"]) end; | |
| 9 broadcaster = function () end; | |
| 10 get_affiliation = function () end; | |
| 11 capabilities = {}; | |
| 12 } }; | |
| 13 local default_node_config = { __index = { | |
| 14 ["persist_items"] = false; | |
| 15 ["max_items"] = 20; | |
| 16 } }; | |
| 17 | |
| 18 local function new(config) | |
| 19 config = config or {}; | |
| 20 return setmetatable({ | |
| 21 config = setmetatable(config, default_config); | |
| 22 node_defaults = setmetatable(config.node_defaults or {}, default_node_config); | |
| 23 affiliations = {}; | |
| 24 subscriptions = {}; | |
| 25 nodes = {}; | |
| 26 data = {}; | |
| 27 events = events.new(); | |
| 28 }, service_mt); | |
| 29 end | |
| 30 | |
| 31 function service:jids_equal(jid1, jid2) | |
| 32 local normalize = self.config.normalize_jid; | |
| 33 return normalize(jid1) == normalize(jid2); | |
| 34 end | |
| 35 | |
| 36 function service:may(node, actor, action) | |
| 37 if actor == true then return true; end | |
| 38 | |
| 39 local node_obj = self.nodes[node]; | |
| 40 local node_aff = node_obj and node_obj.affiliations[actor]; | |
| 41 local service_aff = self.affiliations[actor] | |
| 42 or self.config.get_affiliation(actor, node, action) | |
| 43 or "none"; | |
| 44 | |
| 45 -- Check if node allows/forbids it | |
| 46 local node_capabilities = node_obj and node_obj.capabilities; | |
| 47 if node_capabilities then | |
| 48 local caps = node_capabilities[node_aff or service_aff]; | |
| 49 if caps then | |
| 50 local can = caps[action]; | |
| 51 if can ~= nil then | |
| 52 return can; | |
| 53 end | |
| 54 end | |
| 55 end | |
| 56 | |
| 57 -- Check service-wide capabilities instead | |
| 58 local service_capabilities = self.config.capabilities; | |
| 59 local caps = service_capabilities[node_aff or service_aff]; | |
| 60 if caps then | |
| 61 local can = caps[action]; | |
| 62 if can ~= nil then | |
| 63 return can; | |
| 64 end | |
| 65 end | |
| 66 | |
| 67 return false; | |
| 68 end | |
| 69 | |
| 70 function service:set_affiliation(node, actor, jid, affiliation) | |
| 71 -- Access checking | |
| 72 if not self:may(node, actor, "set_affiliation") then | |
| 73 return false, "forbidden"; | |
| 74 end | |
| 75 -- | |
| 76 local node_obj = self.nodes[node]; | |
| 77 if not node_obj then | |
| 78 return false, "item-not-found"; | |
| 79 end | |
| 80 node_obj.affiliations[jid] = affiliation; | |
| 81 local _, jid_sub = self:get_subscription(node, true, jid); | |
| 82 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then | |
| 83 local ok, err = self:add_subscription(node, true, jid); | |
| 84 if not ok then | |
| 85 return ok, err; | |
| 86 end | |
| 87 elseif jid_sub and not self:may(node, jid, "be_subscribed") then | |
| 88 local ok, err = self:add_subscription(node, true, jid); | |
| 89 if not ok then | |
| 90 return ok, err; | |
| 91 end | |
| 92 end | |
| 93 return true; | |
| 94 end | |
| 95 | |
| 96 function service:add_subscription(node, actor, jid, options) | |
| 97 -- Access checking | |
| 98 local cap; | |
| 99 if actor == true or jid == actor or self:jids_equal(actor, jid) then | |
| 100 cap = "subscribe"; | |
| 101 else | |
| 102 cap = "subscribe_other"; | |
| 103 end | |
| 104 if not self:may(node, actor, cap) then | |
| 105 return false, "forbidden"; | |
| 106 end | |
| 107 if not self:may(node, jid, "be_subscribed") then | |
| 108 return false, "forbidden"; | |
| 109 end | |
| 110 -- | |
| 111 local node_obj = self.nodes[node]; | |
| 112 if not node_obj then | |
| 113 if not self.config.autocreate_on_subscribe then | |
| 114 return false, "item-not-found"; | |
| 115 else | |
| 116 local ok, err = self:create(node, true); | |
| 117 if not ok then | |
| 118 return ok, err; | |
| 119 end | |
| 120 node_obj = self.nodes[node]; | |
| 121 end | |
| 122 end | |
| 123 node_obj.subscribers[jid] = options or true; | |
| 124 local normal_jid = self.config.normalize_jid(jid); | |
| 125 local subs = self.subscriptions[normal_jid]; | |
| 126 if subs then | |
| 127 if not subs[jid] then | |
| 128 subs[jid] = { [node] = true }; | |
| 129 else | |
| 130 subs[jid][node] = true; | |
| 131 end | |
| 132 else | |
| 133 self.subscriptions[normal_jid] = { [jid] = { [node] = true } }; | |
| 134 end | |
| 135 self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options }); | |
| 136 return true; | |
| 137 end | |
| 138 | |
| 139 function service:remove_subscription(node, actor, jid) | |
| 140 -- Access checking | |
| 141 local cap; | |
| 142 if actor == true or jid == actor or self:jids_equal(actor, jid) then | |
| 143 cap = "unsubscribe"; | |
| 144 else | |
| 145 cap = "unsubscribe_other"; | |
| 146 end | |
| 147 if not self:may(node, actor, cap) then | |
| 148 return false, "forbidden"; | |
| 149 end | |
| 150 if not self:may(node, jid, "be_unsubscribed") then | |
| 151 return false, "forbidden"; | |
| 152 end | |
| 153 -- | |
| 154 local node_obj = self.nodes[node]; | |
| 155 if not node_obj then | |
| 156 return false, "item-not-found"; | |
| 157 end | |
| 158 if not node_obj.subscribers[jid] then | |
| 159 return false, "not-subscribed"; | |
| 160 end | |
| 161 node_obj.subscribers[jid] = nil; | |
| 162 local normal_jid = self.config.normalize_jid(jid); | |
| 163 local subs = self.subscriptions[normal_jid]; | |
| 164 if subs then | |
| 165 local jid_subs = subs[jid]; | |
| 166 if jid_subs then | |
| 167 jid_subs[node] = nil; | |
| 168 if next(jid_subs) == nil then | |
| 169 subs[jid] = nil; | |
| 170 end | |
| 171 end | |
| 172 if next(subs) == nil then | |
| 173 self.subscriptions[normal_jid] = nil; | |
| 174 end | |
| 175 end | |
| 176 self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid }); | |
| 177 return true; | |
| 178 end | |
| 179 | |
| 180 function service:remove_all_subscriptions(actor, jid) | |
| 181 local normal_jid = self.config.normalize_jid(jid); | |
| 182 local subs = self.subscriptions[normal_jid] | |
| 183 subs = subs and subs[jid]; | |
| 184 if subs then | |
| 185 for node in pairs(subs) do | |
| 186 self:remove_subscription(node, true, jid); | |
| 187 end | |
| 188 end | |
| 189 return true; | |
| 190 end | |
| 191 | |
| 192 function service:get_subscription(node, actor, jid) | |
| 193 -- Access checking | |
| 194 local cap; | |
| 195 if actor == true or jid == actor or self:jids_equal(actor, jid) then | |
| 196 cap = "get_subscription"; | |
| 197 else | |
| 198 cap = "get_subscription_other"; | |
| 199 end | |
| 200 if not self:may(node, actor, cap) then | |
| 201 return false, "forbidden"; | |
| 202 end | |
| 203 -- | |
| 204 local node_obj = self.nodes[node]; | |
| 205 if not node_obj then | |
| 206 return false, "item-not-found"; | |
| 207 end | |
| 208 return true, node_obj.subscribers[jid]; | |
| 209 end | |
| 210 | |
| 211 function service:create(node, actor, options) | |
| 212 -- Access checking | |
| 213 if not self:may(node, actor, "create") then | |
| 214 return false, "forbidden"; | |
| 215 end | |
| 216 -- | |
| 217 if self.nodes[node] then | |
| 218 return false, "conflict"; | |
| 219 end | |
| 220 | |
| 221 self.nodes[node] = { | |
| 222 name = node; | |
| 223 subscribers = {}; | |
| 224 config = setmetatable(options or {}, {__index=self.node_defaults}); | |
| 225 affiliations = {}; | |
| 226 }; | |
| 227 self.data[node] = self.config.itemstore(self.nodes[node].config, node); | |
| 228 self.events.fire_event("node-created", { node = node, actor = actor }); | |
| 229 local ok, err = self:set_affiliation(node, true, actor, "owner"); | |
| 230 if not ok then | |
| 231 self.nodes[node] = nil; | |
| 232 self.data[node] = nil; | |
| 233 end | |
| 234 return ok, err; | |
| 235 end | |
| 236 | |
| 237 function service:delete(node, actor) | |
| 238 -- Access checking | |
| 239 if not self:may(node, actor, "delete") then | |
| 240 return false, "forbidden"; | |
| 241 end | |
| 242 -- | |
| 243 local node_obj = self.nodes[node]; | |
| 244 if not node_obj then | |
| 245 return false, "item-not-found"; | |
| 246 end | |
| 247 self.nodes[node] = nil; | |
| 248 if self.data[node] and self.data[node].clear then | |
| 249 self.data[node]:clear(); | |
| 250 end | |
| 251 self.data[node] = nil; | |
| 252 self.events.fire_event("node-deleted", { node = node, actor = actor }); | |
| 253 self.config.broadcaster("delete", node, node_obj.subscribers); | |
| 254 return true; | |
| 255 end | |
| 256 | |
| 257 function service:publish(node, actor, id, item) | |
| 258 -- Access checking | |
| 259 if not self:may(node, actor, "publish") then | |
| 260 return false, "forbidden"; | |
| 261 end | |
| 262 -- | |
| 263 local node_obj = self.nodes[node]; | |
| 264 if not node_obj then | |
| 265 if not self.config.autocreate_on_publish then | |
| 266 return false, "item-not-found"; | |
| 267 end | |
| 268 local ok, err = self:create(node, true); | |
| 269 if not ok then | |
| 270 return ok, err; | |
| 271 end | |
| 272 node_obj = self.nodes[node]; | |
| 273 end | |
| 274 local node_data = self.data[node]; | |
| 275 local ok = node_data:set(id, item); | |
| 276 if not ok then | |
| 277 return nil, "internal-server-error"; | |
| 278 end | |
| 279 if type(ok) == "string" then id = ok; end | |
| 280 self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item }); | |
| 281 self.config.broadcaster("items", node, node_obj.subscribers, item, actor); | |
| 282 return true; | |
| 283 end | |
| 284 | |
| 285 function service:retract(node, actor, id, retract) | |
| 286 -- Access checking | |
| 287 if not self:may(node, actor, "retract") then | |
| 288 return false, "forbidden"; | |
| 289 end | |
| 290 -- | |
| 291 local node_obj = self.nodes[node]; | |
| 292 if (not node_obj) or (not self.data[node]:get(id)) then | |
| 293 return false, "item-not-found"; | |
| 294 end | |
| 295 local ok = self.data[node]:set(id, nil); | |
| 296 if not ok then | |
| 297 return nil, "internal-server-error"; | |
| 298 end | |
| 299 self.events.fire_event("item-retracted", { node = node, actor = actor, id = id }); | |
| 300 if retract then | |
| 301 self.config.broadcaster("items", node, node_obj.subscribers, retract); | |
| 302 end | |
| 303 return true | |
| 304 end | |
| 305 | |
| 306 function service:purge(node, actor, notify) | |
| 307 -- Access checking | |
| 308 if not self:may(node, actor, "retract") then | |
| 309 return false, "forbidden"; | |
| 310 end | |
| 311 -- | |
| 312 local node_obj = self.nodes[node]; | |
| 313 if not node_obj then | |
| 314 return false, "item-not-found"; | |
| 315 end | |
| 316 if self.data[node] and self.data[node].clear then | |
| 317 self.data[node]:clear() | |
| 318 else | |
| 319 self.data[node] = self.config.itemstore(self.nodes[node].config, node); | |
| 320 end | |
| 321 self.events.fire_event("node-purged", { node = node, actor = actor }); | |
| 322 if notify then | |
| 323 self.config.broadcaster("purge", node, node_obj.subscribers); | |
| 324 end | |
| 325 return true | |
| 326 end | |
| 327 | |
| 328 function service:get_items(node, actor, id) | |
| 329 -- Access checking | |
| 330 if not self:may(node, actor, "get_items") then | |
| 331 return false, "forbidden"; | |
| 332 end | |
| 333 -- | |
| 334 local node_obj = self.nodes[node]; | |
| 335 if not node_obj then | |
| 336 return false, "item-not-found"; | |
| 337 end | |
| 338 if id then -- Restrict results to a single specific item | |
| 339 local with_id = self.data[node]:get(id); | |
| 340 if not with_id then | |
| 341 return false, "item-not-found"; | |
| 342 end | |
| 343 return true, { id, [id] = with_id }; | |
| 344 else | |
| 345 local data = {} | |
| 346 for key, value in self.data[node]:items() do | |
| 347 data[#data+1] = key; | |
| 348 data[key] = value; | |
| 349 end | |
| 350 return true, data; | |
| 351 end | |
| 352 end | |
| 353 | |
| 354 function service:get_nodes(actor) | |
| 355 -- Access checking | |
| 356 if not self:may(nil, actor, "get_nodes") then | |
| 357 return false, "forbidden"; | |
| 358 end | |
| 359 -- | |
| 360 return true, self.nodes; | |
| 361 end | |
| 362 | |
| 363 function service:get_subscriptions(node, actor, jid) | |
| 364 -- Access checking | |
| 365 local cap; | |
| 366 if actor == true or jid == actor or self:jids_equal(actor, jid) then | |
| 367 cap = "get_subscriptions"; | |
| 368 else | |
| 369 cap = "get_subscriptions_other"; | |
| 370 end | |
| 371 if not self:may(node, actor, cap) then | |
| 372 return false, "forbidden"; | |
| 373 end | |
| 374 -- | |
| 375 local node_obj; | |
| 376 if node then | |
| 377 node_obj = self.nodes[node]; | |
| 378 if not node_obj then | |
| 379 return false, "item-not-found"; | |
| 380 end | |
| 381 end | |
| 382 local normal_jid = self.config.normalize_jid(jid); | |
| 383 local subs = self.subscriptions[normal_jid]; | |
| 384 -- We return the subscription object from the node to save | |
| 385 -- a get_subscription() call for each node. | |
| 386 local ret = {}; | |
| 387 if subs then | |
| 388 for subscribed_jid, subscribed_nodes in pairs(subs) do | |
| 389 if node then -- Return only subscriptions to this node | |
| 390 if subscribed_nodes[node] then | |
| 391 ret[#ret+1] = { | |
| 392 node = node; | |
| 393 jid = subscribed_jid; | |
| 394 subscription = node_obj.subscribers[subscribed_jid]; | |
| 395 }; | |
| 396 end | |
| 397 else -- Return subscriptions to all nodes | |
| 398 local nodes = self.nodes; | |
| 399 for subscribed_node in pairs(subscribed_nodes) do | |
| 400 ret[#ret+1] = { | |
| 401 node = subscribed_node; | |
| 402 jid = subscribed_jid; | |
| 403 subscription = nodes[subscribed_node].subscribers[subscribed_jid]; | |
| 404 }; | |
| 405 end | |
| 406 end | |
| 407 end | |
| 408 end | |
| 409 return true, ret; | |
| 410 end | |
| 411 | |
| 412 -- Access models only affect 'none' affiliation caps, service/default access level... | |
| 413 function service:set_node_capabilities(node, actor, capabilities) | |
| 414 -- Access checking | |
| 415 if not self:may(node, actor, "configure") then | |
| 416 return false, "forbidden"; | |
| 417 end | |
| 418 -- | |
| 419 local node_obj = self.nodes[node]; | |
| 420 if not node_obj then | |
| 421 return false, "item-not-found"; | |
| 422 end | |
| 423 node_obj.capabilities = capabilities; | |
| 424 return true; | |
| 425 end | |
| 426 | |
| 427 function service:set_node_config(node, actor, new_config) | |
| 428 if not self:may(node, actor, "configure") then | |
| 429 return false, "forbidden"; | |
| 430 end | |
| 431 | |
| 432 local node_obj = self.nodes[node]; | |
| 433 if not node_obj then | |
| 434 return false, "item-not-found"; | |
| 435 end | |
| 436 | |
| 437 for k,v in pairs(new_config) do | |
| 438 node_obj.config[k] = v; | |
| 439 end | |
| 440 local new_data = self.config.itemstore(self.nodes[node].config, node); | |
| 441 for key, value in self.data[node]:items() do | |
| 442 new_data:set(key, value); | |
| 443 end | |
| 444 self.data[node] = new_data; | |
| 445 return true; | |
| 446 end | |
| 447 | |
| 448 return { | |
| 449 new = new; | |
| 450 }; |
