|
@@ -5,13 +5,44 @@ local neturl = require "net.url";
|
5
|
5
|
local parse = neturl.parseQuery;
|
6
|
6
|
local st = require "util.stanza";
|
7
|
7
|
local get_room_from_jid = module:require "util".get_room_from_jid;
|
|
8
|
+local wrap_async_run = module:require "util".wrap_async_run;
|
|
9
|
+local timer = require "util.timer";
|
8
|
10
|
|
9
|
11
|
-- Options
|
10
|
12
|
local poltergeist_component
|
11
|
13
|
= module:get_option_string("poltergeist_component", module.host);
|
|
14
|
+-- defaults to 3 min
|
|
15
|
+local poltergeist_timeout
|
|
16
|
+ = module:get_option_string("poltergeist_leave_timeout", 180);
|
|
17
|
+-- this basically strips the domain from the conference.domain address
|
|
18
|
+local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")();
|
|
19
|
+if parentHostName == nil then
|
|
20
|
+ log("error", "Failed to start - unable to get parent hostname");
|
|
21
|
+ return;
|
|
22
|
+end
|
|
23
|
+
|
|
24
|
+local parentCtx = module:context(parentHostName);
|
|
25
|
+if parentCtx == nil then
|
|
26
|
+ log("error",
|
|
27
|
+ "Failed to start - unable to get parent context for host: %s",
|
|
28
|
+ tostring(parentHostName));
|
|
29
|
+ return;
|
|
30
|
+end
|
|
31
|
+local token_util = module:require "token/util".new(parentCtx);
|
|
32
|
+
|
|
33
|
+-- option to enable/disable token verifications
|
|
34
|
+local disableTokenVerification
|
|
35
|
+ = module:get_option_boolean("disable_polergeist_token_verification", false);
|
|
36
|
+
|
|
37
|
+-- option to expire poltergeist with custom status text
|
|
38
|
+local poltergeistExpiredStatus
|
|
39
|
+ = module:get_option_string("poltergeist_expired_status");
|
12
|
40
|
|
13
|
41
|
-- table to store all poltergeists we create
|
14
|
42
|
local poltergeists = {};
|
|
43
|
+-- table to mark that outgoing unavailable presences
|
|
44
|
+-- should be marked with ignore
|
|
45
|
+local poltergeists_pr_ignore = {};
|
15
|
46
|
|
16
|
47
|
-- poltergaist management functions
|
17
|
48
|
|
|
@@ -21,8 +52,9 @@ local poltergeists = {};
|
21
|
52
|
-- @return returns room if found or nil
|
22
|
53
|
function get_room(room_name, group)
|
23
|
54
|
local room_address = jid.join(room_name, module:get_host());
|
24
|
|
- -- if there is a group we are in multidomain mode
|
25
|
|
- if group and group ~= "" then
|
|
55
|
+ -- if there is a group we are in multidomain mode and that group is not
|
|
56
|
+ -- our parent host
|
|
57
|
+ if group and group ~= "" and group ~= parentHostName then
|
26
|
58
|
room_address = "["..group.."]"..room_address;
|
27
|
59
|
end
|
28
|
60
|
|
|
@@ -59,6 +91,67 @@ function get_username(room, user_id)
|
59
|
91
|
return poltergeists[room_name][user_id];
|
60
|
92
|
end
|
61
|
93
|
|
|
94
|
+-- Removes poltergeist values from table
|
|
95
|
+-- @param room the room instance
|
|
96
|
+-- @param nick the user nick
|
|
97
|
+function remove_username(room, nick)
|
|
98
|
+ local room_name = jid.node(room.jid);
|
|
99
|
+ if (poltergeists[room_name]) then
|
|
100
|
+ local user_id_to_remove;
|
|
101
|
+ for name,username in pairs(poltergeists[room_name]) do
|
|
102
|
+ if (string.sub(username, 0, 8) == nick) then
|
|
103
|
+ user_id_to_remove = name;
|
|
104
|
+ end
|
|
105
|
+ end
|
|
106
|
+ if (user_id_to_remove) then
|
|
107
|
+ poltergeists[room_name][user_id_to_remove] = nil;
|
|
108
|
+ end
|
|
109
|
+ end
|
|
110
|
+end
|
|
111
|
+
|
|
112
|
+--- Verifies room name, domain name with the values in the token
|
|
113
|
+-- @param token the token we received
|
|
114
|
+-- @param room_name the room name
|
|
115
|
+-- @param group name of the group (optional)
|
|
116
|
+-- @return true if values are ok or false otherwise
|
|
117
|
+function verify_token(token, room_name, group)
|
|
118
|
+ if disableTokenVerification then
|
|
119
|
+ return true;
|
|
120
|
+ end
|
|
121
|
+
|
|
122
|
+ -- if not disableTokenVerification and we do not have token
|
|
123
|
+ -- stop here, cause the main virtual host can have guest access enabled
|
|
124
|
+ -- (allowEmptyToken = true) and we will allow access to rooms info without
|
|
125
|
+ -- a token
|
|
126
|
+ if token == nil then
|
|
127
|
+ log("warn", "no token provided");
|
|
128
|
+ return false;
|
|
129
|
+ end
|
|
130
|
+
|
|
131
|
+ local session = {};
|
|
132
|
+ session.auth_token = token;
|
|
133
|
+ local verified, reason = token_util:process_and_verify_token(session);
|
|
134
|
+ if not verified then
|
|
135
|
+ log("warn", "not a valid token %s", tostring(reason));
|
|
136
|
+ return false;
|
|
137
|
+ end
|
|
138
|
+
|
|
139
|
+ local room_address = jid.join(room_name, module:get_host());
|
|
140
|
+ -- if there is a group we are in multidomain mode and that group is not
|
|
141
|
+ -- our parent host
|
|
142
|
+ if group and group ~= "" and group ~= parentHostName then
|
|
143
|
+ room_address = "["..group.."]"..room_address;
|
|
144
|
+ end
|
|
145
|
+
|
|
146
|
+ if not token_util:verify_room(session, room_address) then
|
|
147
|
+ log("warn", "Token %s not allowed to join: %s",
|
|
148
|
+ tostring(token), tostring(room_address));
|
|
149
|
+ return false;
|
|
150
|
+ end
|
|
151
|
+
|
|
152
|
+ return true;
|
|
153
|
+end
|
|
154
|
+
|
62
|
155
|
-- if we found that a session for a user with id has a poltergiest already
|
63
|
156
|
-- created, retrieve its jid and return it to the authentication
|
64
|
157
|
-- so we can reuse it and we that real user will replace the poltergiest
|
|
@@ -67,7 +160,7 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session)
|
67
|
160
|
if (session.jitsi_meet_context_user) then
|
68
|
161
|
local room = get_room(
|
69
|
162
|
session.jitsi_bosh_query_room,
|
70
|
|
- session.jitsi_meet_context_group);
|
|
163
|
+ session.jitsi_meet_domain);
|
71
|
164
|
|
72
|
165
|
if (not room) then
|
73
|
166
|
return nil;
|
|
@@ -88,7 +181,7 @@ prosody.events.add_handler("pre-jitsi-authentication", function(session)
|
88
|
181
|
-- we will mark it with ignore tag
|
89
|
182
|
local nick = string.sub(username, 0, 8);
|
90
|
183
|
if (have_poltergeist_occupant(room, nick)) then
|
91
|
|
- remove_poltergeist_occupant(room, nick);
|
|
184
|
+ remove_poltergeist_occupant(room, nick, true);
|
92
|
185
|
end
|
93
|
186
|
|
94
|
187
|
return username;
|
|
@@ -102,7 +195,8 @@ end);
|
102
|
195
|
-- @param nick the nick to use for the new occupant
|
103
|
196
|
-- @param name the display name fot the occupant (optional)
|
104
|
197
|
-- @param avatar the avatar to use for the new occupant (optional)
|
105
|
|
-function create_poltergeist_occupant(room, nick, name, avatar)
|
|
198
|
+-- @param status the initial status to use for the new occupant (optional)
|
|
199
|
+function create_poltergeist_occupant(room, nick, name, avatar, status)
|
106
|
200
|
log("debug", "create_poltergeist_occupant %s:", nick);
|
107
|
201
|
-- Join poltergeist occupant to room, with the invited JID as their nick
|
108
|
202
|
local join_presence = st.presence({
|
|
@@ -118,22 +212,103 @@ function create_poltergeist_occupant(room, nick, name, avatar)
|
118
|
212
|
if (avatar) then
|
119
|
213
|
join_presence:tag("avatar-url"):text(avatar):up();
|
120
|
214
|
end
|
|
215
|
+ if (status) then
|
|
216
|
+ join_presence:tag("status"):text(status):up();
|
|
217
|
+ end
|
121
|
218
|
|
122
|
219
|
room:handle_first_presence(
|
123
|
220
|
prosody.hosts[poltergeist_component], join_presence);
|
|
221
|
+
|
|
222
|
+ local timeout = poltergeist_timeout;
|
|
223
|
+ -- the timeout before removing so participants can see the status update
|
|
224
|
+ local removeTimeout = 5;
|
|
225
|
+ if (poltergeistExpiredStatus) then
|
|
226
|
+ timeout = timeout - removeTimeout;
|
|
227
|
+ end
|
|
228
|
+
|
|
229
|
+ timer.add_task(timeout,
|
|
230
|
+ function ()
|
|
231
|
+ if (poltergeistExpiredStatus) then
|
|
232
|
+ update_poltergeist_occupant_status(
|
|
233
|
+ room, nick, poltergeistExpiredStatus);
|
|
234
|
+ -- and remove it after some time so participant can see
|
|
235
|
+ -- the update
|
|
236
|
+ timer.add_task(removeTimeout,
|
|
237
|
+ function ()
|
|
238
|
+ if (have_poltergeist_occupant(room, nick)) then
|
|
239
|
+ remove_poltergeist_occupant(room, nick, false);
|
|
240
|
+ end
|
|
241
|
+ end);
|
|
242
|
+ else
|
|
243
|
+ if (have_poltergeist_occupant(room, nick)) then
|
|
244
|
+ remove_poltergeist_occupant(room, nick, false);
|
|
245
|
+ end
|
|
246
|
+ end
|
|
247
|
+ end);
|
124
|
248
|
end
|
125
|
249
|
|
126
|
250
|
-- Removes poltergeist occupant
|
127
|
251
|
-- @param room the room instance where to remove the occupant
|
128
|
252
|
-- @param nick the nick of the occupant to remove
|
129
|
|
-function remove_poltergeist_occupant(room, nick)
|
|
253
|
+-- @param ignore to mark the poltergeist unavailble presence to be ignored
|
|
254
|
+function remove_poltergeist_occupant(room, nick, ignore)
|
130
|
255
|
log("debug", "remove_poltergeist_occupant %s", nick);
|
131
|
256
|
local leave_presence = st.presence({
|
132
|
257
|
to = room.jid.."/"..nick,
|
133
|
258
|
from = poltergeist_component.."/"..nick,
|
134
|
259
|
type = "unavailable" });
|
|
260
|
+ if (ignore) then
|
|
261
|
+ poltergeists_pr_ignore[room.jid.."/"..nick] = true;
|
|
262
|
+ end
|
135
|
263
|
room:handle_normal_presence(
|
136
|
264
|
prosody.hosts[poltergeist_component], leave_presence);
|
|
265
|
+ remove_username(room, nick);
|
|
266
|
+end
|
|
267
|
+
|
|
268
|
+-- Updates poltergeist occupant status
|
|
269
|
+-- @param room the room instance where to remove the occupant
|
|
270
|
+-- @param nick the nick of the occupant to remove
|
|
271
|
+-- @param status the status to update
|
|
272
|
+function update_poltergeist_occupant_status(room, nick, status)
|
|
273
|
+ local update_presence = get_presence(room, nick);
|
|
274
|
+
|
|
275
|
+ if (not update_presence) then
|
|
276
|
+ -- no presence found for occupant, create one
|
|
277
|
+ update_presence = st.presence({
|
|
278
|
+ to = room.jid.."/"..nick,
|
|
279
|
+ from = poltergeist_component.."/"..nick
|
|
280
|
+ });
|
|
281
|
+ else
|
|
282
|
+ -- update occupant presence with appropriate to and from
|
|
283
|
+ -- so we can send it again
|
|
284
|
+ update_presence = st.clone(update_presence);
|
|
285
|
+ update_presence.attr.to = room.jid.."/"..nick;
|
|
286
|
+ update_presence.attr.from = poltergeist_component.."/"..nick;
|
|
287
|
+ end
|
|
288
|
+
|
|
289
|
+ local once = false;
|
|
290
|
+ -- the status tag we will attach
|
|
291
|
+ local statusTag = st.stanza("status"):text(status);
|
|
292
|
+
|
|
293
|
+ -- if there is already a status tag replace it
|
|
294
|
+ update_presence:maptags(function (tag)
|
|
295
|
+ if tag.name == statusTag.name then
|
|
296
|
+ if not once then
|
|
297
|
+ once = true;
|
|
298
|
+ return statusTag;
|
|
299
|
+ else
|
|
300
|
+ return nil;
|
|
301
|
+ end
|
|
302
|
+ end
|
|
303
|
+ return tag;
|
|
304
|
+ end);
|
|
305
|
+ if (not once) then
|
|
306
|
+ -- no status tag was repleced, attach it
|
|
307
|
+ update_presence:add_child(statusTag);
|
|
308
|
+ end
|
|
309
|
+
|
|
310
|
+ room:handle_normal_presence(
|
|
311
|
+ prosody.hosts[poltergeist_component], update_presence);
|
137
|
312
|
end
|
138
|
313
|
|
139
|
314
|
-- Checks for existance of a poltergeist occupant
|
|
@@ -145,12 +320,26 @@ function have_poltergeist_occupant(room, nick)
|
145
|
320
|
return not not room:get_occupant_jid(poltergeist_component.."/"..nick);
|
146
|
321
|
end
|
147
|
322
|
|
|
323
|
+-- Returns the last presence of occupant
|
|
324
|
+-- @param room the room instance where to check for occupant
|
|
325
|
+-- @param nick the nick of the occupant
|
|
326
|
+-- @return presence of the occupant
|
|
327
|
+function get_presence(room, nick)
|
|
328
|
+ local occupant_jid
|
|
329
|
+ = room:get_occupant_jid(poltergeist_component.."/"..nick);
|
|
330
|
+ if (occupant_jid) then
|
|
331
|
+ return room:get_occupant_by_nick(occupant_jid):get_presence();
|
|
332
|
+ end
|
|
333
|
+
|
|
334
|
+ return nil;
|
|
335
|
+end
|
|
336
|
+
|
148
|
337
|
-- Event handlers
|
149
|
338
|
|
150
|
339
|
--- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100,
|
151
|
340
|
--- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100.
|
152
|
341
|
module:hook("muc-decline", function (event)
|
153
|
|
- remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from));
|
|
342
|
+ remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from), false);
|
154
|
343
|
end, -100);
|
155
|
344
|
-- before sending the presence for a poltergeist leaving add ignore tag
|
156
|
345
|
-- as poltergeist is leaving just before the real user joins and in the client
|
|
@@ -158,34 +347,54 @@ end, -100);
|
158
|
347
|
-- user will reuse all currently created UI components for the same nick
|
159
|
348
|
module:hook("muc-broadcast-presence", function (event)
|
160
|
349
|
if (bare(event.occupant.jid) == poltergeist_component) then
|
161
|
|
- if(event.stanza.attr.type == "unavailable") then
|
|
350
|
+ if(event.stanza.attr.type == "unavailable"
|
|
351
|
+ and poltergeists_pr_ignore[event.occupant.nick]) then
|
162
|
352
|
event.stanza:tag(
|
163
|
353
|
"ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
|
|
354
|
+ poltergeists_pr_ignore[event.occupant.nick] = nil;
|
164
|
355
|
end
|
165
|
356
|
end
|
166
|
357
|
end, -100);
|
167
|
358
|
|
|
359
|
+-- cleanup room table after room is destroyed
|
|
360
|
+module:hook("muc-room-destroyed",function(event)
|
|
361
|
+ local room_name = jid.node(event.room.jid);
|
|
362
|
+ if (poltergeists[room_name]) then
|
|
363
|
+ poltergeists[room_name] = nil;
|
|
364
|
+ end
|
|
365
|
+end);
|
|
366
|
+
|
168
|
367
|
--- Handles request for creating/managing poltergeists
|
169
|
368
|
-- @param event the http event, holds the request query
|
170
|
369
|
-- @return GET response, containing a json with response details
|
171
|
370
|
function handle_create_poltergeist (event)
|
|
371
|
+ if (not event.request.url.query) then
|
|
372
|
+ return 400;
|
|
373
|
+ end
|
|
374
|
+
|
172
|
375
|
local params = parse(event.request.url.query);
|
173
|
376
|
local user_id = params["user"];
|
174
|
377
|
local room_name = params["room"];
|
175
|
378
|
local group = params["group"];
|
176
|
379
|
local name = params["name"];
|
177
|
380
|
local avatar = params["avatar"];
|
|
381
|
+ local status = params["status"];
|
|
382
|
+
|
|
383
|
+ if not verify_token(params["token"], room_name, group) then
|
|
384
|
+ return 403;
|
|
385
|
+ end
|
178
|
386
|
|
179
|
387
|
local room = get_room(room_name, group);
|
180
|
388
|
if (not room) then
|
181
|
|
- log("error", "no room found %s", room_address);
|
|
389
|
+ log("error", "no room found %s", room_name);
|
182
|
390
|
return 404;
|
183
|
391
|
end
|
184
|
392
|
|
185
|
393
|
local username = generate_uuid();
|
186
|
394
|
store_username(room, user_id, username)
|
187
|
395
|
|
188
|
|
- create_poltergeist_occupant(room, string.sub(username,0,8), name, avatar);
|
|
396
|
+ create_poltergeist_occupant(
|
|
397
|
+ room, string.sub(username,0,8), name, avatar, status);
|
189
|
398
|
|
190
|
399
|
return 200;
|
191
|
400
|
end
|
|
@@ -194,15 +403,23 @@ end
|
194
|
403
|
-- @param event the http event, holds the request query
|
195
|
404
|
-- @return GET response, containing a json with response details
|
196
|
405
|
function handle_update_poltergeist (event)
|
|
406
|
+ if (not event.request.url.query) then
|
|
407
|
+ return 400;
|
|
408
|
+ end
|
|
409
|
+
|
197
|
410
|
local params = parse(event.request.url.query);
|
198
|
411
|
local user_id = params["user"];
|
199
|
412
|
local room_name = params["room"];
|
200
|
413
|
local group = params["group"];
|
201
|
414
|
local status = params["status"];
|
202
|
415
|
|
|
416
|
+ if not verify_token(params["token"], room_name, group) then
|
|
417
|
+ return 403;
|
|
418
|
+ end
|
|
419
|
+
|
203
|
420
|
local room = get_room(room_name, group);
|
204
|
421
|
if (not room) then
|
205
|
|
- log("error", "no room found %s", room_address);
|
|
422
|
+ log("error", "no room found %s", room_name);
|
206
|
423
|
return 404;
|
207
|
424
|
end
|
208
|
425
|
|
|
@@ -213,28 +430,58 @@ function handle_update_poltergeist (event)
|
213
|
430
|
|
214
|
431
|
local nick = string.sub(username, 0, 8);
|
215
|
432
|
if (have_poltergeist_occupant(room, nick)) then
|
216
|
|
- local update_presence = st.presence({
|
217
|
|
- to = room.jid.."/"..nick,
|
218
|
|
- from = poltergeist_component.."/"..nick
|
219
|
|
- }):tag("status"):text(status):up();
|
|
433
|
+ update_poltergeist_occupant_status(room, nick, status);
|
|
434
|
+ return 200;
|
|
435
|
+ else
|
|
436
|
+ return 404;
|
|
437
|
+ end
|
|
438
|
+end
|
220
|
439
|
|
221
|
|
- room:handle_normal_presence(
|
222
|
|
- prosody.hosts[poltergeist_component], update_presence);
|
|
440
|
+--- Handles remove poltergeists
|
|
441
|
+-- @param event the http event, holds the request query
|
|
442
|
+-- @return GET response, containing a json with response details
|
|
443
|
+function handle_remove_poltergeist (event)
|
|
444
|
+ if (not event.request.url.query) then
|
|
445
|
+ return 400;
|
|
446
|
+ end
|
|
447
|
+
|
|
448
|
+ local params = parse(event.request.url.query);
|
|
449
|
+ local user_id = params["user"];
|
|
450
|
+ local room_name = params["room"];
|
|
451
|
+ local group = params["group"];
|
|
452
|
+
|
|
453
|
+ if not verify_token(params["token"], room_name, group) then
|
|
454
|
+ return 403;
|
|
455
|
+ end
|
223
|
456
|
|
|
457
|
+ local room = get_room(room_name, group);
|
|
458
|
+ if (not room) then
|
|
459
|
+ log("error", "no room found %s", room_name);
|
|
460
|
+ return 404;
|
|
461
|
+ end
|
|
462
|
+
|
|
463
|
+ local username = get_username(room, user_id);
|
|
464
|
+ if (not username) then
|
|
465
|
+ return 404;
|
|
466
|
+ end
|
|
467
|
+
|
|
468
|
+ local nick = string.sub(username, 0, 8);
|
|
469
|
+ if (have_poltergeist_occupant(room, nick)) then
|
|
470
|
+ remove_poltergeist_occupant(room, nick, false);
|
224
|
471
|
return 200;
|
225
|
472
|
else
|
226
|
473
|
return 404;
|
227
|
474
|
end
|
228
|
475
|
end
|
229
|
476
|
|
230
|
|
-
|
231
|
477
|
log("info", "Loading poltergeist service");
|
232
|
478
|
module:depends("http");
|
233
|
479
|
module:provides("http", {
|
234
|
480
|
default_path = "/";
|
235
|
481
|
name = "poltergeist";
|
236
|
482
|
route = {
|
237
|
|
- ["GET /poltergeist/create"] = handle_create_poltergeist;
|
238
|
|
- ["GET /poltergeist/update"] = handle_update_poltergeist;
|
|
483
|
+ ["GET /poltergeist/create"] = function (event) return wrap_async_run(event,handle_create_poltergeist) end;
|
|
484
|
+ ["GET /poltergeist/update"] = function (event) return wrap_async_run(event,handle_update_poltergeist) end;
|
|
485
|
+ ["GET /poltergeist/remove"] = function (event) return wrap_async_run(event,handle_remove_poltergeist) end;
|
239
|
486
|
};
|
240
|
487
|
});
|