view mod_compat_roles/mod_compat_roles.lua @ 5668:ecfd7aece33b

mod_measure_modules: Report module statuses via OpenMetrics Someone in the chat asked about a health check endpoint, which reminded me of mod_http_status, which provides access to module statuses with full details. After that, this idea came about, which seems natural. As noted in the README, it could be used to monitor that critical modules are in fact loaded correctly. As more modules use the status API, the more useful this module and mod_http_status becomes.
author Kim Alvefur <zash@zash.se>
date Fri, 06 Oct 2023 18:34:39 +0200
parents 825c6fb76c48
children f8b9095f7862
line wrap: on
line source

-- 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][role_name][permission_name] = is_permitted
local permissions = {};

local role_inheritance = {
	["prosody:operator"] = "prosody:admin";
	["prosody:admin"] = "prosody:member";
	["prosody:member"] = "prosody:registered";
	["prosody:registered"] = "prosody:guest";

	-- COMPAT
	["prosody:user"] = "prosody:registered";
};

local function role_may(host, role_name, permission)
	local host_roles = permissions[host];
	if not host_roles then
		return false;
	end
	local role_permissions = host_roles[role_name];
	if not role_permissions then
		return false;
	end
	local next_role = role_inheritance[role_name];
	return not not permissions[role_name][permission] or (next_role and role_may(host, next_role, 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(self.host, 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(self.host, 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 p = permissions[self.host];
	if not p then
		p = {};
		permissions[self.host] = p;
	end
	local r = p[role_name];
	if not r then
		r = {};
		p[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