Mercurial > prosody-hg
diff util/pubsub.lua @ 14118:221f3378e7f4
mod_pubsub, util.pubsub: Support for 'authorize' access model
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Tue, 31 Mar 2026 14:18:06 +0100 |
| parents | c8458864dcea |
| children | d975d280baec |
line wrap: on
line diff
--- a/util/pubsub.lua Tue Mar 31 14:15:00 2026 +0100 +++ b/util/pubsub.lua Tue Mar 31 14:18:06 2026 +0100 @@ -34,8 +34,39 @@ be_subscribed = false; be_unsubscribed = true; + request_subscribe = false; + request_subscribe_other = false; + set_affiliation = false; }; + -- 'unauthorized' is the default for unaffiliated in access_model 'authorize' + unauthorized = { + create = false; + publish = false; + retract = false; + get_nodes = true; + + subscribe = false; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = false; + get_metadata = true; + + subscribe_other = false; + unsubscribe_other = false; + get_subscription_other = false; + get_subscriptions_other = false; + + be_subscribed = true; + be_unsubscribed = true; + + request_subscribe = true; + request_subscribe_other = false; + + set_affiliation = false; + }; + -- default for unaffiliated in open access_model none = { create = false; publish = false; @@ -57,6 +88,9 @@ be_subscribed = true; be_unsubscribed = true; + request_subscribe = true; + request_subscribe_other = false; + set_affiliation = false; }; member = { @@ -80,6 +114,9 @@ be_subscribed = true; be_unsubscribed = true; + request_subscribe = true; + request_subscribe_other = false; + set_affiliation = false; }; publisher = { @@ -104,6 +141,9 @@ be_subscribed = true; be_unsubscribed = true; + request_subscribe = true; + request_subscribe_other = false; + set_affiliation = false; }; owner = { @@ -128,9 +168,14 @@ get_subscription_other = true; get_subscriptions_other = true; + get_pending_subscriptions = true; + be_subscribed = true; be_unsubscribed = true; + request_subscribe = true; + request_subscribe_other = true; + set_affiliation = true; }; }; @@ -151,6 +196,7 @@ local function load_node_from_store(service, node_name) local node = service.config.nodestore:get(node_name); node.config = setmetatable(node.config or {}, {__index=service.node_defaults}); + node.pending_subscribers = node.pending_subscribers or {}; return node; end @@ -159,6 +205,7 @@ name = node.name; config = node.config; subscribers = node.subscribers; + pending_subscribers = node.pending_subscribers; affiliations = node.affiliations; }); end @@ -263,6 +310,8 @@ return "member"; elseif access_model == "whitelist" then return "outcast"; + elseif access_model == "authorize" then + return "unauthorized"; end if self.config.access_models then @@ -314,6 +363,66 @@ return true; end +function service:request_subscription(node, actor, jid, options) --> ok, err + -- Access checking + local cap; + if actor == true or jid == actor or self:jids_equal(actor, jid) then + cap = "request_subscribe"; + else + cap = "request_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 + local old_subscription = node_obj.subscribers[jid]; + if old_subscription then + return false, "already-subscribed"; + end + node_obj.pending_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] = false }; + else + subs[jid][node] = false; + end + else + self.subscriptions[normal_jid] = { [jid] = { [node] = false } }; + end + + if self.config.nodestore then + -- TODO pass the error from storage to caller eg wrapped in an util.error + local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err + if not ok then + -- Store failed, so clear in-memory structures + node_obj.pending_subscribers[jid] = nil; + self.subscriptions[normal_jid][jid][node] = nil; + return ok, "internal-server-error"; + end + end + + self.events.fire_event("subscription-requested", { service = self, node = node, jid = jid, normalized_jid = normal_jid, options = options }); + return true; +end + function service:add_subscription(node, actor, jid, options) --> ok, err -- Access checking local cap; @@ -342,7 +451,18 @@ end end local old_subscription = node_obj.subscribers[jid]; + local pending_subscription = node_obj.pending_subscribers[jid]; + + if not options and pending_subscription then + -- Use the pending subscription request's options now we + -- are upgrading to a real subscription. + options = pending_subscription; + end node_obj.subscribers[jid] = options or true; + + -- Clear pending subscription request, if any + node_obj.pending_subscribers[jid] = nil; + local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then @@ -359,8 +479,10 @@ -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then + -- Store failed, revert in-memory to old subscription node_obj.subscribers[jid] = old_subscription; self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; + self.pending_subscribers[normal_jid] = pending_subscription; return ok, "internal-server-error"; end end @@ -388,11 +510,17 @@ if not node_obj then return false, "item-not-found"; end - if not node_obj.subscribers[jid] then + + local old_subscription = node_obj.subscribers[jid]; + local pending_subscription = node_obj.pending_subscribers[jid]; + + if not old_subscription and not pending_subscription then return false, "not-subscribed"; end - local old_subscription = node_obj.subscribers[jid]; + node_obj.subscribers[jid] = nil; + node_obj.pending_subscribers[jid] = nil; + local normal_jid = self.config.normalize_jid(jid); local subs = self.subscriptions[normal_jid]; if subs then @@ -412,8 +540,14 @@ -- TODO pass the error from storage to caller eg wrapped in an util.error local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err if not ok then + -- Restore previous in-memory state node_obj.subscribers[jid] = old_subscription; - self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil; + node_obj.pending_subscribers[jid] = pending_subscription; + if pending_subscription then + self.subscriptions[normal_jid][jid][node] = false; + elseif old_subscription then + self.subscriptions[normal_jid][jid][node] = true; + end return ok, "internal-server-error"; end end @@ -441,6 +575,21 @@ return true, node_obj.subscribers[jid]; end +function service:get_pending_subscriptions(node, actor) + -- Access checking + if not self:may(node, actor, "get_pending_subscriptions") 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.pending_subscribers; +end + function service:create(node, actor, options) --> ok, err -- Access checking if not self:may(node, actor, "create") then @@ -463,6 +612,7 @@ self.nodes[node] = { name = node; subscribers = {}; + pending_subscribers = {}; config = config; affiliations = {}; }; @@ -739,10 +889,11 @@ return true, self.nodes; end -local function flatten_subscriptions(ret, serv, subs, node, node_obj) +local function flatten_subscriptions(ret, serv, subs, node, node_obj, inc_pending) for subscribed_jid, subscribed_nodes in pairs(subs) do if node then -- Return only subscriptions to this node - if subscribed_nodes[node] then + local state = subscribed_nodes[node]; + if state or (state == false and inc_pending) then ret[#ret+1] = { node = node; jid = subscribed_jid; @@ -751,18 +902,20 @@ end else -- Return subscriptions to all nodes local nodes = serv.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]; - }; + for subscribed_node, state in pairs(subscribed_nodes) do + if state or inc_pending then + ret[#ret+1] = { + node = subscribed_node; + jid = subscribed_jid; + subscription = nodes[subscribed_node].subscribers[subscribed_jid]; + }; + end end end end end -function service:get_subscriptions(node, actor, jid) --> (true, array) or (false, err) +function service:get_subscriptions(node, actor, jid, include_pending) --> (true, array) or (false, err) -- Access checking local cap; if actor == true or jid == actor or self:jids_equal(actor, jid) then @@ -784,7 +937,7 @@ local ret = {}; if jid == nil then for _, subs in pairs(self.subscriptions) do - flatten_subscriptions(ret, self, subs, node, node_obj) + flatten_subscriptions(ret, self, subs, node, node_obj, include_pending) end return true, ret; end @@ -793,7 +946,7 @@ -- We return the subscription object from the node to save -- a get_subscription() call for each node. if subs then - flatten_subscriptions(ret, self, subs, node, node_obj) + flatten_subscriptions(ret, self, subs, node, node_obj, include_pending) end return true, ret; end
