util.pubsub: Use correct node name when returning a list of multiple nodes in a subsc...
[prosody.git] / util / pubsub.lua
1 module("pubsub", package.seeall);
2
3 local service = {};
4 local service_mt = { __index = service };
5
6 local default_config = {
7         broadcaster = function () end;
8         get_affiliation = function () end;
9         capabilities = {};
10 };
11
12 function new(config)
13         config = config or {};
14         return setmetatable({
15                 config = setmetatable(config, { __index = default_config });
16                 affiliations = {};
17                 subscriptions = {};
18                 nodes = {};
19         }, service_mt);
20 end
21
22 function service:jids_equal(jid1, jid2)
23         local normalize = self.config.normalize_jid;
24         return normalize(jid1) == normalize(jid2);
25 end
26
27 function service:may(node, actor, action)
28         if actor == true then return true; end
29         
30         
31         local node_obj = self.nodes[node];
32         local node_aff = node_obj and node_obj.affiliations[actor];
33         local service_aff = self.affiliations[actor]
34                          or self.config.get_affiliation(actor, node, action)
35                          or "none";
36         
37         local node_capabilities = node_obj and node_obj.capabilities;
38         local service_capabilities = self.config.capabilities;
39         
40         -- Check if node allows/forbids it      
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         -- Check service-wide capabilities instead
51         local caps = service_capabilities[node_aff or service_aff];
52         if caps then
53                 local can = caps[action];
54                 if can ~= nil then
55                         return can;
56                 end
57         end
58         
59         return false;
60 end
61
62 function service:set_affiliation(node, actor, jid, affiliation)
63         -- Access checking
64         if not self:may(node, actor, "set_affiliation") then
65                 return false, "forbidden";
66         end
67         --
68         local node_obj = self.nodes[node];
69         if not node_obj then
70                 return false, "item-not-found";
71         end
72         node_obj.affiliations[jid] = affiliation;
73         local _, jid_sub = self:get_subscription(node, nil, jid);
74         if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
75                 local ok, err = self:add_subscription(node, nil, jid);
76                 if not ok then
77                         return ok, err;
78                 end
79         elseif jid_sub and not self:may(node, jid, "be_subscribed") then
80                 local ok, err = self:add_subscription(node, nil, jid);
81                 if not ok then
82                         return ok, err;
83                 end
84         end
85         return true;
86 end
87
88 function service:add_subscription(node, actor, jid, options)
89         -- Access checking
90         local cap;
91         if jid == actor or self:jids_equal(actor, jid) then
92                 cap = "subscribe";
93         else
94                 cap = "subscribe_other";
95         end
96         if not self:may(node, actor, cap) then
97                 return false, "forbidden";
98         end
99         if not self:may(node, jid, "be_subscribed") then
100                 return false, "forbidden";
101         end
102         --
103         local node_obj = self.nodes[node];
104         if not node_obj then
105                 if not self.config.autocreate_on_subscribe then
106                         return false, "item-not-found";
107                 else
108                         local ok, err = self:create(node, actor);
109                         if not ok then
110                                 return ok, err;
111                         end
112                         node_obj = self.nodes[node];
113                 end
114         end
115         node_obj.subscribers[jid] = options or true;
116         local normal_jid = self.config.normalize_jid(jid);
117         local subs = self.subscriptions[normal_jid];
118         if subs then
119                 if not subs[jid] then
120                         subs[jid] = { [node] = true };
121                 else
122                         subs[jid][node] = true;
123                 end
124         else
125                 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
126         end
127         return true;
128 end
129
130 function service:remove_subscription(node, actor, jid)
131         -- Access checking
132         local cap;
133         if jid == actor or self:jids_equal(actor, jid) then
134                 cap = "unsubscribe";
135         else
136                 cap = "unsubscribe_other";
137         end
138         if not self:may(node, actor, cap) then
139                 return false, "forbidden";
140         end
141         if not self:may(node, jid, "be_unsubscribed") then
142                 return false, "forbidden";
143         end
144         --
145         local node_obj = self.nodes[node];
146         if not node_obj then
147                 return false, "item-not-found";
148         end
149         if not node_obj.subscribers[jid] then
150                 return false, "not-subscribed";
151         end
152         node_obj.subscribers[jid] = nil;
153         local normal_jid = self.config.normalize_jid(jid);
154         local subs = self.subscriptions[normal_jid];
155         if subs then
156                 local jid_subs = subs[jid];
157                 if jid_subs then
158                         jid_subs[node] = nil;
159                         if next(jid_subs) == nil then
160                                 subs[jid] = nil;
161                         end
162                 end
163                 if next(subs) == nil then
164                         self.subscriptions[normal_jid] = nil;
165                 end
166         end
167         return true;
168 end
169
170 function service:get_subscription(node, actor, jid)
171         -- Access checking
172         local cap;
173         if jid == actor or self:jids_equal(actor, jid) then
174                 cap = "get_subscription";
175         else
176                 cap = "get_subscription_other";
177         end
178         if not self:may(node, actor, cap) then
179                 return false, "forbidden";
180         end
181         --
182         local node_obj = self.nodes[node];
183         if not node_obj then
184                 return false, "item-not-found";
185         end
186         return true, node_obj.subscribers[jid];
187 end
188
189 function service:create(node, actor)
190         -- Access checking
191         if not self:may(node, actor, "create") then
192                 return false, "forbidden";
193         end
194         --
195         if self.nodes[node] then
196                 return false, "conflict";
197         end
198         
199         self.nodes[node] = {
200                 name = node;
201                 subscribers = {};
202                 config = {};
203                 data = {};
204                 affiliations = {};
205         };
206         local ok, err = self:set_affiliation(node, true, actor, "owner");
207         if not ok then
208                 self.nodes[node] = nil;
209         end
210         return ok, err;
211 end
212
213 function service:publish(node, actor, id, item)
214         -- Access checking
215         if not self:may(node, actor, "publish") then
216                 return false, "forbidden";
217         end
218         --
219         local node_obj = self.nodes[node];
220         if not node_obj then
221                 if not self.config.autocreate_on_publish then
222                         return false, "item-not-found";
223                 end
224                 local ok, err = self:create(node, actor);
225                 if not ok then
226                         return ok, err;
227                 end
228                 node_obj = self.nodes[node];
229         end
230         node_obj.data[id] = item;
231         self.config.broadcaster(node, node_obj.subscribers, item);
232         return true;
233 end
234
235 function service:retract(node, actor, id, retract)
236         -- Access checking
237         if not self:may(node, actor, "retract") then
238                 return false, "forbidden";
239         end
240         --
241         local node_obj = self.nodes[node];
242         if (not node_obj) or (not node_obj.data[id]) then
243                 return false, "item-not-found";
244         end
245         node_obj.data[id] = nil;
246         if retract then
247                 self.config.broadcaster(node, node_obj.subscribers, retract);
248         end
249         return true
250 end
251
252 function service:get_items(node, actor, id)
253         -- Access checking
254         if not self:may(node, actor, "get_items") then
255                 return false, "forbidden";
256         end
257         --
258         local node_obj = self.nodes[node];
259         if not node_obj then
260                 return false, "item-not-found";
261         end
262         if id then -- Restrict results to a single specific item
263                 return true, { [id] = node_obj.data[id] };
264         else
265                 return true, node_obj.data;
266         end
267 end
268
269 function service:get_nodes(actor)
270         -- Access checking
271         if not self:may(nil, actor, "get_nodes") then
272                 return false, "forbidden";
273         end
274         --
275         return true, self.nodes;
276 end
277
278 function service:get_subscriptions(node, actor, jid)
279         -- Access checking
280         local cap;
281         if jid == actor or self:jids_equal(actor, jid) then
282                 cap = "get_subscriptions";
283         else
284                 cap = "get_subscriptions_other";
285         end
286         if not self:may(node, actor, cap) then
287                 return false, "forbidden";
288         end
289         --
290         local node_obj;
291         if node then
292                 node_obj = self.nodes[node];
293                 if not node_obj then
294                         return false, "item-not-found";
295                 end
296         end
297         local normal_jid = self.config.normalize_jid(jid);
298         local subs = self.subscriptions[normal_jid];
299         -- We return the subscription object from the node to save
300         -- a get_subscription() call for each node.
301         local ret = {};
302         if subs then
303                 for jid, subscribed_nodes in pairs(subs) do
304                         if node then -- Return only subscriptions to this node
305                                 if subscribed_nodes[node] then
306                                         ret[#ret+1] = {
307                                                 node = node;
308                                                 jid = jid;
309                                                 subscription = node_obj.subscribers[jid];
310                                         };
311                                 end
312                         else -- Return subscriptions to all nodes
313                                 local nodes = self.nodes;
314                                 for subscribed_node in pairs(subscribed_nodes) do
315                                         ret[#ret+1] = {
316                                                 node = subscribed_node;
317                                                 jid = jid;
318                                                 subscription = nodes[subscribed_node].subscribers[jid];
319                                         };
320                                 end
321                         end
322                 end
323         end
324         return true, ret;
325 end
326
327 -- Access models only affect 'none' affiliation caps, service/default access level...
328 function service:set_node_capabilities(node, actor, capabilities)
329         -- Access checking
330         if not self:may(node, actor, "configure") 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         node_obj.capabilities = capabilities;
339         return true;
340 end
341
342 return _M;