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