comparison plugins/muc/muc.lib.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parents 6e051bfca12d
children 8f709577fe8e
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
21 local jid_resource = require "util.jid".resource; 21 local jid_resource = require "util.jid".resource;
22 local resourceprep = require "util.encodings".stringprep.resourceprep; 22 local resourceprep = require "util.encodings".stringprep.resourceprep;
23 local st = require "util.stanza"; 23 local st = require "util.stanza";
24 local base64 = require "util.encodings".base64; 24 local base64 = require "util.encodings".base64;
25 local md5 = require "util.hashes".md5; 25 local md5 = require "util.hashes".md5;
26 local new_id = require "util.id".medium;
26 27
27 local log = module._log; 28 local log = module._log;
28 29
29 local occupant_lib = module:require "muc/occupant" 30 local occupant_lib = module:require "muc/occupant"
30 local muc_util = module:require "muc/util"; 31 local muc_util = module:require "muc/util";
37 function room_mt:__tostring() 38 function room_mt:__tostring()
38 return "MUC room ("..self.jid..")"; 39 return "MUC room ("..self.jid..")";
39 end 40 end
40 41
41 function room_mt.save() 42 function room_mt.save()
42 -- overriden by mod_muc.lua 43 -- overridden by mod_muc.lua
43 end 44 end
44 45
45 function room_mt:get_occupant_jid(real_jid) 46 function room_mt:get_occupant_jid(real_jid)
46 return self._jid_nick[real_jid] 47 return self._jid_nick[real_jid]
47 end 48 end
215 end 216 end
216 end 217 end
217 218
218 -- Broadcasts an occupant's presence to the whole room 219 -- Broadcasts an occupant's presence to the whole room
219 -- Takes the x element that goes into the stanzas 220 -- Takes the x element that goes into the stanzas
220 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) 221 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable)
221 local base_x = x.base or x; 222 local base_x = x.base or x;
222 -- Build real jid and (optionally) occupant jid template presences 223 -- Build real jid and (optionally) occupant jid template presences
223 local base_presence do 224 local base_presence do
224 -- Try to use main jid's presence 225 -- Try to use main jid's presence
225 local pr = occupant:get_presence(); 226 local pr = occupant:get_presence();
226 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") then 227 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") and not force_unavailable then
227 base_presence = st.clone(pr); 228 base_presence = st.clone(pr);
228 else -- user is leaving but didn't send a leave presence. make one for them 229 else -- user is leaving but didn't send a leave presence. make one for them
229 base_presence = st.presence {from = occupant.nick; type = "unavailable";}; 230 base_presence = st.presence {from = occupant.nick; type = "unavailable";};
230 end 231 end
231 end 232 end
277 self_x = st.clone(x.self or base_x); 278 self_x = st.clone(x.self or base_x);
278 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); 279 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason);
279 self_p = st.clone(base_presence):add_child(self_x); 280 self_p = st.clone(base_presence):add_child(self_x);
280 end 281 end
281 282
282 -- General populance 283 local broadcast_roles = self:get_presence_broadcast();
284
285 -- General populace
283 for occupant_nick, n_occupant in self:each_occupant() do 286 for occupant_nick, n_occupant in self:each_occupant() do
284 if occupant_nick ~= occupant.nick then 287 if occupant_nick ~= occupant.nick then
285 local pr; 288 local pr;
286 if can_see_real_jids(whois, n_occupant) then 289 if can_see_real_jids(whois, n_occupant) then
287 pr = get_full_p(); 290 pr = get_full_p();
288 elseif occupant.bare_jid == n_occupant.bare_jid then 291 elseif occupant.bare_jid == n_occupant.bare_jid then
289 pr = self_p; 292 pr = self_p;
290 else 293 else
291 pr = get_anon_p(); 294 pr = get_anon_p();
292 end 295 end
293 self:route_to_occupant(n_occupant, pr); 296 if broadcast_roles[occupant.role or "none"] or force_unavailable then
297 self:route_to_occupant(n_occupant, pr);
298 elseif prev_role and broadcast_roles[prev_role] then
299 pr.attr.type = 'unavailable';
300 self:route_to_occupant(n_occupant, pr);
301 end
302
294 end 303 end
295 end 304 end
296 305
297 -- Presences for occupant itself 306 -- Presences for occupant itself
298 self_x:tag("status", {code = "110";}):up(); 307 self_x:tag("status", {code = "110";}):up();
312 321
313 function room_mt:send_occupant_list(to, filter) 322 function room_mt:send_occupant_list(to, filter)
314 local to_bare = jid_bare(to); 323 local to_bare = jid_bare(to);
315 local is_anonymous = false; 324 local is_anonymous = false;
316 local whois = self:get_whois(); 325 local whois = self:get_whois();
326 local broadcast_roles = self:get_presence_broadcast();
317 if whois ~= "anyone" then 327 if whois ~= "anyone" then
318 local affiliation = self:get_affiliation(to); 328 local affiliation = self:get_affiliation(to);
319 if affiliation ~= "admin" and affiliation ~= "owner" then 329 if affiliation ~= "admin" and affiliation ~= "owner" then
320 local occupant = self:get_occupant_by_real_jid(to); 330 local occupant = self:get_occupant_by_real_jid(to);
321 if not (occupant and can_see_real_jids(whois, occupant)) then 331 if not (occupant and can_see_real_jids(whois, occupant)) then
328 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); 338 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
329 self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids 339 self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids
330 local pres = st.clone(occupant:get_presence()); 340 local pres = st.clone(occupant:get_presence());
331 pres.attr.to = to; 341 pres.attr.to = to;
332 pres:add_child(x); 342 pres:add_child(x);
333 self:route_stanza(pres); 343 if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then
344 self:route_stanza(pres);
345 end
334 end 346 end
335 end 347 end
336 end 348 end
337 349
338 function room_mt:get_disco_info(stanza) 350 function room_mt:get_disco_info(stanza)
389 if is_last_session then 401 if is_last_session then
390 x:tag("status", {code = "333"}); 402 x:tag("status", {code = "333"});
391 end 403 end
392 self:publicise_occupant_status(new_occupant or occupant, x); 404 self:publicise_occupant_status(new_occupant or occupant, x);
393 if is_last_session then 405 if is_last_session then
394 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 406 module:fire_event("muc-occupant-left", {
407 room = self;
408 nick = occupant.nick;
409 occupant = occupant;
410 });
395 end 411 end
396 return true; 412 return true;
397 end 413 end
398 414
399 -- Give the room creator owner affiliation 415 -- Give the room creator owner affiliation
426 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); 442 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
427 return true; 443 return true;
428 end 444 end
429 end, 1); 445 end, 1);
430 446
447 module:hook("muc-occupant-pre-join", function(event)
448 local nick = jid_resource(event.occupant.nick);
449 if not resourceprep(nick, true) then -- strict
450 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation"));
451 return true;
452 end
453 end, 2);
454
455 module:hook("muc-occupant-pre-change", function(event)
456 local nick = jid_resource(event.dest_occupant.nick);
457 if not resourceprep(nick, true) then -- strict
458 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation"));
459 return true;
460 end
461 end, 2);
462
431 function room_mt:handle_first_presence(origin, stanza) 463 function room_mt:handle_first_presence(origin, stanza)
432 if not stanza:get_child("x", "http://jabber.org/protocol/muc") then
433 module:log("debug", "Room creation without <x>, possibly desynced");
434
435 origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
436 return true;
437 end
438
439 local real_jid = stanza.attr.from; 464 local real_jid = stanza.attr.from;
440 local dest_jid = stanza.attr.to; 465 local dest_jid = stanza.attr.to;
441 local bare_jid = jid_bare(real_jid); 466 local bare_jid = jid_bare(real_jid);
442 if module:fire_event("muc-room-pre-create", { 467 if module:fire_event("muc-room-pre-create", {
443 room = self; 468 room = self;
503 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); 528 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc");
504 529
505 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then 530 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then
506 module:log("debug", "Attempted join without <x>, possibly desynced"); 531 module:log("debug", "Attempted join without <x>, possibly desynced");
507 origin.send(st.error_reply(stanza, "cancel", "item-not-found", 532 origin.send(st.error_reply(stanza, "cancel", "item-not-found",
508 "You must join the room before sending presence updates")); 533 "You are not currently connected to this chat"));
509 return true; 534 return true;
510 end 535 end
511 536
512 local is_first_dest_session; 537 local is_first_dest_session;
513 local dest_occupant; 538 local dest_occupant;
608 -- self:build_item_list(orig_occupant, x, false, dest_nick); 633 -- self:build_item_list(orig_occupant, x, false, dest_nick);
609 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); 634 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick);
610 x:tag("status", {code = "303";}):up(); 635 x:tag("status", {code = "303";}):up();
611 x:tag("status", {code = "110";}):up(); 636 x:tag("status", {code = "110";}):up();
612 self:route_stanza(generated_unavail:add_child(x)); 637 self:route_stanza(generated_unavail:add_child(x));
613 dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant 638 dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
614 end 639 end
615 end 640 end
616 641
617 self:save_occupant(orig_occupant); 642 self:save_occupant(orig_occupant);
618 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); 643 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick);
874 self:save_occupant(occupant); 899 self:save_occupant(occupant);
875 occupants_updated[occupant] = true; 900 occupants_updated[occupant] = true;
876 end 901 end
877 for occupant in pairs(occupants_updated) do 902 for occupant in pairs(occupants_updated) do
878 self:publicise_occupant_status(occupant, x); 903 self:publicise_occupant_status(occupant, x);
879 module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;}); 904 module:fire_event("muc-occupant-left", {
905 room = self;
906 nick = occupant.nick;
907 occupant = occupant;
908 });
880 end 909 end
881 end 910 end
882 911
883 function room_mt:destroy(newjid, reason, password) 912 function room_mt:destroy(newjid, reason, password)
884 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 913 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
967 local item = stanza.tags[1].tags[1]; 996 local item = stanza.tags[1].tags[1];
968 local _aff = item.attr.affiliation; 997 local _aff = item.attr.affiliation;
969 local _aff_rank = valid_affiliations[_aff or "none"]; 998 local _aff_rank = valid_affiliations[_aff or "none"];
970 local _rol = item.attr.role; 999 local _rol = item.attr.role;
971 if _aff and _aff_rank and not _rol then 1000 if _aff and _aff_rank and not _rol then
972 -- You need to be at least an admin, and be requesting info about your affifiliation or lower 1001 -- You need to be at least an admin, and be requesting info about your affiliation or lower
973 -- e.g. an admin can't ask for a list of owners 1002 -- e.g. an admin can't ask for a list of owners
974 local affiliation_rank = valid_affiliations[affiliation or "none"]; 1003 local affiliation_rank = valid_affiliations[affiliation or "none"];
975 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) 1004 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
976 or (self:get_whois() == "anyone") then 1005 or (self:get_whois() == "anyone") then
977 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 1006 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
1044 end 1073 end
1045 1074
1046 function room_mt:handle_groupchat_to_room(origin, stanza) 1075 function room_mt:handle_groupchat_to_room(origin, stanza)
1047 local from = stanza.attr.from; 1076 local from = stanza.attr.from;
1048 local occupant = self:get_occupant_by_real_jid(from); 1077 local occupant = self:get_occupant_by_real_jid(from);
1078 if not stanza.attr.id then
1079 stanza.attr.id = new_id()
1080 end
1049 if module:fire_event("muc-occupant-groupchat", { 1081 if module:fire_event("muc-occupant-groupchat", {
1050 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; 1082 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
1051 }) then return true; end 1083 }) then return true; end
1052 stanza.attr.from = occupant.nick; 1084 stanza.attr.from = occupant.nick;
1053 self:broadcast_message(stanza); 1085 self:broadcast_message(stanza);
1292 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 1324 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
1293 if occupant.bare_jid == jid or ( 1325 if occupant.bare_jid == jid or (
1294 -- Outcast can be by host. 1326 -- Outcast can be by host.
1295 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host 1327 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
1296 ) then 1328 ) then
1297 -- need to publcize in all cases; as affiliation in <item/> has changed. 1329 -- need to publicize in all cases; as affiliation in <item/> has changed.
1298 occupants_updated[occupant] = occupant.role; 1330 occupants_updated[occupant] = occupant.role;
1299 if occupant.role ~= role and ( 1331 if occupant.role ~= role and (
1300 is_downgrade or 1332 is_downgrade or
1301 valid_roles[occupant.role or "none"] < role_rank -- upgrade 1333 valid_roles[occupant.role or "none"] < role_rank -- upgrade
1302 ) then 1334 ) then
1319 1351
1320 if next(occupants_updated) ~= nil then 1352 if next(occupants_updated) ~= nil then
1321 for occupant, old_role in pairs(occupants_updated) do 1353 for occupant, old_role in pairs(occupants_updated) do
1322 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1354 self:publicise_occupant_status(occupant, x, nil, actor, reason);
1323 if occupant.role == nil then 1355 if occupant.role == nil then
1324 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1356 module:fire_event("muc-occupant-left", {
1357 room = self;
1358 nick = occupant.nick;
1359 occupant = occupant;
1360 });
1325 elseif is_semi_anonymous and 1361 elseif is_semi_anonymous and
1326 (old_role == "moderator" and occupant.role ~= "moderator") or 1362 (old_role == "moderator" and occupant.role ~= "moderator") or
1327 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status 1363 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status
1328 -- Send everyone else's presences (as jid visibility has changed) 1364 -- Send everyone else's presences (as jid visibility has changed)
1329 for real_jid in occupant:each_session() do 1365 for real_jid in occupant:each_session() do
1371 function room_mt:get_role(nick) 1407 function room_mt:get_role(nick)
1372 local occupant = self:get_occupant_by_nick(nick); 1408 local occupant = self:get_occupant_by_nick(nick);
1373 return occupant and occupant.role or nil; 1409 return occupant and occupant.role or nil;
1374 end 1410 end
1375 1411
1412 function room_mt:may_set_role(actor, occupant, role)
1413 local event = {
1414 room = self,
1415 actor = actor,
1416 occupant = occupant,
1417 role = role,
1418 };
1419
1420 module:fire_event("muc-pre-set-role", event);
1421 if event.allowed ~= nil then
1422 return event.allowed, event.error, event.condition;
1423 end
1424
1425 -- Can't do anything to other owners or admins
1426 local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
1427 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
1428 return nil, "cancel", "not-allowed";
1429 end
1430
1431 -- If you are trying to give or take moderator role you need to be an owner or admin
1432 if occupant.role == "moderator" or role == "moderator" then
1433 local actor_affiliation = self:get_affiliation(actor);
1434 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
1435 return nil, "cancel", "not-allowed";
1436 end
1437 end
1438
1439 -- Need to be in the room and a moderator
1440 local actor_occupant = self:get_occupant_by_real_jid(actor);
1441 if not actor_occupant or actor_occupant.role ~= "moderator" then
1442 return nil, "cancel", "not-allowed";
1443 end
1444
1445 return true;
1446 end
1447
1376 function room_mt:set_role(actor, occupant_jid, role, reason) 1448 function room_mt:set_role(actor, occupant_jid, role, reason)
1377 if not actor then return nil, "modify", "not-acceptable"; end 1449 if not actor then return nil, "modify", "not-acceptable"; end
1378 1450
1379 local occupant = self:get_occupant_by_nick(occupant_jid); 1451 local occupant = self:get_occupant_by_nick(occupant_jid);
1380 if not occupant then return nil, "modify", "item-not-found"; end 1452 if not occupant then return nil, "modify", "item-not-found"; end
1385 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` 1457 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil`
1386 1458
1387 if actor == true then 1459 if actor == true then
1388 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below 1460 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
1389 else 1461 else
1390 -- Can't do anything to other owners or admins 1462 local allowed, err, condition = self:may_set_role(actor, occupant, role)
1391 local occupant_affiliation = self:get_affiliation(occupant.bare_jid); 1463 if not allowed then
1392 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then 1464 return allowed, err, condition;
1393 return nil, "cancel", "not-allowed";
1394 end
1395
1396 -- If you are trying to give or take moderator role you need to be an owner or admin
1397 if occupant.role == "moderator" or role == "moderator" then
1398 local actor_affiliation = self:get_affiliation(actor);
1399 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
1400 return nil, "cancel", "not-allowed";
1401 end
1402 end
1403
1404 -- Need to be in the room and a moderator
1405 local actor_occupant = self:get_occupant_by_real_jid(actor);
1406 if not actor_occupant or actor_occupant.role ~= "moderator" then
1407 return nil, "cancel", "not-allowed";
1408 end 1465 end
1409 end 1466 end
1410 1467
1411 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); 1468 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
1412 if not role then 1469 if not role then
1413 x:tag("status", {code = "307"}):up(); 1470 x:tag("status", {code = "307"}):up();
1414 end 1471 end
1472
1473 local prev_role = occupant.role;
1415 occupant.role = role; 1474 occupant.role = role;
1416 self:save_occupant(occupant); 1475 self:save_occupant(occupant);
1417 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1476 self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role);
1418 if role == nil then 1477 if role == nil then
1419 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1478 module:fire_event("muc-occupant-left", {
1479 room = self;
1480 nick = occupant.nick;
1481 occupant = occupant;
1482 });
1420 end 1483 end
1421 return true; 1484 return true;
1422 end 1485 end
1423 1486
1424 local whois = module:require "muc/whois"; 1487 local whois = module:require "muc/whois";