From 428fa3f16a02b153884176cd8264c3705426d16c Mon Sep 17 00:00:00 2001 From: paweldomas Date: Tue, 22 Dec 2015 12:51:43 -0600 Subject: [PATCH] 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. --- debian/control | 2 +- debian/jitsi-meet-tokens.postinst | 24 ++-- debian/jitsi-meet-tokens.postrm | 21 +--- .../prosody.cfg.lua-jvb.example | 7 +- prosody-plugins/mod_auth_token.lua | 115 ++++++++++++------ prosody-plugins/mod_bosh.lua.patch | 12 ++ prosody-plugins/mod_token_verification.lua | 59 ++++++--- prosody-plugins/token/util.lib.lua | 25 +++- 8 files changed, 171 insertions(+), 94 deletions(-) create mode 100644 prosody-plugins/mod_bosh.lua.patch diff --git a/debian/control b/debian/control index 47d274ce98..3d4368f9a2 100644 --- a/debian/control +++ b/debian/control @@ -35,6 +35,6 @@ Description: Prosody configuration for Jitsi Meet Package: jitsi-meet-tokens 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 diff --git a/debian/jitsi-meet-tokens.postinst b/debian/jitsi-meet-tokens.postinst index ac194ff4b0..d298316ab2 100644 --- a/debian/jitsi-meet-tokens.postinst +++ b/debian/jitsi-meet-tokens.postinst @@ -62,25 +62,23 @@ case "$1" in 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/ --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_secret=example_app_secret/ app_secret=$APP_SECRET/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 - # Configure Jicofo properties - JICOFO_CONFIG="/etc/jitsi/jicofo/sip-communicator.properties" - if ! grep -q "org.jitsi.jicofo.auth.jwt.APP_ID" $JICOFO_CONFIG && \ - ! 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 + # Install luajwt + if ! luarocks install luajwt; then + echo "Failed to install luajwt - try installing it manually" fi if [ -x "/etc/init.d/prosody" ]; then - invoke-rc.d prosody reload + invoke-rc.d prosody restart 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 echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet" fi diff --git a/debian/jitsi-meet-tokens.postrm b/debian/jitsi-meet-tokens.postrm index 44582f20ad..b57fe1ee75 100644 --- a/debian/jitsi-meet-tokens.postrm +++ b/debian/jitsi-meet-tokens.postrm @@ -39,27 +39,12 @@ case "$1" in # 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/ 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_secret=$APP_SECRET/ --app_secret=example_app_secret/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 - 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 - invoke-rc.d prosody reload + invoke-rc.d prosody restart fi fi diff --git a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example index dd4302c63f..36efd73f5e 100644 --- a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example +++ b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example @@ -4,11 +4,10 @@ VirtualHost "jitmeet.example.com" -- enabled = false -- Remove this line to enable this host 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" - --allow_unencrypted_plain_auth = true; - --app_id=example_app_id - --app_secret=example_app_secret + --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 diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index a8a899daca..1b24e94c25 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -1,41 +1,46 @@ -- Token authentication -- Copyright (C) 2015 Atlassian -local usermanager = require "core.usermanager"; +local generate_uuid = require "util.uuid".generate; local new_sasl = require "util.sasl".new; - -local log = module._log; -local host = module.host; - +local sasl = require "util.sasl"; +local formdecode = require "util.http".formdecode; 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 host = module.host; local appId = module:get_option_string("app_id"); local appSecret = module:get_option_string("app_secret"); +local allowEmptyToken = module:get_option_boolean("allow_empty_token"); -function provider.test_password(username, password) - local result, msg = token_util.verify_password(password, appId, appSecret, nil); - if result == true then - return true; - else - log("error", "Token auth failed for user %s, reason: %s",username, msg); - return nil, msg; +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) + return nil, "Password based auth not supported"; end function provider.get_password(username) @@ -50,10 +55,6 @@ 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 @@ -62,13 +63,59 @@ 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; +function provider.get_sasl_handler(session) + -- JWT token extracted from BOSH URL + local token = session.auth_token; + + 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 + + -- 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 - }; - return new_sasl(host, testpass_authentication_profile); + end + + return new_sasl(host, { anonymous = get_username_from_token }); end 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); + diff --git a/prosody-plugins/mod_bosh.lua.patch b/prosody-plugins/mod_bosh.lua.patch new file mode 100644 index 0000000000..928261661a --- /dev/null +++ b/prosody-plugins/mod_bosh.lua.patch @@ -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; diff --git a/prosody-plugins/mod_token_verification.lua b/prosody-plugins/mod_token_verification.lua index 79933040f5..597664ebd9 100644 --- a/prosody-plugins/mod_token_verification.lua +++ b/prosody-plugins/mod_token_verification.lua @@ -4,7 +4,6 @@ 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; @@ -16,23 +15,35 @@ 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)); + 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 allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token"); -log("debug", "%s - starting MUC token verifier app_id: %s app_secret: %s", - tostring(host), tostring(appId), tostring(appSecret)); +log("debug", + "%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; - local token = stanza:get_child("token", "http://jitsi.org/jitmeet/auth-token"); + if allowEmptyToken and session.auth_token == nil then + module:log( + "debug", + "Skipped room token verification - empty tokens are allowed"); + return nil; + end -- token not required for admin users - local user_jid = stanza.attr.from; + 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; @@ -41,21 +52,33 @@ local function handle_pre_create(event) local room = string.match(stanza.attr.to, "^(%w+)@"); log("debug", "Will verify token for user: %s, room: %s ", user_jid, room); 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; end - if token ~= nil then - token = token[1]; - end - - local result, msg = token_util.verify_password(token, appId, appSecret, room); - if result ~= true then - log("debug", "Token verification failed: %s", msg); - origin.send(st.error_reply(stanza, "cancel", "not-allowed", msg)); + local token = session.auth_token; + local auth_room = session.jitsi_meet_room; + if room ~= auth_room then + log("error", "Token %s not allowed to join: %s", + tostring(token), tostring(auth_room)); + session.send( + st.error_reply( + stanza, "cancel", "not-allowed", "Room and token mismatched")); return true; end + log("debug", "allowed: %s to enter/create room: %s", user_jid, room); 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); diff --git a/prosody-plugins/token/util.lib.lua b/prosody-plugins/token/util.lib.lua index 6781f07b94..c281c3d953 100644 --- a/prosody-plugins/token/util.lib.lua +++ b/prosody-plugins/token/util.lib.lua @@ -5,9 +5,18 @@ local jwt = require "luajwt"; 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 function _verify_token(token, appId, appSecret, roomName) - local claims, err = jwt.decode(password, appSecret, true); + local claims, err = jwt.decode(token, appSecret, true); if claims == nil then return nil, err; end @@ -27,12 +36,16 @@ local function verify_password_impl(password, appId, appSecret, roomName) if roomName ~= nil and roomName ~= roomClaim then return nil, "Invalid room name('room' claim)"; end - + return true; end -function _M.verify_password(password, appId, appSecret, roomName) - return verify_password_impl(password, appId, appSecret, roomName); +function _M.verify_token(token, appId, appSecret, roomName) + return _verify_token(token, appId, appSecret, roomName); +end + +function _M.get_room_name(token, appSecret) + return _get_room_name(token, appSecret); end -return _M; +return _M; \ No newline at end of file