Comparison

plugins/mod_authz_internal.lua @ 12648:f299e570a0fe

mod_authz_internal: Use util.roles, some API changes and config support This commit was too awkward to split (hg record didn't like it), so: - Switch to the new util.roles lib to provide a consistent representation of a role object. - Change API method from get_role_info() to get_role_by_name() (touches sessionmanager and usermanager) - Change get_roles() to get_user_roles(), take a username instead of a JID This is more consistent with all other usermanager API methods. - Support configuration of custom roles and permissions via the config file (to be documented).
author Matthew Wild <mwild1@gmail.com>
date Tue, 19 Jul 2022 18:02:02 +0100
parent 12642:9061f9621330
child 12662:07424992d7fc
comparison
equal deleted inserted replaced
12647:a661292d074a 12648:f299e570a0fe
1 local array = require "util.array"; 1 local array = require "util.array";
2 local it = require "util.iterators"; 2 local it = require "util.iterators";
3 local set = require "util.set"; 3 local set = require "util.set";
4 local jid_split, jid_bare = require "util.jid".split, require "util.jid".bare; 4 local jid_split, jid_bare = require "util.jid".split, require "util.jid".bare;
5 local normalize = require "util.jid".prep; 5 local normalize = require "util.jid".prep;
6 local roles = require "util.roles";
7
6 local config_global_admin_jids = module:context("*"):get_option_set("admins", {}) / normalize; 8 local config_global_admin_jids = module:context("*"):get_option_set("admins", {}) / normalize;
7 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize; 9 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize;
8 local host = module.host; 10 local host = module.host;
9 local role_store = module:open_store("roles"); 11 local role_store = module:open_store("roles");
10 local role_map_store = module:open_store("roles", "map"); 12 local role_map_store = module:open_store("roles", "map");
11 13
12 local role_methods = {}; 14 local role_registry = {};
13 local role_mt = { __index = role_methods }; 15
14 16 function register_role(role)
15 local role_registry = { 17 if role_registry[role.name] ~= nil then
16 ["prosody:operator"] = { 18 return error("A role '"..role.name.."' is already registered");
17 default = true; 19 end
18 priority = 75; 20 if not roles.is_role(role) then
19 includes = { "prosody:admin" }; 21 -- Convert table syntax to real role object
20 }; 22 for i, inherited_role in ipairs(role.inherits or {}) do
21 ["prosody:admin"] = { 23 if type(inherited_role) == "string" then
22 default = true; 24 role.inherits[i] = assert(role_registry[inherited_role], "The named role '"..inherited_role.."' is not registered");
23 priority = 50; 25 end
24 includes = { "prosody:user" }; 26 end
25 }; 27 if not role.permissions then role.permissions = {}; end
26 ["prosody:user"] = { 28 for _, allow_permission in ipairs(role.allow or {}) do
27 default = true; 29 role.permissions[allow_permission] = true;
28 priority = 25; 30 end
29 includes = { "prosody:restricted" }; 31 for _, deny_permission in ipairs(role.deny or {}) do
30 }; 32 role.permissions[deny_permission] = false;
31 ["prosody:restricted"] = { 33 end
32 default = true; 34 role = roles.new(role);
33 priority = 15; 35 end
34 }; 36 role_registry[role.name] = role;
35 }; 37 end
36 38
37 -- Some processing on the role registry 39 -- Default roles
38 for role_name, role_info in pairs(role_registry) do 40 register_role {
39 role_info.name = role_name; 41 name = "prosody:restricted";
40 role_info.includes = set.new(role_info.includes) / function (included_role_name) 42 priority = 15;
41 return role_registry[included_role_name]; 43 };
42 end; 44
43 if not role_info.permissions then 45 register_role {
44 role_info.permissions = {}; 46 name = "prosody:user";
45 end 47 priority = 25;
46 setmetatable(role_info, role_mt); 48 inherits = { "prosody:restricted" };
47 end 49 };
48 50
49 function role_methods:may(action, context) 51 register_role {
50 local policy = self.permissions[action]; 52 name = "prosody:admin";
51 if policy ~= nil then 53 priority = 50;
52 return policy; 54 inherits = { "prosody:user" };
53 end 55 };
54 for inherited_role in self.includes do 56
55 module:log("debug", "Checking included role '%s' for %s", inherited_role.name, action); 57 register_role {
56 policy = inherited_role:may(action, context); 58 name = "prosody:operator";
57 if policy ~= nil then 59 priority = 75;
58 return policy; 60 inherits = { "prosody:admin" };
59 end 61 };
60 end 62
61 return false; 63
64 -- Process custom roles from config
65
66 local custom_roles = module:get_option("custom_roles", {});
67 for n, role_config in ipairs(custom_roles) do
68 local ok, err = pcall(register_role, role_config);
69 if not ok then
70 module:log("error", "Error registering custom role %s: %s", role_config.name or tostring(n), err);
71 end
72 end
73
74 -- Process custom permissions from config
75
76 local config_add_perms = module:get_option("add_permissions", {});
77 local config_remove_perms = module:get_option("remove_permissions", {});
78
79 for role_name, added_permissions in pairs(config_add_perms) do
80 if not role_registry[role_name] then
81 module:log("error", "Cannot add permissions to unknown role '%s'", role_name);
82 else
83 for _, permission in ipairs(added_permissions) do
84 role_registry[role_name]:set_permission(permission, true, true);
85 end
86 end
87 end
88
89 for role_name, removed_permissions in pairs(config_remove_perms) do
90 if not role_registry[role_name] then
91 module:log("error", "Cannot remove permissions from unknown role '%s'", role_name);
92 else
93 for _, permission in ipairs(removed_permissions) do
94 role_registry[role_name]:set_permission(permission, false, true);
95 end
96 end
62 end 97 end
63 98
64 -- Public API 99 -- Public API
65 100
66 local config_operator_role_set = { 101 local config_operator_role_set = {
67 ["prosody:operator"] = role_registry["prosody:operator"]; 102 ["prosody:operator"] = role_registry["prosody:operator"];
68 }; 103 };
69 local config_admin_role_set = { 104 local config_admin_role_set = {
70 ["prosody:admin"] = role_registry["prosody:admin"]; 105 ["prosody:admin"] = role_registry["prosody:admin"];
106 };
107 local default_role_set = {
108 ["prosody:user"] = role_registry["prosody:user"];
71 }; 109 };
72 110
73 function get_user_roles(user) 111 function get_user_roles(user)
74 local bare_jid = user.."@"..host; 112 local bare_jid = user.."@"..host;
75 if config_global_admin_jids:contains(bare_jid) then 113 if config_global_admin_jids:contains(bare_jid) then
76 return config_operator_role_set; 114 return config_operator_role_set;
77 elseif config_admin_jids:contains(bare_jid) then 115 elseif config_admin_jids:contains(bare_jid) then
78 return config_admin_role_set; 116 return config_admin_role_set;
79 end 117 end
80 local role_names = role_store:get(user); 118 local role_names = role_store:get(user);
81 if not role_names then return {}; end 119 if not role_names then return default_role_set; end
82 local roles = {}; 120 local user_roles = {};
83 for role_name in pairs(role_names) do 121 for role_name in pairs(role_names) do
84 roles[role_name] = role_registry[role_name]; 122 user_roles[role_name] = role_registry[role_name];
85 end 123 end
86 return roles; 124 return user_roles;
87 end 125 end
88 126
89 function set_user_roles(user, roles) 127 function set_user_roles(user, user_roles)
90 role_store:set(user, roles) 128 role_store:set(user, user_roles)
91 return true; 129 return true;
92 end 130 end
93 131
94 function get_user_default_role(user) 132 function get_user_default_role(user)
95 local roles = get_user_roles(user); 133 local user_roles = get_user_roles(user);
96 if not roles then return nil; end 134 if not user_roles then return nil; end
97 local default_role; 135 local default_role;
98 for role_name, role_info in pairs(roles) do --luacheck: ignore 213/role_name 136 for role_name, role_info in pairs(user_roles) do --luacheck: ignore 213/role_name
99 if role_info.default and (not default_role or role_info.priority > default_role.priority) then 137 if role_info.default ~= false and (not default_role or role_info.priority > default_role.priority) then
100 default_role = role_info; 138 default_role = role_info;
101 end 139 end
102 end 140 end
103 if not default_role then return nil; end 141 if not default_role then return nil; end
104 return default_role; 142 return default_role;
132 return role_registry["prosody:admin"]; 170 return role_registry["prosody:admin"];
133 end 171 end
134 return nil; 172 return nil;
135 end 173 end
136 174
137 function set_jid_role(jid) -- luacheck: ignore 212 175 function set_jid_role(jid, role_name) -- luacheck: ignore 212
138 return false; 176 return false;
139 end 177 end
140 178
141 function get_jids_with_role(role_name) 179 function get_jids_with_role(role_name)
142 -- Fetch role users from storage 180 -- Fetch role users from storage
155 local role = role_registry[role_name]; 193 local role = role_registry[role_name];
156 if not role then 194 if not role then
157 module:log("warn", "Attempt to add default permission for unknown role: %s", role_name); 195 module:log("warn", "Attempt to add default permission for unknown role: %s", role_name);
158 return nil, "no-such-role"; 196 return nil, "no-such-role";
159 end 197 end
160 if role.permissions[action] == nil then 198 if policy == nil then policy = true; end
161 if policy == nil then 199 module:log("debug", "Adding policy %s for permission %s on role %s", policy, action, role_name);
162 policy = true; 200 return role:set_permission(action, policy);
163 end 201 end
164 module:log("debug", "Adding permission, role '%s' may '%s': %s", role_name, action, policy and "allow" or "deny"); 202
165 role.permissions[action] = policy; 203 function get_role_by_name(role_name)
166 end 204 return assert(role_registry[role_name], role_name);
167 return true; 205 end
168 end
169
170 function get_role_info(role_name)
171 return role_registry[role_name];
172 end