Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

mod_muc_poltergeist.lua 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. local bare = require "util.jid".bare;
  2. local generate_uuid = require "util.uuid".generate;
  3. local jid = require "util.jid";
  4. local neturl = require "net.url";
  5. local parse = neturl.parseQuery;
  6. local st = require "util.stanza";
  7. local get_room_from_jid = module:require "util".get_room_from_jid;
  8. local timer = require "util.timer";
  9. -- Options
  10. local poltergeist_component
  11. = module:get_option_string("poltergeist_component", module.host);
  12. -- defaults to 3 min
  13. local poltergeist_timeout
  14. = module:get_option_string("poltergeist_leave_timeout", 180);
  15. -- this basically strips the domain from the conference.domain address
  16. local parentHostName = string.gmatch(tostring(module.host), "%w+.(%w.+)")();
  17. if parentHostName == nil then
  18. log("error", "Failed to start - unable to get parent hostname");
  19. return;
  20. end
  21. local parentCtx = module:context(parentHostName);
  22. if parentCtx == nil then
  23. log("error",
  24. "Failed to start - unable to get parent context for host: %s",
  25. tostring(parentHostName));
  26. return;
  27. end
  28. local token_util = module:require "token/util".new(parentCtx);
  29. -- option to enable/disable token verifications
  30. local disableTokenVerification
  31. = module:get_option_boolean("disable_polergeist_token_verification", false);
  32. -- table to store all poltergeists we create
  33. local poltergeists = {};
  34. -- table to mark that outgoing unavailable presences
  35. -- should be marked with ignore
  36. local poltergeists_pr_ignore = {};
  37. -- poltergaist management functions
  38. -- Returns the room if available, work and in multidomain mode
  39. -- @param room_name the name of the room
  40. -- @param group name of the group (optional)
  41. -- @return returns room if found or nil
  42. function get_room(room_name, group)
  43. local room_address = jid.join(room_name, module:get_host());
  44. -- if there is a group we are in multidomain mode and that group is not
  45. -- our parent host
  46. if group and group ~= "" and group ~= parentHostName then
  47. room_address = "["..group.."]"..room_address;
  48. end
  49. return get_room_from_jid(room_address);
  50. end
  51. -- Stores the username in the table where we store poltergeist usernames
  52. -- based on their room names
  53. -- @param room the room instance
  54. -- @param user_id the user id
  55. -- @param username the username to store
  56. function store_username(room, user_id, username)
  57. local room_name = jid.node(room.jid);
  58. -- we store in poltergeist user ids for room names
  59. if (not poltergeists[room_name]) then
  60. poltergeists[room_name] = {};
  61. end
  62. poltergeists[room_name][user_id] = username;
  63. log("debug", "stored in session: %s", username);
  64. end
  65. -- Retrieve the username for a user
  66. -- @param room the room instance
  67. -- @param user_id the user id
  68. -- @return returns the stored username for user or nil
  69. function get_username(room, user_id)
  70. local room_name = jid.node(room.jid);
  71. if (not poltergeists[room_name]) then
  72. return nil;
  73. end
  74. return poltergeists[room_name][user_id];
  75. end
  76. -- Removes poltergeist values from table
  77. -- @param room the room instance
  78. -- @param nick the user nick
  79. function remove_username(room, nick)
  80. local room_name = jid.node(room.jid);
  81. if (poltergeists[room_name]) then
  82. local user_id_to_remove;
  83. for name,username in pairs(poltergeists[room_name]) do
  84. if (string.sub(username, 0, 8) == nick) then
  85. user_id_to_remove = name;
  86. end
  87. end
  88. if (user_id_to_remove) then
  89. poltergeists[room_name][user_id_to_remove] = nil;
  90. end
  91. end
  92. end
  93. --- Verifies room name, domain name with the values in the token
  94. -- @param token the token we received
  95. -- @param room_name the room name
  96. -- @param group name of the group (optional)
  97. -- @return true if values are ok or false otherwise
  98. function verify_token(token, room_name, group)
  99. if disableTokenVerification then
  100. return true;
  101. end
  102. -- if not disableTokenVerification and we do not have token
  103. -- stop here, cause the main virtual host can have guest access enabled
  104. -- (allowEmptyToken = true) and we will allow access to rooms info without
  105. -- a token
  106. if token == nil then
  107. log("warn", "no token provided");
  108. return false;
  109. end
  110. local session = {};
  111. session.auth_token = token;
  112. local verified, reason = token_util:process_and_verify_token(session);
  113. if not verified then
  114. log("warn", "not a valid token %s", tostring(reason));
  115. return false;
  116. end
  117. local room_address = jid.join(room_name, module:get_host());
  118. -- if there is a group we are in multidomain mode and that group is not
  119. -- our parent host
  120. if group and group ~= "" and group ~= parentHostName then
  121. room_address = "["..group.."]"..room_address;
  122. end
  123. if not token_util:verify_room(session, room_address) then
  124. log("warn", "Token %s not allowed to join: %s",
  125. tostring(token), tostring(room_address));
  126. return false;
  127. end
  128. return true;
  129. end
  130. -- if we found that a session for a user with id has a poltergiest already
  131. -- created, retrieve its jid and return it to the authentication
  132. -- so we can reuse it and we that real user will replace the poltergiest
  133. prosody.events.add_handler("pre-jitsi-authentication", function(session)
  134. if (session.jitsi_meet_context_user) then
  135. local room = get_room(
  136. session.jitsi_bosh_query_room,
  137. session.jitsi_meet_domain);
  138. if (not room) then
  139. return nil;
  140. end
  141. local username
  142. = get_username(room, session.jitsi_meet_context_user["id"]);
  143. if (not username) then
  144. return nil;
  145. end
  146. log("debug", "Found predefined username %s", username);
  147. -- let's find the room and if the poltergeist occupant is there
  148. -- lets remove him before the real participant joins
  149. -- when we see the unavailable presence to go out the server
  150. -- we will mark it with ignore tag
  151. local nick = string.sub(username, 0, 8);
  152. if (have_poltergeist_occupant(room, nick)) then
  153. remove_poltergeist_occupant(room, nick, true);
  154. end
  155. return username;
  156. end
  157. return nil;
  158. end);
  159. -- Creates poltergeist occupant
  160. -- @param room the room instance where we create the occupant
  161. -- @param nick the nick to use for the new occupant
  162. -- @param name the display name fot the occupant (optional)
  163. -- @param avatar the avatar to use for the new occupant (optional)
  164. -- @param status the initial status to use for the new occupant (optional)
  165. function create_poltergeist_occupant(room, nick, name, avatar, status)
  166. log("debug", "create_poltergeist_occupant %s:", nick);
  167. -- Join poltergeist occupant to room, with the invited JID as their nick
  168. local join_presence = st.presence({
  169. to = room.jid.."/"..nick,
  170. from = poltergeist_component.."/"..nick
  171. }):tag("x", { xmlns = "http://jabber.org/protocol/muc" }):up();
  172. if (name) then
  173. join_presence:tag(
  174. "nick",
  175. { xmlns = "http://jabber.org/protocol/nick" }):text(name):up();
  176. end
  177. if (avatar) then
  178. join_presence:tag("avatar-url"):text(avatar):up();
  179. end
  180. if (status) then
  181. join_presence:tag("status"):text(status):up();
  182. end
  183. room:handle_first_presence(
  184. prosody.hosts[poltergeist_component], join_presence);
  185. timer.add_task(poltergeist_timeout,
  186. function ()
  187. if (have_poltergeist_occupant(room, nick)) then
  188. remove_poltergeist_occupant(room, nick, false);
  189. end
  190. end);
  191. end
  192. -- Removes poltergeist occupant
  193. -- @param room the room instance where to remove the occupant
  194. -- @param nick the nick of the occupant to remove
  195. -- @param ignore to mark the poltergeist unavailble presence to be ignored
  196. function remove_poltergeist_occupant(room, nick, ignore)
  197. log("debug", "remove_poltergeist_occupant %s", nick);
  198. local leave_presence = st.presence({
  199. to = room.jid.."/"..nick,
  200. from = poltergeist_component.."/"..nick,
  201. type = "unavailable" });
  202. if (ignore) then
  203. poltergeists_pr_ignore[room.jid.."/"..nick] = true;
  204. end
  205. room:handle_normal_presence(
  206. prosody.hosts[poltergeist_component], leave_presence);
  207. remove_username(room, nick);
  208. end
  209. -- Checks for existance of a poltergeist occupant
  210. -- @param room the room instance where to check for occupant
  211. -- @param nick the nick of the occupant
  212. -- @return true if occupant is found, false otherwise
  213. function have_poltergeist_occupant(room, nick)
  214. -- Find out if we have a poltergeist occupant in the room for this JID
  215. return not not room:get_occupant_jid(poltergeist_component.."/"..nick);
  216. end
  217. -- Returns the last presence of occupant
  218. -- @param room the room instance where to check for occupant
  219. -- @param nick the nick of the occupant
  220. -- @return presence of the occupant
  221. function get_presence(room, nick)
  222. local occupant_jid
  223. = room:get_occupant_jid(poltergeist_component.."/"..nick);
  224. if (occupant_jid) then
  225. return room:get_occupant_by_nick(occupant_jid):get_presence();
  226. end
  227. return nil;
  228. end
  229. -- Event handlers
  230. --- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100,
  231. --- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100.
  232. module:hook("muc-decline", function (event)
  233. remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from), false);
  234. end, -100);
  235. -- before sending the presence for a poltergeist leaving add ignore tag
  236. -- as poltergeist is leaving just before the real user joins and in the client
  237. -- we ignore this presence to avoid leaving/joining experience and the real
  238. -- user will reuse all currently created UI components for the same nick
  239. module:hook("muc-broadcast-presence", function (event)
  240. if (bare(event.occupant.jid) == poltergeist_component) then
  241. if(event.stanza.attr.type == "unavailable"
  242. and poltergeists_pr_ignore[event.occupant.nick]) then
  243. event.stanza:tag(
  244. "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up();
  245. poltergeists_pr_ignore[event.occupant.nick] = nil;
  246. end
  247. end
  248. end, -100);
  249. -- cleanup room table after room is destroyed
  250. module:hook("muc-room-destroyed",function(event)
  251. local room_name = jid.node(event.room.jid);
  252. if (poltergeists[room_name]) then
  253. poltergeists[room_name] = nil;
  254. end
  255. end);
  256. --- Handles request for creating/managing poltergeists
  257. -- @param event the http event, holds the request query
  258. -- @return GET response, containing a json with response details
  259. function handle_create_poltergeist (event)
  260. if (not event.request.url.query) then
  261. return 400;
  262. end
  263. local params = parse(event.request.url.query);
  264. local user_id = params["user"];
  265. local room_name = params["room"];
  266. local group = params["group"];
  267. local name = params["name"];
  268. local avatar = params["avatar"];
  269. local status = params["status"];
  270. if not verify_token(params["token"], room_name, group) then
  271. return 403;
  272. end
  273. local room = get_room(room_name, group);
  274. if (not room) then
  275. log("error", "no room found %s", room_name);
  276. return 404;
  277. end
  278. local username = generate_uuid();
  279. store_username(room, user_id, username)
  280. create_poltergeist_occupant(
  281. room, string.sub(username,0,8), name, avatar, status);
  282. return 200;
  283. end
  284. --- Handles request for updating poltergeists status
  285. -- @param event the http event, holds the request query
  286. -- @return GET response, containing a json with response details
  287. function handle_update_poltergeist (event)
  288. if (not event.request.url.query) then
  289. return 400;
  290. end
  291. local params = parse(event.request.url.query);
  292. local user_id = params["user"];
  293. local room_name = params["room"];
  294. local group = params["group"];
  295. local status = params["status"];
  296. if not verify_token(params["token"], room_name, group) then
  297. return 403;
  298. end
  299. local room = get_room(room_name, group);
  300. if (not room) then
  301. log("error", "no room found %s", room_name);
  302. return 404;
  303. end
  304. local username = get_username(room, user_id);
  305. if (not username) then
  306. return 404;
  307. end
  308. local nick = string.sub(username, 0, 8);
  309. if (have_poltergeist_occupant(room, nick)) then
  310. local update_presence = get_presence(room, nick);
  311. if (not update_presence) then
  312. -- no presence found for occupant, create one
  313. update_presence = st.presence({
  314. to = room.jid.."/"..nick,
  315. from = poltergeist_component.."/"..nick
  316. });
  317. else
  318. -- update occupant presence with appropriate to and from
  319. -- so we can send it again
  320. update_presence = st.clone(update_presence);
  321. update_presence.attr.to = room.jid.."/"..nick;
  322. update_presence.attr.from = poltergeist_component.."/"..nick;
  323. end
  324. local once = false;
  325. -- the status tag we will attach
  326. local statusTag = st.stanza("status"):text(status);
  327. -- if there is already a status tag replace it
  328. update_presence:maptags(function (tag)
  329. if tag.name == statusTag.name then
  330. if not once then
  331. once = true;
  332. return statusTag;
  333. else
  334. return nil;
  335. end
  336. end
  337. return tag;
  338. end);
  339. if (not once) then
  340. -- no status tag was repleced, attach it
  341. update_presence:add_child(statusTag);
  342. end
  343. room:handle_normal_presence(
  344. prosody.hosts[poltergeist_component], update_presence);
  345. return 200;
  346. else
  347. return 404;
  348. end
  349. end
  350. --- Handles remove poltergeists
  351. -- @param event the http event, holds the request query
  352. -- @return GET response, containing a json with response details
  353. function handle_remove_poltergeist (event)
  354. if (not event.request.url.query) then
  355. return 400;
  356. end
  357. local params = parse(event.request.url.query);
  358. local user_id = params["user"];
  359. local room_name = params["room"];
  360. local group = params["group"];
  361. if not verify_token(params["token"], room_name, group) then
  362. return 403;
  363. end
  364. local room = get_room(room_name, group);
  365. if (not room) then
  366. log("error", "no room found %s", room_name);
  367. return 404;
  368. end
  369. local username = get_username(room, user_id);
  370. if (not username) then
  371. return 404;
  372. end
  373. local nick = string.sub(username, 0, 8);
  374. if (have_poltergeist_occupant(room, nick)) then
  375. remove_poltergeist_occupant(room, nick, false);
  376. return 200;
  377. else
  378. return 404;
  379. end
  380. end
  381. log("info", "Loading poltergeist service");
  382. module:depends("http");
  383. module:provides("http", {
  384. default_path = "/";
  385. name = "poltergeist";
  386. route = {
  387. ["GET /poltergeist/create"] = handle_create_poltergeist;
  388. ["GET /poltergeist/update"] = handle_update_poltergeist;
  389. ["GET /poltergeist/remove"] = handle_remove_poltergeist;
  390. };
  391. });