Adds Prosody plugin for token authentication.
This commit is contained in:
parent
b08308e5e4
commit
531b81cce3
|
@ -32,3 +32,9 @@ Description: Prosody configuration for Jitsi Meet
|
|||
.
|
||||
This package contains configuration for Prosody to be used with
|
||||
Jitsi Meet.
|
||||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody | prosody-trunk, jitsi-meet-prosody
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Token authentication plugin for Jitsi Meet
|
||||
----------------------------
|
||||
|
||||
Jitsi Meet is a WebRTC video conferencing application. This package contains
|
||||
Prosody plugin which enables token authentication in Jitsi Meet installation.
|
||||
|
||||
-- Pawel Domas <pawel.domas@jitsi.org> Mon, 2 Nov 2015 14:45:00 -0600
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
db_input critical jitsi-meet-tokens/appid || true
|
||||
db_go
|
||||
|
||||
db_input critical jitsi-meet-tokens/appsecret || true
|
||||
db_go
|
|
@ -0,0 +1 @@
|
|||
prosody-plugins/ /usr/share/jitsi-meet/
|
|
@ -0,0 +1,91 @@
|
|||
#!/bin/bash
|
||||
# postinst script for jitsi-meet-tokens
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
|
||||
if [ -f "/etc/jitsi/videobridge/config" ] ; then
|
||||
. /etc/jitsi/videobridge/config
|
||||
fi
|
||||
|
||||
if [ -f "/etc/jitsi/jicofo/config" ] ; then
|
||||
. /etc/jitsi/jicofo/config
|
||||
fi
|
||||
|
||||
# loading debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
db_get jitsi-meet-tokens/appid
|
||||
if [ "$RET" = "false" ] ; then
|
||||
echo "Application ID is mandatory"
|
||||
exit 1
|
||||
fi
|
||||
APP_ID=$RET
|
||||
|
||||
db_get jitsi-meet-tokens/appsecret
|
||||
if [ "$RET" = "false" ] ; then
|
||||
echo "Application secret is mandatory"
|
||||
fi
|
||||
APP_SECRET=$RET
|
||||
|
||||
# We can adjust Prosody config only if there is Jvb or Jicofo domain configured
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
|
||||
if [ ! -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JICOFO_HOSTNAME.cfg.lua"
|
||||
fi
|
||||
|
||||
# Store config filename for purge
|
||||
db_set jitsi-meet-prosody/prosody_config $PROSODY_HOST_CONFIG
|
||||
|
||||
db_stop
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
if grep -q "--plugin_paths" "$PROSODY_HOST_CONFIG"; then
|
||||
# enable tokens in prosody host config
|
||||
sed -i "s/--plugin_paths/plugin_paths/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/--app_id=example_app_id/app_id=$APP_ID/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/--app_secret=example_app_secret/app_secret=$APP_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/--modules_enabled = { "token_verification" }/modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
else
|
||||
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
||||
fi
|
||||
else
|
||||
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
|
||||
fi
|
||||
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,73 @@
|
|||
#!/bin/sh
|
||||
# postrm script for jitsi-meet-tokens
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postrm> `remove'
|
||||
# * <postrm> `purge'
|
||||
# * <old-postrm> `upgrade' <new-version>
|
||||
# * <new-postrm> `failed-upgrade' <old-version>
|
||||
# * <new-postrm> `abort-install'
|
||||
# * <new-postrm> `abort-install' <old-version>
|
||||
# * <new-postrm> `abort-upgrade' <old-version>
|
||||
# * <disappearer's-postrm> `disappear' <overwriter>
|
||||
# <overwriter-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
# Load debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
|
||||
db_get jitsi-meet-prosody/prosody_config
|
||||
PROSODY_HOST_CONFIG=$RET
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
|
||||
db_get jitsi-meet-tokens/appid
|
||||
APP_ID=$RET
|
||||
|
||||
db_get jitsi-meet-tokens/appsecret
|
||||
APP_SECRET=$RET
|
||||
|
||||
# Revert prosody config
|
||||
sed -i "s/plugin_paths/--plugin_paths/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/app_id=$APP_ID/--app_id=example_app_id/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/app_secret=$APP_SECRET/--app_secret=example_app_secret/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/modules_enabled = { "token_verification" }/--modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
fi
|
||||
fi
|
||||
|
||||
db_stop
|
||||
;;
|
||||
|
||||
purge)
|
||||
;;
|
||||
|
||||
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postrm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
db_stop
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,14 @@
|
|||
Template: jitsi-meet-tokens/appid
|
||||
Type: string
|
||||
_Description: The application ID to be used by token authentication plugin:
|
||||
Application ID:
|
||||
|
||||
Template: jitsi-meet-tokens/appsecret
|
||||
Type: password
|
||||
_Description: The application secret to be used by token authentication plugin:
|
||||
Application secret:
|
||||
|
||||
Template: jitsi-meet-prosody/prosody_config
|
||||
Type: string
|
||||
_Description: The location of Jitsi Meet Prosody config file
|
||||
Jitsi-meet Prosody config file location:
|
|
@ -1,6 +1,13 @@
|
|||
-- Plugins path gets uncommented during jitsi-meet-tokens package install - that's where token plugin is located
|
||||
--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
|
||||
|
||||
VirtualHost "jitmeet.example.com"
|
||||
-- enabled = false -- Remove this line to enable this host
|
||||
authentication = "anonymous"
|
||||
-- Two properties below get uncommented by jitsi-meet-tokens package config
|
||||
-- and authentication above is switched to "token"
|
||||
--app_id=example_app_id
|
||||
--app_secret=example_app_secret
|
||||
-- Assign this host a certificate for TLS, otherwise it would use the one
|
||||
-- set in the global section (if any).
|
||||
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
||||
|
@ -17,13 +24,14 @@ VirtualHost "jitmeet.example.com"
|
|||
}
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
--modules_enabled = { "token_verification" }
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
Component "jitsi-videobridge.jitmeet.example.com"
|
||||
component_secret = "jitmeetSecret"
|
||||
|
||||
VirtualHost "auth.jitmeet.example.com"
|
||||
authentication = "internal_plain"
|
||||
authentication = "internal_plain"
|
||||
|
||||
Component "focus.jitmeet.example.com"
|
||||
component_secret = "focusSecret"
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local usermanager = require "core.usermanager";
|
||||
local new_sasl = require "util.sasl".new;
|
||||
|
||||
local log = module._log;
|
||||
local host = module.host;
|
||||
|
||||
local token_util = module:require "token/util";
|
||||
|
||||
-- define auth provider
|
||||
local provider = {};
|
||||
|
||||
--do
|
||||
-- local list;
|
||||
-- for mechanism in pairs(new_sasl(module.host):mechanisms()) do
|
||||
-- list = (not(list) and mechanism) or (list..", "..mechanism);
|
||||
-- end
|
||||
-- if not list then
|
||||
-- module:log("error", "No mechanisms");
|
||||
-- else
|
||||
-- module:log("error", "Mechanisms: %s", list);
|
||||
-- end
|
||||
--end
|
||||
|
||||
|
||||
local appId = module:get_option_string("app_id");
|
||||
local appSecret = module:get_option_string("app_secret");
|
||||
local tokenLifetime = module:get_option_number("token_lifetime");
|
||||
|
||||
function provider.test_password(username, password)
|
||||
local result, msg = token_util.verify_password(password, appId, appSecret, tokenLifetime);
|
||||
if result == true then
|
||||
return true;
|
||||
else
|
||||
log("error", "Token auth failed for user %s, reason: %s",username, msg);
|
||||
return nil, msg;
|
||||
end
|
||||
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.users()
|
||||
return next, hosts[module.host].sessions, nil;
|
||||
end
|
||||
|
||||
function provider.create_user(username, password)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.delete_user(username)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler()
|
||||
local testpass_authentication_profile = {
|
||||
plain_test = function(sasl, username, password, realm)
|
||||
return usermanager.test_password(username, realm, password), true;
|
||||
end
|
||||
};
|
||||
return new_sasl(host, testpass_authentication_profile);
|
||||
end
|
||||
|
||||
module:provides("auth", provider);
|
|
@ -0,0 +1,52 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local log = module._log;
|
||||
local host = module.host;
|
||||
local st = require "util.stanza";
|
||||
local token_util = module:require("token/util");
|
||||
local is_admin = require "core.usermanager".is_admin;
|
||||
|
||||
|
||||
local parentHostName = string.gmatch(tostring(host), "%w+.(%w.+)")();
|
||||
if parentHostName == nil then
|
||||
log("error", "Failed to start - unable to get parent hostname");
|
||||
return;
|
||||
end
|
||||
|
||||
local parentCtx = module:context(parentHostName);
|
||||
if parentCtx == nil then
|
||||
log("error", "Failed to start - unable to get parent context for host: %s", tostring(parentHostName));
|
||||
return;
|
||||
end
|
||||
|
||||
local appId = parentCtx:get_option_string("app_id");
|
||||
local appSecret = parentCtx:get_option_string("app_secret");
|
||||
local tokenLifetime = parentCtx:get_option_string("token_lifetime");
|
||||
|
||||
log("debug", "%s - starting MUC token verifier app_id: %s app_secret: %s token-lifetime: %s",
|
||||
tostring(host), tostring(appId), tostring(appSecret), tostring(tokenLifetime));
|
||||
|
||||
local function handle_pre_create(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local token = stanza:get_child("token", "http://jitsi.org/jitmeet/auth-token");
|
||||
-- token not required for admin users
|
||||
local user_jid = stanza.attr.from;
|
||||
if is_admin(user_jid) then
|
||||
log("debug", "Token not required from admin user: %s", user_jid);
|
||||
return nil;
|
||||
end
|
||||
log("debug", "Will verify token for user: %s ", user_jid);
|
||||
if token ~= nil then
|
||||
token = token[1];
|
||||
end
|
||||
local result, msg = token_util.verify_password(token, appId, appSecret, tokenLifetime);
|
||||
if result ~= true then
|
||||
log("debug", "Token verification failed: %s", msg);
|
||||
origin.send(st.error_reply(stanza, "cancel", "not-allowed", msg));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
module:hook("muc-room-pre-create", handle_pre_create);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local hashes = require "util.hashes";
|
||||
|
||||
local _M = {};
|
||||
|
||||
local function calc_hash(password, appId, appSecret)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
if hash ~= nil and room ~= nil and ts ~= nil then
|
||||
log("debug", "Hash: '%s' room: '%s', ts: '%s'", hash, room, ts);
|
||||
local toHash = room .. ts .. appId .. appSecret;
|
||||
log("debug", "to be hashed: '%s'", toHash);
|
||||
local hash = hashes.sha256(toHash, true);
|
||||
log("debug", "hash: '%s'", hash);
|
||||
return hash;
|
||||
else
|
||||
log("error", "Invalid password format: '%s'", password);
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
local function extract_hash(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return hash;
|
||||
end
|
||||
|
||||
local function extract_ts(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return ts;
|
||||
end
|
||||
|
||||
local function get_utc_timestamp()
|
||||
return os.time(os.date("!*t")) * 1000;
|
||||
end
|
||||
|
||||
local function verify_timestamp(ts, tokenLifetime)
|
||||
return get_utc_timestamp() - ts <= tokenLifetime;
|
||||
end
|
||||
|
||||
local function verify_password_impl(password, appId, appSecret, tokenLifetime)
|
||||
|
||||
if password == nil then
|
||||
return nil, "password is missing";
|
||||
end
|
||||
|
||||
if tokenLifetime == nil then
|
||||
tokenLifetime = 24 * 60 * 60 * 1000;
|
||||
end
|
||||
|
||||
local ts = extract_ts(password);
|
||||
if ts == nil then
|
||||
return nil, "timestamp not found in the password";
|
||||
end
|
||||
local os_ts = get_utc_timestamp();
|
||||
log("debug", "System TS: '%s' user TS: %s", tostring(os_ts), tostring(ts));
|
||||
local isValid = verify_timestamp(ts, tokenLifetime);
|
||||
if not isValid then
|
||||
return nil, "token expired";
|
||||
end
|
||||
|
||||
local realHash = calc_hash(password, appId, appSecret);
|
||||
local givenhash = extract_hash(password);
|
||||
log("debug", "Compare '%s' to '%s'", tostring(realHash), tostring(givenhash));
|
||||
if realHash == givenhash then
|
||||
return true;
|
||||
else
|
||||
return nil, "invalid hash";
|
||||
end
|
||||
end
|
||||
|
||||
function _M.verify_password(password, appId, appSecret, tokenLifetime)
|
||||
return verify_password_impl(password, appId, appSecret, tokenLifetime);
|
||||
end
|
||||
|
||||
return _M;
|
Loading…
Reference in New Issue