New JWT token impl that does not require token verification in Jicofo and uses anonymous authentication method(token goes as BOSH query param). Adds 'allow_empty_token" config option.
This commit is contained in:
parent
ac8e1ce388
commit
428fa3f16a
|
@ -35,6 +35,6 @@ Description: Prosody configuration for Jitsi Meet
|
||||||
|
|
||||||
Package: jitsi-meet-tokens
|
Package: jitsi-meet-tokens
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${misc:Depends}, prosody | prosody-trunk, jitsi-meet-prosody
|
Depends: ${misc:Depends}, prosody-trunk (>= 1nightly603), libssl-dev, luarocks, jitsi-meet-prosody
|
||||||
Description: Prosody token authentication plugin for Jitsi Meet
|
Description: Prosody token authentication plugin for Jitsi Meet
|
||||||
|
|
||||||
|
|
|
@ -62,25 +62,23 @@ case "$1" in
|
||||||
sed -i 's/--plugin_paths/plugin_paths/g' $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/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||||
sed -i 's/ --allow_unencrypted_plain_auth/ allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
sed -i 's/ --allow_unencrypted_plain_auth/ allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
||||||
sed -i "s/ --app_id=example_app_id/ app_id=$APP_ID/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/ --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
|
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||||
|
|
||||||
# Configure Jicofo properties
|
# Install luajwt
|
||||||
JICOFO_CONFIG="/etc/jitsi/jicofo/sip-communicator.properties"
|
if ! luarocks install luajwt; then
|
||||||
if ! grep -q "org.jitsi.jicofo.auth.jwt.APP_ID" $JICOFO_CONFIG && \
|
echo "Failed to install luajwt - try installing it manually"
|
||||||
! grep -q "org.jitsi.jicofo.auth.jwt.APP_SECRET" $JICOFO_CONFIG; then
|
|
||||||
echo "org.jitsi.jicofo.auth.jwt.APP_ID=$APP_ID" >> $JICOFO_CONFIG
|
|
||||||
echo "org.jitsi.jicofo.auth.jwt.APP_SECRET=$APP_SECRET" >> $JICOFO_CONFIG
|
|
||||||
# Restart Jicofo
|
|
||||||
if [ -x "/etc/init.d/jicofo" ]; then
|
|
||||||
invoke-rc.d jicofo restart
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -x "/etc/init.d/prosody" ]; then
|
if [ -x "/etc/init.d/prosody" ]; then
|
||||||
invoke-rc.d prosody reload
|
invoke-rc.d prosody restart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "This package requires BOSH Prosody module to be patched !"
|
||||||
|
echo "Use the following command, after this package has been installed and"
|
||||||
|
echo "after every prosody-trunk upgrade:"
|
||||||
|
echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
|
||||||
else
|
else
|
||||||
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -39,27 +39,12 @@ case "$1" in
|
||||||
# Revert prosody config
|
# Revert prosody config
|
||||||
sed -i 's/plugin_paths/--plugin_paths/g' $PROSODY_HOST_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/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||||
sed -i 's/ allow_unencrypted_plain_auth/ --allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
sed -i "s/ app_id=\"$APP_ID\"/ --app_id=\"example_app_id\"/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/ 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
|
sed -i 's/ modules_enabled = { "token_verification" }/ --modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||||
|
|
||||||
JICOFO_CONFIG="/etc/jitsi/jicofo/sip-communicator.properties"
|
|
||||||
if [ -f "$JICOFO_CONFIG" ] ; then
|
|
||||||
if grep -q "org.jitsi.jicofo.auth.jwt.APP_ID" $JICOFO_CONFIG || \
|
|
||||||
grep -q "org.jitsi.jicofo.auth.jwt.APP_SECRET" $JICOFO_CONFIG; then
|
|
||||||
sed -i.old "/org.jitsi.jicofo.auth.jwt.APP_ID=$APP_ID/d" $JICOFO_CONFIG
|
|
||||||
sed -i.old "/org.jitsi.jicofo.auth.jwt.APP_SECRET=$APP_SECRET/d" $JICOFO_CONFIG
|
|
||||||
rm "$JICOFO_CONFIG.old"
|
|
||||||
# Restart Jicofo
|
|
||||||
if [ -x "/etc/init.d/jicofo" ]; then
|
|
||||||
invoke-rc.d jicofo restart
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -x "/etc/init.d/prosody" ]; then
|
if [ -x "/etc/init.d/prosody" ]; then
|
||||||
invoke-rc.d prosody reload
|
invoke-rc.d prosody restart
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
VirtualHost "jitmeet.example.com"
|
VirtualHost "jitmeet.example.com"
|
||||||
-- enabled = false -- Remove this line to enable this host
|
-- enabled = false -- Remove this line to enable this host
|
||||||
authentication = "anonymous"
|
authentication = "anonymous"
|
||||||
-- Three properties below get uncommented by jitsi-meet-tokens package config
|
-- Properties below are modified by jitsi-meet-tokens package config
|
||||||
-- and authentication above is switched to "token"
|
-- and authentication above is switched to "token"
|
||||||
--allow_unencrypted_plain_auth = true;
|
--app_id="example_app_id"
|
||||||
--app_id=example_app_id
|
--app_secret="example_app_secret"
|
||||||
--app_secret=example_app_secret
|
|
||||||
-- Assign this host a certificate for TLS, otherwise it would use the one
|
-- Assign this host a certificate for TLS, otherwise it would use the one
|
||||||
-- set in the global section (if any).
|
-- set in the global section (if any).
|
||||||
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
||||||
|
|
|
@ -1,41 +1,46 @@
|
||||||
-- Token authentication
|
-- Token authentication
|
||||||
-- Copyright (C) 2015 Atlassian
|
-- Copyright (C) 2015 Atlassian
|
||||||
|
|
||||||
local usermanager = require "core.usermanager";
|
local generate_uuid = require "util.uuid".generate;
|
||||||
local new_sasl = require "util.sasl".new;
|
local new_sasl = require "util.sasl".new;
|
||||||
|
local sasl = require "util.sasl";
|
||||||
local log = module._log;
|
local formdecode = require "util.http".formdecode;
|
||||||
local host = module.host;
|
|
||||||
|
|
||||||
local token_util = module:require "token/util";
|
local token_util = module:require "token/util";
|
||||||
|
|
||||||
-- define auth provider
|
-- define auth provider
|
||||||
local provider = {};
|
local provider = {};
|
||||||
|
|
||||||
--do
|
local host = module.host;
|
||||||
-- 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 appId = module:get_option_string("app_id");
|
||||||
local appSecret = module:get_option_string("app_secret");
|
local appSecret = module:get_option_string("app_secret");
|
||||||
|
local allowEmptyToken = module:get_option_boolean("allow_empty_token");
|
||||||
|
|
||||||
|
if allowEmptyToken == true then
|
||||||
|
module:log("warn", "WARNING - empty tokens allowed");
|
||||||
|
end
|
||||||
|
|
||||||
|
if appId == nil then
|
||||||
|
module:log("error", "'app_id' must not be empty");
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if appSecret == nil then
|
||||||
|
module:log("error", "'app_secret' must not be empty");
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Extract 'token' param from BOSH URL when session is created
|
||||||
|
module:hook("bosh-session", function(event)
|
||||||
|
local session, request = event.session, event.request;
|
||||||
|
local query = request.url.query;
|
||||||
|
if query ~= nil then
|
||||||
|
session.auth_token = query and formdecode(query).token or nil;
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
function provider.test_password(username, password)
|
function provider.test_password(username, password)
|
||||||
local result, msg = token_util.verify_password(password, appId, appSecret, nil);
|
return nil, "Password based auth not supported";
|
||||||
if result == true then
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
log("error", "Token auth failed for user %s, reason: %s",username, msg);
|
|
||||||
return nil, msg;
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function provider.get_password(username)
|
function provider.get_password(username)
|
||||||
|
@ -50,10 +55,6 @@ function provider.user_exists(username)
|
||||||
return nil;
|
return nil;
|
||||||
end
|
end
|
||||||
|
|
||||||
function provider.users()
|
|
||||||
return next, hosts[module.host].sessions, nil;
|
|
||||||
end
|
|
||||||
|
|
||||||
function provider.create_user(username, password)
|
function provider.create_user(username, password)
|
||||||
return nil;
|
return nil;
|
||||||
end
|
end
|
||||||
|
@ -62,13 +63,59 @@ function provider.delete_user(username)
|
||||||
return nil;
|
return nil;
|
||||||
end
|
end
|
||||||
|
|
||||||
function provider.get_sasl_handler()
|
function provider.get_sasl_handler(session)
|
||||||
local testpass_authentication_profile = {
|
-- JWT token extracted from BOSH URL
|
||||||
plain_test = function(sasl, username, password, realm)
|
local token = session.auth_token;
|
||||||
return usermanager.test_password(username, realm, password), true;
|
|
||||||
|
local function get_username_from_token(self, message)
|
||||||
|
|
||||||
|
if token == nil then
|
||||||
|
if allowEmptyToken == true then
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false, "not-allowed", "token required";
|
||||||
|
end
|
||||||
end
|
end
|
||||||
};
|
|
||||||
return new_sasl(host, testpass_authentication_profile);
|
-- here we check if 'room' claim exists
|
||||||
|
local room, roomErr = token_util.get_room_name(token, appSecret);
|
||||||
|
if room == nil then
|
||||||
|
return false, "not-allowed", roomErr;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- now verify the whole token
|
||||||
|
local result, msg
|
||||||
|
= token_util.verify_token(token, appId, appSecret, room);
|
||||||
|
if result == true then
|
||||||
|
-- Binds room name to the session which is later checked on MUC join
|
||||||
|
session.jitsi_meet_room = room;
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false, "not-allowed", msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_sasl(host, { anonymous = get_username_from_token });
|
||||||
end
|
end
|
||||||
|
|
||||||
module:provides("auth", provider);
|
module:provides("auth", provider);
|
||||||
|
|
||||||
|
local function anonymous(self, 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);
|
||||||
|
|
||||||
|
self.username = username;
|
||||||
|
|
||||||
|
if result == true then
|
||||||
|
return "success"
|
||||||
|
else
|
||||||
|
|
||||||
|
return "failure", err, msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
--- /usr/lib/prosody/modules/mod_bosh.lua 2015-12-16 14:28:34.000000000 -0600
|
||||||
|
+++ /usr/lib/prosody/modules/mod_bosh.lua 2015-12-22 10:45:59.818197967 -0600
|
||||||
|
@@ -294,6 +294,9 @@
|
||||||
|
|
||||||
|
session.log("debug", "BOSH session created for request from %s", session.ip);
|
||||||
|
log("info", "New BOSH session, assigned it sid '%s'", sid);
|
||||||
|
+
|
||||||
|
+ hosts[session.host].events.fire_event(
|
||||||
|
+ "bosh-session", { session = session, request = request });
|
||||||
|
|
||||||
|
-- Send creation response
|
||||||
|
local creating_session = true;
|
|
@ -4,7 +4,6 @@
|
||||||
local log = module._log;
|
local log = module._log;
|
||||||
local host = module.host;
|
local host = module.host;
|
||||||
local st = require "util.stanza";
|
local st = require "util.stanza";
|
||||||
local token_util = module:require("token/util");
|
|
||||||
local is_admin = require "core.usermanager".is_admin;
|
local is_admin = require "core.usermanager".is_admin;
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,23 +15,35 @@ end
|
||||||
|
|
||||||
local parentCtx = module:context(parentHostName);
|
local parentCtx = module:context(parentHostName);
|
||||||
if parentCtx == nil then
|
if parentCtx == nil then
|
||||||
log("error", "Failed to start - unable to get parent context for host: %s", tostring(parentHostName));
|
log("error",
|
||||||
|
"Failed to start - unable to get parent context for host: %s",
|
||||||
|
tostring(parentHostName));
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
|
|
||||||
local appId = parentCtx:get_option_string("app_id");
|
local appId = parentCtx:get_option_string("app_id");
|
||||||
local appSecret = parentCtx:get_option_string("app_secret");
|
local appSecret = parentCtx:get_option_string("app_secret");
|
||||||
|
local allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token");
|
||||||
|
|
||||||
log("debug", "%s - starting MUC token verifier app_id: %s app_secret: %s",
|
log("debug",
|
||||||
tostring(host), tostring(appId), tostring(appSecret));
|
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
|
||||||
|
tostring(host), tostring(appId), tostring(appSecret),
|
||||||
|
tostring(allowEmptyToken));
|
||||||
|
|
||||||
local function handle_pre_create(event)
|
local function verify_user(session, stanza)
|
||||||
|
log("debug", "Session token: %s, session room: %s",
|
||||||
|
tostring(session.auth_token),
|
||||||
|
tostring(session.jitsi_meet_room));
|
||||||
|
|
||||||
local origin, stanza = event.origin, event.stanza;
|
if allowEmptyToken and session.auth_token == nil then
|
||||||
local token = stanza:get_child("token", "http://jitsi.org/jitmeet/auth-token");
|
module:log(
|
||||||
|
"debug",
|
||||||
|
"Skipped room token verification - empty tokens are allowed");
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
-- token not required for admin users
|
-- token not required for admin users
|
||||||
local user_jid = stanza.attr.from;
|
local user_jid = stanza.attr.from;
|
||||||
if is_admin(user_jid) then
|
if is_admin(user_jid) then
|
||||||
log("debug", "Token not required from admin user: %s", user_jid);
|
log("debug", "Token not required from admin user: %s", user_jid);
|
||||||
return nil;
|
return nil;
|
||||||
|
@ -41,21 +52,33 @@ local function handle_pre_create(event)
|
||||||
local room = string.match(stanza.attr.to, "^(%w+)@");
|
local room = string.match(stanza.attr.to, "^(%w+)@");
|
||||||
log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
|
log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
|
||||||
if room == nil then
|
if room == nil then
|
||||||
log("error", "Unable to get name of the MUC room ? to: %s", stanza.attr.to);
|
log("error",
|
||||||
|
"Unable to get name of the MUC room ? to: %s", stanza.attr.to);
|
||||||
return nil;
|
return nil;
|
||||||
end
|
end
|
||||||
|
|
||||||
if token ~= nil then
|
local token = session.auth_token;
|
||||||
token = token[1];
|
local auth_room = session.jitsi_meet_room;
|
||||||
end
|
if room ~= auth_room then
|
||||||
|
log("error", "Token %s not allowed to join: %s",
|
||||||
local result, msg = token_util.verify_password(token, appId, appSecret, room);
|
tostring(token), tostring(auth_room));
|
||||||
if result ~= true then
|
session.send(
|
||||||
log("debug", "Token verification failed: %s", msg);
|
st.error_reply(
|
||||||
origin.send(st.error_reply(stanza, "cancel", "not-allowed", msg));
|
stanza, "cancel", "not-allowed", "Room and token mismatched"));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
log("debug", "allowed: %s to enter/create room: %s", user_jid, room);
|
||||||
end
|
end
|
||||||
|
|
||||||
module:hook("muc-room-pre-create", handle_pre_create);
|
module:hook("muc-room-pre-create", function(event)
|
||||||
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
log("debug", "pre create: %s %s", tostring(origin), tostring(stanza));
|
||||||
|
return verify_user(origin, stanza);
|
||||||
|
end);
|
||||||
|
|
||||||
|
module:hook("muc-occupant-pre-join", function(event)
|
||||||
|
local origin, room, stanza = event.origin, event.room, event.stanza;
|
||||||
|
log("debug", "pre join: %s %s", tostring(room), tostring(stanza));
|
||||||
|
return verify_user(origin, stanza);
|
||||||
|
end);
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,18 @@ local jwt = require "luajwt";
|
||||||
|
|
||||||
local _M = {};
|
local _M = {};
|
||||||
|
|
||||||
local function verify_password_impl(password, appId, appSecret, roomName)
|
local function _get_room_name(token, appSecret)
|
||||||
|
local claims, err = jwt.decode(token, appSecret);
|
||||||
|
if claims ~= nil then
|
||||||
|
return claims["room"];
|
||||||
|
else
|
||||||
|
return nil, err;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local claims, err = jwt.decode(password, appSecret, true);
|
local function _verify_token(token, appId, appSecret, roomName)
|
||||||
|
|
||||||
|
local claims, err = jwt.decode(token, appSecret, true);
|
||||||
if claims == nil then
|
if claims == nil then
|
||||||
return nil, err;
|
return nil, err;
|
||||||
end
|
end
|
||||||
|
@ -27,12 +36,16 @@ local function verify_password_impl(password, appId, appSecret, roomName)
|
||||||
if roomName ~= nil and roomName ~= roomClaim then
|
if roomName ~= nil and roomName ~= roomClaim then
|
||||||
return nil, "Invalid room name('room' claim)";
|
return nil, "Invalid room name('room' claim)";
|
||||||
end
|
end
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
function _M.verify_password(password, appId, appSecret, roomName)
|
function _M.verify_token(token, appId, appSecret, roomName)
|
||||||
return verify_password_impl(password, appId, appSecret, roomName);
|
return _verify_token(token, appId, appSecret, roomName);
|
||||||
end
|
end
|
||||||
|
|
||||||
return _M;
|
function _M.get_room_name(token, appSecret)
|
||||||
|
return _get_room_name(token, appSecret);
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M;
|
Loading…
Reference in New Issue