Skip to content

Instantly share code, notes, and snippets.

@fgrsnau
Last active February 15, 2023 17:15
Show Gist options
  • Save fgrsnau/a6599df5d1f6097eec96581a66b3a737 to your computer and use it in GitHub Desktop.
Save fgrsnau/a6599df5d1f6097eec96581a66b3a737 to your computer and use it in GitHub Desktop.
Synchronize MUC admins/owners/members with usermanager

  • 'Stage-Alpha' summary: Synchronize MUC admins/owners/members with usermanager ...

Introduction

This module allows to synchronize the affiliation of a group chat room (MUC) with the members of the organization prosody is running for. The idea is that every user known the prosody's usermanager should be affiliated as a "member" of the MUC. Administrative users will receive the MUC affiliation "owner". This simple scheme allows the module to work without a complicated configuration and it only needs a list of MUC names to manage.

Note that this module will take over managing the affiliations "owner", "admin" and "member" exclusively. Every manual management of these affiliation will get overruled by this module if enabled for the specific MUC. It will not touch affiliations of type "outcast" (banned JIDs) and it will especially not remove the "outcast" affiliation for any JID (at least if a JID is not found to be owner, admin or member).

This module work especially well if you use LDAP authentication like mod_auth_ldap.

Configuration

This module should be enabled for a muc component in your config. It will load the configuration item muc_organization_rooms which is a list of MUC room names to manage.

Example

Assuming that dummy.invalid is your domain, an example configuration would look like this:

Component "conference.dummy.invalid" "muc"
  muc_organization_rooms = { "general", "off-topic" }
  modules_enabled = { "muc_ldap" }

Missing Features

Right now all users (across all virtual hosts) in prosody's usermanager will get granted at least member permission. One could think of restricting this the a list of virtual hosts. This is not implemented right now.

It uses a timer right now to do its work regurlary, but hooking the correct places inside prosody should be complicated.

local usermanager = require "core.usermanager"
local async = require "util.async"
local jid_bare = require "util.jid".bare
local jid_join = require "util.jid".join
local jid_split = require "util.jid".split
local function sched_yield()
wait, done = async.waiter()
module:add_timer(0, done)
wait()
end
local function get_target_affiliation(jid)
-- if it is not a bare jid, we don't want to have this entry
local bjid = jid_bare(jid)
if bjid ~= jid then
return "none"
end
-- we check if it is a local user
local username, hostname = jid_split(jid)
if prosody.hosts[hostname] then
if usermanager.user_exists(username, hostname) then
if usermanager.is_admin(bjid, hostname) then
return "owner"
else
return "member"
end
end
end
return "none"
end
local function update_affiliation_for_jid(room, jid)
local affiliation = room:get_affiliation(jid)
local target_affiliation = get_target_affiliation(jid)
if affiliation ~= target_affiliation then
module:log("debug", "updating affiliation for %s in %s...", jid, room.jid)
room:set_affiliation(true, jid, target_affiliation)
end
end
local function configure_room(room_name)
local host_name = module:get_host()
local room_jid = room_name .. "@" .. host_name
module:log("debug", "configuring room %s...", room_jid)
local mod_muc = prosody.hosts[host_name].modules.muc
local room = mod_muc.get_room_from_jid(room_jid)
if not room then
room = mod_muc.create_room(room_jid)
end
room:set_persistent(true)
room:set_hidden(false)
-- First we go through all present affiliation and check if they match the
-- computed target affiliation.
for jid, _ in pairs(room._affiliations) do
update_affiliation_for_jid(room, jid)
sched_yield()
end
-- Now we go through all bound users (authenticated to any of our vhosts) and
-- do the same.
for hostname, host in pairs(prosody.hosts) do
for username, _ in pairs(host.sessions or {}) do
local jid = jid_join(username, hostname)
update_affiliation_for_jid(room, jid)
sched_yield()
end
end
end
local function configure_rooms()
local rooms = module:get_option("muc_organization_rooms", {})
for _, room_name in pairs(rooms) do
configure_room(room_name)
sched_yield()
end
end
local function handle_presence(event)
if event.stanza.name == "presence" and event.stanza.attr.type == "unavailable" then
-- this is a leaving event
return
end
local room_jid = jid_bare(event.stanza.attr.to)
local joiner_jid = jid_bare(event.stanza.attr.from)
module:log("debug", "Checking privileges for %s joining %s", joiner_jid, room_jid)
local mod_muc = prosody.hosts[module:get_host()].modules.muc
local room = mod_muc.get_room_from_jid(room_jid)
if room then
update_affiliation_for_jid(room, joiner_jid)
end
end
module:hook("presence/bare", handle_presence)
module:hook("presence/full", handle_presence)
-- Queue initial room configuration in event loop. We rerun the configuration
-- every 24h to keep the member list up to date. Note that privileges will be
-- checked for every user trying to enter a room in real time anyway. We just
-- run it regurlary in case users after privileges have changed.
module:add_timer(5, function()
async.runner(function()
configure_rooms()
end):run({})
return 24 * 60 * 60 -- 24h
end)
-- vim: set ts=8 sts=2 sw=2 et:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment