"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-6312/resources/prosody-plugins/token/util.lib.lua" (4 Jul 2022, 14473 Bytes) of package /linux/misc/jitsi-meet-6312.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 -- Token authentication
2 -- Copyright (C) 2015 Atlassian
3
4 local basexx = require "basexx";
5 local have_async, async = pcall(require, "util.async");
6 local hex = require "util.hex";
7 local jwt = module:require "luajwtjitsi";
8 local jid = require "util.jid";
9 local json_safe = require "cjson.safe";
10 local path = require "util.paths";
11 local sha256 = require "util.hashes".sha256;
12 local main_util = module:require "util";
13 local http_get_with_retry = main_util.http_get_with_retry;
14 local extract_subdomain = main_util.extract_subdomain;
15
16 local nr_retries = 3;
17
18 -- TODO: Figure out a less arbitrary default cache size.
19 local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
20
21 local Util = {}
22 Util.__index = Util
23
24 --- Constructs util class for token verifications.
25 -- Constructor that uses the passed module to extract all the
26 -- needed configurations.
27 -- If confuguration is missing returns nil
28 -- @param module the module in which options to check for configs.
29 -- @return the new instance or nil
30 function Util.new(module)
31 local self = setmetatable({}, Util)
32
33 self.appId = module:get_option_string("app_id");
34 self.appSecret = module:get_option_string("app_secret");
35 self.asapKeyServer = module:get_option_string("asap_key_server");
36 self.signatureAlgorithm = module:get_option_string("signature_algorithm");
37 self.allowEmptyToken = module:get_option_boolean("allow_empty_token");
38
39 self.cache = require"util.cache".new(cacheSize);
40
41 --[[
42 Multidomain can be supported in some deployments. In these deployments
43 there is a virtual conference muc, which address contains the subdomain
44 to use. Those deployments are accessible
45 by URL https://domain/subdomain.
46 Then the address of the room will be:
47 roomName@conference.subdomain.domain. This is like a virtual address
48 where there is only one muc configured by default with address:
49 conference.domain and the actual presentation of the room in that muc
50 component is [subdomain]roomName@conference.domain.
51 These setups relay on configuration 'muc_domain_base' which holds
52 the main domain and we use it to substract subdomains from the
53 virtual addresses.
54 The following confgurations are for multidomain setups and domain name
55 verification:
56 --]]
57
58 -- optional parameter for custom muc component prefix,
59 -- defaults to "conference"
60 self.muc_domain_prefix = module:get_option_string(
61 "muc_mapper_domain_prefix", "conference");
62 -- domain base, which is the main domain used in the deployment,
63 -- the main VirtualHost for the deployment
64 self.muc_domain_base = module:get_option_string("muc_mapper_domain_base");
65 -- The "real" MUC domain that we are proxying to
66 if self.muc_domain_base then
67 self.muc_domain = module:get_option_string(
68 "muc_mapper_domain",
69 self.muc_domain_prefix.."."..self.muc_domain_base);
70 end
71 -- whether domain name verification is enabled, by default it is disabled
72 self.enableDomainVerification = module:get_option_boolean(
73 "enable_domain_verification", false);
74
75 if self.allowEmptyToken == true then
76 module:log("warn", "WARNING - empty tokens allowed");
77 end
78
79 if self.appId == nil then
80 module:log("error", "'app_id' must not be empty");
81 return nil;
82 end
83
84 if self.appSecret == nil and self.asapKeyServer == nil then
85 module:log("error", "'app_secret' or 'asap_key_server' must be specified");
86 return nil;
87 end
88
89 -- Set defaults for signature algorithm
90 if self.signatureAlgorithm == nil then
91 if self.asapKeyServer ~= nil then
92 self.signatureAlgorithm = "RS256"
93 elseif self.appSecret ~= nil then
94 self.signatureAlgorithm = "HS256"
95 end
96 end
97
98 --array of accepted issuers: by default only includes our appId
99 self.acceptedIssuers = module:get_option_array('asap_accepted_issuers',{self.appId})
100
101 --array of accepted audiences: by default only includes our appId
102 self.acceptedAudiences = module:get_option_array('asap_accepted_audiences',{'*'})
103
104 self.requireRoomClaim = module:get_option_boolean('asap_require_room_claim', true);
105
106 if self.asapKeyServer and not have_async then
107 module:log("error", "requires a version of Prosody with util.async");
108 return nil;
109 end
110
111 return self
112 end
113
114 function Util:set_asap_key_server(asapKeyServer)
115 self.asapKeyServer = asapKeyServer;
116 end
117
118 function Util:set_asap_accepted_issuers(acceptedIssuers)
119 self.acceptedIssuers = acceptedIssuers;
120 end
121
122 function Util:set_asap_accepted_audiences(acceptedAudiences)
123 self.acceptedAudiences = acceptedAudiences;
124 end
125
126 function Util:set_asap_require_room_claim(checkRoom)
127 self.requireRoomClaim = checkRoom;
128 end
129
130 function Util:clear_asap_cache()
131 self.cache = require"util.cache".new(cacheSize);
132 end
133
134 --- Returns the public key by keyID
135 -- @param keyId the key ID to request
136 -- @return the public key (the content of requested resource) or nil
137 function Util:get_public_key(keyId)
138 local content = self.cache:get(keyId);
139 if content == nil then
140 -- If the key is not found in the cache.
141 module:log("debug", "Cache miss for key: %s", keyId);
142 local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
143 module:log("debug", "Fetching public key from: %s", keyurl);
144 content = http_get_with_retry(keyurl, nr_retries);
145 if content ~= nil then
146 self.cache:set(keyId, content);
147 end
148 return content;
149 else
150 -- If the key is in the cache, use it.
151 module:log("debug", "Cache hit for key: %s", keyId);
152 return content;
153 end
154 end
155
156 --- Verifies token and process needed values to be stored in the session.
157 -- Token is obtained from session.auth_token.
158 -- Stores in session the following values:
159 -- session.jitsi_meet_room - the room name value from the token
160 -- session.jitsi_meet_domain - the domain name value from the token
161 -- session.jitsi_meet_context_user - the user details from the token
162 -- session.jitsi_meet_context_group - the group value from the token
163 -- session.jitsi_meet_context_features - the features value from the token
164 -- @param session the current session
165 -- @param acceptedIssuers optional list of accepted issuers to check
166 -- @return false and error
167 function Util:process_and_verify_token(session, acceptedIssuers)
168 if not acceptedIssuers then
169 acceptedIssuers = self.acceptedIssuers;
170 end
171
172 if session.auth_token == nil then
173 if self.allowEmptyToken then
174 return true;
175 else
176 return false, "not-allowed", "token required";
177 end
178 end
179
180 local key;
181 if session.public_key then
182 -- We're using an public key stored in the session
183 module:log("debug","Public key was found on the session");
184 key = session.public_key;
185 elseif self.asapKeyServer and session.auth_token ~= nil then
186 -- We're fetching an public key from an ASAP server
187 local dotFirst = session.auth_token:find("%.");
188 if not dotFirst then return false, "not-allowed", "Invalid token" end
189 local header, err = json_safe.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
190 if err then
191 return false, "not-allowed", "bad token format";
192 end
193 local kid = header["kid"];
194 if kid == nil then
195 return false, "not-allowed", "'kid' claim is missing";
196 end
197 local alg = header["alg"];
198 if alg == nil then
199 return false, "not-allowed", "'alg' claim is missing";
200 end
201 if alg.sub(alg,1,2) ~= "RS" then
202 return false, "not-allowed", "'kid' claim only support with RS family";
203 end
204 key = self:get_public_key(kid);
205 if key == nil then
206 return false, "not-allowed", "could not obtain public key";
207 end
208 elseif self.appSecret ~= nil then
209 -- We're using a symmetric secret
210 key = self.appSecret
211 end
212
213 if key == nil then
214 return false, "not-allowed", "signature verification key is missing";
215 end
216
217 -- now verify the whole token
218 local claims, msg = jwt.verify(
219 session.auth_token,
220 self.signatureAlgorithm,
221 key,
222 acceptedIssuers,
223 self.acceptedAudiences
224 )
225 if claims ~= nil then
226 if self.requireRoomClaim then
227 local roomClaim = claims["room"];
228 if roomClaim == nil then
229 return false, "'room' claim is missing";
230 end
231 end
232
233 -- Binds room name to the session which is later checked on MUC join
234 session.jitsi_meet_room = claims["room"];
235 -- Binds domain name to the session
236 session.jitsi_meet_domain = claims["sub"];
237
238 -- Binds the user details to the session if available
239 if claims["context"] ~= nil then
240 if claims["context"]["user"] ~= nil then
241 session.jitsi_meet_context_user = claims["context"]["user"];
242 end
243
244 if claims["context"]["group"] ~= nil then
245 -- Binds any group details to the session
246 session.jitsi_meet_context_group = claims["context"]["group"];
247 end
248
249 if claims["context"]["features"] ~= nil then
250 -- Binds any features details to the session
251 session.jitsi_meet_context_features = claims["context"]["features"];
252 end
253 if claims["context"]["room"] ~= nil then
254 session.jitsi_meet_context_room = claims["context"]["room"]
255 end
256 end
257 return true;
258 else
259 return false, "not-allowed", msg;
260 end
261 end
262
263 --- Verifies room name and domain if necesarry.
264 -- Checks configs and if necessary checks the room name extracted from
265 -- room_address against the one saved in the session when token was verified.
266 -- Also verifies domain name from token against the domain in the room_address,
267 -- if enableDomainVerification is enabled.
268 -- @param session the current session
269 -- @param room_address the whole room address as received
270 -- @return returns true in case room was verified or there is no need to verify
271 -- it and returns false in case verification was processed
272 -- and was not successful
273 function Util:verify_room(session, room_address)
274 if self.allowEmptyToken and session.auth_token == nil then
275 module:log(
276 "debug",
277 "Skipped room token verification - empty tokens are allowed");
278 return true;
279 end
280
281 -- extract room name using all chars, except the not allowed ones
282 local room,_,_ = jid.split(room_address);
283 if room == nil then
284 log("error",
285 "Unable to get name of the MUC room ? to: %s", room_address);
286 return true;
287 end
288
289 local auth_room = session.jitsi_meet_room;
290 if auth_room then
291 auth_room = string.lower(auth_room);
292 end
293 if not self.enableDomainVerification then
294 -- if auth_room is missing, this means user is anonymous (no token for
295 -- its domain) we let it through, jicofo is verifying creation domain
296 if auth_room and room ~= auth_room and auth_room ~= '*' then
297 return false;
298 end
299
300 return true;
301 end
302
303 local room_address_to_verify = jid.bare(room_address);
304 local room_node = jid.node(room_address);
305 -- parses bare room address, for multidomain expected format is:
306 -- [subdomain]roomName@conference.domain
307 local target_subdomain, target_room = extract_subdomain(room_node);
308
309 -- if we have '*' as room name in token, this means all rooms are allowed
310 -- so we will use the actual name of the room when constructing strings
311 -- to verify subdomains and domains to simplify checks
312 local room_to_check;
313 if auth_room == '*' then
314 -- authorized for accessing any room assign to room_to_check the actual
315 -- room name
316 if target_room ~= nil then
317 -- we are in multidomain mode and we were able to extract room name
318 room_to_check = target_room;
319 else
320 -- no target_room, room_address_to_verify does not contain subdomain
321 -- so we get just the node which is the room name
322 room_to_check = room_node;
323 end
324 else
325 -- no wildcard, so check room against authorized room from the token
326 if session.jitsi_meet_context_room and (session.jitsi_meet_context_room["regex"] == true or session.jitsi_meet_context_room["regex"] == "true") then
327 if target_room ~= nil then
328 -- room with subdomain
329 room_to_check = target_room:match(auth_room);
330 else
331 room_to_check = room_node:match(auth_room);
332 end
333 else
334 -- not a regex
335 room_to_check = auth_room;
336 end
337 module:log("debug", "room to check: %s", room_to_check)
338 if not room_to_check then
339 return false
340 end
341 end
342
343 local auth_domain = string.lower(session.jitsi_meet_domain);
344 local subdomain_to_check;
345 if target_subdomain then
346 if auth_domain == '*' then
347 -- check for wildcard in JWT claim, allow access if found
348 subdomain_to_check = target_subdomain;
349 else
350 -- no wildcard in JWT claim, so check subdomain against sub in token
351 subdomain_to_check = auth_domain;
352 end
353 -- from this point we depend on muc_domain_base,
354 -- deny access if option is missing
355 if not self.muc_domain_base then
356 module:log("warn", "No 'muc_domain_base' option set, denying access!");
357 return false;
358 end
359
360 return room_address_to_verify == jid.join(
361 "["..subdomain_to_check.."]"..room_to_check, self.muc_domain);
362 else
363 if auth_domain == '*' then
364 -- check for wildcard in JWT claim, allow access if found
365 subdomain_to_check = self.muc_domain;
366 else
367 -- no wildcard in JWT claim, so check subdomain against sub in token
368 subdomain_to_check = self.muc_domain_prefix.."."..auth_domain;
369 end
370 -- we do not have a domain part (multidomain is not enabled)
371 -- verify with info from the token
372 return room_address_to_verify == jid.join(room_to_check, subdomain_to_check);
373 end
374 end
375
376 return Util;