"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-7305/resources/prosody-plugins/mod_external_services.lua" (26 May 2023, 6803 Bytes) of package /linux/misc/jitsi-meet-7305.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 
    2 local dt = require "util.datetime";
    3 local base64 = require "util.encodings".base64;
    4 local hashes = require "util.hashes";
    5 local st = require "util.stanza";
    6 local jid = require "util.jid";
    7 local array = require "util.array";
    8 local set = require "util.set";
    9 
   10 local default_host = module:get_option_string("external_service_host", module.host);
   11 local default_port = module:get_option_number("external_service_port");
   12 local default_secret = module:get_option_string("external_service_secret");
   13 local default_ttl = module:get_option_number("external_service_ttl", 86400);
   14 
   15 local configured_services = module:get_option_array("external_services", {});
   16 
   17 local access = module:get_option_set("external_service_access", {});
   18 
   19 -- https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
   20 local function behave_turn_rest_credentials(srv, item, secret)
   21     local ttl = default_ttl;
   22     if type(item.ttl) == "number" then
   23         ttl = item.ttl;
   24     end
   25     local expires = srv.expires or os.time() + ttl;
   26     local username;
   27     if type(item.username) == "string" then
   28         username = string.format("%d:%s", expires, item.username);
   29     else
   30         username = string.format("%d", expires);
   31     end
   32     srv.username = username;
   33     srv.password = base64.encode(hashes.hmac_sha1(secret, srv.username));
   34 end
   35 
   36 local algorithms = {
   37     turn = behave_turn_rest_credentials;
   38 }
   39 
   40 -- filter config into well-defined service records
   41 local function prepare(item)
   42     if type(item) ~= "table" then
   43         module:log("error", "Service definition is not a table: %q", item);
   44         return nil;
   45     end
   46 
   47     local srv = {
   48         type = nil;
   49         transport = nil;
   50         host = default_host;
   51         port = default_port;
   52         username = nil;
   53         password = nil;
   54         restricted = nil;
   55         expires = nil;
   56     };
   57 
   58     if type(item.type) == "string" then
   59         srv.type = item.type;
   60     else
   61         module:log("error", "Service missing mandatory 'type' field: %q", item);
   62         return nil;
   63     end
   64     if type(item.transport) == "string" then
   65         srv.transport = item.transport;
   66     end
   67     if type(item.host) == "string" then
   68         srv.host = item.host;
   69     end
   70     if type(item.port) == "number" then
   71         srv.port = item.port;
   72     end
   73     if type(item.username) == "string" then
   74         srv.username = item.username;
   75     end
   76     if type(item.password) == "string" then
   77         srv.password = item.password;
   78         srv.restricted = true;
   79     end
   80     if item.restricted == true then
   81         srv.restricted = true;
   82     end
   83     if type(item.expires) == "number" then
   84         srv.expires = item.expires;
   85     elseif type(item.ttl) == "number" then
   86         srv.expires = os.time() + item.ttl;
   87     end
   88     if (item.secret == true and default_secret) or type(item.secret) == "string" then
   89         local secret_cb = item.credentials_cb or algorithms[item.algorithm] or algorithms[srv.type];
   90         local secret = item.secret;
   91         if secret == true then
   92             secret = default_secret;
   93         end
   94         if secret_cb then
   95             secret_cb(srv, item, secret);
   96             srv.restricted = true;
   97         end
   98     end
   99     return srv;
  100 end
  101 
  102 function module.load()
  103     -- Trigger errors on startup
  104     local services = configured_services / prepare;
  105     if #services == 0 then
  106         module:log("warn", "No services configured or all had errors");
  107     end
  108 end
  109 
  110 -- Ensure only valid items are added in events
  111 local services_mt = {
  112     __index = getmetatable(array()).__index;
  113     __newindex = function (self, i, v)
  114         rawset(self, i, assert(prepare(v), "Invalid service entry added"));
  115     end;
  116 }
  117 
  118 function get_services()
  119     local extras = module:get_host_items("external_service");
  120     local services = ( configured_services + extras ) / prepare;
  121 
  122     setmetatable(services, services_mt);
  123 
  124     return services;
  125 end
  126 
  127 function services_xml(services, name, namespace)
  128     local reply = st.stanza(name or "services", { xmlns = namespace or "urn:xmpp:extdisco:2" });
  129 
  130     for _, srv in ipairs(services) do
  131         reply:tag("service", {
  132                 type = srv.type;
  133                 transport = srv.transport;
  134                 host = srv.host;
  135                 port = srv.port and string.format("%d", srv.port) or nil;
  136                 username = srv.username;
  137                 password = srv.password;
  138                 expires = srv.expires and dt.datetime(srv.expires) or nil;
  139                 restricted = srv.restricted and "1" or nil;
  140             }):up();
  141     end
  142 
  143     return reply;
  144 end
  145 
  146 local function handle_services(event)
  147     local origin, stanza = event.origin, event.stanza;
  148     local action = stanza.tags[1];
  149 
  150     local user_bare = jid.bare(stanza.attr.from);
  151     local user_host = jid.host(user_bare);
  152     if not ((access:empty() and origin.type == "c2s") or access:contains(user_bare) or access:contains(user_host)) then
  153         origin.send(st.error_reply(stanza, "auth", "forbidden"));
  154         return true;
  155     end
  156 
  157     local services = get_services();
  158 
  159     local requested_type = action.attr.type;
  160     if requested_type then
  161         services:filter(function(item)
  162             return item.type == requested_type;
  163         end);
  164     end
  165 
  166     module:fire_event("external_service/services", {
  167             origin = origin;
  168             stanza = stanza;
  169             requested_type = requested_type;
  170             services = services;
  171         });
  172 
  173     local reply = st.reply(stanza):add_child(services_xml(services, action.name, action.attr.xmlns));
  174 
  175     origin.send(reply);
  176     return true;
  177 end
  178 
  179 local function handle_credentials(event)
  180     local origin, stanza = event.origin, event.stanza;
  181     local action = stanza.tags[1];
  182 
  183     if origin.type ~= "c2s" then
  184         origin.send(st.error_reply(stanza, "auth", "forbidden", "The 'port' and 'type' attributes are required."));
  185         return true;
  186     end
  187 
  188     local services = get_services();
  189     services:filter(function (item)
  190         return item.restricted;
  191     end)
  192 
  193     local requested_credentials = set.new();
  194     for service in action:childtags("service") do
  195         if not service.attr.type or not service.attr.host then
  196             origin.send(st.error_reply(stanza, "modify", "bad-request"));
  197             return true;
  198         end
  199 
  200         requested_credentials:add(string.format("%s:%s:%d", service.attr.type, service.attr.host,
  201             tonumber(service.attr.port) or 0));
  202     end
  203 
  204     module:fire_event("external_service/credentials", {
  205             origin = origin;
  206             stanza = stanza;
  207             requested_credentials = requested_credentials;
  208             services = services;
  209         });
  210 
  211     services:filter(function (srv)
  212         local port_key = string.format("%s:%s:%d", srv.type, srv.host, srv.port or 0);
  213         local portless_key = string.format("%s:%s:%d", srv.type, srv.host, 0);
  214         return requested_credentials:contains(port_key) or requested_credentials:contains(portless_key);
  215     end);
  216 
  217     local reply = st.reply(stanza):add_child(services_xml(services, action.name, action.attr.xmlns));
  218 
  219     origin.send(reply);
  220     return true;
  221 end
  222 
  223 -- XEP-0215 v0.7
  224 module:add_feature("urn:xmpp:extdisco:2");
  225 module:hook("iq-get/host/urn:xmpp:extdisco:2:services", handle_services);
  226 module:hook("iq-get/host/urn:xmpp:extdisco:2:credentials", handle_credentials);
  227 
  228 -- COMPAT XEP-0215 v0.6
  229 -- Those still on the old version gets to deal with undefined attributes until they upgrade.
  230 module:add_feature("urn:xmpp:extdisco:1");
  231 module:hook("iq-get/host/urn:xmpp:extdisco:1:services", handle_services);
  232 module:hook("iq-get/host/urn:xmpp:extdisco:1:credentials", handle_credentials);