Mercurial > prosody-modules
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_pep_plus/util_pubsub.lib.lua Wed Oct 18 09:56:29 2017 +0200 @@ -0,0 +1,450 @@ +local events = require "util.events"; +local cache = require "util.cache"; + +local service = {}; +local service_mt = { __index = service }; + +local default_config = { __index = { + itemstore = function (config, _) return cache.new(config["max_items"]) end; + broadcaster = function () end; + get_affiliation = function () end; + capabilities = {}; +} }; +local default_node_config = { __index = { + ["persist_items"] = false; + ["max_items"] = 20; +} }; + +local function new(config) + config = config or {}; + return setmetatable({ + config = setmetatable(config, default_config); + node_defaults = setmetatable(config.node_defaults or {}, default_node_config); + affiliations = {}; + subscriptions = {}; + nodes = {}; + data = {}; + events = events.new(); + }, service_mt); +end + +function service:jids_equal(jid1, jid2) + local normalize = self.config.normalize_jid; + return normalize(jid1) == normalize(jid2); +end + +function service:may(node, actor, action) + if actor == true then return true; end + + local node_obj = self.nodes[node]; + local node_aff = node_obj and node_obj.affiliations[actor]; + local service_aff = self.affiliations[actor] + or self.config.get_affiliation(actor, node, action) + or "none"; + + -- Check if node allows/forbids it + local node_capabilities = node_obj and node_obj.capabilities; + if node_capabilities then + local caps = node_capabilities[node_aff or service_aff]; + if caps then + local can = caps[action]; + if can ~= nil then + return can; + end + end + end + + -- Check service-wide capabilities instead + local service_capabilities = self.config.capabilities; + local caps = service_capabilities[node_aff or service_aff]; + if caps then + local can = caps[action]; + if can ~= nil then + return can; + end + end + + return false; +end + +function service:set_affiliation(node, actor, jid, affiliation) + -- Access checking + if not self:may(node, actor, "set_affiliation") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + node_obj.affiliations[jid] = affiliation; + local _, jid_sub = self:get_subscription(node, true, jid); + if not jid_sub and not self:may(node, jid, "be_unsubscribed") then + local ok, err = self:add_subscription(node, true, jid); + if not ok then + return ok, err; + end + elseif jid_sub and not self:may(node, jid, "be_subscribed") then + local ok, err = self:add_subscription(node, true, jid); + if not ok then + return ok, err; + end + end + return true; +end + +function service:add_subscription(node, actor, jid, options) + -- Access checking + local cap; + if actor == true or jid == actor or self:jids_equal(actor, jid) then + cap = "subscribe"; + else + cap = "subscribe_other"; + end + if not self:may(node, actor, cap) then + return false, "forbidden"; + end + if not self:may(node, jid, "be_subscribed") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + if not self.config.autocreate_on_subscribe then + return false, "item-not-found"; + else + local ok, err = self:create(node, true); + if not ok then + return ok, err; + end + node_obj = self.nodes[node]; + end + end + node_obj.subscribers[jid] = options or true; + local normal_jid = self.config.normalize_jid(jid); + local subs = self.subscriptions[normal_jid]; + if subs then + if not subs[jid] then + subs[jid] = { [node] = true }; + else + subs[jid][node] = true; + end + else + self.subscriptions[normal_jid] = { [jid] = { [node] = true } }; + end + self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options }); + return true; +end + +function service:remove_subscription(node, actor, jid) + -- Access checking + local cap; + if actor == true or jid == actor or self:jids_equal(actor, jid) then + cap = "unsubscribe"; + else + cap = "unsubscribe_other"; + end + if not self:may(node, actor, cap) then + return false, "forbidden"; + end + if not self:may(node, jid, "be_unsubscribed") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + if not node_obj.subscribers[jid] then + return false, "not-subscribed"; + end + node_obj.subscribers[jid] = nil; + local normal_jid = self.config.normalize_jid(jid); + local subs = self.subscriptions[normal_jid]; + if subs then + local jid_subs = subs[jid]; + if jid_subs then + jid_subs[node] = nil; + if next(jid_subs) == nil then + subs[jid] = nil; + end + end + if next(subs) == nil then + self.subscriptions[normal_jid] = nil; + end + end + self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid }); + return true; +end + +function service:remove_all_subscriptions(actor, jid) + local normal_jid = self.config.normalize_jid(jid); + local subs = self.subscriptions[normal_jid] + subs = subs and subs[jid]; + if subs then + for node in pairs(subs) do + self:remove_subscription(node, true, jid); + end + end + return true; +end + +function service:get_subscription(node, actor, jid) + -- Access checking + local cap; + if actor == true or jid == actor or self:jids_equal(actor, jid) then + cap = "get_subscription"; + else + cap = "get_subscription_other"; + end + if not self:may(node, actor, cap) then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + return true, node_obj.subscribers[jid]; +end + +function service:create(node, actor, options) + -- Access checking + if not self:may(node, actor, "create") then + return false, "forbidden"; + end + -- + if self.nodes[node] then + return false, "conflict"; + end + + self.nodes[node] = { + name = node; + subscribers = {}; + config = setmetatable(options or {}, {__index=self.node_defaults}); + affiliations = {}; + }; + self.data[node] = self.config.itemstore(self.nodes[node].config, node); + self.events.fire_event("node-created", { node = node, actor = actor }); + local ok, err = self:set_affiliation(node, true, actor, "owner"); + if not ok then + self.nodes[node] = nil; + self.data[node] = nil; + end + return ok, err; +end + +function service:delete(node, actor) + -- Access checking + if not self:may(node, actor, "delete") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + self.nodes[node] = nil; + if self.data[node] and self.data[node].clear then + self.data[node]:clear(); + end + self.data[node] = nil; + self.events.fire_event("node-deleted", { node = node, actor = actor }); + self.config.broadcaster("delete", node, node_obj.subscribers); + return true; +end + +function service:publish(node, actor, id, item) + -- Access checking + if not self:may(node, actor, "publish") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + if not self.config.autocreate_on_publish then + return false, "item-not-found"; + end + local ok, err = self:create(node, true); + if not ok then + return ok, err; + end + node_obj = self.nodes[node]; + end + local node_data = self.data[node]; + local ok = node_data:set(id, item); + if not ok then + return nil, "internal-server-error"; + end + if type(ok) == "string" then id = ok; end + self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item }); + self.config.broadcaster("items", node, node_obj.subscribers, item, actor); + return true; +end + +function service:retract(node, actor, id, retract) + -- Access checking + if not self:may(node, actor, "retract") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if (not node_obj) or (not self.data[node]:get(id)) then + return false, "item-not-found"; + end + local ok = self.data[node]:set(id, nil); + if not ok then + return nil, "internal-server-error"; + end + self.events.fire_event("item-retracted", { node = node, actor = actor, id = id }); + if retract then + self.config.broadcaster("items", node, node_obj.subscribers, retract); + end + return true +end + +function service:purge(node, actor, notify) + -- Access checking + if not self:may(node, actor, "retract") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + if self.data[node] and self.data[node].clear then + self.data[node]:clear() + else + self.data[node] = self.config.itemstore(self.nodes[node].config, node); + end + self.events.fire_event("node-purged", { node = node, actor = actor }); + if notify then + self.config.broadcaster("purge", node, node_obj.subscribers); + end + return true +end + +function service:get_items(node, actor, id) + -- Access checking + if not self:may(node, actor, "get_items") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + if id then -- Restrict results to a single specific item + local with_id = self.data[node]:get(id); + if not with_id then + return false, "item-not-found"; + end + return true, { id, [id] = with_id }; + else + local data = {} + for key, value in self.data[node]:items() do + data[#data+1] = key; + data[key] = value; + end + return true, data; + end +end + +function service:get_nodes(actor) + -- Access checking + if not self:may(nil, actor, "get_nodes") then + return false, "forbidden"; + end + -- + return true, self.nodes; +end + +function service:get_subscriptions(node, actor, jid) + -- Access checking + local cap; + if actor == true or jid == actor or self:jids_equal(actor, jid) then + cap = "get_subscriptions"; + else + cap = "get_subscriptions_other"; + end + if not self:may(node, actor, cap) then + return false, "forbidden"; + end + -- + local node_obj; + if node then + node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + end + local normal_jid = self.config.normalize_jid(jid); + local subs = self.subscriptions[normal_jid]; + -- We return the subscription object from the node to save + -- a get_subscription() call for each node. + local ret = {}; + if subs then + for subscribed_jid, subscribed_nodes in pairs(subs) do + if node then -- Return only subscriptions to this node + if subscribed_nodes[node] then + ret[#ret+1] = { + node = node; + jid = subscribed_jid; + subscription = node_obj.subscribers[subscribed_jid]; + }; + end + else -- Return subscriptions to all nodes + local nodes = self.nodes; + for subscribed_node in pairs(subscribed_nodes) do + ret[#ret+1] = { + node = subscribed_node; + jid = subscribed_jid; + subscription = nodes[subscribed_node].subscribers[subscribed_jid]; + }; + end + end + end + end + return true, ret; +end + +-- Access models only affect 'none' affiliation caps, service/default access level... +function service:set_node_capabilities(node, actor, capabilities) + -- Access checking + if not self:may(node, actor, "configure") then + return false, "forbidden"; + end + -- + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + node_obj.capabilities = capabilities; + return true; +end + +function service:set_node_config(node, actor, new_config) + if not self:may(node, actor, "configure") then + return false, "forbidden"; + end + + local node_obj = self.nodes[node]; + if not node_obj then + return false, "item-not-found"; + end + + for k,v in pairs(new_config) do + node_obj.config[k] = v; + end + local new_data = self.config.itemstore(self.nodes[node].config, node); + for key, value in self.data[node]:items() do + new_data:set(key, value); + end + self.data[node] = new_data; + return true; +end + +return { + new = new; +};