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:
paweldomas 2015-12-22 12:51:43 -06:00
parent ac8e1ce388
commit 428fa3f16a
8 changed files with 171 additions and 94 deletions

2
debian/control vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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