Bladeren bron

Merge pull request #7518 from jitsi/aaronkvanmeerten/jibri-queue-component-modules

FEAT: prosody jibri queue component module
master
Aaron van Meerten 4 jaren geleden
bovenliggende
commit
c3329ec931
No account linked to committer's email address

+ 5
- 0
resources/prosody-plugins/mod_jibri_queue.lua Bestand weergeven

@@ -0,0 +1,5 @@
1
+local jibri_queue_component
2
+    = module:get_option_string(
3
+        "jibri_queue_component", "jibriqueue"..module.host);
4
+
5
+module:add_identity("component", "jibri-queue", jibri_queue_component);

+ 559
- 0
resources/prosody-plugins/mod_jibri_queue_component.lua Bestand weergeven

@@ -0,0 +1,559 @@
1
+local st = require "util.stanza";
2
+local jid = require "util.jid";
3
+local http = require "net.http";
4
+local json = require "cjson";
5
+local inspect = require('inspect');
6
+local socket = require "socket";
7
+local uuid_gen = require "util.uuid".generate;
8
+local jwt = require "luajwtjitsi";
9
+local it = require "util.iterators";
10
+local neturl = require "net.url";
11
+local parse = neturl.parseQuery;
12
+
13
+local get_room_from_jid = module:require "util".get_room_from_jid;
14
+local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
15
+local is_healthcheck_room = module:require "util".is_healthcheck_room;
16
+local room_jid_split_subdomain = module:require "util".room_jid_split_subdomain;
17
+local internal_room_jid_match_rewrite = module:require "util".internal_room_jid_match_rewrite;
18
+local async_handler_wrapper = module:require "util".async_handler_wrapper;
19
+
20
+-- this basically strips the domain from the conference.domain address
21
+local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")();
22
+if parentHostName == nil then
23
+    log("error", "Failed to start - unable to get parent hostname");
24
+    return;
25
+end
26
+
27
+local parentCtx = module:context(parentHostName);
28
+if parentCtx == nil then
29
+    log("error",
30
+        "Failed to start - unable to get parent context for host: %s",
31
+        tostring(parentHostName));
32
+    return;
33
+end
34
+local token_util = module:require "token/util".new(parentCtx);
35
+
36
+local ASAPKeyServer;
37
+local ASAPKeyPath;
38
+local ASAPKeyId;
39
+local ASAPIssuer;
40
+local ASAPAudience;
41
+local ASAPAcceptedIssuers;
42
+local ASAPAcceptedAudiences;
43
+local ASAPTTL;
44
+local ASAPTTL_THRESHOLD;
45
+local ASAPKey;
46
+local JibriRegion;
47
+local disableTokenVerification;
48
+local muc_component_host;
49
+local external_api_url;
50
+local jwtKeyCacheSize;
51
+local jwtKeyCache;
52
+
53
+local function load_config()
54
+    ASAPKeyServer = module:get_option_string("asap_key_server");
55
+
56
+    if ASAPKeyServer then
57
+        module:log("debug", "ASAP Public Key URL %s", ASAPKeyServer);
58
+        token_util:set_asap_key_server(ASAPKeyServer);
59
+    end
60
+
61
+    ASAPKeyPath
62
+        = module:get_option_string("asap_key_path", '/etc/prosody/certs/asap.key');
63
+
64
+    ASAPKeyId
65
+        = module:get_option_string("asap_key_id", 'jitsi');        
66
+
67
+    ASAPIssuer
68
+        = module:get_option_string("asap_issuer", 'jitsi');
69
+
70
+    ASAPAudience
71
+        = module:get_option_string("asap_audience", 'jibri-queue');
72
+
73
+    ASAPAcceptedIssuers
74
+        = module:get_option_array('asap_accepted_issuers',{'jibri-queue'});
75
+    module:log("debug", "ASAP Accepted Issuers %s", ASAPAcceptedIssuers);
76
+    token_util:set_asap_accepted_issuers(ASAPAcceptedIssuers);
77
+
78
+    ASAPAcceptedAudiences 
79
+        = module:get_option_array('asap_accepted_audiences',{'*'});
80
+    module:log("debug", "ASAP Accepted Audiences %s", ASAPAcceptedAudiences);
81
+    token_util:set_asap_accepted_audiences(ASAPAcceptedAudiences);
82
+
83
+    -- do not require room to be set on tokens for jibri queue
84
+    token_util:set_asap_require_room_claim(false);
85
+
86
+    ASAPTTL
87
+        = module:get_option_number("asap_ttl", 3600);
88
+ 
89
+    ASAPTTL_THRESHOLD
90
+        = module:get_option_number("asap_ttl_threshold", 600);
91
+
92
+    queueServiceURL
93
+        = module:get_option_string("jibri_queue_url");
94
+
95
+    JibriRegion
96
+        = module:get_option_string("jibri_region", 'default');
97
+
98
+    -- option to enable/disable token verifications
99
+    disableTokenVerification
100
+        = module:get_option_boolean("disable_jibri_queue_token_verification", false);
101
+
102
+    muc_component_host 
103
+        = module:get_option_string("muc_component");
104
+
105
+    external_api_url = module:get_option_string("external_api_url",tostring(parentHostName));
106
+    module:log("debug", "External advertised API URL", external_api_url);
107
+
108
+
109
+    -- TODO: Figure out a less arbitrary default cache size.
110
+    jwtKeyCacheSize 
111
+        = module:get_option_number("jwt_pubkey_cache_size", 128);
112
+    jwtKeyCache = require"util.cache".new(jwtKeyCacheSize);
113
+
114
+    if queueServiceURL == nil then
115
+        log("error", "No jibri_queue_url specified. No service to contact!");
116
+        return;
117
+    end
118
+
119
+    if muc_component_host == nil then
120
+        log("error", "No muc_component specified. No muc to operate on for jibri queue!");
121
+        return;
122
+    end
123
+
124
+    -- Read ASAP key once on module startup
125
+    local f = io.open(ASAPKeyPath, "r");
126
+    if f then
127
+        ASAPKey = f:read("*all");
128
+        f:close();
129
+        if not ASAPKey then
130
+            module:log("warn", "No ASAP Key read from %s, disabling jibri queue component plugin", ASAPKeyPath);
131
+            return
132
+        end
133
+    else
134
+        module:log("warn", "Error reading ASAP Key %s, disabling jibri queue component plugin", ASAPKeyPath);
135
+        return
136
+    end
137
+
138
+    return true;
139
+end
140
+
141
+local function reload_config()
142
+    module:log("info", "Reloading configuration for jibri queue component");
143
+    local config_success = load_config();
144
+
145
+    -- clear ASAP public key cache on config reload
146
+    token_util:clear_asap_cache();
147
+
148
+    if not config_success then
149
+        log("error", "Unsuccessful reconfiguration, jibri queue component may misbehave");
150
+    end
151
+end
152
+
153
+local config_success = load_config();
154
+
155
+if not config_success then
156
+    log("error", "Unsuccessful configuration step, jibri queue component disabled")
157
+    return;
158
+end
159
+
160
+
161
+local http_headers = {
162
+    ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")",
163
+    ["Content-Type"] = "application/json"
164
+};
165
+
166
+-- we use async to detect Prosody 0.10 and earlier
167
+local have_async = pcall(require, "util.async");
168
+if not have_async then
169
+    module:log("warn", "conference duration will not work with Prosody version 0.10 or less.");
170
+    return;
171
+end
172
+
173
+
174
+log("info", "Starting jibri queue handling for %s", muc_component_host);
175
+
176
+local function round(num, numDecimalPlaces)
177
+    local mult = 10^(numDecimalPlaces or 0)
178
+    return math.floor(num * mult + 0.5) / mult
179
+end
180
+      
181
+local function generateToken(audience)
182
+    audience = audience or ASAPAudience
183
+    local t = os.time()
184
+    local err
185
+    local exp_key = 'asap_exp.'..audience
186
+    local token_key = 'asap_token.'..audience
187
+    local exp = jwtKeyCache:get(exp_key)
188
+    local token = jwtKeyCache:get(token_key)
189
+
190
+    --if we find a token and it isn't too far from expiry, then use it
191
+    if token ~= nil and exp ~= nil then
192
+        exp = tonumber(exp)
193
+        if (exp - t) > ASAPTTL_THRESHOLD then
194
+            return token
195
+        end
196
+    end
197
+
198
+    --expiry is the current time plus TTL
199
+    exp = t + ASAPTTL
200
+    local payload = {
201
+        iss = ASAPIssuer,
202
+        aud = audience,
203
+        nbf = t,
204
+        exp = exp,
205
+    }
206
+
207
+    -- encode
208
+    local alg = "RS256"
209
+    token, err = jwt.encode(payload, ASAPKey, alg, {kid = ASAPKeyId})
210
+    if not err then
211
+        token = 'Bearer '..token
212
+        jwtKeyCache:set(exp_key,exp)
213
+        jwtKeyCache:set(token_key,token)
214
+        return token
215
+    else
216
+        return ''
217
+    end
218
+end
219
+
220
+local function sendIq(participant,action,requestId,time,position,token)
221
+    local iqId = uuid_gen();
222
+    local from = module:get_host();
223
+    local outStanza = st.iq({type = 'set', from = from, to = participant, id = iqId}):tag("jibri-queue", 
224
+       { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId, action = action });
225
+
226
+    if token then
227
+        outStanza:tag("token"):text(token):up()
228
+    end
229
+    if time then
230
+        outStanza:tag("time"):text(tostring(time)):up()
231
+    end
232
+    if position then
233
+        outStanza:tag("position"):text(tostring(position)):up()
234
+    end
235
+
236
+    module:send(outStanza);
237
+end
238
+
239
+local function cb(content_, code_, response_, request_)
240
+    if code_ == 200 or code_ == 204 then
241
+        module:log("debug", "URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s",
242
+                code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
243
+    else
244
+        module:log("warn", "URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s",
245
+                code_, content_, inspect(request_), inspect(response_));
246
+    end
247
+end
248
+
249
+local function sendEvent(type,room_address,participant,requestId,replyIq,replyError)
250
+    local event_ts = round(socket.gettime()*1000);
251
+    local node, host, resource, target_subdomain = room_jid_split_subdomain(room_address);
252
+    local room_param = '';
253
+    if target_subdomain then
254
+        room_param = target_subdomain..'/'..node;
255
+    else
256
+        room_param = node;
257
+    end
258
+
259
+    local out_event = {
260
+        ["conference"] = room_address,
261
+        ["roomParam"] = room_param,
262
+        ["eventType"] = type,
263
+        ["participant"] = participant,
264
+        ["externalApiUrl"] = external_api_url.."/jibriqueue/update",
265
+        ["requestId"] = requestId,
266
+        ["region"] = JibriRegion,
267
+    }
268
+    module:log("debug","Sending event %s",inspect(out_event));
269
+
270
+    local headers = http_headers or {}
271
+    headers['Authorization'] = generateToken()
272
+
273
+    module:log("debug","Sending headers %s",inspect(headers));
274
+    local requestURL = queueServiceURL.."/job/recording"
275
+    if type=="LeaveQueue" then
276
+        requestURL = requestURL .."/cancel"
277
+    end
278
+    local request = http.request(requestURL, {
279
+        headers = headers,
280
+        method = "POST",
281
+        body = json.encode(out_event)
282
+    }, function (content_, code_, response_, request_)
283
+        if code_ == 200 or code_ == 204 then
284
+            module:log("debug", "URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s",
285
+                    code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
286
+            if (replyIq) then
287
+                module:log("debug", "sending reply IQ %s",inspect(replyIq));
288
+                module:send(replyIq);
289
+            end
290
+        else
291
+            module:log("warn", "URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s",
292
+                    code_, content_, inspect(request_), inspect(response_));
293
+            if (replyError) then
294
+                module:log("warn", "sending reply error IQ %s",inspect(replyError));
295
+                module:send(replyError);
296
+            end
297
+        end
298
+    end);
299
+end
300
+
301
+function clearRoomQueueByOccupant(room, occupant)
302
+    room.jibriQueue[occupant.jid] = nil;
303
+end
304
+
305
+function addRoomQueueByOccupant(room, occupant, requestId)
306
+    room.jibriQueue[occupant.jid] = requestId;
307
+end
308
+
309
+-- receives iq from client currently connected to the room
310
+function on_iq(event)
311
+    local requestId;
312
+    -- Check the type of the incoming stanza to avoid loops:
313
+    if event.stanza.attr.type == "error" then
314
+        return; -- We do not want to reply to these, so leave.
315
+    end
316
+    if event.stanza.attr.to == module:get_host() then
317
+        if event.stanza.attr.type == "set" then
318
+            local reply = st.reply(event.stanza);
319
+            local replyError = st.error_reply(event.stanza,'cancel','internal-server-error',"Queue Server Error");
320
+
321
+            local jibriQueue
322
+                = event.stanza:get_child('jibri-queue', 'http://jitsi.org/protocol/jibri-queue');
323
+            if jibriQueue then
324
+                module:log("debug", "Received Jibri Queue Request: %s ",inspect(jibriQueue));
325
+
326
+                local roomAddress = jibriQueue.attr.room;
327
+                local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
328
+
329
+                if not room then
330
+                    module:log("warn", "No room found %s", roomAddress);
331
+                    return false;
332
+                end
333
+
334
+                local from = event.stanza.attr.from;
335
+
336
+                local occupant = room:get_occupant_by_real_jid(from);
337
+                if not occupant then
338
+                    module:log("warn", "No occupant %s found for %s", from, roomAddress);
339
+                    return false;
340
+                end
341
+
342
+                local action = jibriQueue.attr.action;
343
+                if action == 'join' then
344
+                    -- join action, so send event out
345
+                    requestId = uuid_gen();
346
+                    module:log("debug","Received join queue request for jid %s occupant %s requestId %s",roomAddress,occupant.jid,requestId);
347
+
348
+                    -- now handle new jibri queue message
349
+                    addRoomQueueByOccupant(room, occupant, requestId);
350
+                    reply:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
351
+                    replyError:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
352
+
353
+                    module:log("debug","Sending JoinQueue event for jid %s occupant %s reply %s",roomAddress,occupant.jid,inspect(reply));
354
+                    sendEvent('JoinQueue',roomAddress,occupant.jid,requestId,reply,replyError);
355
+                end
356
+                if action == 'leave' then
357
+                    requestId = jibriQueue.attr.requestId;
358
+                    module:log("debug","Received leave queue request for jid %s occupant %s requestId %s",roomAddress,occupant.jid,requestId);
359
+
360
+                    -- TODO: check that requestId is the same as cached value
361
+                    clearRoomQueueByOccupant(room, occupant);
362
+                    reply:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
363
+                    replyError:add_child(st.stanza("jibri-queue", { xmlns = 'http://jitsi.org/protocol/jibri-queue', requestId = requestId})):up()
364
+
365
+                    module:log("debug","Sending LeaveQueue event for jid %s occupant %s reply %s",roomAddress,occupant.jid,inspect(reply));
366
+                    sendEvent('LeaveQueue',roomAddress,occupant.jid,requestId,reply,replyError);
367
+                end
368
+            else
369
+                module:log("warn","Jibri Queue Stanza missing child %s",inspect(event.stanza))
370
+            end
371
+        end
372
+    end
373
+    return true
374
+end
375
+
376
+-- create recorder queue cache for the room
377
+function room_created(event)
378
+    local room = event.room;
379
+
380
+    if is_healthcheck_room(room.jid) then
381
+        return;
382
+    end
383
+
384
+    room.jibriQueue = {};
385
+end
386
+
387
+-- Conference ended, clear all queue cache jids
388
+function room_destroyed(event)
389
+    local room = event.room;
390
+
391
+    if is_healthcheck_room(room.jid) then
392
+        return;
393
+    end
394
+    for jid, x in pairs(room.jibriQueue) do
395
+        if x then
396
+            sendEvent('LeaveQueue',internal_room_jid_match_rewrite(room.jid),jid,x);
397
+        end
398
+    end
399
+end
400
+
401
+-- Occupant left remove it from the queue if it joined the queue
402
+function occupant_leaving(event)
403
+    local room = event.room;
404
+
405
+    if is_healthcheck_room(room.jid) then
406
+        return;
407
+    end
408
+
409
+    local occupant = event.occupant;
410
+    local requestId = room.jibriQueue[occupant.jid];
411
+    -- check if user has cached queue request
412
+    if requestId then
413
+        -- remove occupant from queue cache, signal backend
414
+        room.jibriQueue[occupant.jid] = nil;
415
+        sendEvent('LeaveQueue',internal_room_jid_match_rewrite(room.jid),occupant.jid,requestId);
416
+    end
417
+end
418
+
419
+module:hook("iq/host", on_iq);
420
+
421
+-- executed on every host added internally in prosody, including components
422
+function process_host(host)
423
+    if host == muc_component_host then -- the conference muc component
424
+        module:log("debug","Hook to muc events on %s", host);
425
+
426
+        local muc_module = module:context(host);
427
+        muc_module:hook("muc-room-created", room_created, -1);
428
+        -- muc_module:hook("muc-occupant-joined", occupant_joined, -1);
429
+        muc_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
430
+        muc_module:hook("muc-room-destroyed", room_destroyed, -1);
431
+    end
432
+end
433
+
434
+if prosody.hosts[muc_component_host] == nil then
435
+    module:log("debug","No muc component found, will listen for it: %s", muc_component_host)
436
+
437
+    -- when a host or component is added
438
+    prosody.events.add_handler("host-activated", process_host);
439
+else
440
+    process_host(muc_component_host);
441
+end
442
+
443
+module:log("info", "Loading jibri_queue_component");
444
+
445
+--- Verifies room name, domain name with the values in the token
446
+-- @param token the token we received
447
+-- @param room_name the room name
448
+-- @param group name of the group (optional)
449
+-- @param session the session to use for storing token specific fields
450
+-- @return true if values are ok or false otherwise
451
+function verify_token(token, room_jid, session)
452
+    if disableTokenVerification then
453
+        return true;
454
+    end
455
+
456
+    -- if not disableTokenVerification and we do not have token
457
+    -- stop here, cause the main virtual host can have guest access enabled
458
+    -- (allowEmptyToken = true) and we will allow access to rooms info without
459
+    -- a token
460
+    if token == nil then
461
+        log("warn", "no token provided");
462
+        return false;
463
+    end
464
+
465
+    session.auth_token = token;
466
+    local verified, reason, message = token_util:process_and_verify_token(session);
467
+    if not verified then
468
+        log("warn", "not a valid token %s: %s", tostring(reason), tostring(message));
469
+        log("debug", "invalid token %s", token);
470
+        return false;
471
+    end
472
+
473
+    return true;
474
+end
475
+
476
+--- Handles request for updating jibri queue status
477
+-- @param event the http event, holds the request query
478
+-- @return GET response, containing a json with response details
479
+function handle_update_jibri_queue(event)
480
+    local body = json.decode(event.request.body);
481
+
482
+    module:log("debug","Update Jibri Queue Event Received: %s",inspect(body));
483
+
484
+    local token = event.request.headers["authorization"];
485
+    if not token then
486
+        token = ''
487
+    else
488
+        local prefixStart, prefixEnd = token:find("Bearer ");
489
+        if prefixStart ~= 1 then
490
+            module:log("error", "REST event: Invalid authorization header format. The header must start with the string 'Bearer '");
491
+            return { status_code = 403; };
492
+        end
493
+        token = token:sub(prefixEnd + 1);
494
+    end
495
+
496
+    local user_jid = body["participant"];
497
+    local roomAddress = body["conference"];
498
+    local userJWT = body["token"];
499
+    local action = body["action"];
500
+    local time = body["time"];
501
+    local position = body["position"];
502
+    local requestId = body["requestId"];
503
+
504
+    if not action then
505
+        if userJWT then
506
+            action = 'token';
507
+        else
508
+            action = 'info';
509
+        end
510
+    end
511
+
512
+    local room_jid = room_jid_match_rewrite(roomAddress);
513
+
514
+    if not verify_token(token, room_jid, {}) then
515
+        log("error", "REST event: Invalid token for room %s to route action %s for requestId %s", roomAddress, action, requestId);
516
+        return { status_code = 403; };
517
+    end
518
+
519
+    local room = get_room_from_jid(room_jid);
520
+    if (not room) then
521
+        log("error", "REST event: no room found %s to route action %s for requestId %s", roomAddress, action, requestId);
522
+        return { status_code = 404; };
523
+    end
524
+
525
+    local occupant = room:get_occupant_by_real_jid(user_jid);
526
+    if not occupant then
527
+        log("warn", "REST event: No occupant %s found for %s to route action %s for requestId %s", user_jid, roomAddress, action, requestId);
528
+        return { status_code = 404; };
529
+    end
530
+
531
+    if not room.jibriQueue[occupant.jid] then
532
+        log("warn", "REST event: No queue request found for occupant %s in conference %s to route action %s for requestId %s",occupant.jid,room.jid, action, requestId)
533
+        return { status_code = 404; };
534
+    end
535
+
536
+    if not requestId then
537
+        requestId = room.jibriQueue[occupant.jid];
538
+    end
539
+
540
+    if action == 'token' and userJWT then
541
+        log("debug", "REST event: Token received for occupant %s in conference %s requestId %s, clearing room queue");
542
+        clearRoomQueueByOccupant(room, occupant);
543
+    end
544
+
545
+    log("debug", "REST event: Sending update for occupant %s in conference %s to route action %s for requestId %s",occupant.jid,room.jid, action, requestId);
546
+    sendIq(occupant.jid,action,requestId,time,position,userJWT);
547
+    return { status_code = 200; };
548
+end
549
+
550
+module:depends("http");
551
+module:provides("http", {
552
+    default_path = "/";
553
+    name = "jibriqueue";
554
+    route = {
555
+        ["POST /jibriqueue/update"] = function (event) return async_handler_wrapper(event,handle_update_jibri_queue) end;
556
+    };
557
+});
558
+
559
+module:hook_global('config-reloaded', reload_config);

+ 56
- 21
resources/prosody-plugins/token/util.lib.lua Bestand weergeven

@@ -93,6 +93,8 @@ function Util.new(module)
93 93
     --array of accepted audiences: by default only includes our appId
94 94
     self.acceptedAudiences = module:get_option_array('asap_accepted_audiences',{'*'})
95 95
 
96
+    self.requireRoomClaim = module:get_option_boolean('asap_require_room_claim', true);
97
+
96 98
     if self.asapKeyServer and not have_async then
97 99
         module:log("error", "requires a version of Prosody with util.async");
98 100
         return nil;
@@ -102,7 +104,23 @@ function Util.new(module)
102 104
 end
103 105
 
104 106
 function Util:set_asap_key_server(asapKeyServer)
105
-    self.asapKeyServer = asapKeyServer
107
+    self.asapKeyServer = asapKeyServer;
108
+end
109
+
110
+function Util:set_asap_accepted_issuers(acceptedIssuers)
111
+    self.acceptedIssuers = acceptedIssuers;
112
+end
113
+
114
+function Util:set_asap_accepted_audiences(acceptedAudiences)
115
+    self.acceptedAudiences = acceptedAudiences;
116
+end
117
+
118
+function Util:set_asap_require_room_claim(checkRoom)
119
+    self.requireRoomClaim = checkRoom;
120
+end
121
+
122
+function Util:clear_asap_cache()
123
+    self.cache = require"util.cache".new(cacheSize);
106 124
 end
107 125
 
108 126
 --- Returns the public key by keyID
@@ -114,27 +132,25 @@ function Util:get_public_key(keyId)
114 132
         -- If the key is not found in the cache.
115 133
         module:log("debug", "Cache miss for key: "..keyId);
116 134
         local code;
135
+        local timeout_occurred;
117 136
         local wait, done = async.waiter();
137
+
138
+        local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
139
+
118 140
         local function cb(content_, code_, response_, request_)
119
-            content, code = content_, code_;
120
-            if code == 200 or code == 204 then
121
-                self.cache:set(keyId, content);
141
+            if timeout_occurred == nil then
142
+                content, code = content_, code_;
143
+                if code == 200 or code == 204 then
144
+                    self.cache:set(keyId, content);
145
+                else
146
+                    module:log("warn", "Error on public key request: Code %s, Content %s",
147
+                    code_, content_);
148
+                end
149
+                done();
122 150
             else
123
-                module:log("warn", "Error on public key request: Code %s, Content %s",
124
-                code_, content_);
151
+                module:log("warn", "public key reply delivered after timeout from: %s",keyurl);
125 152
             end
126
-            done();
127 153
         end
128
-        local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
129
-        module:log("debug", "Fetching public key from: "..keyurl);
130
-
131
-        -- We hash the key ID to work around some legacy behavior and make
132
-        -- deployment easier. It also helps prevent directory
133
-        -- traversal attacks (although path cleaning could have done this too).
134
-        local request = http.request(keyurl, {
135
-            headers = http_headers or {},
136
-            method = "GET"
137
-        }, cb);
138 154
 
139 155
         -- TODO: Is the done() call racey? Can we cancel this if the request
140 156
         --       succeedes?
@@ -142,13 +158,25 @@ function Util:get_public_key(keyId)
142 158
             -- TODO: This check is racey. Not likely to be a problem, but we should
143 159
             --       still stick a mutex on content / code at some point.
144 160
             if code == nil then
145
-                -- no longer present in prosody 0.11, so check before calling
161
+                timeout_occurred = true;
162
+                module:log("warn", "Timeout %s seconds fetching public key from: %s",http_timeout,keyurl);
146 163
                 if http.destroy_request ~= nil then
147 164
                     http.destroy_request(request);
148 165
                 end
149 166
                 done();
150 167
             end
151 168
         end
169
+
170
+        module:log("debug", "Fetching public key from: "..keyurl);
171
+
172
+        -- We hash the key ID to work around some legacy behavior and make
173
+        -- deployment easier. It also helps prevent directory
174
+        -- traversal attacks (although path cleaning could have done this too).
175
+        local request = http.request(keyurl, {
176
+            headers = http_headers or {},
177
+            method = "GET"
178
+        }, cb);
179
+
152 180
         timer.add_task(http_timeout, cancel);
153 181
         wait();
154 182
 
@@ -169,6 +197,10 @@ end
169 197
 -- @param 'acceptedIssuers' list of issuers to check
170 198
 -- @return nil and error string or true for accepted claim
171 199
 function Util:verify_issuer(issClaim, acceptedIssuers)
200
+    if not acceptedIssuers then
201
+        acceptedIssuers = self.acceptedIssuers
202
+    end
203
+    module:log("debug","verify_issuer claim: %s against accepted: %s",issClaim, acceptedIssuers);
172 204
     for i, iss in ipairs(acceptedIssuers) do
173 205
         if issClaim == iss then
174 206
             --claim matches an accepted issuer so return success
@@ -183,6 +215,7 @@ end
183 215
 -- @param 'aud' claim from the token to verify
184 216
 -- @return nil and error string or true for accepted claim
185 217
 function Util:verify_audience(audClaim)
218
+    module:log("debug","verify_audience claim: %s against accepted: %s",audClaim, self.acceptedAudiences);
186 219
     for i, aud in ipairs(self.acceptedAudiences) do
187 220
         if aud == '*' then
188 221
             --* indicates to accept any audience in the claims so return success
@@ -223,9 +256,11 @@ function Util:verify_token(token, secret, acceptedIssuers)
223 256
         return nil, issCheckErr;
224 257
     end
225 258
 
226
-    local roomClaim = claims["room"];
227
-    if roomClaim == nil then
228
-        return nil, "'room' claim is missing";
259
+    if self.requireRoomClaim then
260
+        local roomClaim = claims["room"];
261
+        if roomClaim == nil then
262
+            return nil, "'room' claim is missing";
263
+        end
229 264
     end
230 265
 
231 266
     local audClaim = claims["aud"];

+ 27
- 2
resources/prosody-plugins/util.lib.lua Bestand weergeven

@@ -18,14 +18,20 @@ local escaped_muc_domain_prefix = muc_domain_prefix:gsub("%p", "%%%1");
18 18
 local target_subdomain_pattern
19 19
     = "^"..escaped_muc_domain_prefix..".([^%.]+)%."..escaped_muc_domain_base;
20 20
 
21
+-- Utility function to split room JID to include room name and subdomain
22
+local function room_jid_split_subdomain(room_jid)
23
+    local node, host, resource = jid.split(room_jid);
24
+    local target_subdomain = host and host:match(target_subdomain_pattern);
25
+    return node, host, resource, target_subdomain
26
+end
27
+
21 28
 --- Utility function to check and convert a room JID from
22 29
 -- virtual room1@muc.foo.example.com to real [foo]room1@muc.example.com
23 30
 -- @param room_jid the room jid to match and rewrite if needed
24 31
 -- @return returns room jid [foo]room1@muc.example.com when it has subdomain
25 32
 -- otherwise room1@muc.example.com(the room_jid value untouched)
26 33
 local function room_jid_match_rewrite(room_jid)
27
-    local node, host, resource = jid.split(room_jid);
28
-    local target_subdomain = host and host:match(target_subdomain_pattern);
34
+    local node, host, resource, target_subdomain = room_jid_split_subdomain(room_jid);
29 35
     if not target_subdomain then
30 36
         module:log("debug", "No need to rewrite out 'to' %s", room_jid);
31 37
         return room_jid;
@@ -38,6 +44,23 @@ local function room_jid_match_rewrite(room_jid)
38 44
     return room_jid
39 45
 end
40 46
 
47
+local function internal_room_jid_match_rewrite(room_jid)
48
+    local node, host, resource = jid.split(room_jid);
49
+    if host ~= muc_domain or not node then
50
+        module:log("debug", "No need to rewrite %s (not from the MUC host)", room_jid);
51
+        return room_jid;
52
+    end
53
+    local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
54
+    if not (target_node and target_subdomain) then
55
+        module:log("debug", "Not rewriting... unexpected node format: %s", node);
56
+        return room_jid;
57
+    end
58
+    -- Ok, rewrite room_jid address to pretty format
59
+    local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
60
+    room_jid = jid.join(new_node, new_host, new_resource);
61
+    module:log("debug", "Rewrote to %s", room_jid);
62
+    return room_jid
63
+end
41 64
 
42 65
 --- Finds and returns room by its jid
43 66
 -- @param room_jid the room jid to search in the muc component
@@ -191,5 +214,7 @@ return {
191 214
     get_room_from_jid = get_room_from_jid;
192 215
     async_handler_wrapper = async_handler_wrapper;
193 216
     room_jid_match_rewrite = room_jid_match_rewrite;
217
+    room_jid_split_subdomain = room_jid_split_subdomain;
218
+    internal_room_jid_match_rewrite = internal_room_jid_match_rewrite;
194 219
     update_presence_identity = update_presence_identity;
195 220
 };

Laden…
Annuleren
Opslaan