Mercurial > prosody-hg
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 |
| parents | f299e570a0fe |
| children | 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 |
