Procházet zdrojové kódy

Merge pull request #1797 from jitsi/module-poltergeist_update

Module poltergeist update
master
Aaron van Meerten před 8 roky
rodič
revize
c369330054

+ 267
- 20
resources/prosody-plugins/mod_muc_poltergeist.lua Zobrazit soubor

@@ -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
 });

+ 11
- 2
resources/prosody-plugins/mod_muc_size.lua Zobrazit soubor

@@ -9,6 +9,7 @@ local it = require "util.iterators";
9 9
 local json = require "util.json";
10 10
 local iterators = require "util.iterators";
11 11
 local array = require"util.array";
12
+local wrap_async_run = module:require "util".wrap_async_run;
12 13
 
13 14
 local tostring = tostring;
14 15
 local neturl = require "net.url";
@@ -72,6 +73,10 @@ end
72 73
 -- @return GET response, containing a json with participants count,
73 74
 --         tha value is without counting the focus.
74 75
 function handle_get_room_size(event)
76
+    if (not event.request.url.query) then
77
+        return 400;
78
+    end
79
+
75 80
 	local params = parse(event.request.url.query);
76 81
 	local room_name = params["room"];
77 82
 	local domain_name = params["domain"];
@@ -121,6 +126,10 @@ end
121 126
 -- @param event the http event, holds the request query
122 127
 -- @return GET response, containing a json with participants details
123 128
 function handle_get_room (event)
129
+    if (not event.request.url.query) then
130
+        return 400;
131
+    end
132
+
124 133
 	local params = parse(event.request.url.query);
125 134
 	local room_name = params["room"];
126 135
 	local domain_name = params["domain"];
@@ -184,9 +193,9 @@ function module.load()
184 193
 	module:provides("http", {
185 194
 		default_path = "/";
186 195
 		route = {
187
-			["GET room-size"] = handle_get_room_size;
196
+			["GET room-size"] = function (event) return wrap_async_run(event,handle_get_room_size) end;
188 197
 			["GET sessions"] = function () return tostring(it.count(it.keys(prosody.full_sessions))); end;
189
-			["GET room"] = handle_get_room;
198
+			["GET room"] = function (event) return wrap_async_run(event,handle_get_room) end;
190 199
 		};
191 200
 	});
192 201
 end

+ 15
- 0
resources/prosody-plugins/util.lib.lua Zobrazit soubor

@@ -1,4 +1,5 @@
1 1
 local jid = require "util.jid";
2
+local runner, waiter = require "util.async".runner, require "util.async".waiter;
2 3
 
3 4
 --- Finds and returns room by its jid
4 5
 -- @param room_jid the room jid to search in the muc component
@@ -20,6 +21,20 @@ function get_room_from_jid(room_jid)
20 21
     end
21 22
 end
22 23
 
24
+
25
+function wrap_async_run(event,handler)
26
+    local result;
27
+    local async_func = runner(function (event)
28
+        local wait, done = waiter();
29
+        result=handler(event);
30
+        done();
31
+        return result;
32
+    end)
33
+    async_func:run(event)
34
+    return result;
35
+end
36
+
23 37
 return {
24 38
     get_room_from_jid = get_room_from_jid;
39
+    wrap_async_run = wrap_async_run;
25 40
 };

Načítá se…
Zrušit
Uložit