Software /
code /
prosody
Comparison
plugins/mod_authz_internal.lua @ 12662:07424992d7fc
mod_authz_internal, and more: New iteration of role API
These changes to the API (hopefully the last) introduce a cleaner separation
between the user's primary (default) role, and their secondary (optional)
roles.
To keep the code sane and reduce complexity, a data migration is needed for
people using stored roles in 0.12. This can be performed with
prosodyctl mod_authz_internal migrate <host>
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Wed, 17 Aug 2022 16:38:53 +0100 |
parent | 12648:f299e570a0fe |
child | 12663:cf88f6b03942 |
comparison
equal
deleted
inserted
replaced
12661:1c391c17a907 | 12662:07424992d7fc |
---|---|
6 local roles = require "util.roles"; | 6 local roles = require "util.roles"; |
7 | 7 |
8 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; |
9 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize; | 9 local config_admin_jids = module:get_option_inherited_set("admins", {}) / normalize; |
10 local host = module.host; | 10 local host = module.host; |
11 local role_store = module:open_store("roles"); | 11 |
12 local role_map_store = module:open_store("roles", "map"); | 12 local role_store = module:open_store("account_roles"); |
13 local role_map_store = module:open_store("account_roles", "map"); | |
13 | 14 |
14 local role_registry = {}; | 15 local role_registry = {}; |
15 | 16 |
16 function register_role(role) | 17 function register_role(role) |
17 if role_registry[role.name] ~= nil then | 18 if role_registry[role.name] ~= nil then |
96 end | 97 end |
97 end | 98 end |
98 | 99 |
99 -- Public API | 100 -- Public API |
100 | 101 |
101 local config_operator_role_set = { | 102 -- Get the primary role of a user |
102 ["prosody:operator"] = role_registry["prosody:operator"]; | 103 function get_user_role(user) |
103 }; | |
104 local config_admin_role_set = { | |
105 ["prosody:admin"] = role_registry["prosody:admin"]; | |
106 }; | |
107 local default_role_set = { | |
108 ["prosody:user"] = role_registry["prosody:user"]; | |
109 }; | |
110 | |
111 function get_user_roles(user) | |
112 local bare_jid = user.."@"..host; | 104 local bare_jid = user.."@"..host; |
105 | |
106 -- Check config first | |
113 if config_global_admin_jids:contains(bare_jid) then | 107 if config_global_admin_jids:contains(bare_jid) then |
114 return config_operator_role_set; | 108 return role_registry["prosody:operator"]; |
115 elseif config_admin_jids:contains(bare_jid) then | 109 elseif config_admin_jids:contains(bare_jid) then |
116 return config_admin_role_set; | 110 return role_registry["prosody:admin"]; |
117 end | 111 end |
118 local role_names = role_store:get(user); | 112 |
119 if not role_names then return default_role_set; end | 113 -- Check storage |
120 local user_roles = {}; | 114 local stored_roles, err = role_store:get(user); |
121 for role_name in pairs(role_names) do | 115 if not stored_roles then |
122 user_roles[role_name] = role_registry[role_name]; | 116 if err then |
123 end | 117 -- Unable to fetch role, fail |
124 return user_roles; | 118 return nil, err; |
125 end | 119 end |
126 | 120 -- No role set, use default role |
127 function set_user_roles(user, user_roles) | 121 return role_registry["prosody:user"]; |
128 role_store:set(user, user_roles) | 122 end |
129 return true; | 123 if stored_roles._default == nil then |
130 end | 124 -- No primary role explicitly set, return default |
131 | 125 return role_registry["prosody:user"]; |
132 function get_user_default_role(user) | 126 end |
133 local user_roles = get_user_roles(user); | 127 local primary_stored_role = role_registry[stored_roles._default]; |
134 if not user_roles then return nil; end | 128 if not primary_stored_role then |
135 local default_role; | 129 return nil, "unknown-role"; |
136 for role_name, role_info in pairs(user_roles) do --luacheck: ignore 213/role_name | 130 end |
137 if role_info.default ~= false and (not default_role or role_info.priority > default_role.priority) then | 131 return primary_stored_role; |
138 default_role = role_info; | 132 end |
139 end | 133 |
140 end | 134 -- Set the primary role of a user |
141 if not default_role then return nil; end | 135 function set_user_role(user, role_name) |
142 return default_role; | 136 local role = role_registry[role_name]; |
143 end | 137 if not role then |
144 | 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 -- This function is *expensive* | |
145 function get_users_with_role(role_name) | 185 function get_users_with_role(role_name) |
146 local storage_role_users = it.to_array(it.keys(role_map_store:get_all(role_name) or {})); | 186 local function role_filter(username, default_role) --luacheck: ignore 212/username |
187 return default_role == role_name; | |
188 end | |
189 local primary_role_users = set.new(it.to_array(it.filter(role_filter, pairs(role_map_store:get_all("_default") or {})))); | |
190 local secondary_role_users = set.new(it.to_array(it.keys(role_map_store:get_all(role_name) or {}))); | |
191 | |
147 local config_set; | 192 local config_set; |
148 if role_name == "prosody:admin" then | 193 if role_name == "prosody:admin" then |
149 config_set = config_admin_jids; | 194 config_set = config_admin_jids; |
150 elseif role_name == "prosody:operator" then | 195 elseif role_name == "prosody:operator" then |
151 config_set = config_global_admin_jids; | 196 config_set = config_global_admin_jids; |
155 local j_node, j_host = jid_split(admin_jid); | 200 local j_node, j_host = jid_split(admin_jid); |
156 if j_host == host then | 201 if j_host == host then |
157 return j_node; | 202 return j_node; |
158 end | 203 end |
159 end; | 204 end; |
160 return it.to_array(config_admin_users + set.new(storage_role_users)); | 205 return it.to_array(config_admin_users + primary_role_users + secondary_role_users); |
161 end | 206 end |
162 return storage_role_users; | 207 return it.to_array(primary_role_users + secondary_role_users); |
163 end | 208 end |
164 | 209 |
165 function get_jid_role(jid) | 210 function get_jid_role(jid) |
166 local bare_jid = jid_bare(jid); | 211 local bare_jid = jid_bare(jid); |
167 if config_global_admin_jids:contains(bare_jid) then | 212 if config_global_admin_jids:contains(bare_jid) then |
201 end | 246 end |
202 | 247 |
203 function get_role_by_name(role_name) | 248 function get_role_by_name(role_name) |
204 return assert(role_registry[role_name], role_name); | 249 return assert(role_registry[role_name], role_name); |
205 end | 250 end |
251 | |
252 -- COMPAT: Migrate from 0.12 role storage | |
253 local function do_migration(migrate_host) | |
254 local old_role_store = assert(module:context(migrate_host):open_store("roles")); | |
255 local new_role_store = assert(module:context(migrate_host):open_store("account_roles")); | |
256 | |
257 local migrated, failed, skipped = 0, 0, 0; | |
258 -- Iterate all users | |
259 for username in assert(old_role_store:users()) do | |
260 local old_roles = it.to_array(it.filter(function (k) return k:sub(1,1) ~= "_"; end, it.keys(old_role_store:get(username)))); | |
261 if #old_roles == 1 then | |
262 local ok, err = new_role_store:set(username, { | |
263 _default = old_roles[1]; | |
264 }); | |
265 if ok then | |
266 migrated = migrated + 1; | |
267 else | |
268 failed = failed + 1; | |
269 print("EE: Failed to store new role info for '"..username.."': "..err); | |
270 end | |
271 else | |
272 print("WW: User '"..username.."' has multiple roles and cannot be automatically migrated"); | |
273 skipped = skipped + 1; | |
274 end | |
275 end | |
276 return migrated, failed, skipped; | |
277 end | |
278 | |
279 function module.command(arg) | |
280 if arg[1] == "migrate" then | |
281 table.remove(arg, 1); | |
282 local migrate_host = arg[1]; | |
283 if not migrate_host or not prosody.hosts[migrate_host] then | |
284 print("EE: Please supply a valid host to migrate to the new role storage"); | |
285 return 1; | |
286 end | |
287 | |
288 -- Initialize storage layer | |
289 require "core.storagemanager".initialize_host(migrate_host); | |
290 | |
291 print("II: Migrating roles..."); | |
292 local migrated, failed, skipped = do_migration(migrate_host); | |
293 print(("II: %d migrated, %d failed, %d skipped"):format(migrated, failed, skipped)); | |
294 return (failed + skipped == 0) and 0 or 1; | |
295 else | |
296 print("EE: Unknown command: "..(arg[1] or "<none given>")); | |
297 print(" Hint: try 'migrate'?"); | |
298 end | |
299 end |