Skip to content

Commit

Permalink
Introduce module to prevent accidental affiliation changes in circles
Browse files Browse the repository at this point in the history
With mod_authz_delegate (see parent commit), all Snikket admins are now
owners in all circle MUCs (and non-circle MUCs, for that matter). This
implies that they're able to change affiliations.

As that is a footgun which may cause desyncs between the circle state
and the MUC membership list, we block both invites and affiliation
changes, unless triggered by code.
  • Loading branch information
horazont committed Apr 1, 2023
1 parent d386ded commit 0e2b7fc
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ansible/files/prosody.cfg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,11 @@ Component ("groups."..DOMAIN) "muc"
"muc_offline_delivery";
"snikket_restricted_users";
"muc_auto_reserve_nicks";
"snikket_circle_muc_protection";
}
restrict_room_creation = "local"
muc_local_only = { "general@groups."..DOMAIN }
circle_muc_protection_main_domain = DOMAIN

authorization = "delegate"
authz_delegate_to = DOMAIN
Expand Down
1 change: 1 addition & 0 deletions ansible/tasks/prosody.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
- mod_snikket_client_id
- mod_snikket_ios_preserve_push
- mod_snikket_restricted_users
- mod_snikket_circle_muc_protection

- name: "Install lua-ossl for encrypted push notifications"
apt:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
--[[
The purpose of this module is to avoid accidental modification of the member
list by users.
With mod_authz_delegate enabled, snikket admins suddenly are granted ownership
on circle MUCs; while this is great for setting avatars, it is problematic
when considering that this also allows affiliation changes which would not be
reflected in the circle membership, causing all kinds of desyncs.
This is a first order solution to this challenge. Future solutions may allow
more modifications as well as synchronisation between MUC affiliation lists
and circle membership or stuff like that.
We want to get a release shipped which allows setting avatars first :-).
]]
local modulemanager = require"core.modulemanager";
local st = require "prosody.util.stanza";

local main_host_name = module:get_option("circle_muc_protection_main_domain");
local main_host_groups = nil;

local function get_groups_module()
if main_host_groups then
return main_host_groups;
end

module:log("debug", "lazy-initializing MUC protection module");
if not main_host_name then
return error("main host name required for circle MUC affiliation protection")
end

local target_module = modulemanager.get_module(main_host_name, "groups_internal");
if not target_module then
return error("groups_internal not available on "..main_host_name);
end

main_host_groups = target_module;
return target_module;
end

local function get_muc_circle(muc_jid)
for group_id in get_groups_module().groups() do
local group_data = main_host_groups.get_info(group_id);
if group_data.muc_jid == muc_jid then
return group_id
end
end
return nil
end

module:hook("muc-pre-set-affiliation", function(event)
if event.actor == nil then
module:log("debug", "affiliation change in %s granted because the actor is nil.", event.room.jid);
return;
end
local group_id = get_muc_circle(event.room.jid);
if group_id ~= nil then
module:log("warn", "affiliation change blocked as %s is associated with circle %s", event.room.jid, group_id);
event.allowed = false;
else
module:log("debug", "affiliation change not blocked as %s is not associated with any circle", event.room.jid);
end
end);

module:hook("muc-pre-invite", function(event)
local room = event.room;
local group_id = get_muc_circle(room.jid);
if group_id ~= nil then
module:log("warn", "invite blocked as %s is associated with circle %s", room.jid, group_id);
event.origin.send(st.error_reply(event.stanza, "cancel", "not-allowed", nil, room.jid));
return true;
else
module:log("debug", "invite not blocked as %s is not associated with any circle", event.room.jid);
end
end, -1); -- run after the main members_only permission check

0 comments on commit 0e2b7fc

Please sign in to comment.