Mercurial > prosody-hg
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 |
