mod_compat_roles/mod_compat_roles.lua
changeset 4987 7c77058a1ac5
child 5101 d414fa8b37dc
--- /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