Mercurial > prosody-hg
comparison core/usermanager.lua @ 13955:01f95f3de6fc
usermanager: More consistent method behaviour, errors, and (pre-)events
This commit refactors most of the exported usermanager methods substantially
to ensure that all of them correctly check for the presence of the host and
method they are calling and return consistent errors if either is incorrect or
missing.
It also unifies the event and logging code, so as a result there are more
events being fired than before this commit (it also adds pre- events so that
modules can block an action from going ahead).
We don't have good test coverage of this API, but some manual testing suggests
it seems to be working okay...
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 25 Sep 2025 16:53:35 +0100 |
| parents | 4cfabfbb0691 |
| children |
comparison
equal
deleted
inserted
replaced
| 13954:11573716205f | 13955:01f95f3de6fc |
|---|---|
| 96 end | 96 end |
| 97 | 97 |
| 98 end; | 98 end; |
| 99 prosody.events.add_handler("host-activated", initialize_host, 100); | 99 prosody.events.add_handler("host-activated", initialize_host, 100); |
| 100 | 100 |
| 101 local function provider_helpers(provider_type) | |
| 102 local function get_provider(host) | |
| 103 local host_session = hosts[host]; | |
| 104 if not host_session then | |
| 105 return nil, "unknown host"; | |
| 106 end | |
| 107 local provider = host_session[provider_type]; | |
| 108 if not provider then | |
| 109 return nil, "no "..provider_type.." provider"; | |
| 110 end | |
| 111 return provider; | |
| 112 end | |
| 113 | |
| 114 local function get_provider_method(host, method_name) | |
| 115 local provider, err = get_provider(host); | |
| 116 if not provider then | |
| 117 return nil, err; | |
| 118 end | |
| 119 local method = provider[method_name]; | |
| 120 if not method then | |
| 121 return nil, "method not implemented"; | |
| 122 end | |
| 123 return method; | |
| 124 end | |
| 125 | |
| 126 local function call_method(host, method_name, log_message, pre_event, post_event, event, ...) | |
| 127 local method, method_err = get_provider_method(host, method_name); | |
| 128 if not method then return nil, method_err; end | |
| 129 if pre_event and prosody.events.fire_event(pre_event, event) ~= nil then | |
| 130 return nil, event.reason or "prevented by module"; | |
| 131 end | |
| 132 local ok, err = method(...); | |
| 133 if not ok then | |
| 134 return ok, err; | |
| 135 end | |
| 136 if log_message then | |
| 137 log("info", "%s: %s@%s", log_message, event.username, event.host); | |
| 138 end | |
| 139 if post_event then | |
| 140 prosody.events.fire_event(post_event, event); | |
| 141 end | |
| 142 return ok, err; | |
| 143 end | |
| 144 | |
| 145 local function call_quiet_method(host, method_name, ...) | |
| 146 return call_method(host, method_name, nil, nil, nil, nil, ...); | |
| 147 end | |
| 148 | |
| 149 return get_provider, get_provider_method, call_method, call_quiet_method; | |
| 150 end | |
| 151 | |
| 152 local get_provider, _, call_method, call_quiet_method = provider_helpers("users"); | |
| 153 | |
| 101 local function test_password(username, host, password) | 154 local function test_password(username, host, password) |
| 102 return hosts[host].users.test_password(username, password); | 155 return call_quiet_method(host, "test_password", username, password); |
| 103 end | 156 end |
| 104 | 157 |
| 105 local function get_password(username, host) | 158 local function get_password(username, host) |
| 106 return hosts[host].users.get_password(username); | 159 return call_quiet_method(host, "get_password", username); |
| 107 end | 160 end |
| 108 | 161 |
| 109 local function set_password(username, password, host, resource) | 162 local function set_password(username, password, host, resource) |
| 110 local ok, err = hosts[host].users.set_password(username, password); | 163 return call_method(host, "set_password", "Account password changed", "pre-user-password-change", "user-password-changed", { |
| 111 if ok then | 164 username = username, host = host, resource = resource |
| 112 log("info", "Account password changed: %s@%s", username, host); | 165 }, username, password); |
| 113 prosody.events.fire_event("user-password-changed", { username = username, host = host, resource = resource }); | |
| 114 end | |
| 115 return ok, err; | |
| 116 end | 166 end |
| 117 | 167 |
| 118 local function get_account_info(username, host) | 168 local function get_account_info(username, host) |
| 119 local method = hosts[host].users.get_account_info; | 169 return call_quiet_method(host, "get_account_info", username); |
| 120 if not method then return nil, "method not supported"; end | |
| 121 return method(username); | |
| 122 end | 170 end |
| 123 | 171 |
| 124 local function user_exists(username, host) | 172 local function user_exists(username, host) |
| 125 if hosts[host].sessions[username] then return true; end | 173 if hosts[host].sessions[username] then |
| 126 return hosts[host].users.user_exists(username); | 174 -- Shortcut for online users, we know they exist :) |
| 175 return true; | |
| 176 end | |
| 177 return call_quiet_method(host, "user_exists", username); | |
| 127 end | 178 end |
| 128 | 179 |
| 129 local function create_user(username, password, host) | 180 local function create_user(username, password, host) |
| 130 local ok, err = hosts[host].users.create_user(username, password); | 181 return call_method(host, "create_user", "User account created", "pre-create-user", "user-created", { |
| 131 if ok then | 182 username = username, host = host |
| 132 log("info", "User account created: %s@%s", username, host); | 183 }, username, password); |
| 133 end | |
| 134 return ok, err; | |
| 135 end | 184 end |
| 136 | 185 |
| 137 local function delete_user(username, host) | 186 local function delete_user(username, host) |
| 138 local ok, err = hosts[host].users.delete_user(username); | 187 local ok, err = call_method(host, "delete_user", "User account deleted", "pre-delete-user", "user-deleted", { |
| 188 username = username, host = host | |
| 189 }, username); | |
| 139 if not ok then return nil, err; end | 190 if not ok then return nil, err; end |
| 140 log("info", "User account deleted: %s@%s", username, host); | |
| 141 prosody.events.fire_event("user-deleted", { username = username, host = host }); | |
| 142 return storagemanager.purge(username, host); | 191 return storagemanager.purge(username, host); |
| 143 end | 192 end |
| 144 | 193 |
| 145 local function user_is_enabled(username, host) | 194 local function user_is_enabled(username, host) |
| 146 local method = hosts[host].users.is_enabled; | 195 local ret, err = call_quiet_method(host, "is_enabled", username); |
| 147 if method then return method(username); end | 196 if ret ~= nil then |
| 197 return ret; | |
| 198 elseif err ~= "method not implemented" then | |
| 199 return nil, err; | |
| 200 end | |
| 148 | 201 |
| 149 -- Fallback | 202 -- Fallback |
| 150 local info, err = get_account_info(username, host); | 203 local info, err = get_account_info(username, host); |
| 151 if info and info.enabled ~= nil then | 204 if info and info.enabled ~= nil then |
| 152 return info.enabled; | 205 return info.enabled; |
| 158 -- API unsupported implies users are always enabled | 211 -- API unsupported implies users are always enabled |
| 159 return true; | 212 return true; |
| 160 end | 213 end |
| 161 | 214 |
| 162 local function enable_user(username, host) | 215 local function enable_user(username, host) |
| 163 local method = hosts[host].users.enable; | 216 return call_method(host, "enable", "User account enabled", "pre-enable-user", "user-enabled", { |
| 164 if not method then return nil, "method not supported"; end | 217 username = username, host = host |
| 165 local ret, err = method(username); | 218 }, username); |
| 166 if ret then | |
| 167 log("info", "User account enabled: %s@%s", username, host); | |
| 168 prosody.events.fire_event("user-enabled", { username = username, host = host }); | |
| 169 end | |
| 170 return ret, err; | |
| 171 end | 219 end |
| 172 | 220 |
| 173 local function disable_user(username, host, meta) | 221 local function disable_user(username, host, meta) |
| 174 local method = hosts[host].users.disable; | 222 return call_method(host, "disable", "User account disabled", "pre-disable-user", "user-disabled", { |
| 175 if not method then return nil, "method not supported"; end | 223 username = username, host = host, meta = meta |
| 176 local ret, err = method(username, meta); | 224 }, username, meta); |
| 177 if ret then | 225 end |
| 178 log("info", "User account disabled: %s@%s", username, host); | 226 |
| 179 prosody.events.fire_event("user-disabled", { username = username, host = host, meta = meta }); | |
| 180 end | |
| 181 return ret, err; | |
| 182 end | |
| 183 | 227 |
| 184 local function users(host) | 228 local function users(host) |
| 185 return hosts[host].users.users(); | 229 return call_quiet_method(host, "users"); |
| 186 end | 230 end |
| 187 | 231 |
| 188 local function get_sasl_handler(host, session) | 232 local function get_sasl_handler(host, session) |
| 189 return hosts[host].users.get_sasl_handler(session); | 233 return call_quiet_method(host, "get_sasl_handler", session); |
| 190 end | 234 end |
| 191 | 235 |
| 192 local function get_provider(host) | 236 local _, _, call_authz_method, call_quiet_authz_method = provider_helpers("authz"); |
| 193 return hosts[host].users; | 237 |
| 194 end | |
| 195 | 238 |
| 196 local function get_user_role(user, host) | 239 local function get_user_role(user, host) |
| 197 if host and not hosts[host] then return false; end | 240 if type(user) ~= "string" then return false; end |
| 198 if type(user) ~= "string" then return false; end | 241 return call_quiet_authz_method(host, "get_user_role", user); |
| 199 | |
| 200 return hosts[host].authz.get_user_role(user); | |
| 201 end | 242 end |
| 202 | 243 |
| 203 local function set_user_role(user, host, role_name) | 244 local function set_user_role(user, host, role_name) |
| 204 if host and not hosts[host] then return false; end | 245 if type(user) ~= "string" then return false; end |
| 205 if type(user) ~= "string" then return false; end | 246 |
| 206 | 247 local event = { username = user, host = host, role_name = role_name, role = nil }; |
| 207 local role, err = hosts[host].authz.set_user_role(user, role_name); | 248 local role, err = call_authz_method( |
| 208 if role then | 249 host, |
| 209 log("info", "Account %s@%s role changed to %s", user, host, role_name); | 250 "set_user_role", |
| 210 prosody.events.fire_event("user-role-changed", { | 251 "Account role changed to <"..role_name..">", |
| 211 username = user, host = host, role = role; | 252 "pre-user-role-change", |
| 212 }); | 253 nil, |
| 213 end | 254 event, |
| 214 return role, err; | 255 user, |
| 256 role_name | |
| 257 ); | |
| 258 if not role then | |
| 259 return nil, err; | |
| 260 end | |
| 261 event.role = role; | |
| 262 prosody.events.fire_event("user-role-changed", event); | |
| 263 return role; | |
| 215 end | 264 end |
| 216 | 265 |
| 217 local function create_user_with_role(username, password, host, role) | 266 local function create_user_with_role(username, password, host, role) |
| 218 local ok, err = create_user(username, nil, host); | 267 local ok, err = create_user(username, nil, host); |
| 219 if not ok then return ok, err; end | 268 if not ok then return ok, err; end |
| 238 | 287 |
| 239 return true; | 288 return true; |
| 240 end | 289 end |
| 241 | 290 |
| 242 local function user_can_assume_role(user, host, role_name) | 291 local function user_can_assume_role(user, host, role_name) |
| 243 if host and not hosts[host] then return false; end | 292 if type(user) ~= "string" then return false; end |
| 244 if type(user) ~= "string" then return false; end | 293 return call_quiet_authz_method(host, "user_can_assume_role", user, role_name); |
| 245 | |
| 246 return hosts[host].authz.user_can_assume_role(user, role_name); | |
| 247 end | 294 end |
| 248 | 295 |
| 249 local function add_user_secondary_role(user, host, role_name) | 296 local function add_user_secondary_role(user, host, role_name) |
| 250 if host and not hosts[host] then return false; end | 297 if type(user) ~= "string" then return false; end |
| 251 if type(user) ~= "string" then return false; end | 298 |
| 252 | 299 local event = { username = user, host = host, role_name = role_name, role = nil }; |
| 253 local role, err = hosts[host].authz.add_user_secondary_role(user, role_name); | 300 local role, err = call_authz_method( |
| 254 if role then | 301 host, |
| 255 prosody.events.fire_event("user-role-added", { | 302 "add_user_secondary_role", |
| 256 username = user, host = host, role_name = role_name, role = role; | 303 "Account gained role <"..role_name..">", |
| 257 }); | 304 "pre-add-user-role", |
| 258 end | 305 "user-role-added", |
| 259 return role, err; | 306 event, |
| 307 user, | |
| 308 role_name | |
| 309 ); | |
| 310 if not role then | |
| 311 return nil, err; | |
| 312 end | |
| 313 event.role = role; | |
| 314 prosody.events.fire_event("user-role-added", event); | |
| 315 return role; | |
| 260 end | 316 end |
| 261 | 317 |
| 262 local function remove_user_secondary_role(user, host, role_name) | 318 local function remove_user_secondary_role(user, host, role_name) |
| 263 if host and not hosts[host] then return false; end | 319 if type(user) ~= "string" then return false; end |
| 264 if type(user) ~= "string" then return false; end | 320 |
| 265 | 321 return call_authz_method( |
| 266 local ok, err = hosts[host].authz.remove_user_secondary_role(user, role_name); | 322 host, |
| 267 if ok then | 323 "remove_user_secondary_role", |
| 268 prosody.events.fire_event("user-role-removed", { | 324 "Account lost role <"..role_name..">", |
| 269 username = user, host = host, role_name = role_name; | 325 "pre-remove-user-role", |
| 270 }); | 326 "user-role-removed", |
| 271 end | 327 { username = user, host = host, role_name = role_name }, |
| 272 return ok, err; | 328 user, |
| 329 role_name | |
| 330 ); | |
| 273 end | 331 end |
| 274 | 332 |
| 275 local function get_user_secondary_roles(user, host) | 333 local function get_user_secondary_roles(user, host) |
| 276 if host and not hosts[host] then return false; end | 334 if type(user) ~= "string" then return false; end |
| 277 if type(user) ~= "string" then return false; end | 335 return call_quiet_authz_method(host, "get_user_secondary_roles", user); |
| 278 | |
| 279 return hosts[host].authz.get_user_secondary_roles(user); | |
| 280 end | 336 end |
| 281 | 337 |
| 282 local function get_jid_role(jid, host) | 338 local function get_jid_role(jid, host) |
| 283 local jid_node, jid_host = jid_split(jid); | 339 local jid_node, jid_host = jid_split(jid); |
| 284 if host == jid_host and jid_node then | 340 if host == jid_host and jid_node then |
| 285 return hosts[host].authz.get_user_role(jid_node); | 341 return call_quiet_authz_method(host, "get_user_role", jid_node); |
| 286 end | 342 end |
| 287 return hosts[host].authz.get_jid_role(jid); | 343 return call_quiet_authz_method(host, "get_jid_role", jid); |
| 288 end | 344 end |
| 289 | 345 |
| 290 local function set_jid_role(jid, host, role_name) | 346 local function set_jid_role(jid, host, role_name) |
| 291 local _, jid_host = jid_split(jid); | 347 local _, jid_host = jid_split(jid); |
| 292 if host == jid_host then | 348 if host == jid_host then |
| 293 return nil, "unexpected-local-jid"; | 349 return nil, "unexpected-local-jid"; |
| 294 end | 350 end |
| 295 return hosts[host].authz.set_jid_role(jid, role_name) | 351 return call_quiet_authz_method(host, "set_jid_role", jid, role_name); |
| 296 end | 352 end |
| 297 | 353 |
| 298 local strict_deprecate_is_admin; | 354 local strict_deprecate_is_admin; |
| 299 local legacy_admin_roles = { ["prosody:admin"] = true, ["prosody:operator"] = true }; | 355 local legacy_admin_roles = { ["prosody:admin"] = true, ["prosody:operator"] = true }; |
| 300 local function is_admin(jid, host) | 356 local function is_admin(jid, host) |
| 310 local role = get_jid_role(jid, host); | 366 local role = get_jid_role(jid, host); |
| 311 return role and legacy_admin_roles[role.name] or false; | 367 return role and legacy_admin_roles[role.name] or false; |
| 312 end | 368 end |
| 313 | 369 |
| 314 local function get_users_with_role(role, host) | 370 local function get_users_with_role(role, host) |
| 315 if not hosts[host] then return false; end | |
| 316 if type(role) ~= "string" then return false; end | 371 if type(role) ~= "string" then return false; end |
| 317 return hosts[host].authz.get_users_with_role(role); | 372 return call_quiet_authz_method(host, "get_users_with_role", role); |
| 318 end | 373 end |
| 319 | 374 |
| 320 local function get_jids_with_role(role, host) | 375 local function get_jids_with_role(role, host) |
| 321 if host and not hosts[host] then return false; end | |
| 322 if type(role) ~= "string" then return false; end | 376 if type(role) ~= "string" then return false; end |
| 323 return hosts[host].authz.get_jids_with_role(role); | 377 return call_quiet_authz_method(host, "get_jids_with_role", role); |
| 324 end | 378 end |
| 325 | 379 |
| 326 local function get_role_by_name(role_name, host) | 380 local function get_role_by_name(role_name, host) |
| 327 if host and not hosts[host] then return false; end | |
| 328 if type(role_name) ~= "string" then return false; end | 381 if type(role_name) ~= "string" then return false; end |
| 329 return hosts[host].authz.get_role_by_name(role_name); | 382 return call_quiet_authz_method(host, "get_role_by_name", role_name); |
| 330 end | 383 end |
| 331 | 384 |
| 332 local function get_all_roles(host) | 385 local function get_all_roles(host) |
| 333 if host and not hosts[host] then return false; end | 386 if host and not hosts[host] then return false; end |
| 334 return hosts[host].authz.get_all_roles(); | 387 return call_quiet_authz_method(host, "get_all_roles"); |
| 335 end | 388 end |
| 336 | 389 |
| 337 return { | 390 return { |
| 338 new_null_provider = new_null_provider; | 391 new_null_provider = new_null_provider; |
| 339 initialize_host = initialize_host; | 392 initialize_host = initialize_host; |
