From 7f2fa9597cd43e2b54ead91a65cfe4a9e22c2505 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Tue, 23 Aug 2016 14:42:49 -0500 Subject: [PATCH 1/8] Add basic ASAP support to mod_auth_token See: http://s2sauth.bitbucket.org/ --- prosody-plugins/mod_auth_token.lua | 83 ++++++++++++++++++++++++------ prosody-plugins/token/util.lib.lua | 22 ++------ 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 28300aa4b..3f0dbdcfd 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -1,10 +1,17 @@ -- Token authentication -- Copyright (C) 2015 Atlassian -local generate_uuid = require "util.uuid".generate; -local new_sasl = require "util.sasl".new; -local sasl = require "util.sasl"; +local basexx = require 'basexx' +local have_async, async = pcall(require, "util.async"); local formdecode = require "util.http".formdecode; +local generate_uuid = require "util.uuid".generate; +local http = require "net.http"; +local json = require 'cjson' +json.encode_empty_table('array') +local new_sasl = require "util.sasl".new; +local path = require "util.paths"; +local sasl = require "util.sasl"; +local timer = require "util.timer"; local token_util = module:require "token/util"; -- define auth provider @@ -14,6 +21,7 @@ local host = module.host; local appId = module:get_option_string("app_id"); local appSecret = module:get_option_string("app_secret"); +local asapKeyServer = module:get_option_string("asap_key_server"); local allowEmptyToken = module:get_option_boolean("allow_empty_token"); local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints"); @@ -26,8 +34,13 @@ if appId == nil then return; end -if appSecret == nil then - module:log("error", "'app_secret' must not be empty"); +if appSecret == nil and asapKeyServer == nil then + module:log("error", "'app_secret' or 'asap_key_server' must be specified"); + return; +end + +if asapKeyServer and not have_async then + module:log("error", "requires a version of Prosody with util.async"); return; end @@ -64,6 +77,34 @@ function provider.delete_user(username) return nil; end +local http_timeout = 30; +local http_headers = { + ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")" +}; + +-- TODO: This *needs* to be memoized before going to prod. +function get_public_key(keyId) + local wait, done = async.waiter(); + local content, code; --, request, response; + local function cb(content_, code_, response_, request_) + content, code = content_, code_; + done(); + end + local request = http.request(path.join(asapKeyServer, keyId), { + headers = http_headers or {}, + method = "GET" + }, cb); + -- TODO: Is the done() call racey? + timer.add_task(http_timeout, function() http.destroy_request(request); done(); end); + wait(); + + if code == 200 or code == 204 then + return content; + end + + return nil +end + function provider.get_sasl_handler(session) -- JWT token extracted from BOSH URL local token = session.auth_token; @@ -71,25 +112,35 @@ function provider.get_sasl_handler(session) local function get_username_from_token(self, message) if token == nil then - if allowEmptyToken == true then - return true; + if allowEmptyToken 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 and disableRoomNameConstraints ~= true then - if roomErr == nil then - roomErr = "'room' claim is missing"; - end - return false, "not-allowed", roomErr; + local pubKey; + if asapKeyServer and session.auth_token ~= nil then + local dotFirst = session.auth_token:find("%.") + if not dotFirst then return nil, "Invalid token" end + local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1))) + local kid = header["kid"] + if kid == nil then + return false, "not-allowed", "'kid' claim is missing"; + end + pubKey = get_public_key(kid); + if pubKey == nil then + return false, "not-allowed", "could not obtain public key"; + end end -- now verify the whole token - local result, msg - = token_util.verify_token(token, appId, appSecret, room, disableRoomNameConstraints); + local result, msg; + if asapKeyServer then + result, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints); + else + result, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints); + end if result == true then -- Binds room name to the session which is later checked on MUC join session.jitsi_meet_room = room; diff --git a/prosody-plugins/token/util.lib.lua b/prosody-plugins/token/util.lib.lua index 641ce1d78..695f499b1 100644 --- a/prosody-plugins/token/util.lib.lua +++ b/prosody-plugins/token/util.lib.lua @@ -5,16 +5,7 @@ local jwt = require "luajwtjitsi"; local _M = {}; -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, disableRoomNameConstraints) +local function _verify_token(token, appId, appSecret, disableRoomNameConstraints) local claims, err = jwt.decode(token, appSecret, true); if claims == nil then @@ -38,19 +29,12 @@ local function _verify_token(token, appId, appSecret, roomName, disableRoomNameC if roomClaim == nil and disableRoomNameConstraints ~= true then return nil, "'room' claim is missing"; end - if roomName ~= nil and roomName ~= roomClaim and disableRoomNameConstraints ~= true then - return nil, "Invalid room name('room' claim)"; - end return true; end -function _M.verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints) - return _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints); -end - -function _M.get_room_name(token, appSecret) - return _get_room_name(token, appSecret); +function _M.verify_token(token, appId, appSecret, disableRoomNameConstraints) + return _verify_token(token, appId, appSecret, disableRoomNameConstraints); end return _M; From feb1d9d8e102fd962a7a95d2bc12494296576116 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 24 Aug 2016 14:24:40 -0500 Subject: [PATCH 2/8] Add an LRU cache to mod_auth_token --- prosody-plugins/mod_auth_token.lua | 44 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 3f0dbdcfd..2de3616f1 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -1,7 +1,7 @@ -- Token authentication -- Copyright (C) 2015 Atlassian -local basexx = require 'basexx' +local basexx = require 'basexx'; local have_async, async = pcall(require, "util.async"); local formdecode = require "util.http".formdecode; local generate_uuid = require "util.uuid".generate; @@ -25,6 +25,10 @@ local asapKeyServer = module:get_option_string("asap_key_server"); local allowEmptyToken = module:get_option_boolean("allow_empty_token"); local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints"); +-- TODO: Figure out a less arbitrary default cache size. +local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128); +local cache = require"util.cache".new(cacheSize); + if allowEmptyToken == true then module:log("warn", "WARNING - empty tokens allowed"); end @@ -82,23 +86,31 @@ local http_headers = { ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")" }; --- TODO: This *needs* to be memoized before going to prod. function get_public_key(keyId) - local wait, done = async.waiter(); - local content, code; --, request, response; - local function cb(content_, code_, response_, request_) - content, code = content_, code_; - done(); - end - local request = http.request(path.join(asapKeyServer, keyId), { - headers = http_headers or {}, - method = "GET" - }, cb); - -- TODO: Is the done() call racey? - timer.add_task(http_timeout, function() http.destroy_request(request); done(); end); - wait(); + local content = cache:get(keyId); + if content == nil then + -- If the key is not found in the cache. + module:log("debug", "Cache miss for key: "..keyId); + local code; + local wait, done = async.waiter(); + local function cb(content_, code_, response_, request_) + content, code = content_, code_; + done(); + end + local request = http.request(path.join(asapKeyServer, keyId), { + headers = http_headers or {}, + method = "GET" + }, cb); + -- TODO: Is the done() call racey? + timer.add_task(http_timeout, function() http.destroy_request(request); done(); end); + wait(); - if code == 200 or code == 204 then + if code == 200 or code == 204 then + module:log("debug", "Cache hit for key: "..keyId); + return content; + end + else + -- If the key is in the cache, use it. return content; end From f2e369cfc01fd59acf4816d968025a7b9f091155 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 09:48:02 -0500 Subject: [PATCH 3/8] mod_auth_token: Remove broken path.join --- prosody-plugins/mod_auth_token.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 2de3616f1..827bf0d9c 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -9,7 +9,6 @@ local http = require "net.http"; local json = require 'cjson' json.encode_empty_table('array') local new_sasl = require "util.sasl".new; -local path = require "util.paths"; local sasl = require "util.sasl"; local timer = require "util.timer"; local token_util = module:require "token/util"; @@ -97,11 +96,13 @@ function get_public_key(keyId) content, code = content_, code_; done(); end - local request = http.request(path.join(asapKeyServer, keyId), { + module:log("debug", "Fetching public key from: "..asapKeyServer..keyId); + local request = http.request(asapKeyServer..keyId, { headers = http_headers or {}, method = "GET" }, cb); - -- TODO: Is the done() call racey? + -- TODO: Is the done() call racey? Can we cancel this if the request + -- succeedes? timer.add_task(http_timeout, function() http.destroy_request(request); done(); end); wait(); From c951f7f3e9368d791af59fb1108dfb6d1dc62b15 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 14:11:50 -0500 Subject: [PATCH 4/8] Add missing semicolons --- prosody-plugins/mod_auth_token.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 827bf0d9c..9ff42934d 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -115,7 +115,7 @@ function get_public_key(keyId) return content; end - return nil + return nil; end function provider.get_sasl_handler(session) @@ -126,7 +126,7 @@ function provider.get_sasl_handler(session) if token == nil then if allowEmptyToken then - return true + return true; else return false, "not-allowed", "token required"; end @@ -157,7 +157,7 @@ function provider.get_sasl_handler(session) if result == true then -- Binds room name to the session which is later checked on MUC join session.jitsi_meet_room = room; - return true + return true; else return false, "not-allowed", msg end From 4fc86175e122ca1e12412a5889f7084585280d01 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 14:41:06 -0500 Subject: [PATCH 5/8] mod_auth_token: Set room name on session --- prosody-plugins/mod_auth_token.lua | 10 +++++----- prosody-plugins/token/util.lib.lua | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 9ff42934d..8a8b6a6ff 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -148,15 +148,15 @@ function provider.get_sasl_handler(session) end -- now verify the whole token - local result, msg; + local claims, msg; if asapKeyServer then - result, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints); + claims, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints); else - result, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints); + claims, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints); end - if result == true then + if claims ~= true then -- Binds room name to the session which is later checked on MUC join - session.jitsi_meet_room = room; + session.jitsi_meet_room = claims["room"]; return true; else return false, "not-allowed", msg diff --git a/prosody-plugins/token/util.lib.lua b/prosody-plugins/token/util.lib.lua index 695f499b1..7ef885a3a 100644 --- a/prosody-plugins/token/util.lib.lua +++ b/prosody-plugins/token/util.lib.lua @@ -30,7 +30,7 @@ local function _verify_token(token, appId, appSecret, disableRoomNameConstraints return nil, "'room' claim is missing"; end - return true; + return claims; end function _M.verify_token(token, appId, appSecret, disableRoomNameConstraints) From 7fb18d1cb3465fcd03c2fc54adea8cf72232d6fb Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 14:47:34 -0500 Subject: [PATCH 6/8] Fix broken claims comparison --- prosody-plugins/mod_auth_token.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index 8a8b6a6ff..f080d506b 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -154,7 +154,7 @@ function provider.get_sasl_handler(session) else claims, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints); end - if claims ~= true then + if claims ~= nil then -- Binds room name to the session which is later checked on MUC join session.jitsi_meet_room = claims["room"]; return true; From 3793119209327361e9a84991a9027af1a7e3400f Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 16:03:08 -0500 Subject: [PATCH 7/8] mod_auth_token: Fix cache hit log line --- prosody-plugins/mod_auth_token.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index f080d506b..c1ed87ac6 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -107,11 +107,11 @@ function get_public_key(keyId) wait(); if code == 200 or code == 204 then - module:log("debug", "Cache hit for key: "..keyId); return content; end else -- If the key is in the cache, use it. + module:log("debug", "Cache hit for key: "..keyId); return content; end From c17576a931e04c4762d86d6330861e2675e3521c Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 26 Aug 2016 16:17:19 -0500 Subject: [PATCH 8/8] mod_auth_token: Don't timeout finished requests --- prosody-plugins/mod_auth_token.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prosody-plugins/mod_auth_token.lua b/prosody-plugins/mod_auth_token.lua index c1ed87ac6..c7a187fbf 100644 --- a/prosody-plugins/mod_auth_token.lua +++ b/prosody-plugins/mod_auth_token.lua @@ -103,7 +103,15 @@ function get_public_key(keyId) }, cb); -- TODO: Is the done() call racey? Can we cancel this if the request -- succeedes? - timer.add_task(http_timeout, function() http.destroy_request(request); done(); end); + local function cancel() + -- TODO: This check is racey. Not likely to be a problem, but we should + -- still stick a mutex on content / code at some point. + if code == nil then + http.destroy_request(request); + done(); + end + end + timer.add_task(http_timeout, cancel); wait(); if code == 200 or code == 204 then