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