"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-7685/resources/prosody-plugins/mod_filter_iq_rayo.lua" (29 Nov 2023, 10566 Bytes) of package /linux/misc/jitsi-meet-7685.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 local new_throttle = require "util.throttle".create;
    2 local st = require "util.stanza";
    3 
    4 local token_util = module:require "token/util".new(module);
    5 local util = module:require 'util';
    6 local room_jid_match_rewrite = util.room_jid_match_rewrite;
    7 local is_feature_allowed = util.is_feature_allowed;
    8 local get_room_from_jid = util.get_room_from_jid;
    9 local is_healthcheck_room = util.is_healthcheck_room;
   10 local jid_bare = require "util.jid".bare;
   11 
   12 local sessions = prosody.full_sessions;
   13 
   14 local main_muc_component_host = module:get_option_string('main_muc');
   15 if main_muc_component_host == nil then
   16     module:log('error', 'main_muc not configured. Cannot proceed.');
   17     return;
   18 end
   19 local main_muc_service;
   20 
   21 -- no token configuration but required
   22 if token_util == nil then
   23     module:log("error", "no token configuration but it is required");
   24     return;
   25 end
   26 
   27 local um_is_admin = require 'core.usermanager'.is_admin;
   28 local function is_admin(jid)
   29     return um_is_admin(jid, module.host);
   30 end
   31 
   32 -- The maximum number of simultaneous calls,
   33 -- and also the maximum number of new calls per minute that a session is allowed to create.
   34 local limit_outgoing_calls;
   35 local function load_config()
   36     limit_outgoing_calls = module:get_option_number("max_number_outgoing_calls", -1);
   37 end
   38 load_config();
   39 
   40 -- Header names to use to push extra data extracted from token, if any
   41 local OUT_INITIATOR_USER_ATTR_NAME = "X-outbound-call-initiator-user";
   42 local OUT_INITIATOR_GROUP_ATTR_NAME = "X-outbound-call-initiator-group";
   43 local OUTGOING_CALLS_THROTTLE_INTERVAL = 60; -- if max_number_outgoing_calls is enabled it will be
   44                                              -- the max number of outgoing calls a user can try for a minute
   45 
   46 -- filters rayo iq in case of requested from not jwt authenticated sessions
   47 -- or if the session has features in user context and it doesn't mention
   48 -- feature "outbound-call" to be enabled
   49 module:hook("pre-iq/full", function(event)
   50     local stanza = event.stanza;
   51     if stanza.name == "iq" then
   52         local dial = stanza:get_child('dial', 'urn:xmpp:rayo:1');
   53         if dial then
   54             local session = event.origin;
   55             local token = session.auth_token;
   56 
   57             -- find header with attr name 'JvbRoomName' and extract its value
   58             local headerName = 'JvbRoomName';
   59             local roomName;
   60             for _, child in ipairs(dial.tags) do
   61                 if (child.name == 'header'
   62                         and child.attr.name == headerName) then
   63                     roomName = child.attr.value;
   64                     break;
   65                 end
   66             end
   67 
   68             if (token == nil
   69                 or roomName == nil
   70                 or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
   71                 or not is_feature_allowed(session.jitsi_meet_context_features,
   72                             (dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call')))
   73                 -- if current user is not allowed, but was granted moderation by a user
   74                 -- that is allowed by its features we want to allow it
   75                 and not is_feature_allowed(session.granted_jitsi_meet_context_features,
   76                                                 (dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call'))
   77             then
   78                 module:log("warn",
   79                     "Filtering stanza dial, stanza:%s", tostring(stanza));
   80                 session.send(st.error_reply(stanza, "auth", "forbidden"));
   81                 return true;
   82             end
   83 
   84             -- we get current user_id or group, or the one from the granted one
   85             -- so guests and the user that granted rights are sharing same limit, as guest can be without token
   86             local user_id, group_id = nil, session.jitsi_meet_context_group;
   87             if session.jitsi_meet_context_user then
   88                 user_id = session.jitsi_meet_context_user["id"];
   89             else
   90                 user_id = session.granted_jitsi_meet_context_user_id;
   91                 group_id = session.granted_jitsi_meet_context_group_id;
   92             end
   93 
   94             -- now lets check any limits if configured
   95             if limit_outgoing_calls > 0 then
   96                 if not session.dial_out_throttle then
   97                     module:log("debug", "Enabling dial-out throttle session=%s.", session);
   98                     session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
   99                 end
  100 
  101                 if not session.dial_out_throttle:poll(1) -- we first check the throttle so we can mark one incoming dial for the balance
  102                     or get_concurrent_outgoing_count(user_id, group_id) >= limit_outgoing_calls
  103                 then
  104                     module:log("warn",
  105                         "Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
  106                     session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
  107                     return true;
  108                 end
  109             end
  110 
  111             -- now lets insert token information if any
  112             if session and user_id then
  113                 -- First remove any 'header' element if it already
  114                 -- exists, so it cannot be spoofed by a client
  115                 stanza:maptags(
  116                     function(tag)
  117                         if tag.name == "header"
  118                                 and (tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME
  119                                         or tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME) then
  120                             return nil
  121                         end
  122                         return tag
  123                     end
  124                 )
  125 
  126                 local dial = stanza:get_child('dial', 'urn:xmpp:rayo:1');
  127                 -- adds initiator user id from token
  128                 dial:tag("header", {
  129                     xmlns = "urn:xmpp:rayo:1",
  130                     name = OUT_INITIATOR_USER_ATTR_NAME,
  131                     value = user_id });
  132                 dial:up();
  133 
  134                 -- Add the initiator group information if it is present
  135                 if session.jitsi_meet_context_group then
  136                     dial:tag("header", {
  137                         xmlns = "urn:xmpp:rayo:1",
  138                         name = OUT_INITIATOR_GROUP_ATTR_NAME,
  139                         value = session.jitsi_meet_context_group });
  140                     dial:up();
  141                 end
  142             end
  143         end
  144     end
  145 end);
  146 
  147 --- Finds and returns the number of concurrent outgoing calls for a user
  148 -- @param context_user the user id extracted from the token
  149 -- @param context_group the group id extracted from the token
  150 -- @return returns the count of concurrent calls
  151 function get_concurrent_outgoing_count(context_user, context_group)
  152     local count = 0;
  153     local rooms = main_muc_service.live_rooms();
  154 
  155     -- now lets iterate over rooms and occupants and search for
  156     -- call initiated by the user
  157     for room in rooms do
  158         for _, occupant in room:each_occupant() do
  159             for _, presence in occupant:each_session() do
  160 
  161                 local initiator = presence:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
  162 
  163                 local found_user = false;
  164                 local found_group = false;
  165 
  166                 if initiator then
  167                     initiator:maptags(function (tag)
  168                         if tag.name == "header"
  169                             and tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME then
  170                             found_user = tag.attr.value == context_user;
  171                         elseif tag.name == "header"
  172                             and tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME then
  173                             found_group = tag.attr.value == context_group;
  174                         end
  175 
  176                         return tag;
  177                     end );
  178                     -- if found a jigasi participant initiated by the concurrent
  179                     -- participant, count it
  180                     if found_user
  181                         and (context_group == nil or found_group) then
  182                         count = count + 1;
  183                     end
  184                 end
  185             end
  186         end
  187     end
  188 
  189     return count;
  190 end
  191 
  192 module:hook_global('config-reloaded', load_config);
  193 
  194 -- process a host module directly if loaded or hooks to wait for its load
  195 function process_host_module(name, callback)
  196     local function process_host(host)
  197         if host == name then
  198             callback(module:context(host), host);
  199         end
  200     end
  201 
  202     if prosody.hosts[name] == nil then
  203         module:log('debug', 'No host/component found, will wait for it: %s', name)
  204 
  205         -- when a host or component is added
  206         prosody.events.add_handler('host-activated', process_host);
  207     else
  208         process_host(name);
  209     end
  210 end
  211 
  212 function process_set_affiliation(event)
  213     local actor, affiliation, jid, previous_affiliation, room
  214         = event.actor, event.affiliation, event.jid, event.previous_affiliation, event.room;
  215     local actor_session = sessions[actor];
  216 
  217     if is_admin(jid) or is_healthcheck_room(room.jid) or not actor or not previous_affiliation
  218         or not actor_session or not actor_session.jitsi_meet_context_features then
  219         return;
  220     end
  221 
  222     local occupant;
  223     for _, o in room:each_occupant() do
  224         if o.bare_jid == jid then
  225             occupant = o;
  226         end
  227     end
  228 
  229     if not occupant then
  230         return;
  231     end
  232 
  233     local occupant_session = sessions[occupant.jid];
  234     if not occupant_session then
  235         return;
  236     end
  237 
  238     if previous_affiliation == 'none' and affiliation == 'owner' then
  239         occupant_session.granted_jitsi_meet_context_features = actor_session.jitsi_meet_context_features;
  240         occupant_session.granted_jitsi_meet_context_user_id = actor_session.jitsi_meet_context_user["id"];
  241         occupant_session.granted_jitsi_meet_context_group_id = actor_session.jitsi_meet_context_group;
  242     elseif previous_affiliation == 'owner' and ( affiliation == 'member' or affiliation == 'none' ) then
  243         occupant_session.granted_jitsi_meet_context_features = nil;
  244         occupant_session.granted_jitsi_meet_context_user_id = nil;
  245         occupant_session.granted_jitsi_meet_context_group_id = nil;
  246     end
  247 end
  248 
  249 process_host_module(main_muc_component_host, function(host_module, host)
  250     main_muc_service = prosody.hosts[host].modules.muc;
  251 
  252     host_module:hook("muc-pre-set-affiliation", process_set_affiliation);
  253 end);