# HG changeset patch # User Seve Ferrer # Date 1607793554 -3600 # Node ID 08138de4cb8807c4bc748bbb50c82c7af2061d6f # Parent d44a8d3dd57176a0a1d03914b9840c0b20f4c8af Prosodoy module to externalize MUC authorization via HTTP diff -r d44a8d3dd571 -r 08138de4cb88 mod_muc_http_auth/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_http_auth/README.md Sat Dec 12 18:19:14 2020 +0100 @@ -0,0 +1,63 @@ +# Introduction + +This module externalizes MUC authorization via HTTP. +Whenever a user wants to join a MUC, an HTTP GET request is made to `authorization_url` +with the user bare jid (`userJID`) and the MUC jid (`mucJID`) as GET parameters. +Example: +`https://www.prosody.im/users/can-join/?userJID=romeo@example.com&mucJID=teaparty@chat.example.com` + +This allows an external service to decide whether a user is authorized to join a MUC or not. + +When a user is authorized to join a MUC, this module expects the following JSON payload: +``` +{ + allowed: true, + error: "", +} +``` +Otherwise, either the user not being authorized or some failure in the external service: +``` +{ + allowed: false, + error: "Some error message to be displayed in this module's logs", +} +``` + +# Configuring + +## Enabling + +``` {.lua} +Component "rooms.example.net" "muc" + +modules_enabled = { + "muc_http_auth"; +} + +``` + + +## Settings + +|Name |Description |Default | +|-----|------------|--------| +|muc_http_auth_url| URL of the external HTTP service to which send `userJID` and `mucJID` in a GET request | "" | +|muc_http_auth_enabled_for| List of MUC names (node part) to enable this module for | nil | +|muc_http_auth_disabled_for| List of MUC names (node part) to disable this module for | nil | +|muc_http_auth_insecure| Disable certificate verification for request. Only intended for development of the external service. | false | + + +This module can be enabled/disabled for specific rooms. Only one of the following settings must be set. +``` +-- muc_http_auth_enabled_for = {"teaparty"} +-- muc_http_auth_disabled_for = {"teaparty"} +``` +If none or both are found, all rooms in the MUC component will have this module enabled. + +Note: Use the node part of the MUC jid for these lists. Example: + +Wrong: +`muc_http_auth_enabled_for = {"teaparty@rooms.example.net"}` + +Correct: +`muc_http_auth_enabled_for = {"teaparty"}` \ No newline at end of file diff -r d44a8d3dd571 -r 08138de4cb88 mod_muc_http_auth/mod_muc_http_auth.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_http_auth/mod_muc_http_auth.lua Sat Dec 12 18:19:14 2020 +0100 @@ -0,0 +1,80 @@ +local wait_for = require "util.async".wait_for; +local http = require "net.http"; +local json = require "util.json"; +local st = require "util.stanza"; +local jid_node = require "util.jid".node; +local jid_bare = require "util.jid".bare; + +local authorization_url = module:get_option("muc_http_auth_url", "") +local enabled_for = module:get_option_set("muc_http_auth_enabled_for", nil) +local disabled_for = module:get_option_set("muc_http_auth_disabled_for", nil) +local insecure = module:get_option("muc_http_auth_insecure", false) --For development purposes + +local function must_be_authorized(room_node) + -- If none of these is set, all rooms need authorization + if not enabled_for and not disabled_for then return true; end + + if enabled_for and not disabled_for then + for _, _room_node in ipairs(enabled_for) do + if _room_node == room_node then + return true; + end + end + end + + if disabled_for and not enabled_for then + for _, _room_node in ipairs(disabled_for) do + if _room_node == room_node then + return false; + end + end + end + + return true; +end + +local function handle_success(response) + local body = json.decode(response.body or "") or {} + response = { + err = body.error, + allowed = body.allowed, + code = response.code + } + return {response=response, err=response.err}; +end + +local function handle_error(err) + return {err=err}; +end + +local function handle_presence(event) + local stanza = event.stanza; + if stanza.name ~= "presence" or stanza.attr.type == "unavailable" then + return; + end + + local room, origin = event.room, event.origin; + if (not room) or (not origin) then return; end + + if not must_be_authorized(jid_node(room.jid)) then return; end + + local user_bare_jid = jid_bare(stanza.attr.from); + local url = authorization_url .. "?userJID=" .. user_bare_jid .."&mucJID=" .. room.jid; + + local result = wait_for(http.request(url, {method="GET", insecure=insecure}):next(handle_success, handle_error)); + local response, err = result.response, result.err; + + if not (response and response.allowed) then + -- User is not authorized to join this room + err = (response or {}).err or err + module:log("debug", user_bare_jid .. " is not authorized to join " .. room.jid .. " Error: " .. tostring(err)); + origin.send(st.error_reply(stanza, "error", "not-authorized", nil, module.host)); + return true; + end + + module:log("debug", user_bare_jid .. " is authorized to join " .. room.jid); + return; +end + + +module:hook("muc-occupant-pre-join", handle_presence); \ No newline at end of file