Sfoglia il codice sorgente

Moves token related code into util so it can be reused.

master
damencho 8 anni fa
parent
commit
82b27b45fe

+ 8
- 130
resources/prosody-plugins/mod_auth_token.lua Vedi File

@@ -1,58 +1,27 @@
1 1
 -- Token authentication
2 2
 -- Copyright (C) 2015 Atlassian
3 3
 
4
-local basexx = require "basexx";
5
-local have_async, async = pcall(require, "util.async");
6
-local hex = require "util.hex";
7 4
 local formdecode = require "util.http".formdecode;
8 5
 local generate_uuid = require "util.uuid".generate;
9
-local http = require "net.http";
10
-local json = require "cjson";
11 6
 local new_sasl = require "util.sasl".new;
12
-local path = require "util.paths";
13 7
 local sasl = require "util.sasl";
14
-local sha256 = require "util.hashes".sha256;
15
-local timer = require "util.timer";
16
-local token_util = module:require "token/util";
8
+local token_util = module:require "token/util".new(module);
9
+
10
+-- no token configuration
11
+if token_util == nil then
12
+    return;
13
+end
17 14
 
18 15
 -- define auth provider
19 16
 local provider = {};
20 17
 
21 18
 local host = module.host;
22 19
 
23
-local appId = module:get_option_string("app_id");
24
-local appSecret = module:get_option_string("app_secret");
25
-local asapKeyServer = module:get_option_string("asap_key_server");
26
-local allowEmptyToken = module:get_option_boolean("allow_empty_token");
27
-local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
28
-
29
-local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
30
-local cache = require"util.cache".new(cacheSize);
31
-
32
-if allowEmptyToken == true then
33
-	module:log("warn", "WARNING - empty tokens allowed");
34
-end
35
-
36
-if appId == nil then
37
-	module:log("error", "'app_id' must not be empty");
38
-	return;
39
-end
40
-
41
-if appSecret == nil and asapKeyServer == nil then
42
-	module:log("error", "'app_secret' or 'asap_key_server' must be specified");
43
-	return;
44
-end
45
-
46
-if asapKeyServer and not have_async then
47
-	module:log("error", "requires a version of Prosody with util.async");
48
-	return;
49
-end
50
-
51 20
 -- Extract 'token' param from BOSH URL when session is created
52 21
 module:hook("bosh-session", function(event)
53 22
 	local session, request = event.session, event.request;
54 23
 	local query = request.url.query;
24
+
55 25
 	if query ~= nil then
56 26
 		session.auth_token = query and formdecode(query).token or nil;
57 27
 	end
@@ -82,104 +51,12 @@ function provider.delete_user(username)
82 51
 	return nil;
83 52
 end
84 53
 
85
-local http_timeout = 30;
86
-local http_headers = {
87
-	["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
88
-};
89
-
90
-function get_public_key(keyId)
91
-	local content = cache:get(keyId);
92
-	if content == nil then
93
-		-- If the key is not found in the cache.
94
-		module:log("debug", "Cache miss for key: "..keyId);
95
-		local code;
96
-		local wait, done = async.waiter();
97
-		local function cb(content_, code_, response_, request_)
98
-			content, code = content_, code_;
99
-			if code == 200 or code == 204 then
100
-				cache:set(keyId, content);
101
-			end
102
-			done();
103
-		end
104
-		local keyurl = path.join(asapKeyServer, hex.to(sha256(keyId))..'.pem');
105
-		module:log("debug", "Fetching public key from: "..keyurl);
106
-
107
-		-- We hash the key ID to work around some legacy behavior and make
108
-		-- deployment easier. It also helps prevent directory
109
-		-- traversal attacks (although path cleaning could have done this too).
110
-		local request = http.request(keyurl, {
111
-			headers = http_headers or {},
112
-			method = "GET"
113
-		}, cb);
114
-
115
-		-- TODO: Is the done() call racey? Can we cancel this if the request
116
-		--       succeedes?
117
-		local function cancel()
118
-			-- TODO: This check is racey. Not likely to be a problem, but we should
119
-			--       still stick a mutex on content / code at some point.
120
-			if code == nil then
121
-				http.destroy_request(request);
122
-				done();
123
-			end
124
-		end
125
-		timer.add_task(http_timeout, cancel);
126
-		wait();
127
-
128
-		if code == 200 or code == 204 then
129
-			return content;
130
-		end
131
-	else
132
-		-- If the key is in the cache, use it.
133
-		module:log("debug", "Cache hit for key: "..keyId);
134
-		return content;
135
-	end
136
-
137
-	return nil;
138
-end
139
-
140 54
 function provider.get_sasl_handler(session)
141 55
 	-- JWT token extracted from BOSH URL
142 56
 	local token = session.auth_token;
143 57
 
144 58
 	local function get_username_from_token(self, message)
145
-
146
-		if token == nil then
147
-			if allowEmptyToken then
148
-				return true;
149
-			else
150
-				return false, "not-allowed", "token required";
151
-			end
152
-		end
153
-
154
-		local pubKey;
155
-		if asapKeyServer and session.auth_token ~= nil then
156
-			local dotFirst = session.auth_token:find("%.");
157
-			if not dotFirst then return nil, "Invalid token" end
158
-			local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
159
-			local kid = header["kid"];
160
-			if kid == nil then
161
-				return false, "not-allowed", "'kid' claim is missing";
162
-			end
163
-			pubKey = get_public_key(kid);
164
-			if pubKey == nil then
165
-				return false, "not-allowed", "could not obtain public key";
166
-			end
167
-		end
168
-
169
-		-- now verify the whole token
170
-		local claims, msg;
171
-		if asapKeyServer then
172
-			claims, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints);
173
-		else
174
-			claims, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints);
175
-		end
176
-		if claims ~= nil then
177
-			-- Binds room name to the session which is later checked on MUC join
178
-			session.jitsi_meet_room = claims["room"];
179
-			return true;
180
-		else
181
-			return false, "not-allowed", msg;
182
-		end
59
+        return token_util:process_and_verify_token(session, token);
183 60
 	end
184 61
 
185 62
 	return new_sasl(host, { anonymous = get_username_from_token });

+ 20
- 32
resources/prosody-plugins/mod_token_verification.lua Vedi File

@@ -21,15 +21,17 @@ if parentCtx == nil then
21 21
 	return;
22 22
 end
23 23
 
24
-local appId = parentCtx:get_option_string("app_id");
25
-local appSecret = parentCtx:get_option_string("app_secret");
26
-local allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token");
27
-local disableRoomNameConstraints = parentCtx:get_option_boolean("disable_room_name_constraints")
24
+local token_util = module:require "token/util".new(parentCtx);
25
+
26
+-- no token configuration
27
+if token_util == nil then
28
+    return;
29
+end
28 30
 
29 31
 log("debug",
30 32
 	"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
31
-	tostring(host), tostring(appId), tostring(appSecret),
32
-	tostring(allowEmptyToken));
33
+	tostring(host), tostring(token_util.appId), tostring(token_util.appSecret),
34
+	tostring(token_util.allowEmptyToken));
33 35
 
34 36
 local function verify_user(session, stanza)
35 37
 	log("debug", "Session token: %s, session room: %s",
@@ -43,32 +45,18 @@ local function verify_user(session, stanza)
43 45
 		return nil;
44 46
 	end
45 47
 
46
-	if allowEmptyToken and session.auth_token == nil then
47
-		module:log(
48
-			"debug",
49
-			"Skipped room token verification - empty tokens are allowed");
50
-		return nil;
51
-	end
52
-
53
-	local room = string.match(stanza.attr.to, "^(%w+)@");
54
-	log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
55
-	if room == nil then
56
-		log("error",
57
-			"Unable to get name of the MUC room ? to: %s", stanza.attr.to);
58
-		return nil;
59
-	end
60
-
61
-	local token = session.auth_token;
62
-	local auth_room = session.jitsi_meet_room;
63
-	if disableRoomNameConstraints ~= true and room ~= string.lower(auth_room) then
64
-		log("error", "Token %s not allowed to join: %s",
65
-			tostring(token), tostring(auth_room));
66
-		session.send(
67
-			st.error_reply(
68
-				stanza, "cancel", "not-allowed", "Room and token mismatched"));
69
-		return true;
70
-	end
71
-	log("debug", "allowed: %s to enter/create room: %s", user_jid, room);
48
+    log("debug",
49
+        "Will verify token for user: %s, room: %s ", user_jid, stanza.attr.to);
50
+    if not token_util:verify_room(session, stanza.attr.to) then
51
+        log("error", "Token %s not allowed to join: %s",
52
+            tostring(session.auth_token), tostring(session.jitsi_meet_room));
53
+        session.send(
54
+            st.error_reply(
55
+                stanza, "cancel", "not-allowed", "Room and token mismatched"));
56
+        return false; -- we need to just return non nil
57
+    end
58
+	log("debug",
59
+        "allowed: %s to enter/create room: %s", user_jid, stanza.attr.to);
72 60
 end
73 61
 
74 62
 module:hook("muc-room-pre-create", function(event)

+ 209
- 25
resources/prosody-plugins/token/util.lib.lua Vedi File

@@ -1,40 +1,224 @@
1 1
 -- Token authentication
2 2
 -- Copyright (C) 2015 Atlassian
3 3
 
4
+local basexx = require "basexx";
5
+local have_async, async = pcall(require, "util.async");
6
+local hex = require "util.hex";
4 7
 local jwt = require "luajwtjitsi";
8
+local http = require "net.http";
9
+local json = require "cjson";
10
+local path = require "util.paths";
11
+local sha256 = require "util.hashes".sha256;
12
+local timer = require "util.timer";
5 13
 
6
-local _M = {};
14
+local http_timeout = 30;
15
+local http_headers = {
16
+    ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
17
+};
7 18
 
8
-local function _verify_token(token, appId, appSecret, disableRoomNameConstraints)
19
+-- TODO: Figure out a less arbitrary default cache size.
20
+local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
21
+local cache = require"util.cache".new(cacheSize);
9 22
 
10
-	local claims, err = jwt.decode(token, appSecret, true);
11
-	if claims == nil then
12
-		return nil, err;
13
-	end
23
+local Util = {}
24
+Util.__index = Util
14 25
 
15
-	local alg = claims["alg"];
16
-	if alg ~= nil and (alg == "none" or alg == "") then
17
-		return nil, "'alg' claim must not be empty";
18
-	end
26
+--- Constructs util class for token verifications.
27
+-- Constructor that uses the passed module to extract all the
28
+-- needed configurations.
29
+-- If confuguration is missing returns nil
30
+-- @param module the module in which options to check for configs.
31
+-- @return the new instance or nil
32
+function Util.new(module)
33
+    local self = setmetatable({}, Util)
19 34
 
20
-	local issClaim = claims["iss"];
21
-	if issClaim == nil then
22
-		return nil, "'iss' claim is missing";
23
-	end
24
-	if issClaim ~= appId then
25
-		return nil, "Invalid application ID('iss' claim)";
26
-	end
35
+    self.appId = module:get_option_string("app_id");
36
+    self.appSecret = module:get_option_string("app_secret");
37
+    self.asapKeyServer = module:get_option_string("asap_key_server");
38
+    self.allowEmptyToken = module:get_option_boolean("allow_empty_token");
39
+    self.disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
27 40
 
28
-	local roomClaim = claims["room"];
29
-	if roomClaim == nil and disableRoomNameConstraints ~= true then
30
-		return nil, "'room' claim is missing";
31
-	end
41
+    if self.allowEmptyToken == true then
42
+        module:log("warn", "WARNING - empty tokens allowed");
43
+    end
32 44
 
33
-	return claims;
45
+    if self.appId == nil then
46
+        module:log("error", "'app_id' must not be empty");
47
+        return nil;
48
+    end
49
+
50
+    if self.appSecret == nil and self.asapKeyServer == nil then
51
+        module:log("error", "'app_secret' or 'asap_key_server' must be specified");
52
+        return nil;
53
+    end
54
+
55
+    if self.asapKeyServer and not have_async then
56
+        module:log("error", "requires a version of Prosody with util.async");
57
+        return nil;
58
+    end
59
+
60
+    return self
61
+end
62
+
63
+--- Returns the public key by keyID
64
+-- @param keyId the key ID to request
65
+-- @return the public key (the content of requested resource) or nil
66
+function Util:get_public_key(keyId)
67
+    local content = cache:get(keyId);
68
+    if content == nil then
69
+        -- If the key is not found in the cache.
70
+        module:log("debug", "Cache miss for key: "..keyId);
71
+        local code;
72
+        local wait, done = async.waiter();
73
+        local function cb(content_, code_, response_, request_)
74
+            content, code = content_, code_;
75
+            if code == 200 or code == 204 then
76
+                cache:set(keyId, content);
77
+            end
78
+            done();
79
+        end
80
+        local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
81
+        module:log("debug", "Fetching public key from: "..keyurl);
82
+
83
+        -- We hash the key ID to work around some legacy behavior and make
84
+        -- deployment easier. It also helps prevent directory
85
+        -- traversal attacks (although path cleaning could have done this too).
86
+        local request = http.request(keyurl, {
87
+            headers = http_headers or {},
88
+            method = "GET"
89
+        }, cb);
90
+
91
+        -- TODO: Is the done() call racey? Can we cancel this if the request
92
+        --       succeedes?
93
+        local function cancel()
94
+            -- TODO: This check is racey. Not likely to be a problem, but we should
95
+            --       still stick a mutex on content / code at some point.
96
+            if code == nil then
97
+                http.destroy_request(request);
98
+                done();
99
+            end
100
+        end
101
+        timer.add_task(http_timeout, cancel);
102
+        wait();
103
+
104
+        if code == 200 or code == 204 then
105
+            return content;
106
+        end
107
+    else
108
+        -- If the key is in the cache, use it.
109
+        module:log("debug", "Cache hit for key: "..keyId);
110
+        return content;
111
+    end
112
+
113
+    return nil;
34 114
 end
35 115
 
36
-function _M.verify_token(token, appId, appSecret, disableRoomNameConstraints)
37
-	return _verify_token(token, appId, appSecret, disableRoomNameConstraints);
116
+--- Verifies token
117
+-- @param token the token to verify
118
+-- @return nil and error or the extracted claims from the token
119
+function Util:verify_token(token)
120
+    local claims, err = jwt.decode(token, self.appSecret, true);
121
+    if claims == nil then
122
+        return nil, err;
123
+    end
124
+
125
+    local alg = claims["alg"];
126
+    if alg ~= nil and (alg == "none" or alg == "") then
127
+        return nil, "'alg' claim must not be empty";
128
+    end
129
+
130
+    local issClaim = claims["iss"];
131
+    if issClaim == nil then
132
+        return nil, "'iss' claim is missing";
133
+    end
134
+    if issClaim ~= self.appId then
135
+        return nil, "Invalid application ID('iss' claim)";
136
+    end
137
+
138
+    local roomClaim = claims["room"];
139
+    if roomClaim == nil and self.disableRoomNameConstraints ~= true then
140
+        return nil, "'room' claim is missing";
141
+    end
142
+
143
+    return claims;
144
+end
145
+
146
+--- Verifies token and process needed values to be stored in the session.
147
+-- Stores in session the following values:
148
+-- session.jitsi_meet_room - the room name value from the token
149
+-- @param session the current session
150
+-- @param token the token to verify
151
+-- @return false and error
152
+function Util:process_and_verify_token(session, token)
153
+
154
+    if token == nil then
155
+        if self.allowEmptyToken then
156
+            return true;
157
+        else
158
+            return false, "not-allowed", "token required";
159
+        end
160
+    end
161
+
162
+    local pubKey;
163
+    if self.asapKeyServer and session.auth_token ~= nil then
164
+        local dotFirst = session.auth_token:find("%.");
165
+        if not dotFirst then return nil, "Invalid token" end
166
+        local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
167
+        local kid = header["kid"];
168
+        if kid == nil then
169
+            return false, "not-allowed", "'kid' claim is missing";
170
+        end
171
+        pubKey = self:get_public_key(kid);
172
+        if pubKey == nil then
173
+            return false, "not-allowed", "could not obtain public key";
174
+        end
175
+    end
176
+
177
+    -- now verify the whole token
178
+    local claims, msg;
179
+    if self.asapKeyServer then
180
+        claims, msg = self:verify_token(token);
181
+    else
182
+        claims, msg = self:verify_token(token);
183
+    end
184
+    if claims ~= nil then
185
+        -- Binds room name to the session which is later checked on MUC join
186
+        session.jitsi_meet_room = claims["room"];
187
+        return true;
188
+    else
189
+        return false, "not-allowed", msg;
190
+    end
191
+end
192
+
193
+--- Verifies room name if necesarry.
194
+-- Checks configs and if necessary checks the room name extracted from
195
+-- room_address against the one saved in the session when token was verified
196
+-- @param session the current session
197
+-- @param room_address the whole room address as received
198
+-- @return returns true in case room was verified or there is no need to verify
199
+--         it and returns false in case verification was processed
200
+--         and was not successful
201
+function Util:verify_room(session, room_address)
202
+    if self.allowEmptyToken and session.auth_token == nil then
203
+        module:log(
204
+            "debug",
205
+            "Skipped room token verification - empty tokens are allowed");
206
+        return true;
207
+    end
208
+
209
+    local room = string.match(room_address, "^(%w+)@");
210
+    if room == nil then
211
+        log("error",
212
+            "Unable to get name of the MUC room ? to: %s", room_address);
213
+        return true;
214
+    end
215
+
216
+    local auth_room = session.jitsi_meet_room;
217
+    if self.disableRoomNameConstraints ~= true and room ~= string.lower(auth_room) then
218
+        return false;
219
+    end
220
+
221
+    return true;
38 222
 end
39 223
 
40
-return _M;
224
+return Util;

Loading…
Annulla
Salva