Adds Prosody plugin for token authentication.

This commit is contained in:
paweldomas 2015-11-02 15:02:50 -06:00
parent b08308e5e4
commit 531b81cce3
12 changed files with 414 additions and 1 deletions

6
debian/control vendored
View File

@ -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

View File

@ -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

10
debian/jitsi-meet-tokens.config vendored Normal file
View File

@ -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
debian/jitsi-meet-tokens.docs vendored Normal file
View File

1
debian/jitsi-meet-tokens.install vendored Normal file
View File

@ -0,0 +1 @@
prosody-plugins/ /usr/share/jitsi-meet/

91
debian/jitsi-meet-tokens.postinst vendored Normal file
View File

@ -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

73
debian/jitsi-meet-tokens.postrm vendored Normal file
View File

@ -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

14
debian/jitsi-meet-tokens.templates vendored Normal file
View File

@ -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:

View File

@ -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,6 +24,7 @@ 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"

View File

@ -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);

View File

@ -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);

View File

@ -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;