From 2b1e8cdeffff4a5f92b5fdeb099e7c09a52b93b8 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 14 Jul 2017 22:03:36 -0500 Subject: [PATCH 1/4] Creates util for modules and adds method get_room_from_jid in it. --- resources/prosody-plugins/mod_muc_size.lua | 21 +----------------- resources/prosody-plugins/util.lib.lua | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 resources/prosody-plugins/util.lib.lua diff --git a/resources/prosody-plugins/mod_muc_size.lua b/resources/prosody-plugins/mod_muc_size.lua index dfc9c7a65..d8d2716c5 100644 --- a/resources/prosody-plugins/mod_muc_size.lua +++ b/resources/prosody-plugins/mod_muc_size.lua @@ -19,6 +19,7 @@ local enableTokenVerification = module:get_option_boolean("enable_roomsize_token_verification", false); local token_util = module:require "token/util".new(module); +local get_room_from_jid = module:require "util".get_room_from_jid; -- no token configuration but required if token_util == nil and enableTokenVerification then @@ -31,26 +32,6 @@ end local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference"); ---- Finds and returns room by its jid --- @param room_jid the room jid to search in the muc component --- @return returns room if found or nil -function get_room_from_jid(room_jid) - local _, host = jid.split(room_jid); - local component = hosts[host]; - if component then - local muc = component.modules.muc - if muc and rawget(muc,"rooms") then - -- We're running 0.9.x or 0.10 (old MUC API) - return muc.rooms[room_jid]; - elseif muc and rawget(muc,"get_room_from_jid") then - -- We're running >0.10 (new MUC API) - return muc.get_room_from_jid(room_jid); - else - return - end - end -end - --- Verifies room name, domain name with the values in the token -- @param token the token we received -- @param room_address the full room address jid diff --git a/resources/prosody-plugins/util.lib.lua b/resources/prosody-plugins/util.lib.lua new file mode 100644 index 000000000..b0a6a38f0 --- /dev/null +++ b/resources/prosody-plugins/util.lib.lua @@ -0,0 +1,25 @@ +local jid = require "util.jid"; + +--- Finds and returns room by its jid +-- @param room_jid the room jid to search in the muc component +-- @return returns room if found or nil +function get_room_from_jid(room_jid) + local _, host = jid.split(room_jid); + local component = hosts[host]; + if component then + local muc = component.modules.muc + if muc and rawget(muc,"rooms") then + -- We're running 0.9.x or 0.10 (old MUC API) + return muc.rooms[room_jid]; + elseif muc and rawget(muc,"get_room_from_jid") then + -- We're running >0.10 (new MUC API) + return muc.get_room_from_jid(room_jid); + else + return + end + end +end + +return { + get_room_from_jid = get_room_from_jid; +}; \ No newline at end of file From 599d84a889187697eca25ef25cce76b7071a81d1 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 14 Jul 2017 22:08:41 -0500 Subject: [PATCH 2/4] Stores the room name from the bosh url into the session. --- resources/prosody-plugins/mod_auth_token.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/prosody-plugins/mod_auth_token.lua b/resources/prosody-plugins/mod_auth_token.lua index d83f0b7bb..fd9b264e7 100644 --- a/resources/prosody-plugins/mod_auth_token.lua +++ b/resources/prosody-plugins/mod_auth_token.lua @@ -23,8 +23,12 @@ module:hook("bosh-session", function(event) local query = request.url.query; if query ~= nil then - session.auth_token = query and formdecode(query).token or nil; - end + local params = formdecode(query); + session.auth_token = query and params.token or nil; + + -- The room name from the bosh query + session.jitsi_bosh_query_room = params.room; + end end); function provider.test_password(username, password) From cc79b073f0f0bd4976925a3287a6e2b927d4c9ea Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 14 Jul 2017 22:12:56 -0500 Subject: [PATCH 3/4] Fires event before setting username, allows listeners to override it. This is a hook to override the username that will be used when authenticating token users (which are using anonymous login with auto-generated username). --- resources/prosody-plugins/mod_auth_token.lua | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/resources/prosody-plugins/mod_auth_token.lua b/resources/prosody-plugins/mod_auth_token.lua index fd9b264e7..94b065eaf 100644 --- a/resources/prosody-plugins/mod_auth_token.lua +++ b/resources/prosody-plugins/mod_auth_token.lua @@ -58,7 +58,18 @@ end function provider.get_sasl_handler(session) local function get_username_from_token(self, message) - return token_util:process_and_verify_token(session); + local res = token_util:process_and_verify_token(session); + + local customUsername + = prosody.events.fire_event("pre-jitsi-authentication", session); + + if (customUsername) then + self.username = customUsername; + else + self.username = message; + end + + return res; end return new_sasl(host, { anonymous = get_username_from_token }); @@ -73,8 +84,6 @@ local function anonymous(self, message) -- This calls the handler created in 'provider.get_sasl_handler(session)' local result, err, msg = self.profile.anonymous(self, username, self.realm); - self.username = username; - if result == true then return "success"; else From 58d06fe7e6439b84835a445868db773329a68867 Mon Sep 17 00:00:00 2001 From: damencho Date: Fri, 14 Jul 2017 22:18:23 -0500 Subject: [PATCH 4/4] A poltergeist module. Thanks to Matthew Wild for the initial help of creating these. Module with REST interface to create poltergeist participants and change their statuses. When user with same id joins the room, the poltergeist is removed. We also make sure that that user uses same username when authenticates. This way we are sure that user will join the room with the same nick as the poltergeist. --- .../prosody-plugins/mod_muc_poltergeist.lua | 240 ++++++++++++++++++ .../mod_poltergeist_component.lua | 21 ++ 2 files changed, 261 insertions(+) create mode 100644 resources/prosody-plugins/mod_muc_poltergeist.lua create mode 100644 resources/prosody-plugins/mod_poltergeist_component.lua diff --git a/resources/prosody-plugins/mod_muc_poltergeist.lua b/resources/prosody-plugins/mod_muc_poltergeist.lua new file mode 100644 index 000000000..8e8db4e53 --- /dev/null +++ b/resources/prosody-plugins/mod_muc_poltergeist.lua @@ -0,0 +1,240 @@ +local bare = require "util.jid".bare; +local generate_uuid = require "util.uuid".generate; +local jid = require "util.jid"; +local neturl = require "net.url"; +local parse = neturl.parseQuery; +local st = require "util.stanza"; +local get_room_from_jid = module:require "util".get_room_from_jid; + +-- Options +local poltergeist_component + = module:get_option_string("poltergeist_component", module.host); + +-- table to store all poltergeists we create +local poltergeists = {}; + +-- poltergaist management functions + +-- Returns the room if available, work and in multidomain mode +-- @param room_name the name of the room +-- @param group name of the group (optional) +-- @return returns room if found or nil +function get_room(room_name, group) + local room_address = jid.join(room_name, module:get_host()); + -- if there is a group we are in multidomain mode + if group and group ~= "" then + room_address = "["..group.."]"..room_address; + end + + return get_room_from_jid(room_address); +end + +-- Stores the username in the table where we store poltergeist usernames +-- based on their room names +-- @param room the room instance +-- @param user_id the user id +-- @param username the username to store +function store_username(room, user_id, username) + local room_name = jid.node(room.jid); + + -- we store in poltergeist user ids for room names + if (not poltergeists[room_name]) then + poltergeists[room_name] = {}; + end + poltergeists[room_name][user_id] = username; + log("debug", "stored in session: %s", username); +end + +-- Retrieve the username for a user +-- @param room the room instance +-- @param user_id the user id +-- @return returns the stored username for user or nil +function get_username(room, user_id) + local room_name = jid.node(room.jid); + + if (not poltergeists[room_name]) then + return nil; + end + + return poltergeists[room_name][user_id]; +end + +-- if we found that a session for a user with id has a poltergiest already +-- created, retrieve its jid and return it to the authentication +-- so we can reuse it and we that real user will replace the poltergiest +prosody.events.add_handler("pre-jitsi-authentication", function(session) + + if (session.jitsi_meet_context_user) then + local room = get_room( + session.jitsi_bosh_query_room, + session.jitsi_meet_context_group); + + if (not room) then + return nil; + end + + local username + = get_username(room, session.jitsi_meet_context_user["id"]); + + if (not username) then + return nil; + end + + log("debug", "Found predefined username %s", username); + + -- let's find the room and if the poltergeist occupant is there + -- lets remove him before the real participant joins + -- when we see the unavailable presence to go out the server + -- we will mark it with ignore tag + local nick = string.sub(username, 0, 8); + if (have_poltergeist_occupant(room, nick)) then + remove_poltergeist_occupant(room, nick); + end + + return username; + end + + return nil; +end); + +-- Creates poltergeist occupant +-- @param room the room instance where we create the occupant +-- @param nick the nick to use for the new occupant +-- @param name the display name fot the occupant (optional) +-- @param avatar the avatar to use for the new occupant (optional) +function create_poltergeist_occupant(room, nick, name, avatar) + log("debug", "create_poltergeist_occupant %s:", nick); + -- Join poltergeist occupant to room, with the invited JID as their nick + local join_presence = st.presence({ + to = room.jid.."/"..nick, + from = poltergeist_component.."/"..nick + }):tag("x", { xmlns = "http://jabber.org/protocol/muc" }):up(); + + if (name) then + join_presence:tag( + "nick", + { xmlns = "http://jabber.org/protocol/nick" }):text(name):up(); + end + if (avatar) then + join_presence:tag("avatar-url"):text(avatar):up(); + end + + room:handle_first_presence( + prosody.hosts[poltergeist_component], join_presence); +end + +-- Removes poltergeist occupant +-- @param room the room instance where to remove the occupant +-- @param nick the nick of the occupant to remove +function remove_poltergeist_occupant(room, nick) + log("debug", "remove_poltergeist_occupant %s", nick); + local leave_presence = st.presence({ + to = room.jid.."/"..nick, + from = poltergeist_component.."/"..nick, + type = "unavailable" }); + room:handle_normal_presence( + prosody.hosts[poltergeist_component], leave_presence); +end + +-- Checks for existance of a poltergeist occupant +-- @param room the room instance where to check for occupant +-- @param nick the nick of the occupant +-- @return true if occupant is found, false otherwise +function have_poltergeist_occupant(room, nick) + -- Find out if we have a poltergeist occupant in the room for this JID + return not not room:get_occupant_jid(poltergeist_component.."/"..nick); +end + +-- Event handlers + +--- Note: mod_muc and some of its sub-modules add event handlers between 0 and -100, +--- e.g. to check for banned users, etc.. Hence adding these handlers at priority -100. +module:hook("muc-decline", function (event) + remove_poltergeist_occupant(event.room, bare(event.stanza.attr.from)); +end, -100); +-- before sending the presence for a poltergeist leaving add ignore tag +-- as poltergeist is leaving just before the real user joins and in the client +-- we ignore this presence to avoid leaving/joining experience and the real +-- user will reuse all currently created UI components for the same nick +module:hook("muc-broadcast-presence", function (event) + if (bare(event.occupant.jid) == poltergeist_component) then + if(event.stanza.attr.type == "unavailable") then + event.stanza:tag( + "ignore", { xmlns = "http://jitsi.org/jitmeet/" }):up(); + end + end +end, -100); + +--- Handles request for creating/managing poltergeists +-- @param event the http event, holds the request query +-- @return GET response, containing a json with response details +function handle_create_poltergeist (event) + local params = parse(event.request.url.query); + local user_id = params["user"]; + local room_name = params["room"]; + local group = params["group"]; + local name = params["name"]; + local avatar = params["avatar"]; + + local room = get_room(room_name, group); + if (not room) then + log("error", "no room found %s", room_address); + return 404; + end + + local username = generate_uuid(); + store_username(room, user_id, username) + + create_poltergeist_occupant(room, string.sub(username,0,8), name, avatar); + + return 200; +end + +--- Handles request for updating poltergeists status +-- @param event the http event, holds the request query +-- @return GET response, containing a json with response details +function handle_update_poltergeist (event) + local params = parse(event.request.url.query); + local user_id = params["user"]; + local room_name = params["room"]; + local group = params["group"]; + local status = params["status"]; + + local room = get_room(room_name, group); + if (not room) then + log("error", "no room found %s", room_address); + return 404; + end + + local username = get_username(room, user_id); + if (not username) then + return 404; + end + + local nick = string.sub(username, 0, 8); + if (have_poltergeist_occupant(room, nick)) then + local update_presence = st.presence({ + to = room.jid.."/"..nick, + from = poltergeist_component.."/"..nick + }):tag("status"):text(status):up(); + + room:handle_normal_presence( + prosody.hosts[poltergeist_component], update_presence); + + return 200; + else + return 404; + end +end + + +log("info", "Loading poltergeist service"); +module:depends("http"); +module:provides("http", { + default_path = "/"; + name = "poltergeist"; + route = { + ["GET /poltergeist/create"] = handle_create_poltergeist; + ["GET /poltergeist/update"] = handle_update_poltergeist; + }; +}); diff --git a/resources/prosody-plugins/mod_poltergeist_component.lua b/resources/prosody-plugins/mod_poltergeist_component.lua new file mode 100644 index 000000000..aa06175db --- /dev/null +++ b/resources/prosody-plugins/mod_poltergeist_component.lua @@ -0,0 +1,21 @@ +local st = require "util.stanza"; + +-- A component which we use to receive all stanzas for the created poltergeists +-- replays with error if an iq is sent +function no_action() + return true; +end + +function error_reply(event) + module:send(st.error_reply(event.stanza, "cancel", "service-unavailable")); + return true; +end + +module:hook("presence/host", no_action); +module:hook("message/host", no_action); +module:hook("presence/full", no_action); +module:hook("message/full", no_action); + +module:hook("iq/host", error_reply); +module:hook("iq/full", error_reply); +module:hook("iq/bare", error_reply);