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;