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 = subscribed_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;