Skip to content
Snippets Groups Projects
Unverified Commit f5a91e27 authored by Emrah Eryilmaz's avatar Emrah Eryilmaz
Browse files

feat(sovereign-workplace-jitsi): Add custom prosody plugins into files

parent ac8423a9
No related branches found
No related tags found
No related merge requests found
-- -----------------------------------------------------------------------------
-- Hybrid Matrix-Token authentication
-- -----------------------------------------------------------------------------
-- This module is an authentication provider for Prosody which supports Matrix
-- and standard Jitsi token at the same time. It senses the type of the token
-- and handles it depending on its type.
-- -----------------------------------------------------------------------------
local async = require "util.async"
local basexx = require 'basexx'
local cjson_safe = require 'cjson.safe'
local formdecode = require "util.http".formdecode
local generate_uuid = require "util.uuid".generate
local http = require "net.http"
local json = require "util.json"
local new_sasl = require "util.sasl".new
local sasl = require "util.sasl"
local sessions = prosody.full_sessions
local token_util = module:require "token/util".new(module)
-- no token configuration
if token_util == nil then
return
end
module:depends("jitsi_session")
local uvsIssuer = {"*"}
local issuer = module:get_option("uvs_issuer", nil)
if issuer then
uvsIssuer = { string.format("%s", issuer) }
end
local uvsUrl = module:get_option("uvs_base_url", nil)
if uvsUrl == nil then
module:log("warn", "Missing 'uvs_base_url' config")
end
local uvsAuthToken = module:get_option("uvs_auth_token", nil)
if uvsAuthToken == nil then
module:log(
"info",
"No uvs_auth_token supplied, not sending authentication headers"
)
end
-- define auth provider
local provider = {}
local host = module.host
-- Extract 'token' and 'room' params from URL when session is created
function init_session(event)
local session, request = event.session, event.request
local query = request.url.query
if query ~= nil then
local params = formdecode(query)
session.auth_token = params and params.token or nil
session.jitsi_room = params and params.room or nil
end
end
module:hook_global("bosh-session", init_session)
module:hook_global("websocket-session", init_session)
function provider.test_password(_username, _password)
return nil, "Password based auth not supported"
end
function provider.get_password(_username)
return nil
end
function provider.set_password(_username, _password)
return nil, "Set password not supported"
end
function provider.user_exists(_username)
return nil
end
function provider.create_user(_username, _password)
return nil
end
function provider.delete_user(_username)
return nil
end
local function split_token(token)
local segments = {}
for seg in string.gmatch(token, "([^.]+)") do
table.insert(segments, seg)
end
return segments
end
local function parse_token(token)
if type(token) ~= "string" then return nil, nil, nil end
local segments = split_token(token)
if #segments ~= 3 then return nil, nil, nil end
local header, err1 = cjson_safe.decode(basexx.from_url64(segments[1]))
if err1 then return nil, nil, nil end
local payload, err2 = cjson_safe.decode(basexx.from_url64(segments[2]))
if err2 then return nil, nil, nil end
local sign, err3 = basexx.from_url64(segments[3])
if err3 then return nil, nil, nil end
return header, payload, sign
end
local function get_options(matrixPayload)
local options = {}
options.method = "POST"
options.headers = {
["Content-Type"] = "application/json",
["User-Agent"] = string.format("Prosody (%s)", prosody.version)
}
if uvsAuthToken ~= nil then
options.headers["Authorization"] = string.format(
"Bearer %s", uvsAuthToken
)
end
local body = {
["token"] = matrixPayload["token"],
["room_id"] = matrixPayload["room_id"]
}
if matrixPayload.server_name then
body["matrix_server_name"] = matrixPayload.server_name
end
options.body = json.encode(body)
return options
end
local function is_user_in_room(session, matrixPayload)
local url = string.format("%s/verify/user_in_room", uvsUrl)
local options = get_options(matrixPayload)
local wait, done = async.waiter()
local httpRes
local function cb(resBody, resCode, _req, _res)
if resCode == 200 then
httpRes = json.decode(resBody)
end
done()
end
http.request(url, options, cb)
wait()
-- no result
if not (httpRes and httpRes.results) then
return false
end
-- not a member of Matrix room
if not (httpRes.results.user and httpRes.results.room_membership) then
return false
end
-- set affiliation as session value according to their power level
session.matrix_affiliation = "member"
session.auth_matrix_user_verification_is_owner = false
if
httpRes.power_levels and httpRes.power_levels.user and
httpRes.power_levels.room and httpRes.power_levels.room.state_default and
httpRes.power_levels.user >= httpRes.power_levels.room.state_default
then
session.matrix_affiliation = "owner"
session.auth_matrix_user_verification_is_owner = true
end
return true
end
local function matrix_handler(session, payload)
if uvsUrl == nil then
module:log("warn", "Missing 'uvs_base_url' config")
session.auth_token = nil
return false, "access-denied", "missing Matrix UVS address"
end
session.public_key = "notused"
local res, error, reason = token_util:process_and_verify_token(
session,
uvsIssuer
)
if res == false then
module:log(
"warn",
"Error verifying token err:%s, reason:%s", error, reason
)
session.auth_token = nil
return res, error, reason
end
if payload.context.matrix.room_id == nil then
module:log("warn", "Missing Matrix room_id in token")
session.auth_token = nil
return false, "bad-request", "Matrix room ID must be given"
end
local decodedRoomId = basexx.from_base32(session.jitsi_room)
if decodedRoomId ~= payload.context.matrix.room_id then
module:log("warn", "Jitsi and Matrix rooms don't match")
session.auth_token = nil
return false, "access-denied", "Jitsi room does not match Matrix room"
end
if not is_user_in_room(session, payload.context.matrix) then
module:log("warn", "Matrix token is invalid or user does not in room")
session.auth_token = nil
return false, "access-denied", "Matrix token invalid or not in room"
end
session.jitsi_meet_context_matrix = payload.context.matrix
return true, nil, nil
end
local function token_handler(session)
-- retrieve custom public key from server and save it on the session
local preEvent = prosody.events.fire_event(
"pre-jitsi-authentication-fetch-key",
session
)
if preEvent ~= nil and preEvent.res == false then
module:log(
"warn",
"Error verifying token on pre authentication stage:%s, reason:%s",
preEvent.error,
preEvent.reason
)
session.auth_token = nil
return preEvent.res, preEvent.error, preEvent.reason
end
local res, error, reason = token_util:process_and_verify_token(session)
if res == false then
module:log(
"warn",
"Error verifying token err:%s, reason:%s", error, reason
)
session.auth_token = nil
return res, error, reason
end
return true, nil, nil
end
local function common_handler(self, session, message)
local shouldAllow = prosody.events.fire_event(
"jitsi-access-ban-check",
session
)
if shouldAllow == false then
module:log("warn", "user is banned")
return false, "not-allowed", "user is banned"
end
local customUsername = prosody.events.fire_event(
"pre-jitsi-authentication",
session
)
if (customUsername) then
self.username = customUsername
elseif (session.previd ~= nil) then
for _, s in pairs(sessions) do
if (s.resumption_token == session.previd) then
self.username = s.username
break
end
end
else
self.username = message
end
local postEvent = prosody.events.fire_event(
"post-jitsi-authentication",
session
)
if postEvent ~= nil and postEvent.res == false then
module:log(
"warn",
"Error verifying token on post authentication :%s, reason:%s",
postEvent.error,
postEvent.reason
)
session.auth_token = nil
return postEvent.res, postEvent.error, postEvent.reason
end
return true, nil, nil
end
function provider.get_sasl_handler(session)
local function handler(self, message)
local payload
if session.auth_token then
_, payload, _ = parse_token(session.auth_token)
end
if payload and payload.context and payload.context.matrix then
module:log("info", "Matrix authentication handler is selected")
local res, error, reason = matrix_handler(session, payload)
if res == false then
return res, error, reason
end
else
module:log("info", "Token authentication handler is selected")
local res, error, reason = token_handler(session)
if res == false then
return res, error, reason
end
end
local res, error, reason = common_handler(self, session, message)
if res == false then
return res, error, reason
end
return true
end
return new_sasl(host, { anonymous = handler })
end
module:provides("auth", provider)
local function anonymous(self, message)
module:log("debug", "Message in anonymous: %s", message)
local username = generate_uuid()
-- This calls the handler created in 'provider.get_sasl_handler(session)'
local result, err, msg = self.profile.anonymous(self, username, self.realm)
if result == true then
if (self.username == nil) then
self.username = username
end
return "success"
else
return "failure", err, msg
end
end
sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous)
-- -----------------------------------------------------------------------------
-- Matrix Affiliation (downgrade only)
-- -----------------------------------------------------------------------------
-- This module updates the affiliation of participants if the room is created
-- by Element's Jitsi widget. This module checks Jitsi room name to understand
-- if this room is created by widget or not...
--
-- base32.decode(jitsi_room_name) should match "!.*:.*[.].*" (regex) for related
-- rooms.
--
-- This module assumes that the authentication is already enabled on Jicofo. So
-- every participants who have a valid token will become moderator (owner) by
-- default (this is not what we want).
--
-- This module downgrades the affiliation level (from owner to member) of
-- the participant if she is not an admin in the related Matrix room.
-- -----------------------------------------------------------------------------
local basexx = require 'basexx'
local is_admin = require "core.usermanager".is_admin
local is_healthcheck_room = module:require "util".is_healthcheck_room
local jid_split = require "util.jid".split
local timer = require "util.timer"
local LOGLEVEL = "debug"
local function _is_admin(jid)
return is_admin(jid, module.host)
end
module:hook("muc-occupant-joined", function (event)
local room, occupant = event.room, event.occupant
if is_healthcheck_room(room.jid) or _is_admin(occupant.jid) then
module:log(LOGLEVEL, "skip affiliation, %s", occupant.jid)
return
end
if not event.origin.auth_token then
module:log(LOGLEVEL, "skip affiliation, no token")
return
end
local roomName, _ = jid_split(room.jid)
local roomId = basexx.from_base32(roomName)
if not roomId then
module:log(LOGLEVEL, "skip affiliation, cannot decode the room name")
return
end
local isMatrixRoom = string.match(roomId, "!.*:.*[.].*")
if not isMatrixRoom then
module:log(LOGLEVEL, "skip affiliation, not a Matrix room")
return
end
if event.origin.matrix_affiliation == "owner" then
module:log(LOGLEVEL, "skip downgrading, valid Matrix owner")
return
end
-- All users who have a valid token are set as owner by jicofo when
-- auhentication is enabled on jicofo. Downgrade the affiliation for all
-- users who are not a Matrix owner (even they have a valid token).
-- A timer is used because Jicofo will update the affiliation after this
-- internal authentication phase is completed. It should be overwritten.
local i = 0.0
while (i < 2.0) do
timer.add_task(i, function()
room:set_affiliation(true, occupant.bare_jid, "member")
end)
i = i + 0.2
end
module:log( "info",
"affiliation is downgraded, occupant: %s",
occupant.bare_jid
)
end)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment

Consent

On this website, we use the web analytics service Matomo to analyze and review the use of our website. Through the collected statistics, we can improve our offerings and make them more appealing for you. Here, you can decide whether to allow us to process your data and set corresponding cookies for these purposes, in addition to technically necessary cookies. Further information on data protection—especially regarding "cookies" and "Matomo"—can be found in our privacy policy. You can withdraw your consent at any time.