You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_auth_token.lua 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. -- Token authentication
  2. -- Copyright (C) 2015 Atlassian
  3. local basexx = require 'basexx';
  4. local have_async, async = pcall(require, "util.async");
  5. local formdecode = require "util.http".formdecode;
  6. local generate_uuid = require "util.uuid".generate;
  7. local http = require "net.http";
  8. local json = require 'cjson'
  9. json.encode_empty_table('array')
  10. local new_sasl = require "util.sasl".new;
  11. local sasl = require "util.sasl";
  12. local timer = require "util.timer";
  13. local token_util = module:require "token/util";
  14. -- define auth provider
  15. local provider = {};
  16. local host = module.host;
  17. local appId = module:get_option_string("app_id");
  18. local appSecret = module:get_option_string("app_secret");
  19. local asapKeyServer = module:get_option_string("asap_key_server");
  20. local allowEmptyToken = module:get_option_boolean("allow_empty_token");
  21. local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
  22. -- TODO: Figure out a less arbitrary default cache size.
  23. local cacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
  24. local cache = require"util.cache".new(cacheSize);
  25. if allowEmptyToken == true then
  26. module:log("warn", "WARNING - empty tokens allowed");
  27. end
  28. if appId == nil then
  29. module:log("error", "'app_id' must not be empty");
  30. return;
  31. end
  32. if appSecret == nil and asapKeyServer == nil then
  33. module:log("error", "'app_secret' or 'asap_key_server' must be specified");
  34. return;
  35. end
  36. if asapKeyServer and not have_async then
  37. module:log("error", "requires a version of Prosody with util.async");
  38. return;
  39. end
  40. -- Extract 'token' param from BOSH URL when session is created
  41. module:hook("bosh-session", function(event)
  42. local session, request = event.session, event.request;
  43. local query = request.url.query;
  44. if query ~= nil then
  45. session.auth_token = query and formdecode(query).token or nil;
  46. end
  47. end)
  48. function provider.test_password(username, password)
  49. return nil, "Password based auth not supported";
  50. end
  51. function provider.get_password(username)
  52. return nil;
  53. end
  54. function provider.set_password(username, password)
  55. return nil, "Set password not supported";
  56. end
  57. function provider.user_exists(username)
  58. return nil;
  59. end
  60. function provider.create_user(username, password)
  61. return nil;
  62. end
  63. function provider.delete_user(username)
  64. return nil;
  65. end
  66. local http_timeout = 30;
  67. local http_headers = {
  68. ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
  69. };
  70. function get_public_key(keyId)
  71. local content = cache:get(keyId);
  72. if content == nil then
  73. -- If the key is not found in the cache.
  74. module:log("debug", "Cache miss for key: "..keyId);
  75. local code;
  76. local wait, done = async.waiter();
  77. local function cb(content_, code_, response_, request_)
  78. content, code = content_, code_;
  79. done();
  80. end
  81. module:log("debug", "Fetching public key from: "..asapKeyServer..keyId);
  82. local request = http.request(asapKeyServer..keyId, {
  83. headers = http_headers or {},
  84. method = "GET"
  85. }, cb);
  86. -- TODO: Is the done() call racey? Can we cancel this if the request
  87. -- succeedes?
  88. timer.add_task(http_timeout, function() http.destroy_request(request); done(); end);
  89. wait();
  90. if code == 200 or code == 204 then
  91. module:log("debug", "Cache hit for key: "..keyId);
  92. return content;
  93. end
  94. else
  95. -- If the key is in the cache, use it.
  96. return content;
  97. end
  98. return nil;
  99. end
  100. function provider.get_sasl_handler(session)
  101. -- JWT token extracted from BOSH URL
  102. local token = session.auth_token;
  103. local function get_username_from_token(self, message)
  104. if token == nil then
  105. if allowEmptyToken then
  106. return true;
  107. else
  108. return false, "not-allowed", "token required";
  109. end
  110. end
  111. local pubKey;
  112. if asapKeyServer and session.auth_token ~= nil then
  113. local dotFirst = session.auth_token:find("%.")
  114. if not dotFirst then return nil, "Invalid token" end
  115. local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)))
  116. local kid = header["kid"]
  117. if kid == nil then
  118. return false, "not-allowed", "'kid' claim is missing";
  119. end
  120. pubKey = get_public_key(kid);
  121. if pubKey == nil then
  122. return false, "not-allowed", "could not obtain public key";
  123. end
  124. end
  125. -- now verify the whole token
  126. local result, msg;
  127. if asapKeyServer then
  128. result, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints);
  129. else
  130. result, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints);
  131. end
  132. if result == true then
  133. -- Binds room name to the session which is later checked on MUC join
  134. session.jitsi_meet_room = room;
  135. return true;
  136. else
  137. return false, "not-allowed", msg
  138. end
  139. end
  140. return new_sasl(host, { anonymous = get_username_from_token });
  141. end
  142. module:provides("auth", provider);
  143. local function anonymous(self, message)
  144. local username = generate_uuid();
  145. -- This calls the handler created in 'provider.get_sasl_handler(session)'
  146. local result, err, msg = self.profile.anonymous(self, username, self.realm);
  147. self.username = username;
  148. if result == true then
  149. return "success"
  150. else
  151. return "failure", err, msg
  152. end
  153. end
  154. sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);