# HG changeset patch # User Matthew Wild # Date 1661179142 -3600 # Node ID bd63feda3704cc3acaceda7215c4138c856b1935 # Parent cce12a660b9899aa581a03ae5cfccdd9cecc7d83# Parent 34fb3d239ac1669b96e3c5a05187916371023e8c Merge role-auth diff -r cce12a660b98 -r bd63feda3704 .luacheckrc --- a/.luacheckrc Tue Aug 16 13:10:39 2022 +0200 +++ b/.luacheckrc Mon Aug 22 15:39:02 2022 +0100 @@ -29,6 +29,8 @@ "module.hourly", "module.broadcast", "module.context", + "module.default_permission", + "module.default_permissions", "module.depends", "module.fire_event", "module.get_directory", @@ -54,6 +56,7 @@ "module.load_resource", "module.log", "module.log_status", + "module.may", "module.measure", "module.metric", "module.open_store", diff -r cce12a660b98 -r bd63feda3704 mod_admin_message/README.markdown --- a/mod_admin_message/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_admin_message/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -29,7 +29,7 @@ ============= --------- --------------- - trunk Works + trunk Doesn't work (uses is_admin) 0.9 Works \<= 0.8 Not supported --------- --------------- diff -r cce12a660b98 -r bd63feda3704 mod_admin_probe/README.markdown --- a/mod_admin_probe/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_admin_probe/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -5,3 +5,11 @@ This module lets server administrators send `` to any local user and receive their presence in response, bypassing roster checks. + +Compatibility +============= + + ------- -------------- + trunk Doesn't work (uses is_admin) + 0.12 Works? + ------- -------------- diff -r cce12a660b98 -r bd63feda3704 mod_block_outgoing/README.markdown --- a/mod_block_outgoing/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_block_outgoing/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -26,3 +26,13 @@ ``` block_outgoing_stanzas defaults to "message" if not specified. + +Compatibility +============= + + ------- -------------- + trunk Doesn't work (uses is_admin) + 0.12 Works + 0.11 Works + ------- -------------- + diff -r cce12a660b98 -r bd63feda3704 mod_broadcast/README.markdown --- a/mod_broadcast/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_broadcast/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -32,4 +32,5 @@ ------ ------- 0.9 Works 0.10 Works + trunk Doesn't work (uses is_admin) ------ ------- diff -r cce12a660b98 -r bd63feda3704 mod_cloud_notify/mod_cloud_notify.lua --- a/mod_cloud_notify/mod_cloud_notify.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_cloud_notify/mod_cloud_notify.lua Mon Aug 22 15:39:02 2022 +0100 @@ -390,7 +390,7 @@ notification_stanza = push_publish; notification_payload = push_notification_payload; original_stanza = stanza; - node = node; + username = node; push_info = push_info; push_summary = form_data; important = not not form_data["last-message-body"]; diff -r cce12a660b98 -r bd63feda3704 mod_data_access/README.markdown --- a/mod_data_access/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_data_access/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -72,3 +72,11 @@ ### TODO - Use `Accept` header. + +Compatibility +============= + + ------- -------------- + trunk Doesn't work (uses is_admin) + 0.12 Works? + ------- -------------- diff -r cce12a660b98 -r bd63feda3704 mod_firewall/README.markdown --- a/mod_firewall/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_firewall/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -435,8 +435,40 @@ NOT SENT DIRECTED PRESENCE TO SENDER? BOUNCE=service-unavailable +### Permissions + +Rules can consult Prosody's internal role and permissions system to check whether a certain action may +be performed. The acting entity, their role, and appropriate context is automatically inferred. All you +need to do is provide the identifier of the permission that should be checked. + + Condition Description + ----------------------- -------------------------------------------------------------------- + `MAY=permission` Checks whether 'permission' is allowed in the current context. + +As with all other conditions, `MAY` can be combined with `NOT` to negate the result of the check. + +Example, blocking outgoing stanzas from users with roles that do not allow the 'xmpp:federate' permission: + +``` +::deliver_remote +MAY NOT: xmpp:federate +BOUNCE=policy-violation (You are not allowed access to the federation) +``` + +### Roles + + Condition Matches + ---------------- ------------------------------------------------------------------------------------- + `TO ROLE` When the recipient JID of the stanza has the named role + `FROM ROLE` When the sender JID of the stanza has the named role + +**Note:** In most cases, you should avoid checking for specific roles, and instead check for +permissions granted by those roles (using the 'MAY' condition). + ### Admins +**Deprecated:** These conditions should no longer be used. Prefer 'MAY', 'TO ROLE' or 'FROM ROLE'. + Prosody allows certain JIDs to be declared as administrators of a host, component or the whole server. Condition Matches diff -r cce12a660b98 -r bd63feda3704 mod_firewall/conditions.lib.lua --- a/mod_firewall/conditions.lib.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_firewall/conditions.lib.lua Mon Aug 22 15:39:02 2022 +0100 @@ -175,22 +175,39 @@ return "not "..table.concat(code, " or "), { "group_contains", "bare_to", "bare_from" }; end +-- COMPAT w/0.12: Deprecated function condition_handlers.FROM_ADMIN_OF(host) return ("is_admin(bare_from, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_from" }; end +-- COMPAT w/0.12: Deprecated function condition_handlers.TO_ADMIN_OF(host) return ("is_admin(bare_to, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_to" }; end +-- COMPAT w/0.12: Deprecated function condition_handlers.FROM_ADMIN() return ("is_admin(bare_from, current_host)"), { "is_admin", "bare_from", "current_host" }; end +-- COMPAT w/0.12: Deprecated function condition_handlers.TO_ADMIN() return ("is_admin(bare_to, current_host)"), { "is_admin", "bare_to", "current_host" }; end +-- MAY: permission_to_check +function condition_handlers.MAY(permission_to_check) + return ("module:may(%q, event)"):format(permission_to_check); +end + +function condition_handlers.TO_ROLE(role_name) + return ("get_jid_role(bare_to, current_host) == %q"):format(role_name), { "get_jid_role", "current_host", "bare_to" }; +end + +function condition_handlers.FROM_ROLE(role_name) + return ("get_jid_role(bare_from, current_host) == %q"):format(role_name), { "get_jid_role", "current_host", "bare_from" }; +end + local day_numbers = { sun = 0, mon = 2, tue = 3, wed = 4, thu = 5, fri = 6, sat = 7 }; local function current_time_check(op, hour, minute) diff -r cce12a660b98 -r bd63feda3704 mod_firewall/mod_firewall.lua --- a/mod_firewall/mod_firewall.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_firewall/mod_firewall.lua Mon Aug 22 15:39:02 2022 +0100 @@ -6,6 +6,9 @@ local it = require "util.iterators"; local set = require "util.set"; +local have_features, features = pcall(require, "core.features"); +features = have_features and features.available or set.new(); + -- [definition_type] = definition_factory(param) local definitions = module:shared("definitions"); @@ -181,7 +184,8 @@ group_contains = { global_code = [[local group_contains = module:depends("groups").group_contains]]; }; - is_admin = { global_code = [[local is_admin = require "core.usermanager".is_admin;]]}; + is_admin = features:contains("permissions") and { global_code = [[local is_admin = require "core.usermanager".is_admin;]]} or nil; + get_jid_role = require "core.usermanager".get_jid_role and { global_code = [[local get_jid_role = require "core.usermanager".get_jid_role;]] } or nil; core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza;]] }; zone = { global_code = function (zone) local var = zone; diff -r cce12a660b98 -r bd63feda3704 mod_http_admin_api/mod_http_admin_api.lua --- a/mod_http_admin_api/mod_http_admin_api.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_http_admin_api/mod_http_admin_api.lua Mon Aug 22 15:39:02 2022 +0100 @@ -1,5 +1,6 @@ local usermanager = require "core.usermanager"; +local it = require "util.iterators"; local json = require "util.json"; local st = require "util.stanza"; local array = require "util.array"; @@ -33,25 +34,24 @@ end if auth_type == "Bearer" then - local token_info = tokens.get_token_info(auth_data); - if not token_info or not token_info.session then - return false; - end - return token_info.session; + return tokens.get_token_session(auth_data); end return nil; end +module:default_permission("prosody:admin", ":access-admin-api"); + function check_auth(routes) local function check_request_auth(event) local session = check_credentials(event.request); if not session then event.response.headers.authorization = www_authenticate_header; return false, 401; - elseif session.auth_scope ~= "prosody:scope:admin" then + end + event.session = session; + if not module:may(":access-admin-api", event) then return false, 403; end - event.session = session; return true; end @@ -179,21 +179,24 @@ end end - local roles = nil; - if usermanager.get_roles then - local roles_map = usermanager.get_roles(username.."@"..module.host, module.host) - roles = array() - if roles_map then - for role in pairs(roles_map) do - roles:push(role) - end + local primary_role, secondary_roles, legacy_roles; + if usermanager.get_user_role then + primary_role = usermanager.get_user_role(username, module.host); + secondary_roles = array.collect(it.keys(usermanager.get_user_secondary_roles(username, module.host))); + elseif usermanager.get_user_roles then -- COMPAT w/0.12 + legacy_roles = array(); + local roles_map = usermanager.get_user_roles(username, module.host); + for role_name in pairs(roles_map) do + legacy_roles:push(role_name); end end return { username = username; display_name = display_name; - roles = roles; + role = primary_role and primary_role.name or nil; + secondary_roles = secondary_roles; + roles = legacy_roles; -- COMPAT w/0.12 }; end @@ -309,7 +312,7 @@ }; -- Online sessions do - local user_sessions = hosts[module.host].sessions[username]; + local user_sessions = prosody.hosts[module.host].sessions[username]; if user_sessions then user_sessions = user_sessions.sessions end @@ -415,8 +418,18 @@ end end - if new_user.roles then - if not usermanager.set_roles then + if new_user.role then + if not usermanager.set_user_role then + return 500, "feature-not-implemented"; + end + if not usermanager.set_user_role(username, module.host, new_user.role) then + module:log("error", "failed to set role %s for %s", new_user.role, username); + return 500; + end + end + + if new_user.roles then -- COMPAT w/0.12 + if not usermanager.set_user_roles then return 500, "feature-not-implemented" end @@ -425,7 +438,7 @@ backend_roles[role] = true; end local jid = username.."@"..module.host; - if not usermanager.set_roles(jid, module.host, backend_roles) then + if not usermanager.set_user_roles(username, module.host, backend_roles) then module:log("error", "failed to set roles %q for %s", backend_roles, jid) return 500 end diff -r cce12a660b98 -r bd63feda3704 mod_http_oauth2/mod_http_oauth2.lua --- a/mod_http_oauth2/mod_http_oauth2.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_http_oauth2/mod_http_oauth2.lua Mon Aug 22 15:39:02 2022 +0100 @@ -14,13 +14,20 @@ local clients = module:open_store("oauth2_clients", "map"); -local function filter_scopes(request_jid, requested_scope_string) --luacheck: ignore 212/requested_scope_string - -- We currently don't really support scopes, so override - -- to whatever real permissions the user has - if usermanager.is_admin(request_jid, module.host) then - return "prosody:scope:admin"; +local function filter_scopes(username, host, requested_scope_string) + if host ~= module.host then + return usermanager.get_jid_role(username.."@"..host, module.host).name; end - return "prosody:scope:default"; + + if requested_scope_string then -- Specific role requested + -- TODO: The requested scope string is technically a space-delimited list + -- of scopes, but for simplicity we're mapping this slot to role names. + if usermanager.user_can_assume_role(username, module.host, requested_scope_string) then + return requested_scope_string; + end + end + + return usermanager.get_user_role(username, module.host).name; end local function code_expires_in(code) @@ -81,7 +88,7 @@ end local granted_jid = jid.join(request_username, request_host, request_resource); - local granted_scopes = filter_scopes(granted_jid, params.scope); + local granted_scopes = filter_scopes(request_username, request_host, params.scope); return json.encode(new_access_token(granted_jid, granted_scopes, nil)); end @@ -99,7 +106,7 @@ return oauth_error("invalid_client", "incorrect credentials"); end - local granted_scopes = filter_scopes(granted_jid, params.scope); + local granted_scopes = filter_scopes(client_owner, client_host, params.scope); local code = uuid.generate(); local ok = codes:set(params.client_id .. "#" .. code, { diff -r cce12a660b98 -r bd63feda3704 mod_http_xep227/mod_http_xep227.lua --- a/mod_http_xep227/mod_http_xep227.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_http_xep227/mod_http_xep227.lua Mon Aug 22 15:39:02 2022 +0100 @@ -346,11 +346,7 @@ end if auth_type == "Bearer" then - local token_info = tokens.get_token_info(auth_data); - if not token_info or not token_info.session then - return false; - end - return token_info.session; + return tokens.get_token_session(auth_data); end return nil; end diff -r cce12a660b98 -r bd63feda3704 mod_invites_adhoc/mod_invites_adhoc.lua --- a/mod_invites_adhoc/mod_invites_adhoc.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_invites_adhoc/mod_invites_adhoc.lua Mon Aug 22 15:39:02 2022 +0100 @@ -13,9 +13,19 @@ -- on the server, use the option above instead. local allow_contact_invites = module:get_option_boolean("allow_contact_invites", true); +-- These options are deprecated since module:may() local allow_user_invite_roles = module:get_option_set("allow_user_invites_by_roles"); local deny_user_invite_roles = module:get_option_set("deny_user_invites_by_roles"); +if module.may then + if allow_user_invites then + module:default_permission("prosody:user", ":invite-new-users"); + end + if not allow_user_invite_roles:empty() or not deny_user_invite_roles:empty() then + return error("allow_user_invites_by_roles and deny_user_invites_by_roles are deprecated options"); + end +end + local invites; if prosody.shutdown then -- COMPAT hack to detect prosodyctl invites = module:depends("invites"); @@ -42,8 +52,10 @@ -- This is for checking if the specified JID may create invites -- that allow people to register accounts on this host. -local function may_invite_new_users(jid) - if usermanager.get_roles then +local function may_invite_new_users(jid, context) + if module.may then + return module:may(":invite-new-users", context); + elseif usermanager.get_roles then -- COMPAT w/0.12 local user_roles = usermanager.get_roles(jid, module.host); if not user_roles then return; end if user_roles["prosody:admin"] then @@ -87,7 +99,7 @@ }; }; end - local invite = invites.create_contact(username, may_invite_new_users(data.from), { + local invite = invites.create_contact(username, may_invite_new_users(data.from, data), { source = data.from }); --TODO: check errors diff -r cce12a660b98 -r bd63feda3704 mod_isolate_host/mod_isolate_host.lua --- a/mod_isolate_host/mod_isolate_host.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_isolate_host/mod_isolate_host.lua Mon Aug 22 15:39:02 2022 +0100 @@ -1,6 +1,5 @@ local jid = require "util.jid"; -local jid_bare, jid_split = jid.bare, jid.split; -local is_admin = require "core.usermanager".is_admin; +local jid_bare, jid_host = jid.bare, jid.host; local set = require "util.set"; local st = require "util.stanza"; @@ -10,10 +9,14 @@ local except_domains = module:get_option_inherited_set("isolate_except_domains", {}); local except_users = module:get_option_inherited_set("isolate_except_users", {}); +if not module.may then + module:depends("compat_roles"); +end + function check_stanza(event) local origin, stanza = event.origin, event.stanza; if origin.no_host_isolation then return; end - local to_user, to_host = jid_split(event.stanza.attr.to); + local to_host = jid_host(event.stanza.attr.to); if to_host and to_host ~= origin.host and not except_domains:contains(to_host) then if to_host:match("^[^.]+%.(.+)$") == origin.host then -- Permit subdomains except_domains:add(to_host); @@ -31,10 +34,12 @@ end end +module:default_permission("prosody:admin", "xmpp:federate"); + function check_user_isolated(event) local session = event.session; local bare_jid = jid_bare(session.full_jid); - if is_admin(bare_jid, module.host) or except_users:contains(bare_jid) then + if module:may("xmpp:federate") or except_users:contains(bare_jid) then session.no_host_isolation = true; end module:log("debug", "%s is %sisolated", session.full_jid or "[?]", session.no_host_isolation and "" or "not "); diff -r cce12a660b98 -r bd63feda3704 mod_muc_config_restrict/README.markdown --- a/mod_muc_config_restrict/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_muc_config_restrict/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -57,7 +57,6 @@ ============= ------- -------------- - trunk Works - 0.9 Doesn't work - 0.8 Doesn't work + trunk Doesn't work (uses is_admin) + 0.12 Works? ------- -------------- diff -r cce12a660b98 -r bd63feda3704 mod_muc_restrict_rooms/README.markdown --- a/mod_muc_restrict_rooms/README.markdown Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_muc_restrict_rooms/README.markdown Mon Aug 22 15:39:02 2022 +0100 @@ -49,6 +49,7 @@ ============= ----- ------------- + trunk Doesn't work (uses is_admin) 0.9 Works 0.8 Should work ----- ------------- diff -r cce12a660b98 -r bd63feda3704 mod_rest/mod_rest.lua --- a/mod_rest/mod_rest.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_rest/mod_rest.lua Mon Aug 22 15:39:02 2022 +0100 @@ -49,11 +49,15 @@ end return { username = username, host = module.host }; elseif auth_type == "Bearer" then - local token_info = tokens.get_token_info(auth_data); - if not token_info or not token_info.session then - return false; + if tokens.get_token_session then + return tokens.get_token_session(auth_data); + else -- COMPAT w/0.12 + local token_info = tokens.get_token_info(auth_data); + if not token_info or not token_info.session then + return false; + end + return token_info.session; end - return token_info.session; end return nil; end diff -r cce12a660b98 -r bd63feda3704 mod_sentry/mod_sentry.lua --- a/mod_sentry/mod_sentry.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_sentry/mod_sentry.lua Mon Aug 22 15:39:02 2022 +0100 @@ -29,6 +29,8 @@ end; }; +local serialize = require "util.serialization".serialize; + local function sentry_error_handler(e) module:log("error", "Failed to submit event to sentry: %s", e); end diff -r cce12a660b98 -r bd63feda3704 mod_sentry/sentry.lib.lua --- a/mod_sentry/sentry.lib.lua Tue Aug 16 13:10:39 2022 +0200 +++ b/mod_sentry/sentry.lib.lua Mon Aug 22 15:39:02 2022 +0100 @@ -221,6 +221,7 @@ local data = json.decode(response.body); return data; end + module:log("warn", "Unexpected response from server: %d: %s", response.code, response.body); return promise.reject(response); end