Comparison

mod_pep_plus/mod_pep_plus.lua @ 2801:cb2342cf3f3c

mod_pep_plus: Snapshot from Prosody trunk 910d3c3f60a6 including dependencies
author Kim Alvefur <zash@zash.se>
date Wed, 18 Oct 2017 09:56:29 +0200
parent 2668:f8fc79b3051a
comparison
equal deleted inserted replaced
2800:8d9aed6d1f87 2801:cb2342cf3f3c
1 local pubsub = require "util.pubsub"; 1 local pubsub = module:require "util_pubsub";
2 local jid_bare = require "util.jid".bare; 2 local jid_bare = require "util.jid".bare;
3 local jid_split = require "util.jid".split; 3 local jid_split = require "util.jid".split;
4 local jid_join = require "util.jid".join;
4 local set_new = require "util.set".new; 5 local set_new = require "util.set".new;
5 local st = require "util.stanza"; 6 local st = require "util.stanza";
6 local calculate_hash = require "util.caps".calculate_hash; 7 local calculate_hash = require "util.caps".calculate_hash;
7 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; 8 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
9 local cache = require "util.cache";
10 local set = require "util.set";
8 11
9 local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; 12 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
10 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; 13 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
11 local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; 14 local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
12 15
13 local lib_pubsub = module:require "pubsub"; 16 local lib_pubsub = module:require "pubsub";
14 local handlers = lib_pubsub.handlers;
15 local pubsub_error_reply = lib_pubsub.pubsub_error_reply;
16 17
17 local empty_set = set_new(); 18 local empty_set = set_new();
18 19
19 local services = {}; 20 local services = {};
20 local recipients = {}; 21 local recipients = {};
21 local hash_map = {}; 22 local hash_map = {};
22 23
24 local host = module.host;
25
26 local known_nodes_map = module:open_store("pep", "map");
27 local known_nodes = module:open_store("pep");
28
23 function module.save() 29 function module.save()
24 return { services = services }; 30 return { services = services };
25 end 31 end
26 32
27 function module.restore(data) 33 function module.restore(data)
28 services = data.services; 34 services = data.services;
29 end 35 end
30 36
31 local function subscription_presence(user_bare, recipient) 37 local function subscription_presence(username, recipient)
38 local user_bare = jid_join(username, host);
32 local recipient_bare = jid_bare(recipient); 39 local recipient_bare = jid_bare(recipient);
33 if (recipient_bare == user_bare) then return true; end 40 if (recipient_bare == user_bare) then return true; end
34 local username, host = jid_split(user_bare);
35 return is_contact_subscribed(username, host, recipient_bare); 41 return is_contact_subscribed(username, host, recipient_bare);
36 end 42 end
37 43
38 local function get_broadcaster(name) 44 local function simple_itemstore(username)
45 return function (config, node)
46 if config["persist_items"] then
47 module:log("debug", "Creating new persistent item store for user %s, node %q", username, node);
48 known_nodes_map:set(username, node, true);
49 local archive = module:open_store("pep_"..node, "archive");
50 return lib_pubsub.archive_itemstore(archive, config, username, node, false);
51 else
52 module:log("debug", "Creating new ephemeral item store for user %s, node %q", username, node);
53 known_nodes_map:set(username, node, nil);
54 return cache.new(tonumber(config["max_items"]));
55 end
56 end
57 end
58
59 local function get_broadcaster(username)
60 local user_bare = jid_join(username, host);
39 local function simple_broadcast(kind, node, jids, item) 61 local function simple_broadcast(kind, node, jids, item)
40 if item then 62 if item then
41 item = st.clone(item); 63 item = st.clone(item);
42 item.attr.xmlns = nil; -- Clear the pubsub namespace 64 item.attr.xmlns = nil; -- Clear the pubsub namespace
43 end 65 end
44 local message = st.message({ from = name, type = "headline" }) 66 local message = st.message({ from = user_bare, type = "headline" })
45 :tag("event", { xmlns = xmlns_pubsub_event }) 67 :tag("event", { xmlns = xmlns_pubsub_event })
46 :tag(kind, { node = node }) 68 :tag(kind, { node = node })
47 :add_child(item); 69 :add_child(item);
48 for jid in pairs(jids) do 70 for jid in pairs(jids) do
49 module:log("debug", "Sending notification to %s from %s: %s", jid, name, tostring(item)); 71 module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item));
50 message.attr.to = jid; 72 message.attr.to = jid;
51 module:send(message); 73 module:send(message);
52 end 74 end
53 end 75 end
54 return simple_broadcast; 76 return simple_broadcast;
55 end 77 end
56 78
57 function get_pep_service(name) 79 function get_pep_service(username)
58 local service = services[name]; 80 module:log("debug", "get_pep_service(%q)", username);
81 local user_bare = jid_join(username, host);
82 local service = services[username];
59 if service then 83 if service then
60 return service; 84 return service;
61 end 85 end
62 service = pubsub.new({ 86 service = pubsub.new({
63 capabilities = { 87 capabilities = {
153 set_affiliation = true; 177 set_affiliation = true;
154 }; 178 };
155 }; 179 };
156 180
157 node_defaults = { 181 node_defaults = {
158 ["pubsub#max_items"] = "1"; 182 ["max_items"] = 1;
183 ["persist_items"] = true;
159 }; 184 };
160 185
161 autocreate_on_publish = true; 186 autocreate_on_publish = true;
162 autocreate_on_subscribe = true; 187 autocreate_on_subscribe = true;
163 188
164 broadcaster = get_broadcaster(name); 189 itemstore = simple_itemstore(username);
190 broadcaster = get_broadcaster(username);
165 get_affiliation = function (jid) 191 get_affiliation = function (jid)
166 if jid_bare(jid) == name then 192 if jid_bare(jid) == user_bare then
167 return "owner"; 193 return "owner";
168 elseif subscription_presence(name, jid) then 194 elseif subscription_presence(username, jid) then
169 return "subscriber"; 195 return "subscriber";
170 end 196 end
171 end; 197 end;
172 198
173 normalize_jid = jid_bare; 199 normalize_jid = jid_bare;
174 }); 200 });
175 services[name] = service; 201 local nodes, err = known_nodes:get(username);
176 module:add_item("pep-service", { service = service, jid = name }); 202 if nodes then
203 module:log("debug", "Restoring nodes for user %s", username);
204 for node in pairs(nodes) do
205 module:log("debug", "Restoring node %q", node);
206 service:create(node, true);
207 end
208 elseif err then
209 module:log("error", "Could not restore nodes for %s: %s", username, err);
210 else
211 module:log("debug", "No known nodes");
212 end
213 services[username] = service;
214 module:add_item("pep-service", { service = service, jid = user_bare });
177 return service; 215 return service;
178 end 216 end
179 217
180 function handle_pubsub_iq(event) 218 function handle_pubsub_iq(event)
181 local origin, stanza = event.origin, event.stanza; 219 local origin, stanza = event.origin, event.stanza;
182 local pubsub = stanza.tags[1]; 220 local service_name = origin.username;
183 local action = pubsub.tags[1]; 221 if stanza.attr.to ~= nil then
184 if not action then 222 service_name = jid_split(stanza.attr.to);
185 return origin.send(st.error_reply(stanza, "cancel", "bad-request")); 223 end
186 end
187 local service_name = stanza.attr.to or origin.username.."@"..origin.host
188 local service = get_pep_service(service_name); 224 local service = get_pep_service(service_name);
189 local handler = handlers[stanza.attr.type.."_"..action.name]; 225
190 if handler then 226 return lib_pubsub.handle_pubsub_iq(event, service)
191 handler(origin, stanza, action, service);
192 return true;
193 end
194 end 227 end
195 228
196 module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); 229 module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
197 module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); 230 module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
198 231
223 end 256 end
224 257
225 local function resend_last_item(jid, node, service) 258 local function resend_last_item(jid, node, service)
226 local ok, items = service:get_items(node, jid); 259 local ok, items = service:get_items(node, jid);
227 if not ok then return; end 260 if not ok then return; end
228 for i, id in ipairs(items) do 261 for _, id in ipairs(items) do
229 service.config.broadcaster("items", node, { [jid] = true }, items[id]); 262 service.config.broadcaster("items", node, { [jid] = true }, items[id]);
230 end 263 end
231 end 264 end
232 265
233 local function update_subscriptions(recipient, service_name, nodes) 266 local function update_subscriptions(recipient, service_name, nodes)
266 end 299 end
267 300
268 module:hook("presence/bare", function(event) 301 module:hook("presence/bare", function(event)
269 -- inbound presence to bare JID recieved 302 -- inbound presence to bare JID recieved
270 local origin, stanza = event.origin, event.stanza; 303 local origin, stanza = event.origin, event.stanza;
271 local user = stanza.attr.to or (origin.username..'@'..origin.host);
272 local t = stanza.attr.type; 304 local t = stanza.attr.type;
273 local self = not stanza.attr.to; 305 local is_self = not stanza.attr.to;
274 local service = get_pep_service(user); 306 local username = jid_split(stanza.attr.to);
307 local user_bare = jid_bare(stanza.attr.to);
308 if is_self then
309 username = origin.username;
310 user_bare = jid_join(username, host);
311 end
275 312
276 if not t then -- available presence 313 if not t then -- available presence
277 if self or subscription_presence(user, stanza.attr.from) then 314 if is_self or subscription_presence(username, stanza.attr.from) then
278 local recipient = stanza.attr.from; 315 local recipient = stanza.attr.from;
279 local current = recipients[user] and recipients[user][recipient]; 316 local current = recipients[username] and recipients[username][recipient];
280 local hash, query_node = get_caps_hash_from_presence(stanza, current); 317 local hash, query_node = get_caps_hash_from_presence(stanza, current);
281 if current == hash or (current and current == hash_map[hash]) then return; end 318 if current == hash or (current and current == hash_map[hash]) then return; end
282 if not hash then 319 if not hash then
283 update_subscriptions(recipient, user); 320 update_subscriptions(recipient, username);
284 else 321 else
285 recipients[user] = recipients[user] or {}; 322 recipients[username] = recipients[username] or {};
286 if hash_map[hash] then 323 if hash_map[hash] then
287 update_subscriptions(recipient, user, hash_map[hash]); 324 update_subscriptions(recipient, username, hash_map[hash]);
288 else 325 else
289 recipients[user][recipient] = hash; 326 recipients[username][recipient] = hash;
290 local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host; 327 local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host;
291 if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then 328 if is_self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then
292 -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute 329 -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute
293 origin.send( 330 origin.send(
294 st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"}) 331 st.stanza("iq", {from=user_bare, to=stanza.attr.from, id="disco", type="get"})
295 :tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node}) 332 :tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node})
296 ); 333 );
297 end 334 end
298 end 335 end
299 end 336 end
300 end 337 end
301 elseif t == "unavailable" then 338 elseif t == "unavailable" then
302 update_subscriptions(stanza.attr.from, user); 339 update_subscriptions(stanza.attr.from, username);
303 elseif not self and t == "unsubscribe" then 340 elseif not is_self and t == "unsubscribe" then
304 local from = jid_bare(stanza.attr.from); 341 local from = jid_bare(stanza.attr.from);
305 local subscriptions = recipients[user]; 342 local subscriptions = recipients[username];
306 if subscriptions then 343 if subscriptions then
307 for subscriber in pairs(subscriptions) do 344 for subscriber in pairs(subscriptions) do
308 if jid_bare(subscriber) == from then 345 if jid_bare(subscriber) == from then
309 update_subscriptions(subscriber, user); 346 update_subscriptions(subscriber, username);
310 end 347 end
311 end 348 end
312 end 349 end
313 end 350 end
314 end, 10); 351 end, 10);
319 if not disco then 356 if not disco then
320 return; 357 return;
321 end 358 end
322 359
323 -- Process disco response 360 -- Process disco response
324 local self = not stanza.attr.to; 361 local is_self = stanza.attr.to == nil;
325 local user = stanza.attr.to or (origin.username..'@'..origin.host); 362 local user_bare = jid_bare(stanza.attr.to);
363 local username = jid_split(stanza.attr.to);
364 if is_self then
365 username = origin.username;
366 user_bare = jid_join(username, host);
367 end
326 local contact = stanza.attr.from; 368 local contact = stanza.attr.from;
327 local current = recipients[user] and recipients[user][contact]; 369 local current = recipients[username] and recipients[username][contact];
328 if type(current) ~= "string" then return; end -- check if waiting for recipient's response 370 if type(current) ~= "string" then return; end -- check if waiting for recipient's response
329 local ver = current; 371 local ver = current;
330 if not string.find(current, "#") then 372 if not string.find(current, "#") then
331 ver = calculate_hash(disco.tags); -- calculate hash 373 ver = calculate_hash(disco.tags); -- calculate hash
332 end 374 end
336 local nfeature = feature.attr.var:match("^(.*)%+notify$"); 378 local nfeature = feature.attr.var:match("^(.*)%+notify$");
337 if nfeature then notify:add(nfeature); end 379 if nfeature then notify:add(nfeature); end
338 end 380 end
339 end 381 end
340 hash_map[ver] = notify; -- update hash map 382 hash_map[ver] = notify; -- update hash map
341 if self then 383 if is_self then
384 -- Optimization: Fiddle with other local users
342 for jid, item in pairs(origin.roster) do -- for all interested contacts 385 for jid, item in pairs(origin.roster) do -- for all interested contacts
343 if item.subscription == "both" or item.subscription == "from" then 386 if jid then
344 if not recipients[jid] then recipients[jid] = {}; end 387 local contact_node, contact_host = jid_split(jid);
345 update_subscriptions(contact, jid, notify); 388 if contact_host == host and item.subscription == "both" or item.subscription == "from" then
389 update_subscriptions(user_bare, contact_node, notify);
390 end
346 end 391 end
347 end 392 end
348 end 393 end
349 update_subscriptions(contact, user, notify); 394 update_subscriptions(contact, username, notify);
350 end); 395 end);
351 396
352 module:hook("account-disco-info-node", function(event) 397 module:hook("account-disco-info-node", function(event)
353 local reply, stanza, origin = event.reply, event.stanza, event.origin; 398 local reply, stanza, origin = event.reply, event.stanza, event.origin;
354 local service_name = stanza.attr.to or origin.username.."@"..origin.host 399 local service_name = origin.username;
400 if stanza.attr.to ~= nil then
401 service_name = jid_split(stanza.attr.to);
402 end
355 local service = get_pep_service(service_name); 403 local service = get_pep_service(service_name);
356 local node = event.node; 404 local node = event.node;
357 local ok = service:get_items(node, jid_bare(stanza.attr.from) or true); 405 local ok = service:get_items(node, jid_bare(stanza.attr.from) or true);
358 if not ok then return; end 406 if not ok then return; end
359 event.exists = true; 407 event.exists = true;
360 reply:tag('identity', {category='pubsub', type='leaf'}):up(); 408 reply:tag('identity', {category='pubsub', type='leaf'}):up();
361 end); 409 end);
362 410
363 module:hook("account-disco-info", function(event) 411 module:hook("account-disco-info", function(event)
364 local reply = event.reply; 412 local origin, reply = event.origin, event.reply;
413
365 reply:tag('identity', {category='pubsub', type='pep'}):up(); 414 reply:tag('identity', {category='pubsub', type='pep'}):up();
366 reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up(); 415
416 local username = jid_split(reply.attr.from) or origin.username;
417 local service = get_pep_service(username);
418
419 local suppored_features = lib_pubsub.get_feature_set(service) + set.new{
420 -- Features not covered by the above
421 "access-presence",
422 "auto-subscribe",
423 "filtered-notifications",
424 "last-published",
425 "persistent-items",
426 "presence-notifications",
427 "presence-subscribe",
428 };
429
430 for feature in suppored_features do
431 reply:tag('feature', {var=xmlns_pubsub.."#"..feature}):up();
432 end
367 end); 433 end);
368 434
369 module:hook("account-disco-items-node", function(event) 435 module:hook("account-disco-items-node", function(event)
370 local reply, stanza, origin = event.reply, event.stanza, event.origin; 436 local reply, stanza, origin = event.reply, event.stanza, event.origin;
371 local node = event.node; 437 local node = event.node;
372 local service_name = stanza.attr.to or origin.username.."@"..origin.host 438 local is_self = stanza.attr.to == nil;
373 local service = get_pep_service(service_name); 439 local user_bare = jid_bare(stanza.attr.to);
440 local username = jid_split(stanza.attr.to);
441 if is_self then
442 username = origin.username;
443 user_bare = jid_join(username, host);
444 end
445 local service = get_pep_service(username);
374 local ok, ret = service:get_items(node, jid_bare(stanza.attr.from) or true); 446 local ok, ret = service:get_items(node, jid_bare(stanza.attr.from) or true);
375 if not ok then return; end 447 if not ok then return; end
376 event.exists = true; 448 event.exists = true;
377 for _, id in ipairs(ret) do 449 for _, id in ipairs(ret) do
378 reply:tag("item", { jid = service_name, name = id }):up(); 450 reply:tag("item", { jid = user_bare, name = id }):up();
379 end 451 end
380 end); 452 end);
381 453
382 module:hook("account-disco-items", function(event) 454 module:hook("account-disco-items", function(event)
383 local reply, stanza, origin = event.reply, event.stanza, event.origin; 455 local reply, stanza, origin = event.reply, event.stanza, event.origin;
384 456
385 local service_name = reply.attr.from or origin.username.."@"..origin.host 457 local is_self = stanza.attr.to == nil;
386 local service = get_pep_service(service_name); 458 local user_bare = jid_bare(stanza.attr.to);
459 local username = jid_split(stanza.attr.to);
460 if is_self then
461 username = origin.username;
462 user_bare = jid_join(username, host);
463 end
464 local service = get_pep_service(username);
465
387 local ok, ret = service:get_nodes(jid_bare(stanza.attr.from)); 466 local ok, ret = service:get_nodes(jid_bare(stanza.attr.from));
388 if not ok then return; end 467 if not ok then return; end
389 468
390 for node, node_obj in pairs(ret) do 469 for node, node_obj in pairs(ret) do
391 reply:tag("item", { jid = service_name, node = node, name = node_obj.config.name }):up(); 470 reply:tag("item", { jid = user_bare, node = node, name = node_obj.config.name }):up();
392 end 471 end
393 end); 472 end);