util.debug: Remove 'white' from boundary style (leave at default colour)
[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:publish(node, actor, id, item)
230         -- Access checking
231         if not self:may(node, actor, "publish") then
232                 return false, "forbidden";
233         end
234         --
235         local node_obj = self.nodes[node];
236         if not node_obj then
237                 if not self.config.autocreate_on_publish then
238                         return false, "item-not-found";
239                 end
240                 local ok, err = self:create(node, true);
241                 if not ok then
242                         return ok, err;
243                 end
244                 node_obj = self.nodes[node];
245         end
246         node_obj.data[id] = item;
247         self.config.broadcaster(node, node_obj.subscribers, item);
248         return true;
249 end
250
251 function service:retract(node, actor, id, retract)
252         -- Access checking
253         if not self:may(node, actor, "retract") then
254                 return false, "forbidden";
255         end
256         --
257         local node_obj = self.nodes[node];
258         if (not node_obj) or (not node_obj.data[id]) then
259                 return false, "item-not-found";
260         end
261         node_obj.data[id] = nil;
262         if retract then
263                 self.config.broadcaster(node, node_obj.subscribers, retract);
264         end
265         return true
266 end
267
268 function service:get_items(node, actor, id)
269         -- Access checking
270         if not self:may(node, actor, "get_items") then
271                 return false, "forbidden";
272         end
273         --
274         local node_obj = self.nodes[node];
275         if not node_obj then
276                 return false, "item-not-found";
277         end
278         if id then -- Restrict results to a single specific item
279                 return true, { [id] = node_obj.data[id] };
280         else
281                 return true, node_obj.data;
282         end
283 end
284
285 function service:get_nodes(actor)
286         -- Access checking
287         if not self:may(nil, actor, "get_nodes") then
288                 return false, "forbidden";
289         end
290         --
291         return true, self.nodes;
292 end
293
294 function service:get_subscriptions(node, actor, jid)
295         -- Access checking
296         local cap;
297         if actor == true or jid == actor or self:jids_equal(actor, jid) then
298                 cap = "get_subscriptions";
299         else
300                 cap = "get_subscriptions_other";
301         end
302         if not self:may(node, actor, cap) then
303                 return false, "forbidden";
304         end
305         --
306         local node_obj;
307         if node then
308                 node_obj = self.nodes[node];
309                 if not node_obj then
310                         return false, "item-not-found";
311                 end
312         end
313         local normal_jid = self.config.normalize_jid(jid);
314         local subs = self.subscriptions[normal_jid];
315         -- We return the subscription object from the node to save
316         -- a get_subscription() call for each node.
317         local ret = {};
318         if subs then
319                 for jid, subscribed_nodes in pairs(subs) do
320                         if node then -- Return only subscriptions to this node
321                                 if subscribed_nodes[node] then
322                                         ret[#ret+1] = {
323                                                 node = subscribed_node;
324                                                 jid = jid;
325                                                 subscription = node_obj.subscribers[jid];
326                                         };
327                                 end
328                         else -- Return subscriptions to all nodes
329                                 local nodes = self.nodes;
330                                 for subscribed_node in pairs(subscribed_nodes) do
331                                         ret[#ret+1] = {
332                                                 node = subscribed_node;
333                                                 jid = jid;
334                                                 subscription = nodes[subscribed_node].subscribers[jid];
335                                         };
336                                 end
337                         end
338                 end
339         end
340         return true, ret;
341 end
342
343 -- Access models only affect 'none' affiliation caps, service/default access level...
344 function service:set_node_capabilities(node, actor, capabilities)
345         -- Access checking
346         if not self:may(node, actor, "configure") then
347                 return false, "forbidden";
348         end
349         --
350         local node_obj = self.nodes[node];
351         if not node_obj then
352                 return false, "item-not-found";
353         end
354         node_obj.capabilities = capabilities;
355         return true;
356 end
357
358 return _M;