您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

mod_muc_poltergeist.lua 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. local bare = require "util.jid".bare;
  2. local get_room_by_name_and_subdomain = module:require "util".get_room_by_name_and_subdomain;
  3. local jid = require "util.jid";
  4. local neturl = require "net.url";
  5. local parse = neturl.parseQuery;
  6. local poltergeist = module:require "poltergeist";
  7. local have_async = pcall(require, "util.async");
  8. if not have_async then
  9. module:log("error", "requires a version of Prosody with util.async");
  10. return;
  11. end
  12. module:depends("jitsi_session");
  13. local async_handler_wrapper = module:require "util".async_handler_wrapper;
  14. -- Options
  15. local poltergeist_component
  16. = module:get_option_string("poltergeist_component", module.host);
  17. -- this basically strips the domain from the conference.domain address
  18. local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")();
  19. if parentHostName == nil then
  20. log("error", "Failed to start - unable to get parent hostname");
  21. return;
  22. end
  23. local parentCtx = module:context(parentHostName);
  24. if parentCtx == nil then
  25. log("error",
  26. "Failed to start - unable to get parent context for host: %s",
  27. tostring(parentHostName));
  28. return;
  29. end
  30. local token_util = module:require "token/util".new(parentCtx);
  31. -- option to enable/disable token verifications
  32. local disableTokenVerification
  33. = module:get_option_boolean("disable_polergeist_token_verification", false);
  34. -- poltergaist management functions
  35. --- Verifies room name, domain name with the values in the token
  36. -- @param token the token we received
  37. -- @param room_name the room name
  38. -- @param group name of the group (optional)
  39. -- @param session the session to use for storing token specific fields
  40. -- @return true if values are ok or false otherwise
  41. function verify_token(token, room_name, group, session)
  42. if disableTokenVerification then
  43. return true;
  44. end
  45. -- if not disableTokenVerification and we do not have token
  46. -- stop here, cause the main virtual host can have guest access enabled
  47. -- (allowEmptyToken = true) and we will allow access to rooms info without
  48. -- a token
  49. if token == nil then
  50. log("warn", "no token provided");
  51. return false;
  52. end
  53. session.auth_token = token;
  54. local verified, reason = token_util:process_and_verify_token(session);
  55. if not verified then
  56. log("warn", "not a valid token %s", tostring(reason));
  57. return false;
  58. end
  59. local room_address = jid.join(room_name, module:get_host());
  60. -- if there is a group we are in multidomain mode and that group is not
  61. -- our parent host
  62. if group and group ~= "" and group ~= parentHostName then
  63. room_address = "["..group.."]"..room_address;
  64. end
  65. if not token_util:verify_room(session, room_address) then
  66. log("warn", "Token %s not allowed to join: %s",
  67. tostring(token), tostring(room_address));
  68. return false;
  69. end
  70. return true;
  71. end
  72. -- Event handlers
  73. -- if we found that a session for a user with id has a poltergiest already
  74. -- created, retrieve its jid and return it to the authentication
  75. -- so we can reuse it and we that real user will replace the poltergiest
  76. prosody.events.add_handler("pre-jitsi-authentication", function(session)
  77. if (session.jitsi_meet_context_user) then
  78. local room = get_room_by_name_and_subdomain(
  79. session.jitsi_web_query_room,
  80. session.jitsi_web_query_prefix);
  81. if (not room) then
  82. return nil;
  83. end
  84. local username = poltergeist.get_username(
  85. room,
  86. session.jitsi_meet_context_user["id"]
  87. );
  88. if (not username) then
  89. return nil;
  90. end
  91. log("debug", "Found predefined username %s", username);
  92. -- let's find the room and if the poltergeist occupant is there
  93. -- lets remove him before the real participant joins
  94. -- when we see the unavailable presence to go out the server
  95. -- we will mark it with ignore tag
  96. local nick = poltergeist.create_nick(username);
  97. if (poltergeist.occupies(room, nick)) then
  98. module:log("info", "swapping poltergeist for user: %s/%s", room, nick)
  99. -- notify that user connected using the poltergeist
  100. poltergeist.update(room, nick, "connected");
  101. poltergeist.remove(room, nick, true);
  102. end
  103. return username;
  104. end
  105. return nil;
  106. end);
  107. --- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100,
  108. --- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100.
  109. module:hook("muc-decline", function (event)
  110. poltergeist.remove(event.room, bare(event.stanza.attr.from), false);
  111. end, -100);
  112. -- before sending the presence for a poltergeist leaving add ignore tag
  113. -- as poltergeist is leaving just before the real user joins and in the client
  114. -- we ignore this presence to avoid leaving/joining experience and the real
  115. -- user will reuse all currently created UI components for the same nick
  116. module:hook("muc-broadcast-presence", function (event)
  117. if (bare(event.occupant.jid) == poltergeist_component) then
  118. if(event.stanza.attr.type == "unavailable"
  119. and poltergeist.should_ignore(event.occupant.nick)) then
  120. event.stanza:tag(
  121. "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
  122. poltergeist.reset_ignored(event.occupant.nick);
  123. end
  124. end
  125. end, -100);
  126. -- cleanup room table after room is destroyed
  127. module:hook(
  128. "muc-room-destroyed",
  129. function(event)
  130. poltergeist.remove_room(event.room);
  131. end
  132. );
  133. --- Handles request for creating/managing poltergeists
  134. -- @param event the http event, holds the request query
  135. -- @return GET response, containing a json with response details
  136. function handle_create_poltergeist (event)
  137. if (not event.request.url.query) then
  138. return { status_code = 400; };
  139. end
  140. local params = parse(event.request.url.query);
  141. local user_id = params["user"];
  142. local room_name = params["room"];
  143. local group = params["group"];
  144. local name = params["name"];
  145. local avatar = params["avatar"];
  146. local status = params["status"];
  147. local conversation = params["conversation"];
  148. local session = {};
  149. if not verify_token(params["token"], room_name, group, session) then
  150. return { status_code = 403; };
  151. end
  152. -- If the provided room conference doesn't exist then we
  153. -- can't add a poltergeist to it.
  154. local room = get_room_by_name_and_subdomain(room_name, group);
  155. if (not room) then
  156. log("error", "no room found %s", room_name);
  157. return { status_code = 404; };
  158. end
  159. -- If the poltergiest is already in the conference then it will
  160. -- be in our username store and another can't be added.
  161. local username = poltergeist.get_username(room, user_id);
  162. if (username ~=nil and
  163. poltergeist.occupies(room, poltergeist.create_nick(username))) then
  164. log("warn",
  165. "poltergeist for username:%s already in the room:%s",
  166. username,
  167. room_name
  168. );
  169. return { status_code = 202; };
  170. end
  171. local context = {
  172. user = {
  173. id = user_id;
  174. };
  175. group = group;
  176. creator_user = session.jitsi_meet_context_user;
  177. creator_group = session.jitsi_meet_context_group;
  178. };
  179. if avatar ~= nil then
  180. context.user.avatar = avatar
  181. end
  182. local resources = {};
  183. if conversation ~= nil then
  184. resources["conversation"] = conversation
  185. end
  186. poltergeist.add_to_muc(room, user_id, name, avatar, context, status, resources)
  187. return { status_code = 200; };
  188. end
  189. --- Handles request for updating poltergeists status
  190. -- @param event the http event, holds the request query
  191. -- @return GET response, containing a json with response details
  192. function handle_update_poltergeist (event)
  193. if (not event.request.url.query) then
  194. return { status_code = 400; };
  195. end
  196. local params = parse(event.request.url.query);
  197. local user_id = params["user"];
  198. local room_name = params["room"];
  199. local group = params["group"];
  200. local status = params["status"];
  201. local call_id = params["callid"];
  202. local call_cancel = false
  203. if params["callcancel"] == "true" then
  204. call_cancel = true;
  205. end
  206. if not verify_token(params["token"], room_name, group, {}) then
  207. return { status_code = 403; };
  208. end
  209. local room = get_room_by_name_and_subdomain(room_name, group);
  210. if (not room) then
  211. log("error", "no room found %s", room_name);
  212. return { status_code = 404; };
  213. end
  214. local username = poltergeist.get_username(room, user_id);
  215. if (not username) then
  216. return { status_code = 404; };
  217. end
  218. local call_details = {
  219. ["cancel"] = call_cancel;
  220. ["id"] = call_id;
  221. };
  222. local nick = poltergeist.create_nick(username);
  223. if (not poltergeist.occupies(room, nick)) then
  224. return { status_code = 404; };
  225. end
  226. poltergeist.update(room, nick, status, call_details);
  227. return { status_code = 200; };
  228. end
  229. --- Handles remove poltergeists
  230. -- @param event the http event, holds the request query
  231. -- @return GET response, containing a json with response details
  232. function handle_remove_poltergeist (event)
  233. if (not event.request.url.query) then
  234. return { status_code = 400; };
  235. end
  236. local params = parse(event.request.url.query);
  237. local user_id = params["user"];
  238. local room_name = params["room"];
  239. local group = params["group"];
  240. if not verify_token(params["token"], room_name, group, {}) then
  241. return { status_code = 403; };
  242. end
  243. local room = get_room_by_name_and_subdomain(room_name, group);
  244. if (not room) then
  245. log("error", "no room found %s", room_name);
  246. return { status_code = 404; };
  247. end
  248. local username = poltergeist.get_username(room, user_id);
  249. if (not username) then
  250. return { status_code = 404; };
  251. end
  252. local nick = poltergeist.create_nick(username);
  253. if (not poltergeist.occupies(room, nick)) then
  254. return { status_code = 404; };
  255. end
  256. poltergeist.remove(room, nick, false);
  257. return { status_code = 200; };
  258. end
  259. log("info", "Loading poltergeist service");
  260. module:depends("http");
  261. module:provides("http", {
  262. default_path = "/";
  263. name = "poltergeist";
  264. route = {
  265. ["GET /poltergeist/create"] = function (event) return async_handler_wrapper(event,handle_create_poltergeist) end;
  266. ["GET /poltergeist/update"] = function (event) return async_handler_wrapper(event,handle_update_poltergeist) end;
  267. ["GET /poltergeist/remove"] = function (event) return async_handler_wrapper(event,handle_remove_poltergeist) end;
  268. };
  269. });