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