util.pubsub: Fix traceback when using autocreate-on-subscribe
[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                 nodes = {};
18         }, service_mt);
19 end
20
21 function service:jids_equal(jid1, jid2)
22         local normalize = self.config.normalize_jid;
23         return normalize(jid1) == normalize(jid2);
24 end
25
26 function service:may(node, actor, action)
27         if actor == true then return true; end
28         
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         local node_capabilities = node_obj and node_obj.capabilities;
37         local service_capabilities = self.config.capabilities;
38         
39         -- Check if node allows/forbids it      
40         if node_capabilities then
41                 local caps = node_capabilities[node_aff or service_aff];
42                 if caps then
43                         local can = caps[action];
44                         if can ~= nil then
45                                 return can;
46                         end
47                 end
48         end
49         -- Check service-wide capabilities instead
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, nil, jid);
73         if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
74                 local ok, err = self:add_subscription(node, nil, 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, nil, 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 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         return true;
116 end
117
118 function service:remove_subscription(node, actor, jid)
119         -- Access checking
120         local cap;
121         if jid == actor or self:jids_equal(actor, jid) then
122                 cap = "unsubscribe";
123         else
124                 cap = "unsubscribe_other";
125         end
126         if not self:may(node, actor, cap) then
127                 return false, "forbidden";
128         end
129         if not self:may(node, jid, "be_unsubscribed") then
130                 return false, "forbidden";
131         end
132         --
133         local node_obj = self.nodes[node];
134         if not node_obj then
135                 return false, "item-not-found";
136         end
137         if not node_obj.subscribers[jid] then
138                 return false, "not-subscribed";
139         end
140         node_obj.subscribers[jid] = nil;
141         return true;
142 end
143
144 function service:get_subscription(node, actor, jid)
145         -- Access checking
146         local cap;
147         if jid == actor or self:jids_equal(actor, jid) then
148                 cap = "get_subscription";
149         else
150                 cap = "get_subscription_other";
151         end
152         if not self:may(node, actor, cap) then
153                 return false, "forbidden";
154         end
155         --
156         local node_obj = self.nodes[node];
157         if node_obj then
158                 return true, node_obj.subscribers[jid];
159         end
160 end
161
162 function service:create(node, actor)
163         -- Access checking
164         if not self:may(node, actor, "create") then
165                 return false, "forbidden";
166         end
167         --
168         if self.nodes[node] then
169                 return false, "conflict";
170         end
171         
172         self.nodes[node] = {
173                 name = node;
174                 subscribers = {};
175                 config = {};
176                 data = {};
177                 affiliations = {};
178         };
179         local ok, err = self:set_affiliation(node, true, actor, "owner");
180         if not ok then
181                 self.nodes[node] = nil;
182         end
183         return ok, err;
184 end
185
186 function service:publish(node, actor, id, item)
187         -- Access checking
188         if not self:may(node, actor, "publish") then
189                 return false, "forbidden";
190         end
191         --
192         local node_obj = self.nodes[node];
193         if not node_obj then
194                 if not self.config.autocreate_on_publish then
195                         return false, "item-not-found";
196                 end
197                 local ok, err = self:create(node, actor);
198                 if not ok then
199                         return ok, err;
200                 end
201                 node_obj = self.nodes[node];
202         end
203         node_obj.data[id] = item;
204         self.config.broadcaster(node, node_obj.subscribers, item);
205         return true;
206 end
207
208 function service:retract(node, actor, id, retract)
209         -- Access checking
210         if not self:may(node, actor, "retract") then
211                 return false, "forbidden";
212         end
213         --
214         local node_obj = self.nodes[node];
215         if (not node_obj) or (not node_obj.data[id]) then
216                 return false, "item-not-found";
217         end
218         node_obj.data[id] = nil;
219         if retract then
220                 self.config.broadcaster(node, node_obj.subscribers, retract);
221         end
222         return true
223 end
224
225 function service:get_items(node, actor, id)
226         -- Access checking
227         if not self:may(node, actor, "get_items") then
228                 return false, "forbidden";
229         end
230         --
231         local node_obj = self.nodes[node];
232         if not node_obj then
233                 return false, "item-not-found";
234         end
235         if id then -- Restrict results to a single specific item
236                 return true, { [id] = node_obj.data[id] };
237         else
238                 return true, node_obj.data;
239         end
240 end
241
242 function service:get_nodes(actor)
243         -- Access checking
244         if not self:may(nil, actor, "get_nodes") then
245                 return false, "forbidden";
246         end
247         --
248         return true, self.nodes;
249 end
250
251 -- Access models only affect 'none' affiliation caps, service/default access level...
252 function service:set_node_capabilities(node, actor, capabilities)
253         -- Access checking
254         if not self:may(node, actor, "configure") 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         node_obj.capabilities = capabilities;
263         return true;
264 end
265
266 return _M;