# HG changeset patch # User Matthew Wild # Date 1660236573 -3600 # Node ID 7c77058a1ac5dc8f274587f88627183fb5a05ccb # Parent 8a4b17e2e98435f46932b81d26cadb8823f14f15 mod_compat_roles: New module providing compat shim for trunk's new role API The new role API is translated to is_admin() calls on older versions. On newer versions (which have the role API) this module does nothing. It allows modules to drop their use of is_admin() (which is not available in trunk) and switch to the new role API, while remaining compatible with previous Prosody versions. diff -r 8a4b17e2e984 -r 7c77058a1ac5 mod_compat_roles/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_compat_roles/README.markdown Thu Aug 11 17:49:33 2022 +0100 @@ -0,0 +1,49 @@ +--- +labels: +- Stage-Alpha +summary: 'Compatibility layer for Prosody's future roles API' +... + +Introduction +============ + +This module provides compatibility with Prosody's new role and permissions +system. It aims to run on Prosody 0.11 and 0.12, providing a limited version +of the new API backed by is_admin() (which is not going to be present in trunk +and future Prosody versions). + +It is designed for use by modules which want to be compatible with Prosody +versions with and without the new permissions API. + +Configuration +============= + +There is no configuration. + +Usage (for developers) +====================== + +If you are a module developer, and want your module to work with Prosody trunk +and future releases, you should avoid the `usermanager.is_admin()` function. + +Instead, depend on this module: + +``` +module:depends("compat_roles") +``` + +Then use `module:may()` instead: + +``` +if module:may(":do-something") then + -- Blah +end +``` + +For more information on the new role/permissions API, check Prosody's +developer documentation at https://prosody.im/doc/developers/permissions + +Compatibility +============= + +Requires Prosody 0.11 or 0.12. diff -r 8a4b17e2e984 -r 7c77058a1ac5 mod_compat_roles/mod_compat_roles.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_compat_roles/mod_compat_roles.lua Thu Aug 11 17:49:33 2022 +0100 @@ -0,0 +1,105 @@ +-- Export a module:may() that works on Prosody 0.12 and earlier +-- (i.e. backed by is_admin). + +-- This API is safe because Prosody 0.12 and earlier do not support +-- per-session roles - all authorization is based on JID alone. It is not +-- safe on versions that support per-session authorization. + +module:set_global(); + +local moduleapi = require "core.moduleapi"; + +-- If module.may already exists, abort +if moduleapi.may then return; end + +local jid_split = require "util.jid".split; +local um_is_admin = require "core.usermanager".is_admin; + +local function get_jid_role_name(jid, host) + if um_is_admin(jid, "*") then + return "prosody:operator"; + elseif um_is_admin(jid, host) then + return "prosody:admin"; + end + return nil; +end + +local function get_user_role_name(username, host) + return get_jid_role_name(username.."@"..host, host); +end + +-- permissions[host][permission_name] = permitted_role_name +local permissions = {}; + +local function role_may(role_name, permission) + local role_permissions = permissions[role_name]; + if not role_permissions then + return false; + end + return not not permissions[role_name][permission]; +end + +function moduleapi.may(self, action, context) + if action:byte(1) == 58 then -- action begins with ':' + action = self.name..action; -- prepend module name + end + if type(context) == "string" then -- check JID permissions + local role; + local node, host = jid_split(context); + if host == self.host then + role = get_user_role_name(node, self.host); + else + role = get_jid_role_name(context, self.host); + end + if not role then + self:log("debug", "Access denied: JID <%s> may not %s (no role found)", context, action); + return false; + end + + local permit = role_may(role, action); + if not permit then + self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", context, action, role.name); + end + return permit; + end + + local session = context.origin or context.session; + if type(session) ~= "table" then + error("Unable to identify actor session from context"); + end + if session.type == "s2sin" or (session.type == "c2s" and session.host ~= self.host) then + local actor_jid = context.stanza.attr.from; + local role_name = get_jid_role_name(actor_jid); + if not role_name then + self:log("debug", "Access denied: JID <%s> may not %s (no role found)", actor_jid, action); + return false; + end + local permit = role_may(role_name, action, context); + if not permit then + self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role_name); + end + return permit; + end +end + +function moduleapi.default_permission(self, role_name, permission) + local r = permissions[self.host][role_name]; + if not r then + r = {}; + permissions[self.host][role_name] = r; + end + r[permission] = true; +end + +function moduleapi.default_permissions(self, role_name, permission_list) + for _, permission in ipairs(permission_list) do + self:default_permission(role_name, permission); + end +end + +function module.add_host(host_module) + permissions[host_module.host] = {}; + function host_module.unload() + permissions[host_module.host] = nil; + end +end