"Fossies" - the Fresh Open Source Software Archive

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