Merge 0.9->0.10
[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         local ok, err = self:set_affiliation(node, true, actor, "owner");
223         if not ok then
224                 self.nodes[node] = nil;
225         end
226         return ok, err;
227 end
228
229 function service:delete(node, actor)
230         -- Access checking
231         if not self:may(node, actor, "delete") then
232                 return false, "forbidden";
233         end
234         --
235         local node_obj = self.nodes[node];
236         if not node_obj then
237                 return false, "item-not-found";
238         end
239         self.nodes[node] = nil;
240         self.config.broadcaster("delete", node, node_obj.subscribers);
241         return true;
242 end
243
244 function service:publish(node, actor, id, item)
245         -- Access checking
246         if not self:may(node, actor, "publish") then
247                 return false, "forbidden";
248         end
249         --
250         local node_obj = self.nodes[node];
251         if not node_obj then
252                 if not self.config.autocreate_on_publish then
253                         return false, "item-not-found";
254                 end
255                 local ok, err = self:create(node, true);
256                 if not ok then
257                         return ok, err;
258                 end
259                 node_obj = self.nodes[node];
260         end
261         node_obj.data[id] = item;
262         self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
263         self.config.broadcaster("items", node, node_obj.subscribers, item);
264         return true;
265 end
266
267 function service:retract(node, actor, id, retract)
268         -- Access checking
269         if not self:may(node, actor, "retract") then
270                 return false, "forbidden";
271         end
272         --
273         local node_obj = self.nodes[node];
274         if (not node_obj) or (not node_obj.data[id]) then
275                 return false, "item-not-found";
276         end
277         node_obj.data[id] = nil;
278         if retract then
279                 self.config.broadcaster("items", node, node_obj.subscribers, retract);
280         end
281         return true
282 end
283
284 function service:purge(node, actor, notify)
285         -- Access checking
286         if not self:may(node, actor, "retract") then
287                 return false, "forbidden";
288         end
289         --
290         local node_obj = self.nodes[node];
291         if not node_obj then
292                 return false, "item-not-found";
293         end
294         node_obj.data = {}; -- Purge
295         if notify then
296                 self.config.broadcaster("purge", node, node_obj.subscribers);
297         end
298         return true
299 end
300
301 function service:get_items(node, actor, id)
302         -- Access checking
303         if not self:may(node, actor, "get_items") then
304                 return false, "forbidden";
305         end
306         --
307         local node_obj = self.nodes[node];
308         if not node_obj then
309                 return false, "item-not-found";
310         end
311         if id then -- Restrict results to a single specific item
312                 return true, { [id] = node_obj.data[id] };
313         else
314                 return true, node_obj.data;
315         end
316 end
317
318 function service:get_nodes(actor)
319         -- Access checking
320         if not self:may(nil, actor, "get_nodes") then
321                 return false, "forbidden";
322         end
323         --
324         return true, self.nodes;
325 end
326
327 function service:get_subscriptions(node, actor, jid)
328         -- Access checking
329         local cap;
330         if actor == true or jid == actor or self:jids_equal(actor, jid) then
331                 cap = "get_subscriptions";
332         else
333                 cap = "get_subscriptions_other";
334         end
335         if not self:may(node, actor, cap) then
336                 return false, "forbidden";
337         end
338         --
339         local node_obj;
340         if node then
341                 node_obj = self.nodes[node];
342                 if not node_obj then
343                         return false, "item-not-found";
344                 end
345         end
346         local normal_jid = self.config.normalize_jid(jid);
347         local subs = self.subscriptions[normal_jid];
348         -- We return the subscription object from the node to save
349         -- a get_subscription() call for each node.
350         local ret = {};
351         if subs then
352                 for jid, subscribed_nodes in pairs(subs) do
353                         if node then -- Return only subscriptions to this node
354                                 if subscribed_nodes[node] then
355                                         ret[#ret+1] = {
356                                                 node = node;
357                                                 jid = jid;
358                                                 subscription = node_obj.subscribers[jid];
359                                         };
360                                 end
361                         else -- Return subscriptions to all nodes
362                                 local nodes = self.nodes;
363                                 for subscribed_node in pairs(subscribed_nodes) do
364                                         ret[#ret+1] = {
365                                                 node = subscribed_node;
366                                                 jid = jid;
367                                                 subscription = nodes[subscribed_node].subscribers[jid];
368                                         };
369                                 end
370                         end
371                 end
372         end
373         return true, ret;
374 end
375
376 -- Access models only affect 'none' affiliation caps, service/default access level...
377 function service:set_node_capabilities(node, actor, capabilities)
378         -- Access checking
379         if not self:may(node, actor, "configure") then
380                 return false, "forbidden";
381         end
382         --
383         local node_obj = self.nodes[node];
384         if not node_obj then
385                 return false, "item-not-found";
386         end
387         node_obj.capabilities = capabilities;
388         return true;
389 end
390
391 return _M;