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_muc_poltergeist.lua 10KB

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