annotate mod_protect_last_admin/mod_protect_last_admin.lua @ 6556:7477e97a9045

mod_firewall: Apply pre-reload state before re-reading config This change makes load/reload a bit more robust. module.load() runs before module.restore() and it reads from the config and updates the state (if needed). However, after this, module.restore() could run and apply the old state again.
author Matthew Wild <mwild1@gmail.com>
date Sun, 24 May 2026 20:03:20 +0100
parents d1f64ffca879
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
6326
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
1 module:set_global();
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
2
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
3 local um = require "prosody.core.usermanager";
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
4
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
5 local set = require "prosody.util.set";
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
6
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
7 local protected_roles = module:get_option_set("protect_last_admin_roles", { "prosody:admin", "prosody:operator" });
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
8
6327
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
9 local errors = require "prosody.util.error".init(module.name, {
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
10 ["cannot-remove-only-admin"] = {
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
11 code = 400;
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
12 type = "cancel";
6328
187b957dc528 mod_protect_last_admin: Switch to service-unavailable rather than policy-violation
Matthew Wild <mwild1@gmail.com>
parents: 6327
diff changeset
13 condition = "service-unavailable";
6327
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
14 text = "Cannot remove the only administrator";
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
15 extra = {
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
16 namespace = "https://prosody.im/protocol/errors";
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
17 condition = "cannot-remove-only-admin";
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
18 };
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
19 };
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
20 });
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
21
6326
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
22 local function other_protected_users(username, host)
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
23 local protected_users = set.new();
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
24 for protected_role in protected_roles do
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
25 local users = um.get_users_with_role(protected_role, host);
6437
d1f64ffca879 mod_protect_last_admin: Replace util.serialization.serialize with %q
Link Mauve <linkmauve@linkmauve.fr>
parents: 6328
diff changeset
26 module:log("debug", "Role %s: %q", protected_role, users);
6326
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
27 protected_users:add_list(um.get_users_with_role(protected_role, host));
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
28 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
29 protected_users:remove(username);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
30 return not protected_users:empty();
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
31 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
32
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
33 function check_last_admin(event)
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
34 local username, host = event.username, event.host;
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
35
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
36 local current_role = um.get_user_role(username, host);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
37 if not current_role then
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
38 module:log("warn", "Couldn't detect role for %s@%s", username, host);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
39 return;
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
40 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
41
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
42 module:log("debug", "Checking whether to allow this event...");
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
43
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
44 if event.role_name and protected_roles:contains(event.role_name) then
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
45 module:log("debug", "Permitting role change to %s", event.role_name);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
46 return; -- Switching to another protected role is fine
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
47 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
48
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
49 if not protected_roles:contains(current_role.name) then
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
50 module:log("debug", "Permitting event, role %s is not protected", current_role.name);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
51 return; -- This is not a protected user
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
52 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
53
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
54 if not other_protected_users(username, host) then
6327
4f6fb56572f7 mod_protect_last_admin: Return util.error object to provide more information
Matthew Wild <mwild1@gmail.com>
parents: 6326
diff changeset
55 event.reason = errors.new("cannot-remove-only-admin");
6326
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
56 return false;
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
57 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
58 module:log("debug", "Permitting event, no reason not to!");
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
59 end
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
60
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
61 module:hook("pre-delete-user", check_last_admin);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
62 module:hook("pre-disable-user", check_last_admin);
2623515d8507 mod_protect_last_admin: New module to ensure the last admin account on a server cannot be demoted or removed
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
63 module:hook("pre-user-role-change", check_last_admin);