"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-6312/resources/prosody-plugins/token/util.lib.lua" (4 Jul 2022, 14473 Bytes) of package /linux/misc/jitsi-meet-6312.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 -- Token authentication
    2 -- Copyright (C) 2015 Atlassian
    3 
    4 local basexx = require "basexx";
    5 local have_async, async = pcall(require, "util.async");
    6 local hex = require "util.hex";
    7 local jwt = module:require "luajwtjitsi";
    8 local jid = require "util.jid";
    9 local json_safe = require "cjson.safe";
   10 local path = require "util.paths";
   11 local sha256 = require "util.hashes".sha256;
   12 local main_util = module:require "util";
   13 local http_get_with_retry = main_util.http_get_with_retry;
   14 local extract_subdomain = main_util.extract_subdomain;
   15 
   16 local nr_retries = 3;
   17 
   18 -- TODO: Figure out a less arbitrary default cache size.
   19 local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
   20 
   21 local Util = {}
   22 Util.__index = Util
   23 
   24 --- Constructs util class for token verifications.
   25 -- Constructor that uses the passed module to extract all the
   26 -- needed configurations.
   27 -- If confuguration is missing returns nil
   28 -- @param module the module in which options to check for configs.
   29 -- @return the new instance or nil
   30 function Util.new(module)
   31     local self = setmetatable({}, Util)
   32 
   33     self.appId = module:get_option_string("app_id");
   34     self.appSecret = module:get_option_string("app_secret");
   35     self.asapKeyServer = module:get_option_string("asap_key_server");
   36     self.signatureAlgorithm = module:get_option_string("signature_algorithm");
   37     self.allowEmptyToken = module:get_option_boolean("allow_empty_token");
   38 
   39     self.cache = require"util.cache".new(cacheSize);
   40 
   41     --[[
   42         Multidomain can be supported in some deployments. In these deployments
   43         there is a virtual conference muc, which address contains the subdomain
   44         to use. Those deployments are accessible
   45         by URL https://domain/subdomain.
   46         Then the address of the room will be:
   47         roomName@conference.subdomain.domain. This is like a virtual address
   48         where there is only one muc configured by default with address:
   49         conference.domain and the actual presentation of the room in that muc
   50         component is [subdomain]roomName@conference.domain.
   51         These setups relay on configuration 'muc_domain_base' which holds
   52         the main domain and we use it to substract subdomains from the
   53         virtual addresses.
   54         The following confgurations are for multidomain setups and domain name
   55         verification:
   56      --]]
   57 
   58     -- optional parameter for custom muc component prefix,
   59     -- defaults to "conference"
   60     self.muc_domain_prefix = module:get_option_string(
   61         "muc_mapper_domain_prefix", "conference");
   62     -- domain base, which is the main domain used in the deployment,
   63     -- the main VirtualHost for the deployment
   64     self.muc_domain_base = module:get_option_string("muc_mapper_domain_base");
   65     -- The "real" MUC domain that we are proxying to
   66     if self.muc_domain_base then
   67         self.muc_domain = module:get_option_string(
   68             "muc_mapper_domain",
   69             self.muc_domain_prefix.."."..self.muc_domain_base);
   70     end
   71     -- whether domain name verification is enabled, by default it is disabled
   72     self.enableDomainVerification = module:get_option_boolean(
   73         "enable_domain_verification", false);
   74 
   75     if self.allowEmptyToken == true then
   76         module:log("warn", "WARNING - empty tokens allowed");
   77     end
   78 
   79     if self.appId == nil then
   80         module:log("error", "'app_id' must not be empty");
   81         return nil;
   82     end
   83 
   84     if self.appSecret == nil and self.asapKeyServer == nil then
   85         module:log("error", "'app_secret' or 'asap_key_server' must be specified");
   86         return nil;
   87     end
   88 
   89     -- Set defaults for signature algorithm
   90     if self.signatureAlgorithm == nil then
   91         if self.asapKeyServer ~= nil then
   92             self.signatureAlgorithm = "RS256"
   93         elseif self.appSecret ~= nil then
   94             self.signatureAlgorithm = "HS256"
   95         end
   96     end
   97 
   98     --array of accepted issuers: by default only includes our appId
   99     self.acceptedIssuers = module:get_option_array('asap_accepted_issuers',{self.appId})
  100 
  101     --array of accepted audiences: by default only includes our appId
  102     self.acceptedAudiences = module:get_option_array('asap_accepted_audiences',{'*'})
  103 
  104     self.requireRoomClaim = module:get_option_boolean('asap_require_room_claim', true);
  105 
  106     if self.asapKeyServer and not have_async then
  107         module:log("error", "requires a version of Prosody with util.async");
  108         return nil;
  109     end
  110 
  111     return self
  112 end
  113 
  114 function Util:set_asap_key_server(asapKeyServer)
  115     self.asapKeyServer = asapKeyServer;
  116 end
  117 
  118 function Util:set_asap_accepted_issuers(acceptedIssuers)
  119     self.acceptedIssuers = acceptedIssuers;
  120 end
  121 
  122 function Util:set_asap_accepted_audiences(acceptedAudiences)
  123     self.acceptedAudiences = acceptedAudiences;
  124 end
  125 
  126 function Util:set_asap_require_room_claim(checkRoom)
  127     self.requireRoomClaim = checkRoom;
  128 end
  129 
  130 function Util:clear_asap_cache()
  131     self.cache = require"util.cache".new(cacheSize);
  132 end
  133 
  134 --- Returns the public key by keyID
  135 -- @param keyId the key ID to request
  136 -- @return the public key (the content of requested resource) or nil
  137 function Util:get_public_key(keyId)
  138     local content = self.cache:get(keyId);
  139     if content == nil then
  140         -- If the key is not found in the cache.
  141         module:log("debug", "Cache miss for key: %s", keyId);
  142         local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
  143         module:log("debug", "Fetching public key from: %s", keyurl);
  144         content = http_get_with_retry(keyurl, nr_retries);
  145         if content ~= nil then
  146             self.cache:set(keyId, content);
  147         end
  148         return content;
  149     else
  150         -- If the key is in the cache, use it.
  151         module:log("debug", "Cache hit for key: %s", keyId);
  152         return content;
  153     end
  154 end
  155 
  156 --- Verifies token and process needed values to be stored in the session.
  157 -- Token is obtained from session.auth_token.
  158 -- Stores in session the following values:
  159 -- session.jitsi_meet_room - the room name value from the token
  160 -- session.jitsi_meet_domain - the domain name value from the token
  161 -- session.jitsi_meet_context_user - the user details from the token
  162 -- session.jitsi_meet_context_group - the group value from the token
  163 -- session.jitsi_meet_context_features - the features value from the token
  164 -- @param session the current session
  165 -- @param acceptedIssuers optional list of accepted issuers to check
  166 -- @return false and error
  167 function Util:process_and_verify_token(session, acceptedIssuers)
  168     if not acceptedIssuers then
  169         acceptedIssuers = self.acceptedIssuers;
  170     end
  171 
  172     if session.auth_token == nil then
  173         if self.allowEmptyToken then
  174             return true;
  175         else
  176             return false, "not-allowed", "token required";
  177         end
  178     end
  179 
  180     local key;
  181     if session.public_key then
  182         -- We're using an public key stored in the session
  183         module:log("debug","Public key was found on the session");
  184         key = session.public_key;
  185     elseif self.asapKeyServer and session.auth_token ~= nil then
  186         -- We're fetching an public key from an ASAP server
  187         local dotFirst = session.auth_token:find("%.");
  188         if not dotFirst then return false, "not-allowed", "Invalid token" end
  189         local header, err = json_safe.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)));
  190         if err then
  191             return false, "not-allowed", "bad token format";
  192         end
  193         local kid = header["kid"];
  194         if kid == nil then
  195             return false, "not-allowed", "'kid' claim is missing";
  196         end
  197         local alg = header["alg"];
  198         if alg == nil then
  199             return false, "not-allowed", "'alg' claim is missing";
  200         end
  201         if alg.sub(alg,1,2) ~= "RS" then
  202             return false, "not-allowed", "'kid' claim only support with RS family";
  203         end
  204         key = self:get_public_key(kid);
  205         if key == nil then
  206             return false, "not-allowed", "could not obtain public key";
  207         end
  208     elseif self.appSecret ~= nil then
  209         -- We're using a symmetric secret
  210         key = self.appSecret
  211     end
  212 
  213     if key == nil then
  214         return false, "not-allowed", "signature verification key is missing";
  215     end
  216 
  217     -- now verify the whole token
  218     local claims, msg = jwt.verify(
  219         session.auth_token,
  220         self.signatureAlgorithm,
  221         key,
  222         acceptedIssuers,
  223         self.acceptedAudiences
  224     )
  225     if claims ~= nil then
  226         if self.requireRoomClaim then
  227             local roomClaim = claims["room"];
  228             if roomClaim == nil then
  229                 return false, "'room' claim is missing";
  230             end
  231         end
  232 
  233         -- Binds room name to the session which is later checked on MUC join
  234         session.jitsi_meet_room = claims["room"];
  235         -- Binds domain name to the session
  236         session.jitsi_meet_domain = claims["sub"];
  237 
  238         -- Binds the user details to the session if available
  239         if claims["context"] ~= nil then
  240           if claims["context"]["user"] ~= nil then
  241             session.jitsi_meet_context_user = claims["context"]["user"];
  242           end
  243 
  244           if claims["context"]["group"] ~= nil then
  245             -- Binds any group details to the session
  246             session.jitsi_meet_context_group = claims["context"]["group"];
  247           end
  248 
  249           if claims["context"]["features"] ~= nil then
  250             -- Binds any features details to the session
  251             session.jitsi_meet_context_features = claims["context"]["features"];
  252           end
  253           if claims["context"]["room"] ~= nil then
  254             session.jitsi_meet_context_room = claims["context"]["room"]
  255           end
  256         end
  257         return true;
  258     else
  259         return false, "not-allowed", msg;
  260     end
  261 end
  262 
  263 --- Verifies room name and domain if necesarry.
  264 -- Checks configs and if necessary checks the room name extracted from
  265 -- room_address against the one saved in the session when token was verified.
  266 -- Also verifies domain name from token against the domain in the room_address,
  267 -- if enableDomainVerification is enabled.
  268 -- @param session the current session
  269 -- @param room_address the whole room address as received
  270 -- @return returns true in case room was verified or there is no need to verify
  271 --         it and returns false in case verification was processed
  272 --         and was not successful
  273 function Util:verify_room(session, room_address)
  274     if self.allowEmptyToken and session.auth_token == nil then
  275         module:log(
  276             "debug",
  277             "Skipped room token verification - empty tokens are allowed");
  278         return true;
  279     end
  280 
  281     -- extract room name using all chars, except the not allowed ones
  282     local room,_,_ = jid.split(room_address);
  283     if room == nil then
  284         log("error",
  285             "Unable to get name of the MUC room ? to: %s", room_address);
  286         return true;
  287     end
  288 
  289     local auth_room = session.jitsi_meet_room;
  290     if auth_room then
  291         auth_room = string.lower(auth_room);
  292     end
  293     if not self.enableDomainVerification then
  294         -- if auth_room is missing, this means user is anonymous (no token for
  295         -- its domain) we let it through, jicofo is verifying creation domain
  296         if auth_room and room ~= auth_room and auth_room ~= '*' then
  297             return false;
  298         end
  299 
  300         return true;
  301     end
  302 
  303     local room_address_to_verify = jid.bare(room_address);
  304     local room_node = jid.node(room_address);
  305     -- parses bare room address, for multidomain expected format is:
  306     -- [subdomain]roomName@conference.domain
  307     local target_subdomain, target_room = extract_subdomain(room_node);
  308 
  309     -- if we have '*' as room name in token, this means all rooms are allowed
  310     -- so we will use the actual name of the room when constructing strings
  311     -- to verify subdomains and domains to simplify checks
  312     local room_to_check;
  313     if auth_room == '*' then
  314         -- authorized for accessing any room assign to room_to_check the actual
  315         -- room name
  316         if target_room ~= nil then
  317             -- we are in multidomain mode and we were able to extract room name
  318             room_to_check = target_room;
  319         else
  320             -- no target_room, room_address_to_verify does not contain subdomain
  321             -- so we get just the node which is the room name
  322             room_to_check = room_node;
  323         end
  324     else
  325         -- no wildcard, so check room against authorized room from the token
  326         if session.jitsi_meet_context_room and (session.jitsi_meet_context_room["regex"] == true or session.jitsi_meet_context_room["regex"] == "true") then
  327             if target_room ~= nil then
  328                 -- room with subdomain
  329                 room_to_check = target_room:match(auth_room);
  330             else
  331                 room_to_check = room_node:match(auth_room);
  332             end
  333         else
  334             -- not a regex
  335             room_to_check = auth_room;
  336         end
  337         module:log("debug", "room to check: %s", room_to_check)
  338         if not room_to_check then
  339             return false
  340         end
  341     end
  342 
  343     local auth_domain = string.lower(session.jitsi_meet_domain);
  344     local subdomain_to_check;
  345     if target_subdomain then
  346         if auth_domain == '*' then
  347             -- check for wildcard in JWT claim, allow access if found
  348             subdomain_to_check = target_subdomain;
  349         else
  350             -- no wildcard in JWT claim, so check subdomain against sub in token
  351             subdomain_to_check = auth_domain;
  352         end
  353         -- from this point we depend on muc_domain_base,
  354         -- deny access if option is missing
  355         if not self.muc_domain_base then
  356             module:log("warn", "No 'muc_domain_base' option set, denying access!");
  357             return false;
  358         end
  359 
  360         return room_address_to_verify == jid.join(
  361             "["..subdomain_to_check.."]"..room_to_check, self.muc_domain);
  362     else
  363         if auth_domain == '*' then
  364             -- check for wildcard in JWT claim, allow access if found
  365             subdomain_to_check = self.muc_domain;
  366         else
  367             -- no wildcard in JWT claim, so check subdomain against sub in token
  368             subdomain_to_check = self.muc_domain_prefix.."."..auth_domain;
  369         end
  370         -- we do not have a domain part (multidomain is not enabled)
  371         -- verify with info from the token
  372         return room_address_to_verify == jid.join(room_to_check, subdomain_to_check);
  373     end
  374 end
  375 
  376 return Util;