comparison plugins/mod_authz_internal.lua @ 12674:72f431b4dc2c

Merge role-auth->trunk
author Matthew Wild <mwild1@gmail.com>
date Mon, 22 Aug 2022 13:53:35 +0100
parents cf88f6b03942
children 427dd01f0864
comparison
equal deleted inserted replaced
12639:6d9ee0a3eb4b 12674:72f431b4dc2c
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 = require "util.jid".split; 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
8 local config_global_admin_jids = module:context("*"):get_option_set("admins", {}) / normalize;
6 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize; 9 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize;
7 local host = module.host; 10 local host = module.host;
8 local role_store = module:open_store("roles"); 11
9 local role_map_store = module:open_store("roles", "map"); 12 local role_store = module:open_store("account_roles");
10 13 local role_map_store = module:open_store("account_roles", "map");
11 local admin_role = { ["prosody:admin"] = true }; 14
12 15 local role_registry = {};
13 function get_user_roles(user) 16
14 if config_admin_jids:contains(user.."@"..host) then 17 function register_role(role)
15 return admin_role; 18 if role_registry[role.name] ~= nil then
16 end 19 return error("A role '"..role.name.."' is already registered");
17 return role_store:get(user); 20 end
18 end 21 if not roles.is_role(role) then
19 22 -- Convert table syntax to real role object
20 function set_user_roles(user, roles) 23 for i, inherited_role in ipairs(role.inherits or {}) do
21 role_store:set(user, roles) 24 if type(inherited_role) == "string" then
22 return true; 25 role.inherits[i] = assert(role_registry[inherited_role], "The named role '"..inherited_role.."' is not registered");
23 end 26 end
24 27 end
25 function get_users_with_role(role) 28 if not role.permissions then role.permissions = {}; end
26 local storage_role_users = it.to_array(it.keys(role_map_store:get_all(role) or {})); 29 for _, allow_permission in ipairs(role.allow or {}) do
27 if role == "prosody:admin" then 30 role.permissions[allow_permission] = true;
28 local config_admin_users = config_admin_jids / function (admin_jid) 31 end
32 for _, deny_permission in ipairs(role.deny or {}) do
33 role.permissions[deny_permission] = false;
34 end
35 role = roles.new(role);
36 end
37 role_registry[role.name] = role;
38 end
39
40 -- Default roles
41 register_role {
42 name = "prosody:restricted";
43 priority = 15;
44 };
45
46 register_role {
47 name = "prosody:user";
48 priority = 25;
49 inherits = { "prosody:restricted" };
50 };
51
52 register_role {
53 name = "prosody:admin";
54 priority = 50;
55 inherits = { "prosody:user" };
56 };
57
58 register_role {
59 name = "prosody:operator";
60 priority = 75;
61 inherits = { "prosody:admin" };
62 };
63
64
65 -- Process custom roles from config
66
67 local custom_roles = module:get_option("custom_roles", {});
68 for n, role_config in ipairs(custom_roles) do
69 local ok, err = pcall(register_role, role_config);
70 if not ok then
71 module:log("error", "Error registering custom role %s: %s", role_config.name or tostring(n), err);
72 end
73 end
74
75 -- Process custom permissions from config
76
77 local config_add_perms = module:get_option("add_permissions", {});
78 local config_remove_perms = module:get_option("remove_permissions", {});
79
80 for role_name, added_permissions in pairs(config_add_perms) do
81 if not role_registry[role_name] then
82 module:log("error", "Cannot add permissions to unknown role '%s'", role_name);
83 else
84 for _, permission in ipairs(added_permissions) do
85 role_registry[role_name]:set_permission(permission, true, true);
86 end
87 end
88 end
89
90 for role_name, removed_permissions in pairs(config_remove_perms) do
91 if not role_registry[role_name] then
92 module:log("error", "Cannot remove permissions from unknown role '%s'", role_name);
93 else
94 for _, permission in ipairs(removed_permissions) do
95 role_registry[role_name]:set_permission(permission, false, true);
96 end
97 end
98 end
99
100 -- Public API
101
102 -- Get the primary role of a user
103 function get_user_role(user)
104 local bare_jid = user.."@"..host;
105
106 -- Check config first
107 if config_global_admin_jids:contains(bare_jid) then
108 return role_registry["prosody:operator"];
109 elseif config_admin_jids:contains(bare_jid) then
110 return role_registry["prosody:admin"];
111 end
112
113 -- Check storage
114 local stored_roles, err = role_store:get(user);
115 if not stored_roles then
116 if err then
117 -- Unable to fetch role, fail
118 return nil, err;
119 end
120 -- No role set, use default role
121 return role_registry["prosody:user"];
122 end
123 if stored_roles._default == nil then
124 -- No primary role explicitly set, return default
125 return role_registry["prosody:user"];
126 end
127 local primary_stored_role = role_registry[stored_roles._default];
128 if not primary_stored_role then
129 return nil, "unknown-role";
130 end
131 return primary_stored_role;
132 end
133
134 -- Set the primary role of a user
135 function set_user_role(user, role_name)
136 local role = role_registry[role_name];
137 if not role then
138 return error("Cannot assign default user an unknown role: "..tostring(role_name));
139 end
140 local keys_update = {
141 _default = role_name;
142 -- Primary role cannot be secondary role
143 [role_name] = role_map_store.remove;
144 };
145 if role_name == "prosody:user" then
146 -- Don't store default
147 keys_update._default = role_map_store.remove;
148 end
149 local ok, err = role_map_store:set_keys(user, keys_update);
150 if not ok then
151 return nil, err;
152 end
153 return role;
154 end
155
156 function add_user_secondary_role(user, role_name)
157 if not role_registry[role_name] then
158 return error("Cannot assign default user an unknown role: "..tostring(role_name));
159 end
160 role_map_store:set(user, role_name, true);
161 end
162
163 function remove_user_secondary_role(user, role_name)
164 role_map_store:set(user, role_name, nil);
165 end
166
167 function get_user_secondary_roles(user)
168 local stored_roles, err = role_store:get(user);
169 if not stored_roles then
170 if err then
171 -- Unable to fetch role, fail
172 return nil, err;
173 end
174 -- No role set
175 return {};
176 end
177 stored_roles._default = nil;
178 for role_name in pairs(stored_roles) do
179 stored_roles[role_name] = role_registry[role_name];
180 end
181 return stored_roles;
182 end
183
184 function user_can_assume_role(user, role_name)
185 local primary_role = get_user_role(user);
186 if primary_role and primary_role.role_name == role_name then
187 return true;
188 end
189 local secondary_roles = get_user_secondary_roles(user);
190 if secondary_roles and secondary_roles[role_name] then
191 return true;
192 end
193 return false;
194 end
195
196 -- This function is *expensive*
197 function get_users_with_role(role_name)
198 local function role_filter(username, default_role) --luacheck: ignore 212/username
199 return default_role == role_name;
200 end
201 local primary_role_users = set.new(it.to_array(it.filter(role_filter, pairs(role_map_store:get_all("_default") or {}))));
202 local secondary_role_users = set.new(it.to_array(it.keys(role_map_store:get_all(role_name) or {})));
203
204 local config_set;
205 if role_name == "prosody:admin" then
206 config_set = config_admin_jids;
207 elseif role_name == "prosody:operator" then
208 config_set = config_global_admin_jids;
209 end
210 if config_set then
211 local config_admin_users = config_set / function (admin_jid)
29 local j_node, j_host = jid_split(admin_jid); 212 local j_node, j_host = jid_split(admin_jid);
30 if j_host == host then 213 if j_host == host then
31 return j_node; 214 return j_node;
32 end 215 end
33 end; 216 end;
34 return it.to_array(config_admin_users + set.new(storage_role_users)); 217 return it.to_array(config_admin_users + primary_role_users + secondary_role_users);
35 end 218 end
36 return storage_role_users; 219 return it.to_array(primary_role_users + secondary_role_users);
37 end 220 end
38 221
39 function get_jid_roles(jid) 222 function get_jid_role(jid)
40 if config_admin_jids:contains(jid) then 223 local bare_jid = jid_bare(jid);
41 return admin_role; 224 if config_global_admin_jids:contains(bare_jid) then
225 return role_registry["prosody:operator"];
226 elseif config_admin_jids:contains(bare_jid) then
227 return role_registry["prosody:admin"];
42 end 228 end
43 return nil; 229 return nil;
44 end 230 end
45 231
46 function set_jid_roles(jid) -- luacheck: ignore 212 232 function set_jid_role(jid, role_name) -- luacheck: ignore 212
47 return false; 233 return false;
48 end 234 end
49 235
50 function get_jids_with_role(role) 236 function get_jids_with_role(role_name)
51 -- Fetch role users from storage 237 -- Fetch role users from storage
52 local storage_role_jids = array.map(get_users_with_role(role), function (username) 238 local storage_role_jids = array.map(get_users_with_role(role_name), function (username)
53 return username.."@"..host; 239 return username.."@"..host;
54 end); 240 end);
55 if role == "prosody:admin" then 241 if role_name == "prosody:admin" then
56 return it.to_array(config_admin_jids + set.new(storage_role_jids)); 242 return it.to_array(config_admin_jids + set.new(storage_role_jids));
243 elseif role_name == "prosody:operator" then
244 return it.to_array(config_global_admin_jids + set.new(storage_role_jids));
57 end 245 end
58 return storage_role_jids; 246 return storage_role_jids;
59 end 247 end
248
249 function add_default_permission(role_name, action, policy)
250 local role = role_registry[role_name];
251 if not role then
252 module:log("warn", "Attempt to add default permission for unknown role: %s", role_name);
253 return nil, "no-such-role";
254 end
255 if policy == nil then policy = true; end
256 module:log("debug", "Adding policy %s for permission %s on role %s", policy, action, role_name);
257 return role:set_permission(action, policy);
258 end
259
260 function get_role_by_name(role_name)
261 return assert(role_registry[role_name], role_name);
262 end
263
264 -- COMPAT: Migrate from 0.12 role storage
265 local function do_migration(migrate_host)
266 local old_role_store = assert(module:context(migrate_host):open_store("roles"));
267 local new_role_store = assert(module:context(migrate_host):open_store("account_roles"));
268
269 local migrated, failed, skipped = 0, 0, 0;
270 -- Iterate all users
271 for username in assert(old_role_store:users()) do
272 local old_roles = it.to_array(it.filter(function (k) return k:sub(1,1) ~= "_"; end, it.keys(old_role_store:get(username))));
273 if #old_roles == 1 then
274 local ok, err = new_role_store:set(username, {
275 _default = old_roles[1];
276 });
277 if ok then
278 migrated = migrated + 1;
279 else
280 failed = failed + 1;
281 print("EE: Failed to store new role info for '"..username.."': "..err);
282 end
283 else
284 print("WW: User '"..username.."' has multiple roles and cannot be automatically migrated");
285 skipped = skipped + 1;
286 end
287 end
288 return migrated, failed, skipped;
289 end
290
291 function module.command(arg)
292 if arg[1] == "migrate" then
293 table.remove(arg, 1);
294 local migrate_host = arg[1];
295 if not migrate_host or not prosody.hosts[migrate_host] then
296 print("EE: Please supply a valid host to migrate to the new role storage");
297 return 1;
298 end
299
300 -- Initialize storage layer
301 require "core.storagemanager".initialize_host(migrate_host);
302
303 print("II: Migrating roles...");
304 local migrated, failed, skipped = do_migration(migrate_host);
305 print(("II: %d migrated, %d failed, %d skipped"):format(migrated, failed, skipped));
306 return (failed + skipped == 0) and 0 or 1;
307 else
308 print("EE: Unknown command: "..(arg[1] or "<none given>"));
309 print(" Hint: try 'migrate'?");
310 end
311 end