util.pubsub: Small code tidying for :get_subscription()
[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 not node_obj then
158                 return false, "item-not-found";
159         end
160         return true, node_obj.subscribers[jid];
161 end
162
163 function service:create(node, actor)
164         -- Access checking
165         if not self:may(node, actor, "create") then
166                 return false, "forbidden";
167         end
168         --
169         if self.nodes[node] then
170                 return false, "conflict";
171         end
172         
173         self.nodes[node] = {
174                 name = node;
175                 subscribers = {};
176                 config = {};
177                 data = {};
178                 affiliations = {};
179         };
180         local ok, err = self:set_affiliation(node, true, actor, "owner");
181         if not ok then
182                 self.nodes[node] = nil;
183         end
184         return ok, err;
185 end
186
187 function service:publish(node, actor, id, item)
188         -- Access checking
189         if not self:may(node, actor, "publish") then
190                 return false, "forbidden";
191         end
192         --
193         local node_obj = self.nodes[node];
194         if not node_obj then
195                 if not self.config.autocreate_on_publish then
196                         return false, "item-not-found";
197                 end
198                 local ok, err = self:create(node, actor);
199                 if not ok then
200                         return ok, err;
201                 end
202                 node_obj = self.nodes[node];
203         end
204         node_obj.data[id] = item;
205         self.config.broadcaster(node, node_obj.subscribers, item);
206         return true;
207 end
208
209 function service:retract(node, actor, id, retract)
210         -- Access checking
211         if not self:may(node, actor, "retract") then
212                 return false, "forbidden";
213         end
214         --
215         local node_obj = self.nodes[node];
216         if (not node_obj) or (not node_obj.data[id]) then
217                 return false, "item-not-found";
218         end
219         node_obj.data[id] = nil;
220         if retract then
221                 self.config.broadcaster(node, node_obj.subscribers, retract);
222         end
223         return true
224 end
225
226 function service:get_items(node, actor, id)
227         -- Access checking
228         if not self:may(node, actor, "get_items") then
229                 return false, "forbidden";
230         end
231         --
232         local node_obj = self.nodes[node];
233         if not node_obj then
234                 return false, "item-not-found";
235         end
236         if id then -- Restrict results to a single specific item
237                 return true, { [id] = node_obj.data[id] };
238         else
239                 return true, node_obj.data;
240         end
241 end
242
243 function service:get_nodes(actor)
244         -- Access checking
245         if not self:may(nil, actor, "get_nodes") then
246                 return false, "forbidden";
247         end
248         --
249         return true, self.nodes;
250 end
251
252 -- Access models only affect 'none' affiliation caps, service/default access level...
253 function service:set_node_capabilities(node, actor, capabilities)
254         -- Access checking
255         if not self:may(node, actor, "configure") then
256                 return false, "forbidden";
257         end
258         --
259         local node_obj = self.nodes[node];
260         if not node_obj then
261                 return false, "item-not-found";
262         end
263         node_obj.capabilities = capabilities;
264         return true;
265 end
266
267 return _M;