-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmod_muc_lobby_rooms.lua
439 lines (384 loc) · 16.2 KB
/
mod_muc_lobby_rooms.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
-- This module added under the main virtual host domain
-- It needs a lobby muc component
--
-- VirtualHost "jitmeet.example.com"
-- modules_enabled = {
-- "muc_lobby_rooms"
-- }
-- lobby_muc = "lobby.jitmeet.example.com"
-- main_muc = "conference.jitmeet.example.com"
--
-- Component "lobby.jitmeet.example.com" "muc"
-- storage = "memory"
-- muc_room_cache_size = 1000
-- restrict_room_creation = true
-- muc_room_locking = false
-- muc_room_default_public_jids = true
--
-- we use async to detect Prosody 0.10 and earlier
local have_async = pcall(require, 'util.async');
if not have_async then
module:log('warn', 'Lobby rooms will not work with Prosody version 0.10 or less.');
return;
end
module:depends("jitsi_session");
local jid_split = require 'util.jid'.split;
local jid_bare = require 'util.jid'.bare;
local json = require 'util.json';
local filters = require 'util.filters';
local st = require 'util.stanza';
local MUC_NS = 'http://jabber.org/protocol/muc';
local DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info';
local DISPLAY_NAME_REQUIRED_FEATURE = 'http://jitsi.org/protocol/lobbyrooms#displayname_required';
local LOBBY_IDENTITY_TYPE = 'lobbyrooms';
local NOTIFY_JSON_MESSAGE_TYPE = 'lobby-notify';
local NOTIFY_LOBBY_ENABLED = 'LOBBY-ENABLED';
local NOTIFY_LOBBY_ACCESS_GRANTED = 'LOBBY-ACCESS-GRANTED';
local NOTIFY_LOBBY_ACCESS_DENIED = 'LOBBY-ACCESS-DENIED';
local util = module:require "util";
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
local is_healthcheck_room = util.is_healthcheck_room;
local presence_check_status = util.presence_check_status;
local main_muc_component_config = module:get_option_string('main_muc');
if main_muc_component_config == nil then
module:log('error', 'lobby not enabled missing main_muc config');
return ;
end
local lobby_muc_component_config = module:get_option_string('lobby_muc');
if lobby_muc_component_config == nil then
module:log('error', 'lobby not enabled missing lobby_muc config');
return ;
end
local whitelist;
local check_display_name_required;
local function load_config()
whitelist = module:get_option_set('muc_lobby_whitelist', {});
check_display_name_required
= module:get_option_boolean('muc_lobby_check_display_name_required', true);
end
load_config();
local lobby_muc_service;
local main_muc_service;
function broadcast_json_msg(room, from, json_msg)
json_msg.type = NOTIFY_JSON_MESSAGE_TYPE;
local occupant = room:get_occupant_by_real_jid(from);
if occupant then
room:broadcast_message(
st.message({ type = 'groupchat', from = occupant.nick })
:tag('json-message', {xmlns='http://jitsi.org/jitmeet'})
:text(json.encode(json_msg)):up());
end
end
-- Sends a json message notifying for lobby enabled/disable
-- the message from is the actor that did the operation
function notify_lobby_enabled(room, actor, value)
broadcast_json_msg(room, actor, {
event = NOTIFY_LOBBY_ENABLED,
value = value
});
end
-- Sends a json message notifying that the jid was granted/denied access in lobby
-- the message from is the actor that did the operation
function notify_lobby_access(room, actor, jid, display_name, granted)
local notify_json = {
value = jid,
name = display_name
};
if granted then
notify_json.event = NOTIFY_LOBBY_ACCESS_GRANTED;
else
notify_json.event = NOTIFY_LOBBY_ACCESS_DENIED;
end
broadcast_json_msg(room, actor, notify_json);
end
function filter_stanza(stanza)
if not stanza.attr or not stanza.attr.from or not main_muc_service or not lobby_muc_service then
return stanza;
end
-- Allow self-presence (code=110)
local node, from_domain = jid_split(stanza.attr.from);
if from_domain == lobby_muc_component_config then
if stanza.name == 'presence' then
local muc_x = stanza:get_child('x', MUC_NS..'#user');
if not muc_x or presence_check_status(muc_x, '110') then
return stanza;
end
local lobby_room_jid = jid_bare(stanza.attr.from);
local lobby_room = lobby_muc_service.get_room_from_jid(lobby_room_jid);
if not lobby_room then
module:log('warn', 'No lobby room found %s', lobby_room_jid);
return stanza;
end
-- check is an owner, only owners can receive the presence
-- do not forward presence of owners (other than unavailable)
local room = main_muc_service.get_room_from_jid(jid_bare(node .. '@' .. main_muc_component_config));
local item = muc_x:get_child('item');
if not room
or stanza.attr.type == 'unavailable'
or (room.get_affiliation(room, stanza.attr.to) == 'owner'
and room.get_affiliation(room, item.attr.jid) ~= 'owner') then
return stanza;
end
local is_to_moderator = lobby_room:get_affiliation(stanza.attr.to) == 'owner';
local from_occupant = lobby_room:get_occupant_by_nick(stanza.attr.from);
if not from_occupant then
if is_to_moderator then
return stanza;
end
module:log('warn', 'No lobby occupant found %s', stanza.attr.from);
return nil;
end
local from_real_jid;
for real_jid in from_occupant:each_session() do
from_real_jid = real_jid;
end
if is_to_moderator and lobby_room:get_affiliation(from_real_jid) ~= 'owner' then
return stanza;
end
elseif stanza.name == 'iq' and stanza:get_child('query', DISCO_INFO_NS) then
-- allow disco info from the lobby component
return stanza;
end
return nil;
else
return stanza;
end
end
function filter_session(session)
-- domain mapper is filtering on default priority 0, and we need it after that
filters.add_filter(session, 'stanzas/out', filter_stanza, -1);
end
-- actor can be null if called from backend (another module using hook create-lobby-room)
function attach_lobby_room(room, actor)
local node = jid_split(room.jid);
local lobby_room_jid = node .. '@' .. lobby_muc_component_config;
if not lobby_muc_service.get_room_from_jid(lobby_room_jid) then
local new_room = lobby_muc_service.create_room(lobby_room_jid);
-- set persistent the lobby room to avoid it to be destroyed
-- there are cases like when selecting new moderator after the current one leaves
-- which can leave the room with no occupants and it will be destroyed and we want to
-- avoid lobby destroy while it is enabled
new_room:set_persistent(true);
module:log("info","Lobby room jid = %s created from:%s", lobby_room_jid, actor);
new_room.main_room = room;
room._data.lobbyroom = new_room.jid;
room:save(true);
return true
end
return false
end
-- destroys lobby room for the supplied main room
function destroy_lobby_room(room, newjid, message)
if not message then
message = 'Lobby room closed.';
end
if lobby_muc_service and room and room._data.lobbyroom then
local lobby_room_obj = lobby_muc_service.get_room_from_jid(room._data.lobbyroom);
if lobby_room_obj then
lobby_room_obj:set_persistent(false);
lobby_room_obj:destroy(newjid, message);
end
room._data.lobbyroom = nil;
end
end
-- process a host module directly if loaded or hooks to wait for its load
function process_host_module(name, callback)
local function process_host(host)
if host == name then
callback(module:context(host), host);
end
end
if prosody.hosts[name] == nil then
module:log('debug', 'No host/component found, will wait for it: %s', name)
-- when a host or component is added
prosody.events.add_handler('host-activated', process_host);
else
process_host(name);
end
end
-- operates on already loaded lobby muc module
function process_lobby_muc_loaded(lobby_muc, host_module)
module:log('debug', 'Lobby muc loaded');
lobby_muc_service = lobby_muc;
-- enable filtering presences in the lobby muc rooms
filters.add_filter_hook(filter_session);
-- Advertise lobbyrooms support on main domain so client can pick up the address and use it
module:add_identity('component', LOBBY_IDENTITY_TYPE, lobby_muc_component_config);
-- Tag the disco#info response with a feature that display name is required
-- when the conference name from the web request has a lobby enabled.
host_module:hook('host-disco-info-node', function (event)
local session, reply, node = event.origin, event.reply, event.node;
if node == LOBBY_IDENTITY_TYPE
and session.jitsi_web_query_room
and check_display_name_required then
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if room and room._data.lobbyroom then
reply:tag('feature', { var = DISPLAY_NAME_REQUIRED_FEATURE }):up();
end
end
event.exists = true;
end);
local room_mt = lobby_muc_service.room_mt;
-- we base affiliations (roles) in lobby muc component to be based on the roles in the main muc
room_mt.get_affiliation = function(room, jid)
if not room.main_room then
module:log('error', 'No main room(%s) for %s!', room.jid, jid);
return 'none';
end
-- moderators in main room are moderators here
local role = room.main_room.get_affiliation(room.main_room, jid);
if role then
return role;
end
return 'none';
end
-- listens for kicks in lobby room, 307 is the status for kick according to xep-0045
host_module:hook('muc-broadcast-presence', function (event)
local actor, occupant, room, x = event.actor, event.occupant, event.room, event.x;
if presence_check_status(x, '307') then
local display_name = occupant:get_presence():get_child_text(
'nick', 'http://jabber.org/protocol/nick');
-- we need to notify in the main room
notify_lobby_access(room.main_room, actor, occupant.nick, display_name, false);
end
end);
end
-- process or waits to process the lobby muc component
process_host_module(lobby_muc_component_config, function(host_module, host)
-- lobby muc component created
module:log('info', 'Lobby component loaded %s', host);
local muc_module = prosody.hosts[host].modules.muc;
if muc_module then
process_lobby_muc_loaded(muc_module, host_module);
else
module:log('debug', 'Will wait for muc to be available');
prosody.hosts[host].events.add_handler('module-loaded', function(event)
if (event.module == 'muc') then
process_lobby_muc_loaded(prosody.hosts[host].modules.muc, host_module);
end
end);
end
end);
-- process or waits to process the main muc component
process_host_module(main_muc_component_config, function(host_module, host)
main_muc_service = prosody.hosts[host].modules.muc;
-- hooks when lobby is enabled to create its room, only done here or by admin
host_module:hook('muc-config-submitted', function(event)
local actor, room = event.actor, event.room;
local actor_node = jid_split(actor);
if actor_node == 'focus' then
return;
end
local members_only = event.fields['muc#roomconfig_membersonly'] and true or nil;
if members_only then
local lobby_created = attach_lobby_room(room, actor);
if lobby_created then
event.status_codes['104'] = true;
notify_lobby_enabled(room, actor, true);
end
elseif room._data.lobbyroom then
destroy_lobby_room(room, room.jid);
notify_lobby_enabled(room, actor, false);
end
end);
host_module:hook('muc-room-destroyed',function(event)
local room = event.room;
if room._data.lobbyroom then
destroy_lobby_room(room, nil);
end
end);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
if (room._data.lobbyroom and room:get_members_only()) then
table.insert(event.form, {
name = 'muc#roominfo_lobbyroom';
label = 'Lobby room jid';
value = '';
});
event.formdata['muc#roominfo_lobbyroom'] = room._data.lobbyroom;
end
end);
host_module:hook('muc-occupant-pre-join', function (event)
local room, stanza = event.room, event.stanza;
if is_healthcheck_room(room.jid) or not room:get_members_only() then
return;
end
local join = stanza:get_child('x', MUC_NS);
if not join then
return;
end
local invitee = event.stanza.attr.from;
local invitee_bare_jid = jid_bare(invitee);
local _, invitee_domain = jid_split(invitee);
local whitelistJoin = false;
-- whitelist participants
if whitelist:contains(invitee_domain) or whitelist:contains(invitee_bare_jid) then
whitelistJoin = true;
end
-- begin gkleinereva addition
local context_user = event.origin.jitsi_meet_context_user;
if context_user then
if context_user["affiliation"] == "owner" then
whitelistJoin = true;
elseif context_user["affiliation"] == "moderator" then
whitelistJoin = true;
elseif context_user["affiliation"] == "teacher" then
whitelistJoin = true;
end
end
-- end gkleinereva addition
local password = join:get_child_text('password', MUC_NS);
if password and room:get_password() and password == room:get_password() then
whitelistJoin = true;
end
if whitelistJoin then
local affiliation = room:get_affiliation(invitee);
if not affiliation or affiliation == 0 then
event.occupant.role = 'participant';
room:set_affiliation(true, invitee_bare_jid, 'member');
room:save();
return;
end
end
-- we want to add the custom lobbyroom field to fill in the lobby room jid
local invitee = event.stanza.attr.from;
local affiliation = room:get_affiliation(invitee);
if not affiliation or affiliation == 'none' then
local reply = st.error_reply(stanza, 'auth', 'registration-required'):up();
reply.tags[1].attr.code = '407';
reply:tag('x', {xmlns = MUC_NS}):up();
reply:tag('lobbyroom'):text(room._data.lobbyroom);
event.origin.send(reply:tag('x', {xmlns = MUC_NS}));
return true;
end
end, -4); -- the default hook on members_only module is on -5
-- listens for invites for participants to join the main room
host_module:hook('muc-invite', function(event)
local room, stanza = event.room, event.stanza;
local invitee = stanza.attr.to;
local from = stanza:get_child('x', 'http://jabber.org/protocol/muc#user')
:get_child('invite').attr.from;
if lobby_muc_service and room._data.lobbyroom then
local lobby_room_obj = lobby_muc_service.get_room_from_jid(room._data.lobbyroom);
if lobby_room_obj then
local occupant = lobby_room_obj:get_occupant_by_real_jid(invitee);
if occupant then
local display_name = occupant:get_presence():get_child_text(
'nick', 'http://jabber.org/protocol/nick');
notify_lobby_access(room, from, occupant.nick, display_name, true);
end
end
end
end);
end);
function handle_create_lobby(event)
local room = event.room;
room:set_members_only(true);
attach_lobby_room(room)
end
function handle_destroy_lobby(event)
destroy_lobby_room(event.room, event.newjid, event.message);
end
module:hook_global('config-reloaded', load_config);
module:hook_global('create-lobby-room', handle_create_lobby);
module:hook_global('destroy-lobby-room', handle_destroy_lobby);