"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-6444/resources/prosody-plugins/mod_muc_lobby_rooms.lua" (8 Aug 2022, 17236 Bytes) of package /linux/misc/jitsi-meet-6444.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 or not lobby_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             local muc_x = stanza:get_child('x', MUC_NS..'#user');
  117             if not muc_x or presence_check_status(muc_x, '110') then
  118                 return stanza;
  119             end
  120 
  121             local lobby_room_jid = jid_bare(stanza.attr.from);
  122             local lobby_room = lobby_muc_service.get_room_from_jid(lobby_room_jid);
  123             if not lobby_room then
  124                 module:log('warn', 'No lobby room found %s', lobby_room_jid);
  125                 return stanza;
  126             end
  127 
  128             -- check is an owner, only owners can receive the presence
  129             -- do not forward presence of owners (other than unavailable)
  130             local room = main_muc_service.get_room_from_jid(jid_bare(node .. '@' .. main_muc_component_config));
  131             local item = muc_x:get_child('item');
  132             if not room
  133                 or stanza.attr.type == 'unavailable'
  134                 or (room.get_affiliation(room, stanza.attr.to) == 'owner'
  135                     and room.get_affiliation(room, item.attr.jid) ~= 'owner') then
  136                 return stanza;
  137             end
  138 
  139             local is_to_moderator = lobby_room:get_affiliation(stanza.attr.to) == 'owner';
  140             local from_occupant = lobby_room:get_occupant_by_nick(stanza.attr.from);
  141             if not from_occupant then
  142                 if is_to_moderator then
  143                     return stanza;
  144                 end
  145 
  146                 module:log('warn', 'No lobby occupant found %s', stanza.attr.from);
  147                 return nil;
  148             end
  149 
  150             local from_real_jid;
  151             for real_jid in from_occupant:each_session() do
  152                 from_real_jid = real_jid;
  153             end
  154 
  155             if is_to_moderator and lobby_room:get_affiliation(from_real_jid) ~= 'owner' then
  156                 return stanza;
  157             end
  158         elseif stanza.name == 'iq' and stanza:get_child('query', DISCO_INFO_NS) then
  159             -- allow disco info from the lobby component
  160             return stanza;
  161         elseif stanza.name == 'message' then
  162             -- allow messages to or from moderator
  163             local lobby_room_jid = jid_bare(stanza.attr.from);
  164             local lobby_room = lobby_muc_service.get_room_from_jid(lobby_room_jid);
  165             local is_to_moderator = lobby_room:get_affiliation(stanza.attr.to) == 'owner';
  166             local from_occupant = lobby_room:get_occupant_by_nick(stanza.attr.from);
  167 
  168             local from_real_jid;
  169             if from_occupant then
  170                 for real_jid in from_occupant:each_session() do
  171                     from_real_jid = real_jid;
  172                 end
  173             end
  174 
  175             local is_from_moderator = lobby_room:get_affiliation(from_real_jid) == 'owner';
  176 
  177             if is_to_moderator or is_from_moderator then
  178                 return stanza;
  179             end
  180             return nil;
  181         end
  182 
  183         return nil;
  184     else
  185         return stanza;
  186     end
  187 end
  188 function filter_session(session)
  189     -- domain mapper is filtering on default priority 0, and we need it after that
  190     filters.add_filter(session, 'stanzas/out', filter_stanza, -1);
  191 end
  192 
  193 -- actor can be null if called from backend (another module using hook create-lobby-room)
  194 function attach_lobby_room(room, actor)
  195     local node = jid_split(room.jid);
  196     local lobby_room_jid = node .. '@' .. lobby_muc_component_config;
  197     if not lobby_muc_service.get_room_from_jid(lobby_room_jid) then
  198         local new_room = lobby_muc_service.create_room(lobby_room_jid);
  199         -- set persistent the lobby room to avoid it to be destroyed
  200         -- there are cases like when selecting new moderator after the current one leaves
  201         -- which can leave the room with no occupants and it will be destroyed and we want to
  202         -- avoid lobby destroy while it is enabled
  203         new_room:set_persistent(true);
  204         module:log("info","Lobby room jid = %s created from:%s", lobby_room_jid, actor);
  205         new_room.main_room = room;
  206         room._data.lobbyroom = new_room.jid;
  207         room:save(true);
  208         return true
  209     end
  210     return false
  211 end
  212 
  213 -- destroys lobby room for the supplied main room
  214 function destroy_lobby_room(room, newjid, message)
  215     if not message then
  216         message = 'Lobby room closed.';
  217     end
  218     if lobby_muc_service and room and room._data.lobbyroom then
  219         local lobby_room_obj = lobby_muc_service.get_room_from_jid(room._data.lobbyroom);
  220         if lobby_room_obj then
  221             lobby_room_obj:set_persistent(false);
  222             lobby_room_obj:destroy(newjid, message);
  223         end
  224         room._data.lobbyroom = nil;
  225     end
  226 end
  227 
  228 -- process a host module directly if loaded or hooks to wait for its load
  229 function process_host_module(name, callback)
  230     local function process_host(host)
  231         if host == name then
  232             callback(module:context(host), host);
  233         end
  234     end
  235 
  236     if prosody.hosts[name] == nil then
  237         module:log('debug', 'No host/component found, will wait for it: %s', name)
  238 
  239         -- when a host or component is added
  240         prosody.events.add_handler('host-activated', process_host);
  241     else
  242         process_host(name);
  243     end
  244 end
  245 
  246 -- operates on already loaded lobby muc module
  247 function process_lobby_muc_loaded(lobby_muc, host_module)
  248     module:log('debug', 'Lobby muc loaded');
  249     lobby_muc_service = lobby_muc;
  250 
  251     -- enable filtering presences in the lobby muc rooms
  252     filters.add_filter_hook(filter_session);
  253 
  254     -- Advertise lobbyrooms support on main domain so client can pick up the address and use it
  255     module:add_identity('component', LOBBY_IDENTITY_TYPE, lobby_muc_component_config);
  256 
  257     -- Tag the disco#info response with a feature that display name is required
  258     -- when the conference name from the web request has a lobby enabled.
  259     host_module:hook('host-disco-info-node', function (event)
  260         local session, reply, node = event.origin, event.reply, event.node;
  261         if node == LOBBY_IDENTITY_TYPE
  262             and session.jitsi_web_query_room
  263             and check_display_name_required then
  264             local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
  265 
  266             if room and room._data.lobbyroom then
  267                 reply:tag('feature', { var = DISPLAY_NAME_REQUIRED_FEATURE }):up();
  268             end
  269         end
  270         event.exists = true;
  271     end);
  272 
  273     local room_mt = lobby_muc_service.room_mt;
  274     -- we base affiliations (roles) in lobby muc component to be based on the roles in the main muc
  275     room_mt.get_affiliation = function(room, jid)
  276         if not room.main_room then
  277             module:log('error', 'No main room(%s) for %s!', room.jid, jid);
  278             return 'none';
  279         end
  280 
  281         -- moderators in main room are moderators here
  282         local role = room.main_room.get_affiliation(room.main_room, jid);
  283         if role then
  284             return role;
  285         end
  286 
  287         return 'none';
  288     end
  289 
  290     -- listens for kicks in lobby room, 307 is the status for kick according to xep-0045
  291     host_module:hook('muc-broadcast-presence', function (event)
  292         local actor, occupant, room, x = event.actor, event.occupant, event.room, event.x;
  293         if presence_check_status(x, '307') then
  294             local display_name = occupant:get_presence():get_child_text(
  295                 'nick', 'http://jabber.org/protocol/nick');
  296             -- we need to notify in the main room
  297             notify_lobby_access(room.main_room, actor, occupant.nick, display_name, false);
  298         end
  299     end);
  300 end
  301 
  302 -- process or waits to process the lobby muc component
  303 process_host_module(lobby_muc_component_config, function(host_module, host)
  304     -- lobby muc component created
  305     module:log('info', 'Lobby component loaded %s', host);
  306 
  307     local muc_module = prosody.hosts[host].modules.muc;
  308     if muc_module then
  309         process_lobby_muc_loaded(muc_module, host_module);
  310     else
  311         module:log('debug', 'Will wait for muc to be available');
  312         prosody.hosts[host].events.add_handler('module-loaded', function(event)
  313             if (event.module == 'muc') then
  314                 process_lobby_muc_loaded(prosody.hosts[host].modules.muc, host_module);
  315             end
  316         end);
  317     end
  318 end);
  319 
  320 -- process or waits to process the main muc component
  321 process_host_module(main_muc_component_config, function(host_module, host)
  322     main_muc_service = prosody.hosts[host].modules.muc;
  323 
  324     -- hooks when lobby is enabled to create its room, only done here or by admin
  325     host_module:hook('muc-config-submitted', function(event)
  326         local actor, room = event.actor, event.room;
  327         local actor_node = jid_split(actor);
  328         if actor_node == 'focus' then
  329             return;
  330         end
  331         local members_only = event.fields['muc#roomconfig_membersonly'] and true or nil;
  332         if members_only then
  333             local lobby_created = attach_lobby_room(room, actor);
  334             if lobby_created then
  335                 event.status_codes['104'] = true;
  336                 notify_lobby_enabled(room, actor, true);
  337             end
  338         elseif room._data.lobbyroom then
  339             destroy_lobby_room(room, room.jid);
  340             notify_lobby_enabled(room, actor, false);
  341         end
  342     end);
  343     host_module:hook('muc-room-destroyed',function(event)
  344         local room = event.room;
  345         if room._data.lobbyroom then
  346             destroy_lobby_room(room, nil);
  347         end
  348     end);
  349     host_module:hook('muc-disco#info', function (event)
  350         local room = event.room;
  351         if (room._data.lobbyroom and room:get_members_only()) then
  352             table.insert(event.form, {
  353                 name = 'muc#roominfo_lobbyroom';
  354                 label = 'Lobby room jid';
  355                 value = '';
  356             });
  357             event.formdata['muc#roominfo_lobbyroom'] = room._data.lobbyroom;
  358         end
  359     end);
  360 
  361     host_module:hook('muc-occupant-pre-join', function (event)
  362         local room, stanza = event.room, event.stanza;
  363 
  364         if is_healthcheck_room(room.jid) or not room:get_members_only() then
  365             return;
  366         end
  367 
  368         local join = stanza:get_child('x', MUC_NS);
  369         if not join then
  370             return;
  371         end
  372 
  373         local invitee = event.stanza.attr.from;
  374         local invitee_bare_jid = jid_bare(invitee);
  375         local _, invitee_domain = jid_split(invitee);
  376         local whitelistJoin = false;
  377 
  378         -- whitelist participants
  379         if whitelist:contains(invitee_domain) or whitelist:contains(invitee_bare_jid) then
  380             whitelistJoin = true;
  381         end
  382 
  383         local password = join:get_child_text('password', MUC_NS);
  384         if password and room:get_password() and password == room:get_password() then
  385             whitelistJoin = true;
  386         end
  387 
  388         if whitelistJoin then
  389             local affiliation = room:get_affiliation(invitee);
  390             if not affiliation or affiliation == 0 then
  391                 event.occupant.role = 'participant';
  392                 room:set_affiliation(true, invitee_bare_jid, 'member');
  393                 room:save();
  394 
  395                 return;
  396             end
  397         end
  398 
  399         -- we want to add the custom lobbyroom field to fill in the lobby room jid
  400         local invitee = event.stanza.attr.from;
  401         local affiliation = room:get_affiliation(invitee);
  402         if not affiliation or affiliation == 'none' then
  403             local reply = st.error_reply(stanza, 'auth', 'registration-required');
  404             reply.tags[1].attr.code = '407';
  405             reply:tag('lobbyroom', { xmlns = 'http://jitsi.org/jitmeet' }):text(room._data.lobbyroom):up():up();
  406 
  407             -- TODO: Drop this tag at some point (when all mobile clients and jigasi are updated), as this violates the rfc
  408             reply:tag('lobbyroom'):text(room._data.lobbyroom):up();
  409 
  410             event.origin.send(reply:tag('x', {xmlns = MUC_NS}));
  411             return true;
  412         end
  413     end, -4); -- the default hook on members_only module is on -5
  414 
  415     -- listens for invites for participants to join the main room
  416     host_module:hook('muc-invite', function(event)
  417         local room, stanza = event.room, event.stanza;
  418         local invitee = stanza.attr.to;
  419         local from = stanza:get_child('x', 'http://jabber.org/protocol/muc#user')
  420             :get_child('invite').attr.from;
  421 
  422         if lobby_muc_service and room._data.lobbyroom then
  423             local lobby_room_obj = lobby_muc_service.get_room_from_jid(room._data.lobbyroom);
  424             if lobby_room_obj then
  425                 local occupant = lobby_room_obj:get_occupant_by_real_jid(invitee);
  426                 if occupant then
  427                     local display_name = occupant:get_presence():get_child_text(
  428                             'nick', 'http://jabber.org/protocol/nick');
  429 
  430                     notify_lobby_access(room, from, occupant.nick, display_name, true);
  431                 end
  432             end
  433         end
  434     end);
  435 end);
  436 
  437 function handle_create_lobby(event)
  438     local room = event.room;
  439     room:set_members_only(true);
  440     attach_lobby_room(room)
  441 end
  442 
  443 function handle_destroy_lobby(event)
  444     destroy_lobby_room(event.room, event.newjid, event.message);
  445 end
  446 
  447 module:hook_global('config-reloaded', load_config);
  448 module:hook_global('create-lobby-room', handle_create_lobby);
  449 module:hook_global('destroy-lobby-room', handle_destroy_lobby);