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 };