util.pubsub: Too many changes to list or split sensibly. Added access control to...
[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:may(node, actor, action)
22         if actor == true then return true; end
23         
24         
25         local node_obj = self.nodes[node];
26         local node_aff = node_obj and node_obj.affiliations[actor];
27         local service_aff = self.affiliations[actor]
28                          or self.config.get_affiliation(actor, node, action)
29                          or "none";
30         
31         local node_capabilities = node_obj and node_obj.capabilities;
32         local service_capabilities = self.config.capabilities;
33         
34         -- Check if node allows/forbids it      
35         if node_capabilities then
36                 local caps = node_capabilities[node_aff or service_aff];
37                 if caps then
38                         local can = caps[action];
39                         if can ~= nil then
40                                 return can;
41                         end
42                 end
43         end
44         -- Check service-wide capabilities instead
45         local caps = service_capabilities[node_aff or service_aff];
46         if caps then
47                 local can = caps[action];
48                 if can ~= nil then
49                         return can;
50                 end
51         end
52         
53         return false;
54 end
55
56 function service:set_affiliation(node, actor, jid, affiliation)
57         -- Access checking
58         if not self:may(node, actor, "set_affiliation") then
59                 return false, "forbidden";
60         end
61         --
62         local node_obj = self.nodes[node];
63         if not node_obj then
64                 return false, "item-not-found";
65         end
66         node_obj.affiliations[jid] = affiliation;
67         local _, jid_sub = self:get_subscription(node, nil, jid);
68         if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
69                 local ok, err = self:add_subscription(node, nil, jid);
70                 if not ok then
71                         return ok, err;
72                 end
73         elseif jid_sub and not self:may(node, jid, "be_subscribed") then
74                 local ok, err = self:add_subscription(node, nil, jid);
75                 if not ok then
76                         return ok, err;
77                 end
78         end
79         return true;
80 end
81
82 function service:add_subscription(node, actor, jid, options)
83         -- Access checking
84         local cap;
85         if jid == actor or self.config.jids_equal(actor, jid) then
86                 cap = "subscribe";
87         else
88                 cap = "subscribe_other";
89         end
90         if not self:may(node, actor, cap) then
91                 return false, "forbidden";
92         end
93         if not self:may(node, jid, "be_subscribed") then
94                 return false, "forbidden";
95         end
96         --
97         local node_obj = self.nodes[node];
98         if not node_obj then
99                 if not self.config.autocreate_on_subscribe then
100                         return false, "item-not-found";
101                 else
102                         local ok, err = self:create(node, actor);
103                         if not ok then
104                                 return ok, err;
105                         end
106                 end
107         end
108         node_obj.subscribers[jid] = options or true;
109         return true;
110 end
111
112 function service:remove_subscription(node, actor, jid)
113         -- Access checking
114         local cap;
115         if jid == actor or self.config.jids_equal(actor, jid) then
116                 cap = "unsubscribe";
117         else
118                 cap = "unsubscribe_other";
119         end
120         if not self:may(node, actor, cap) then
121                 return false, "forbidden";
122         end
123         if not self:may(node, jid, "be_unsubscribed") then
124                 return false, "forbidden";
125         end
126         --
127         local node_obj = self.nodes[node];
128         if not node_obj then
129                 return false, "item-not-found";
130         end
131         if not node_obj.subscribers[jid] then
132                 return false, "not-subscribed";
133         end
134         node_obj.subscribers[jid] = nil;
135         return true;
136 end
137
138 function service:get_subscription(node, actor, jid)
139         -- Access checking
140         local cap;
141         if jid == actor or self.config.jids_equal(actor, jid) then
142                 cap = "get_subscription";
143         else
144                 cap = "get_subscription_other";
145         end
146         if not self:may(node, actor, cap) then
147                 return false, "forbidden";
148         end
149         --
150         local node_obj = self.nodes[node];
151         if node_obj then
152                 return true, node_obj.subscribers[jid];
153         end
154 end
155
156 function service:create(node, actor)
157         -- Access checking
158         if not self:may(node, actor, "create") then
159                 return false, "forbidden";
160         end
161         --
162         if self.nodes[node] then
163                 return false, "conflict";
164         end
165         
166         self.nodes[node] = {
167                 name = node;
168                 subscribers = {};
169                 config = {};
170                 data = {};
171                 affiliations = {};
172         };
173         local ok, err = self:set_affiliation(node, true, actor, "owner");
174         if not ok then
175                 self.nodes[node] = nil;
176         end
177         return ok, err;
178 end
179
180 function service:publish(node, actor, id, item)
181         -- Access checking
182         if not self:may(node, actor, "publish") then
183                 return false, "forbidden";
184         end
185         --
186         local node_obj = self.nodes[node];
187         if not node_obj then
188                 if not self.config.autocreate_on_publish then
189                         return false, "item-not-found";
190                 end
191                 local ok, err = self:create(node, actor);
192                 if not ok then
193                         return ok, err;
194                 end
195                 node_obj = self.nodes[node];
196         end
197         node_obj.data[id] = item;
198         self.config.broadcaster(node, node_obj.subscribers, item);
199         return true;
200 end
201
202 function service:retract(node, actor, id, retract)
203         -- Access checking
204         if not self:may(node, actor, "retract") then
205                 return false, "forbidden";
206         end
207         --
208         local node_obj = self.nodes[node];
209         if (not node_obj) or (not node_obj.data[id]) then
210                 return false, "item-not-found";
211         end
212         node_obj.data[id] = nil;
213         if retract then
214                 self.config.broadcaster(node, node_obj.subscribers, retract);
215         end
216         return true
217 end
218
219 function service:get_items(node, actor, id)
220         -- Access checking
221         if not self:may(node, actor, "get_items") then
222                 return false, "forbidden";
223         end
224         --
225         local node_obj = self.nodes[node];
226         if not node_obj then
227                 return false, "item-not-found";
228         end
229         if id then -- Restrict results to a single specific item
230                 return true, { node_obj.data[id] };
231         else
232                 return true, node_obj.data;
233         end
234 end
235
236 function service:get_nodes(actor)
237         -- Access checking
238         if not self:may(node, actor, "get_nodes") then
239                 return false, "forbidden";
240         end
241         --
242         return true, self.nodes;
243 end
244
245 return _M;