Преглед изворни кода

Merge pull request #808 from SamWhited/auth_token_asap

Add support for fetching public keys from a server to mod_auth_token
master
Paweł Domas пре 8 година
родитељ
комит
84834dc4e6
2 измењених фајлова са 92 додато и 36 уклоњено
  1. 88
    16
      prosody-plugins/mod_auth_token.lua
  2. 4
    20
      prosody-plugins/token/util.lib.lua

+ 88
- 16
prosody-plugins/mod_auth_token.lua Прегледај датотеку

1
 -- Token authentication
1
 -- Token authentication
2
 -- Copyright (C) 2015 Atlassian
2
 -- Copyright (C) 2015 Atlassian
3
 
3
 
4
+local basexx = require 'basexx';
5
+local have_async, async = pcall(require, "util.async");
6
+local formdecode = require "util.http".formdecode;
4
 local generate_uuid = require "util.uuid".generate;
7
 local generate_uuid = require "util.uuid".generate;
8
+local http = require "net.http";
9
+local json = require 'cjson'
10
+json.encode_empty_table('array')
5
 local new_sasl = require "util.sasl".new;
11
 local new_sasl = require "util.sasl".new;
6
 local sasl = require "util.sasl";
12
 local sasl = require "util.sasl";
7
-local formdecode = require "util.http".formdecode;
13
+local timer = require "util.timer";
8
 local token_util = module:require "token/util";
14
 local token_util = module:require "token/util";
9
 
15
 
10
 -- define auth provider
16
 -- define auth provider
14
 
20
 
15
 local appId = module:get_option_string("app_id");
21
 local appId = module:get_option_string("app_id");
16
 local appSecret = module:get_option_string("app_secret");
22
 local appSecret = module:get_option_string("app_secret");
23
+local asapKeyServer = module:get_option_string("asap_key_server");
17
 local allowEmptyToken = module:get_option_boolean("allow_empty_token");
24
 local allowEmptyToken = module:get_option_boolean("allow_empty_token");
18
 local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
25
 local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
19
 
26
 
27
+-- TODO: Figure out a less arbitrary default cache size.
28
+local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
29
+local cache = require"util.cache".new(cacheSize);
30
+
20
 if allowEmptyToken == true then
31
 if allowEmptyToken == true then
21
 	module:log("warn", "WARNING - empty tokens allowed");
32
 	module:log("warn", "WARNING - empty tokens allowed");
22
 end
33
 end
26
 	return;
37
 	return;
27
 end
38
 end
28
 
39
 
29
-if appSecret == nil then
30
-	module:log("error", "'app_secret' must not be empty");
40
+if appSecret == nil and asapKeyServer == nil then
41
+	module:log("error", "'app_secret' or 'asap_key_server' must be specified");
42
+	return;
43
+end
44
+
45
+if asapKeyServer and not have_async then
46
+	module:log("error", "requires a version of Prosody with util.async");
31
 	return;
47
 	return;
32
 end
48
 end
33
 
49
 
64
 	return nil;
80
 	return nil;
65
 end
81
 end
66
 
82
 
83
+local http_timeout = 30;
84
+local http_headers = {
85
+	["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
86
+};
87
+
88
+function get_public_key(keyId)
89
+	local content = cache:get(keyId);
90
+	if content == nil then
91
+		-- If the key is not found in the cache.
92
+		module:log("debug", "Cache miss for key: "..keyId);
93
+		local code;
94
+		local wait, done = async.waiter();
95
+		local function cb(content_, code_, response_, request_)
96
+			content, code = content_, code_;
97
+			done();
98
+		end
99
+		module:log("debug", "Fetching public key from: "..asapKeyServer..keyId);
100
+		local request = http.request(asapKeyServer..keyId, {
101
+			headers = http_headers or {},
102
+			method = "GET"
103
+		}, cb);
104
+		-- TODO: Is the done() call racey? Can we cancel this if the request
105
+		--       succeedes?
106
+		local function cancel()
107
+			-- TODO: This check is racey. Not likely to be a problem, but we should
108
+			--       still stick a mutex on content / code at some point.
109
+			if code == nil then
110
+				http.destroy_request(request);
111
+				done();
112
+			end
113
+		end
114
+		timer.add_task(http_timeout, cancel);
115
+		wait();
116
+
117
+		if code == 200 or code == 204 then
118
+			return content;
119
+		end
120
+	else
121
+		-- If the key is in the cache, use it.
122
+		module:log("debug", "Cache hit for key: "..keyId);
123
+		return content;
124
+	end
125
+
126
+	return nil;
127
+end
128
+
67
 function provider.get_sasl_handler(session)
129
 function provider.get_sasl_handler(session)
68
 	-- JWT token extracted from BOSH URL
130
 	-- JWT token extracted from BOSH URL
69
 	local token = session.auth_token;
131
 	local token = session.auth_token;
71
 	local function get_username_from_token(self, message)
133
 	local function get_username_from_token(self, message)
72
 
134
 
73
 		if token == nil then
135
 		if token == nil then
74
-			if allowEmptyToken == true then
136
+			if allowEmptyToken then
75
 				return true;
137
 				return true;
76
 			else
138
 			else
77
 				return false, "not-allowed", "token required";
139
 				return false, "not-allowed", "token required";
78
 			end
140
 			end
79
 		end
141
 		end
80
 
142
 
81
-		-- here we check if 'room' claim exists
82
-		local room, roomErr = token_util.get_room_name(token, appSecret);
83
-		if room == nil and disableRoomNameConstraints ~= true then
84
-            if roomErr == nil then
85
-                roomErr = "'room' claim is missing";
86
-            end
87
-			return false, "not-allowed", roomErr;
143
+		local pubKey;
144
+		if asapKeyServer and session.auth_token ~= nil then
145
+			local dotFirst = session.auth_token:find("%.")
146
+			if not dotFirst then return nil, "Invalid token" end
147
+			local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)))
148
+			local kid = header["kid"]
149
+			if kid == nil then
150
+				return false, "not-allowed", "'kid' claim is missing";
151
+			end
152
+			pubKey = get_public_key(kid);
153
+			if pubKey == nil then
154
+				return false, "not-allowed", "could not obtain public key";
155
+			end
88
 		end
156
 		end
89
 
157
 
90
 		-- now verify the whole token
158
 		-- now verify the whole token
91
-		local result, msg
92
-		= token_util.verify_token(token, appId, appSecret, room, disableRoomNameConstraints);
93
-		if result == true then
159
+		local claims, msg;
160
+		if asapKeyServer then
161
+			claims, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints);
162
+		else
163
+			claims, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints);
164
+		end
165
+		if claims ~= nil then
94
 			-- Binds room name to the session which is later checked on MUC join
166
 			-- Binds room name to the session which is later checked on MUC join
95
-			session.jitsi_meet_room = room;
96
-			return true
167
+			session.jitsi_meet_room = claims["room"];
168
+			return true;
97
 		else
169
 		else
98
 			return false, "not-allowed", msg
170
 			return false, "not-allowed", msg
99
 		end
171
 		end

+ 4
- 20
prosody-plugins/token/util.lib.lua Прегледај датотеку

5
 
5
 
6
 local _M = {};
6
 local _M = {};
7
 
7
 
8
-local function _get_room_name(token, appSecret)
9
-	local claims, err = jwt.decode(token, appSecret);
10
-	if claims ~= nil then
11
-		return claims["room"];
12
-	else
13
-		return nil, err;
14
-	end
15
-end
16
-
17
-local function _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
8
+local function _verify_token(token, appId, appSecret, disableRoomNameConstraints)
18
 
9
 
19
 	local claims, err = jwt.decode(token, appSecret, true);
10
 	local claims, err = jwt.decode(token, appSecret, true);
20
 	if claims == nil then
11
 	if claims == nil then
38
 	if roomClaim == nil and disableRoomNameConstraints ~= true then
29
 	if roomClaim == nil and disableRoomNameConstraints ~= true then
39
 		return nil, "'room' claim is missing";
30
 		return nil, "'room' claim is missing";
40
 	end
31
 	end
41
-	if roomName ~= nil and roomName ~= roomClaim and disableRoomNameConstraints ~= true then
42
-		return nil, "Invalid room name('room' claim)";
43
-	end
44
-
45
-	return true;
46
-end
47
 
32
 
48
-function _M.verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
49
-	return _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints);
33
+	return claims;
50
 end
34
 end
51
 
35
 
52
-function _M.get_room_name(token, appSecret)
53
-	return _get_room_name(token, appSecret);
36
+function _M.verify_token(token, appId, appSecret, disableRoomNameConstraints)
37
+	return _verify_token(token, appId, appSecret, disableRoomNameConstraints);
54
 end
38
 end
55
 
39
 
56
 return _M;
40
 return _M;

Loading…
Откажи
Сачувај