a68b256c7ba23e5f34149107b3eecd17c67c92aa
[prosody.git] / util / pubsub.lua
1 local events = require "util.events";
2
3 module("pubsub", package.seeall);
4
5 local service = {};
6 local service_mt = { __index = service };
7
8 local default_config = {
9         broadcaster = function () end;
10         get_affiliation = function () end;
11         capabilities = {};
12 };
13
14 function new(config)
15         config = config or {};
16         return setmetatable({
17                 config = setmetatable(config, { __index = default_config });
18                 affiliations = {};
19                 subscriptions = {};
20                 nodes = {};
21                 events = events.new();
22         }, service_mt);
23 end
24
25 function service:jids_equal(jid1, jid2)
26         local normalize = self.config.normalize_jid;
27         return normalize(jid1) == normalize(jid2);
28 end
29
30 function service:may(node, actor, action)
31         if actor == true then return true; end
32
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)
37                          or "none";
38
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];
43                 if caps then
44                         local can = caps[action];
45                         if can ~= nil then
46                                 return can;
47                         end
48                 end
49         end
50
51         -- Check service-wide capabilities instead
52         local service_capabilities = self.config.capabilities;
53         local caps = service_capabilities[node_aff or service_aff];
54         if caps then
55                 local can = caps[action];
56                 if can ~= nil then
57                         return can;
58                 end
59         end
60
61         return false;
62 end
63
64 function service:set_affiliation(node, actor, jid, affiliation)
65         -- Access checking
66         if not self:may(node, actor, "set_affiliation") then
67                 return false, "forbidden";
68         end
69         --
70         local node_obj = self.nodes[node];
71         if not node_obj then
72                 return false, "item-not-found";
73         end
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);
78                 if not ok then
79                         return ok, err;
80                 end
81         elseif jid_sub and not self:may(node, jid, "be_subscribed") then
82                 local ok, err = self:add_subscription(node, true, jid);
83                 if not ok then
84                         return ok, err;
85                 end
86         end
87         return true;
88 end
89
90 function service:add_subscription(node, actor, jid, options)
91         -- Access checking
92         local cap;
93         if actor == true or jid == actor or self:jids_equal(actor, jid) then
94                 cap = "subscribe";
95         else
96                 cap = "subscribe_other";
97         end
98         if not self:may(node, actor, cap) then
99                 return false, "forbidden";
100         end
101         if not self:may(node, jid, "be_subscribed") then
102                 return false, "forbidden";
103         end
104         --
105         local node_obj = self.nodes[node];
106         if not node_obj then
107                 if not self.config.autocreate_on_subscribe then
108                         return false, "item-not-found";
109                 else
110                         local ok, err = self:create(node, true);
111                         if not ok then
112                                 return ok, err;
113                         end
114                         node_obj = self.nodes[node];
115                 end
116         end
117         node_obj.subscribers[jid] = options or true;
118         local normal_jid = self.config.normalize_jid(jid);
119         local subs = self.subscriptions[normal_jid];
120         if subs then
121                 if not subs[jid] then
122                         subs[jid] = { [node] = true };
123                 else
124                         subs[jid][node] = true;
125                 end
126         else
127                 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
128         end
129         self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
130         return true;
131 end
132
133 function service:remove_subscription(node, actor, jid)
134         -- Access checking
135         local cap;
136         if actor == true or jid == actor or self:jids_equal(actor, jid) then
137                 cap = "unsubscribe";
138         else
139                 cap = "unsubscribe_other";
140         end
141         if not self:may(node, actor, cap) then
142                 return false, "forbidden";
143         end
144         if not self:may(node, jid, "be_unsubscribed") then
145                 return false, "forbidden";
146         end
147         --
148         local node_obj = self.nodes[node];
149         if not node_obj then
150                 return false, "item-not-found";
151         end
152         if not node_obj.subscribers[jid] then
153                 return false, "not-subscribed";
154         end
155         node_obj.subscribers[jid] = nil;
156         local normal_jid = self.config.normalize_jid(jid);
157         local subs = self.subscriptions[normal_jid];
158         if subs then
159                 local jid_subs = subs[jid];
160                 if jid_subs then
161                         jid_subs[node] = nil;
162                         if next(jid_subs) == nil then
163                                 subs[jid] = nil;
164                         end
165                 end
166                 if next(subs) == nil then
167                         self.subscriptions[normal_jid] = nil;
168                 end
169         end
170         self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
171         return true;
172 end
173
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];
178         if subs then
179                 for node in pairs(subs) do
180                         self:remove_subscription(node, true, jid);
181                 end
182         end
183         return true;
184 end
185
186 function service:get_subscription(node, actor, jid)
187         -- Access checking
188         local cap;
189         if actor == true or jid == actor or self:jids_equal(actor, jid) then
190                 cap = "get_subscription";
191         else
192                 cap = "get_subscription_other";
193         end
194         if not self:may(node, actor, cap) then
195                 return false, "forbidden";
196         end
197         --
198         local node_obj = self.nodes[node];
199         if not node_obj then
200                 return false, "item-not-found";
201         end
202         return true, node_obj.subscribers[jid];
203 end
204
205 function service:create(node, actor)
206         -- Access checking
207         if not self:may(node, actor, "create") then
208                 return false, "forbidden";
209         end
210         --
211         if self.nodes[node] then
212                 return false, "conflict";
213         end
214
215         self.nodes[node] = {
216                 name = node;
217                 subscribers = {};
218                 config = {};
219                 data = {};
220                 affiliations = {};
221         };
222         self.events.fire_event("node-created", { node = node, actor = actor });
223         local ok, err = self:set_affiliation(node, true, actor, "owner");
224         if not ok then
225                 self.nodes[node] = nil;
226         end
227         return ok, err;
228 end
229
230 function service:delete(node, actor)
231         -- Access checking
232         if not self:may(node, actor, "delete") then
233                 return false, "forbidden";
234         end
235         --
236         local node_obj = self.nodes[node];
237         if not node_obj then
238                 return false, "item-not-found";
239         end
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);
243         return true;
244 end
245
246 function service:publish(node, actor, id, item)
247         -- Access checking
248         if not self:may(node, actor, "publish") then
249                 return false, "forbidden";
250         end
251         --
252         local node_obj = self.nodes[node];
253         if not node_obj then
254                 if not self.config.autocreate_on_publish then
255                         return false, "item-not-found";
256                 end
257                 local ok, err = self:create(node, true);
258                 if not ok then
259                         return ok, err;
260                 end
261                 node_obj = self.nodes[node];
262         end
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);
266         return true;
267 end
268
269 function service:retract(node, actor, id, retract)
270         -- Access checking
271         if not self:may(node, actor, "retract") then
272                 return false, "forbidden";
273         end
274         --
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";
278         end
279         self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
280         node_obj.data[id] = nil;
281         if retract then
282                 self.config.broadcaster("items", node, node_obj.subscribers, retract);
283         end
284         return true
285 end
286
287 function service:purge(node, actor, notify)
288         -- Access checking
289         if not self:may(node, actor, "retract") then
290                 return false, "forbidden";
291         end
292         --
293         local node_obj = self.nodes[node];
294         if not node_obj then
295                 return false, "item-not-found";
296         end
297         node_obj.data = {}; -- Purge
298         self.events.fire_event("node-purged", { node = node, actor = actor });
299         if notify then
300                 self.config.broadcaster("purge", node, node_obj.subscribers);
301         end
302         return true
303 end
304
305 function service:get_items(node, actor, id)
306         -- Access checking
307         if not self:may(node, actor, "get_items") then
308                 return false, "forbidden";
309         end
310         --
311         local node_obj = self.nodes[node];
312         if not node_obj then
313                 return false, "item-not-found";
314         end
315         if id then -- Restrict results to a single specific item
316                 return true, { [id] = node_obj.data[id] };
317         else
318                 return true, node_obj.data;
319         end
320 end
321
322 function service:get_nodes(actor)
323         -- Access checking
324         if not self:may(nil, actor, "get_nodes") then
325                 return false, "forbidden";
326         end
327         --
328         return true, self.nodes;
329 end
330
331 function service:get_subscriptions(node, actor, jid)
332         -- Access checking
333         local cap;
334         if actor == true or jid == actor or self:jids_equal(actor, jid) then
335                 cap = "get_subscriptions";
336         else
337                 cap = "get_subscriptions_other";
338         end
339         if not self:may(node, actor, cap) then
340                 return false, "forbidden";
341         end
342         --
343         local node_obj;
344         if node then
345                 node_obj = self.nodes[node];
346                 if not node_obj then
347                         return false, "item-not-found";
348                 end
349         end
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.
354         local ret = {};
355         if subs then
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
359                                         ret[#ret+1] = {
360                                                 node = node;
361                                                 jid = jid;
362                                                 subscription = node_obj.subscribers[jid];
363                                         };
364                                 end
365                         else -- Return subscriptions to all nodes
366                                 local nodes = self.nodes;
367                                 for subscribed_node in pairs(subscribed_nodes) do
368                                         ret[#ret+1] = {
369                                                 node = subscribed_node;
370                                                 jid = jid;
371                                                 subscription = nodes[subscribed_node].subscribers[jid];
372                                         };
373                                 end
374                         end
375                 end
376         end
377         return true, ret;
378 end
379
380 -- Access models only affect 'none' affiliation caps, service/default access level...
381 function service:set_node_capabilities(node, actor, capabilities)
382         -- Access checking
383         if not self:may(node, actor, "configure") then
384                 return false, "forbidden";
385         end
386         --
387         local node_obj = self.nodes[node];
388         if not node_obj then
389                 return false, "item-not-found";
390         end
391         node_obj.capabilities = capabilities;
392         return true;
393 end
394
395 return _M;