1 local events = require "util.events";
3 module("pubsub", package.seeall);
6 local service_mt = { __index = service };
8 local default_config = {
9 broadcaster = function () end;
10 get_affiliation = function () end;
15 config = config or {};
17 config = setmetatable(config, { __index = default_config });
21 events = events.new();
25 function service:jids_equal(jid1, jid2)
26 local normalize = self.config.normalize_jid;
27 return normalize(jid1) == normalize(jid2);
30 function service:may(node, actor, action)
31 if actor == true then return true; end
33 local node_obj = self.nodes[node];
34 local node_aff = node_obj and node_obj.affiliations[actor];
35 local service_aff = self.affiliations[actor]
36 or self.config.get_affiliation(actor, node, action)
39 -- Check if node allows/forbids it
40 local node_capabilities = node_obj and node_obj.capabilities;
41 if node_capabilities then
42 local caps = node_capabilities[node_aff or service_aff];
44 local can = caps[action];
51 -- Check service-wide capabilities instead
52 local service_capabilities = self.config.capabilities;
53 local caps = service_capabilities[node_aff or service_aff];
55 local can = caps[action];
64 function service:set_affiliation(node, actor, jid, affiliation)
66 if not self:may(node, actor, "set_affiliation") then
67 return false, "forbidden";
70 local node_obj = self.nodes[node];
72 return false, "item-not-found";
74 node_obj.affiliations[jid] = affiliation;
75 local _, jid_sub = self:get_subscription(node, true, jid);
76 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
77 local ok, err = self:add_subscription(node, true, jid);
81 elseif jid_sub and not self:may(node, jid, "be_subscribed") then
82 local ok, err = self:add_subscription(node, true, jid);
90 function service:add_subscription(node, actor, jid, options)
93 if actor == true or jid == actor or self:jids_equal(actor, jid) then
96 cap = "subscribe_other";
98 if not self:may(node, actor, cap) then
99 return false, "forbidden";
101 if not self:may(node, jid, "be_subscribed") then
102 return false, "forbidden";
105 local node_obj = self.nodes[node];
107 if not self.config.autocreate_on_subscribe then
108 return false, "item-not-found";
110 local ok, err = self:create(node, true);
114 node_obj = self.nodes[node];
117 node_obj.subscribers[jid] = options or true;
118 local normal_jid = self.config.normalize_jid(jid);
119 local subs = self.subscriptions[normal_jid];
121 if not subs[jid] then
122 subs[jid] = { [node] = true };
124 subs[jid][node] = true;
127 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
129 self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
133 function service:remove_subscription(node, actor, jid)
136 if actor == true or jid == actor or self:jids_equal(actor, jid) then
139 cap = "unsubscribe_other";
141 if not self:may(node, actor, cap) then
142 return false, "forbidden";
144 if not self:may(node, jid, "be_unsubscribed") then
145 return false, "forbidden";
148 local node_obj = self.nodes[node];
150 return false, "item-not-found";
152 if not node_obj.subscribers[jid] then
153 return false, "not-subscribed";
155 node_obj.subscribers[jid] = nil;
156 local normal_jid = self.config.normalize_jid(jid);
157 local subs = self.subscriptions[normal_jid];
159 local jid_subs = subs[jid];
161 jid_subs[node] = nil;
162 if next(jid_subs) == nil then
166 if next(subs) == nil then
167 self.subscriptions[normal_jid] = nil;
170 self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
174 function service:remove_all_subscriptions(actor, jid)
175 local normal_jid = self.config.normalize_jid(jid);
176 local subs = self.subscriptions[normal_jid]
177 subs = subs and subs[jid];
179 for node in pairs(subs) do
180 self:remove_subscription(node, true, jid);
186 function service:get_subscription(node, actor, jid)
189 if actor == true or jid == actor or self:jids_equal(actor, jid) then
190 cap = "get_subscription";
192 cap = "get_subscription_other";
194 if not self:may(node, actor, cap) then
195 return false, "forbidden";
198 local node_obj = self.nodes[node];
200 return false, "item-not-found";
202 return true, node_obj.subscribers[jid];
205 function service:create(node, actor)
207 if not self:may(node, actor, "create") then
208 return false, "forbidden";
211 if self.nodes[node] then
212 return false, "conflict";
222 self.events.fire_event("node-created", { node = node, actor = actor });
223 local ok, err = self:set_affiliation(node, true, actor, "owner");
225 self.nodes[node] = nil;
230 function service:delete(node, actor)
232 if not self:may(node, actor, "delete") then
233 return false, "forbidden";
236 local node_obj = self.nodes[node];
238 return false, "item-not-found";
240 self.nodes[node] = nil;
241 self.events.fire_event("node-deleted", { node = node, actor = actor });
242 self.config.broadcaster("delete", node, node_obj.subscribers);
246 function service:publish(node, actor, id, item)
248 if not self:may(node, actor, "publish") then
249 return false, "forbidden";
252 local node_obj = self.nodes[node];
254 if not self.config.autocreate_on_publish then
255 return false, "item-not-found";
257 local ok, err = self:create(node, true);
261 node_obj = self.nodes[node];
263 node_obj.data[id] = item;
264 self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
265 self.config.broadcaster("items", node, node_obj.subscribers, item);
269 function service:retract(node, actor, id, retract)
271 if not self:may(node, actor, "retract") then
272 return false, "forbidden";
275 local node_obj = self.nodes[node];
276 if (not node_obj) or (not node_obj.data[id]) then
277 return false, "item-not-found";
279 self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
280 node_obj.data[id] = nil;
282 self.config.broadcaster("items", node, node_obj.subscribers, retract);
287 function service:purge(node, actor, notify)
289 if not self:may(node, actor, "retract") then
290 return false, "forbidden";
293 local node_obj = self.nodes[node];
295 return false, "item-not-found";
297 node_obj.data = {}; -- Purge
298 self.events.fire_event("node-purged", { node = node, actor = actor });
300 self.config.broadcaster("purge", node, node_obj.subscribers);
305 function service:get_items(node, actor, id)
307 if not self:may(node, actor, "get_items") then
308 return false, "forbidden";
311 local node_obj = self.nodes[node];
313 return false, "item-not-found";
315 if id then -- Restrict results to a single specific item
316 return true, { [id] = node_obj.data[id] };
318 return true, node_obj.data;
322 function service:get_nodes(actor)
324 if not self:may(nil, actor, "get_nodes") then
325 return false, "forbidden";
328 return true, self.nodes;
331 function service:get_subscriptions(node, actor, jid)
334 if actor == true or jid == actor or self:jids_equal(actor, jid) then
335 cap = "get_subscriptions";
337 cap = "get_subscriptions_other";
339 if not self:may(node, actor, cap) then
340 return false, "forbidden";
345 node_obj = self.nodes[node];
347 return false, "item-not-found";
350 local normal_jid = self.config.normalize_jid(jid);
351 local subs = self.subscriptions[normal_jid];
352 -- We return the subscription object from the node to save
353 -- a get_subscription() call for each node.
356 for jid, subscribed_nodes in pairs(subs) do
357 if node then -- Return only subscriptions to this node
358 if subscribed_nodes[node] then
362 subscription = node_obj.subscribers[jid];
365 else -- Return subscriptions to all nodes
366 local nodes = self.nodes;
367 for subscribed_node in pairs(subscribed_nodes) do
369 node = subscribed_node;
371 subscription = nodes[subscribed_node].subscribers[jid];
380 -- Access models only affect 'none' affiliation caps, service/default access level...
381 function service:set_node_capabilities(node, actor, capabilities)
383 if not self:may(node, actor, "configure") then
384 return false, "forbidden";
387 local node_obj = self.nodes[node];
389 return false, "item-not-found";
391 node_obj.capabilities = capabilities;