view mod_storage_muconference_readonly/mod_storage_muconference_readonly.lua @ 3503:882180b459a0

mod_pubsub_post: Restructure authentication and authorization (BC) This deprecates the default "superuser" actor model and makes the default equivalent to the previous "request.id". A single actor and secret per node is supported because HTTP and WebHooks don't normally include any authorization identity. Allowing authentication bypass when no secret is given should be relatively safe when the actor is unprivileged, as will be unless explicitly configured otherwise.
author Kim Alvefur <zash@zash.se>
date Sat, 30 Mar 2019 21:16:13 +0100
parents a0727d23ee65
children
line wrap: on
line source


-- luacheck: ignore 212/self

local sql = require "util.sql";
local xml_parse = require "util.xml".parse;
local resolve_relative_path = require "util.paths".resolve_relative_path;
local stanza_preserialize = require "util.stanza".preserialize;

local unpack = unpack
local function iterator(result)
	return function(result_)
		local row = result_();
		if row ~= nil then
			return unpack(row);
		end
	end, result, nil;
end

local default_params = { driver = "MySQL" };

local engine;

local host = module.host;
local room, store;

local function get_best_affiliation(a, b)
	if a == 'owner' or b == 'owner' then
		return 'owner';
	elseif a == 'administrator' or b == 'administrator' then
		return 'administrator';
	elseif a == 'outcast' or b == 'outcast' then
		return 'outcast';
	elseif a == 'member' or b == 'member' then
		return 'member';
	end
	assert(false);
end

local function keyval_store_get()
	if store == "config" then
		local room_jid = room.."@"..host;
		local result;
		for row in engine:select("SELECT `name`,`desc`,`topic`,`public`,`secret` FROM `rooms` WHERE `jid`=? LIMIT 1", room_jid or "") do result = row end
		local name = result[1];
		local desc = result[2];
		local subject = result[3];
		local public = result[4];
		local hidden = public == 0 and true or nil;
		local secret = result[5];
		if secret == '' then secret = nil end
		local affiliations = {};
		for row in engine:select("SELECT `jid_user`,`affil` FROM `rooms_lists` WHERE `jid_room`=?", room_jid or "") do
			local jid_user = row[1];
			local affil = row[2];
			-- mu-conference has a bug where full JIDs get stored…
			local bare_jid = jid_user:gsub('/.*', '');
			local old_affil = affiliations[bare_jid];
			-- mu-conference has a bug where it can record multiple affiliations…
			if old_affil ~= nil and old_affil ~= affil then
				affil = get_best_affiliation(old_affil, affil);
			end
			-- terminology is clearly “admin”, not “administrator”.
			if affil == 'administrator' then
				affil = 'admin';
			end
			affiliations[bare_jid] = affil;
		end
		return {
			jid = room_jid,
			_data = {
				persistent = true,
				name = name,
				description = desc,
				subject = subject,
				password = secret,
				hidden = hidden,
			},
			_affiliations = affiliations,
		};
	end
end

--- Key/value store API (default store type)

local keyval_store = {};
keyval_store.__index = keyval_store;
function keyval_store:get(roomname)
	room, store = roomname, self.store;
	local ok, result = engine:transaction(keyval_store_get);
	if not ok then
		module:log("error", "Unable to read from database %s store for %s: %s", store, roomname or "<host>", result);
		return nil, result;
	end
	return result;
end

function keyval_store:users()
	local host_length = host:len() + 1;
	local ok, result = engine:transaction(function()
		return engine:select("SELECT SUBSTRING_INDEX(jid, '@', 1) FROM `rooms`");
	end);
	if not ok then return ok, result end
	return iterator(result);
end

local stores = {
	keyval = keyval_store;
};

--- Implement storage driver API

-- FIXME: Some of these operations need to operate on the archive store(s) too

local driver = {};

function driver:open(store, typ)
	local store_mt = stores[typ or "keyval"];
	if store_mt then
		return setmetatable({ store = store }, store_mt);
	end
	return nil, "unsupported-store";
end

function driver:stores(roomname)
	local query = "SELECT 'config'";
	if roomname == true or not roomname then
		roomname = "";
	end
	local ok, result = engine:transaction(function()
		return engine:select(query, host, roomname);
	end);
	if not ok then return ok, result end
	return iterator(result);
end

--- Initialization


local function normalize_params(params)
	assert(params.driver and params.database, "Configuration error: Both the SQL driver and the database need to be specified");
	return params;
end

function module.load()
	if prosody.prosodyctl then return; end
	local engines = module:shared("/*/sql/connections");
	local params = normalize_params(module:get_option("sql", default_params));
	engine = engines[sql.db2uri(params)];
	if not engine then
		module:log("debug", "Creating new engine");
		engine = sql:create_engine(params);
		engines[sql.db2uri(params)] = engine;
	end

	module:provides("storage", driver);
end