From 23fad122e665006747c8ca930d1c3b435dd6e489 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 8 May 2018 07:11:33 +0100 Subject: mod_websocket: Store the request object on the session for use by other modules --- plugins/mod_websocket.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index ed73962d..edc104df 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -257,6 +257,7 @@ function handle_request(event) local session = sessions[conn]; session.secure = consider_websocket_secure or session.secure; + session.websocket_request = request; session.open_stream = session_open_stream; session.close = session_close; -- cgit v1.2.3 -- cgit v1.2.3 From 1727434f1180a250baaa761266bbc2fa79b3391a Mon Sep 17 00:00:00 2001 From: Lennart Sauerbeck Date: Sat, 18 Mar 2017 18:47:28 +0100 Subject: muc: Allow clients to change multiple affiliations or roles at once (#345) According to XEP-0045 sections 9.2, 9.5 and 9.8 affiliation lists and role lists should allow mass-modification. Prosody however would just use the first entry of the list and ignore the rest. This is fixed by introducing a `for` loop to `set` stanzas of the respective `muc#admin` namespace. In order for this loop to work, the error handling was changed a little. Prosody no longer returns after the first error. Instead, an error reply is sent for each malformed or otherwise wrong entry, but the loop keeps going over the other entries. This may lead to multiple error messages being sent for one client request. A notable exception from this is when the XML Schema for `muc#admin` requests is violated. In that case the loop is aborted with an error message to the client. The change is a bit bigger than that in order to have the loop only for `set` stanzas without changing the behaviour of the `get` stanzas. This is now more in line with trunk, where there are separate methods for each stanza type. References: #345 --- plugins/muc/muc.lib.lua | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5d8c6df5..4674f323 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -802,15 +802,17 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha local affiliation = self:get_affiliation(actor); local current_nick = self._jid_nick[actor]; local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then + if type == "set" then + local at_least_one_item_provided = false; + + for item in stanza.tags[1]:childtags("item") do + at_least_one_item_provided = true; + local callback = function() origin.send(st.reply(stanza)); end if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); if not item.attr.jid then origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; end end if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation @@ -829,8 +831,17 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha if not success then origin.send(st.error_reply(stanza, errtype, err)); end else origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return; end - elseif type == "get" then + end + + if not at_least_one_item_provided then + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return; + end + elseif type == "get" then + local item = stanza.tags[1].tags[1]; + if item and item.name == "item" then local _aff = item.attr.affiliation; local _rol = item.attr.role; if _aff and not _rol then @@ -868,9 +879,9 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then if self:get_affiliation(stanza.attr.from) ~= "owner" then -- cgit v1.2.3 From 310bf706f15ab843eefc8f9ac4b195230b936e9d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 16 May 2018 12:14:21 +0100 Subject: MUC: Import revised, more comprehensive patch for 8da11142fabf (#345) --- plugins/muc/muc.lib.lua | 302 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 207 insertions(+), 95 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4674f323..77ac0b87 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -804,15 +804,19 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); if type == "set" then local at_least_one_item_provided = false; + local callback = function() origin.send(st.reply(stanza)); end + -- Gather all changes to affiliations and roles + local jid_affiliation = {}; + local jidnick_role = {}; for item in stanza.tags[1]:childtags("item") do at_least_one_item_provided = true; - local callback = function() origin.send(st.reply(stanza)); end if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); if not item.attr.jid then origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return; end end if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation @@ -822,13 +826,12 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha local nick = self._jid_nick[item.attr.jid]; if nick then item.attr.nick = select(3, jid_split(nick)); end end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end + jid_affiliation[item.attr.jid] = { ["affiliation"] = item.attr.affiliation, ["reason"] = reason }; elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end + jidnick_role[item.attr.jid.."/"..item.attr.nick] = { ["role"] = item.attr.role, ["reason"] = reason }; else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; @@ -838,6 +841,36 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha if not at_least_one_item_provided then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; + else + local can_set_affiliations, errtype_aff, err_aff = self:can_set_affiliations(actor, jid_affiliation) + local can_set_roles, errtype_role, err_role = self:can_set_roles(actor, jidnick_role) + + if can_set_affiliations and can_set_roles then + local nb_affiliation_changes = 0; + for _ in pairs(jid_affiliation) do nb_affiliation_changes = nb_affiliation_changes + 1; end + local nb_role_changes = 0; + for _ in pairs(jidnick_role) do nb_role_changes = nb_role_changes + 1; end + + if nb_affiliation_changes > 0 and nb_role_changes > 0 then + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + if nb_affiliation_changes > 0 then + self:set_affiliations(actor, jid_affiliation, callback); + end + if nb_role_changes > 0 then + self:set_roles(actor, jidnick_role, callback); + end + else + if not can_set_affiliations then + origin.send(st.error_reply(stanza, errtype_aff, err_aff)); + elseif not can_set_roles then + origin.send(st.error_reply(stanza, errtype_role, err_role)); + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + + return; + end end elseif type == "get" then local item = stanza.tags[1].tags[1]; @@ -1009,82 +1042,125 @@ function room_mt:get_affiliation(jid) if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end -function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) - jid = jid_bare(jid); - if affiliation == "none" then affiliation = nil; end - if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then - return nil, "modify", "not-acceptable"; - end +--- Checks whether the given affiliation changes in jid_affiliation can be applied by actor. +-- Note: Empty tables can always be applied and won't have any effect. +function room_mt:can_set_affiliations(actor, jid_affiliation) + local actor_affiliation; if actor ~= true then - local actor_affiliation = self:get_affiliation(actor); - local target_affiliation = self:get_affiliation(jid); - if target_affiliation == affiliation then -- no change, shortcut - if callback then callback(); end - return true; + actor_affiliation = self:get_affiliation(actor); + end + + -- First let's see if there are any problems with the affiliations given + -- in jid_affiliation + for jid, value in pairs(jid_affiliation) do + local affiliation = value["affiliation"]; + + jid = jid_bare(jid); + if affiliation == "none" then affiliation = nil; end + if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then + return false, "modify", "not-acceptable"; end - if actor_affiliation ~= "owner" then - if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then - return nil, "cancel", "not-allowed"; - end - elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change - local is_last = true; - for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end - if is_last then - return nil, "cancel", "conflict"; + + local target_affiliation = self:get_affiliation(jid); + if target_affiliation == affiliation then + -- no change, no error checking necessary + else + if actor ~= true and actor_affiliation ~= "owner" then + if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then + return false, "cancel", "not-allowed"; + end + elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change + local is_last = true; + for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end + if is_last then + return false, "cancel", "conflict"; + end end end end - self._affiliations[jid] = affiliation; - local role = self:get_default_role(affiliation); - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; - if not role then -- getting kicked - presence_type = "unavailable"; - if affiliation == "outcast" then - x:tag("status", {code="301"}):up(); -- banned - else - x:tag("status", {code="321"}):up(); -- affiliation change - end + + return true; +end +--- Updates the room affiliations by applying the ones given here. +-- Takes the affiliations given in jid_affiliation and applies them to +-- the room, overwriting a potentially existing affiliation for any given +-- jid. +-- @param jid_affiliation A table associating a jid with a table consisting +-- of two subkeys: `affilation` and `reason`. The jids +-- within must not be malformed. +function room_mt:set_affiliations(actor, jid_affiliation, callback) + local can_set, err_type, err_condition = self:can_set_affiliations(actor, jid_affiliation) + + if not can_set then + return false, err_type, err_condition; end -- Your own presence should have status 110 - local self_x = st.clone(x); - self_x:tag("status", {code="110"}); local modified_nicks = {}; - for nick, occupant in pairs(self._occupants) do - if jid_bare(occupant.jid) == jid then - if not role then -- getting kicked - self._occupants[nick] = nil; + local nb_modified_nicks = 0; + -- Now we can be sure that jid_affiliation causes no problems + -- We can actually set them + for jid, value in pairs(jid_affiliation) do + local affiliation = value["affiliation"]; + local reason = value["reason"]; + + self._affiliations[jid] = affiliation; + local role = self:get_default_role(affiliation); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}) + :tag("reason"):text(reason or ""):up() + :up(); + local self_x = st.clone(x); + self_x:tag("status", {code="110"}); + local presence_type = nil; + if not role then -- getting kicked + presence_type = "unavailable"; + if affiliation == "outcast" then + x:tag("status", {code="301"}):up(); -- banned else - occupant.affiliation, occupant.role = affiliation, role; + x:tag("status", {code="321"}):up(); -- affiliation change end - for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick - if not role then self._jid_nick[jid] = nil; end - local p = st.clone(pres); - p.attr.from = nick; - p.attr.type = presence_type; - p.attr.to = jid; - if occupant.jid == jid then + end + for nick, occupant in pairs(self._occupants) do + if jid_bare(occupant.jid) == jid then + if not role then -- getting kicked + self._occupants[nick] = nil; + else + occupant.affiliation, occupant.role = affiliation, role; + end + for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick + if not role then self._jid_nick[jid] = nil; end + local p = st.clone(pres); + p.attr.from = nick; + p.attr.type = presence_type; + p.attr.to = jid; + self:_route_stanza(p); + if occupant.jid == jid then -- Broadcast this presence to everyone else later, with the public variant - local bp = st.clone(p); - bp:add_child(x); - modified_nicks[nick] = bp; + local bp = st.clone(p); + bp:add_child(x); + modified_nicks[nick] = bp; + nb_modified_nicks = nb_modified_nicks + 1; + end + p:add_child(self_x); + self:_route_stanza(p); end - p:add_child(self_x); - self:_route_stanza(p); end end end - if self.save then self:save(); end - if callback then callback(); end + + if nb_modified_nicks > 0 then + if self.save then self:save(); end + if callback then callback(); end + end for nick,p in pairs(modified_nicks) do p.attr.from = nick; self:broadcast_except_nick(p, nick); end return true; end +function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) + return self.set_affiliations(actor, { [jid] = { ["affiliation"] = affiliation, ["reason"] = reason } }, callback) +end function room_mt:get_role(nick) local session = self._occupants[nick]; @@ -1108,45 +1184,81 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) end return nil, "cancel", "not-allowed"; end -function room_mt:set_role(actor, occupant_jid, role, callback, reason) - if role == "none" then role = nil; end - if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end - local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); - if not allowed then return allowed, err_type, err_condition; end - local occupant = self._occupants[occupant_jid]; - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; - if not role then -- kick - presence_type = "unavailable"; - self._occupants[occupant_jid] = nil; - for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick - self._jid_nick[jid] = nil; + +--- Checks whether the given role changes in jidnick_role can be applied by actor. +-- Note: Empty tables can always be applied and won't have any effect. +function room_mt:can_set_roles(actor, jidnick_role) + for jidnick, role in pairs(jidnick_role) do + if role == "none" then role = nil; end + if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return false, "modify", "not-acceptable"; end + local can_set, err_type, err_condition = self:can_set_role(actor, jidnick, role) + if not can_set then + return false, err_type, err_condition; end - x:tag("status", {code = "307"}):up(); - else - occupant.role = role; end - local self_x = st.clone(x); - self_x:tag("status", {code = "110"}):up(); - local bp; - for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick - local p = st.clone(pres); - p.attr.from = occupant_jid; - p.attr.type = presence_type; - p.attr.to = jid; - if occupant.jid == jid then - bp = st.clone(p); - bp:add_child(x); + + return true; +end + +--- Updates the room roles by applying the ones given here. +-- Takes the roles given in jidnick_role and applies them to +-- the room, overwriting a potentially existing role for any given +-- jid. +-- @param jidnick_role A table associating a jid/nick with a table consisting +-- of two subkeys: `role` and `reason`. The jids within +-- must not be malformed. +function room_mt:set_roles(actor, jidnick_role, callback) + local allowed, err_type, err_condition = self:can_set_roles(actor, jidnick_role); + if not allowed then return allowed, err_type, err_condition; end + + local modified_nicks = {}; + local nb_modified_nicks = 0; + for jidnick, value in pairs(jidnick_role) do + local occupant_jid = jidnick; + local role = value["role"]; + local reason = value["reason"]; + + local occupant = self._occupants[occupant_jid]; + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) + :tag("reason"):text(reason or ""):up() + :up(); + local presence_type = nil; + if not role then -- kick + presence_type = "unavailable"; + self._occupants[occupant_jid] = nil; + for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick + self._jid_nick[jid] = nil; + end + x:tag("status", {code = "307"}):up(); + else + occupant.role = role; + end + local self_x = st.clone(x); + self_x:tag("status", {code = "110"}):up(); + local bp; + for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick + local p = st.clone(pres); + p.attr.from = occupant_jid; + p.attr.type = presence_type; + p.attr.to = jid; + self:_route_stanza(p); + if occupant.jid == jid then + bp = st.clone(p); + bp:add_child(x); + modified_nicks[occupant_jid] = p; + nb_modified_nicks = nb_modified_nicks + 1; + end + p:add_child(self_x); + self:route_stanza(p); end - p:add_child(self_x); - self:_route_stanza(p); end - if callback then callback(); end - if bp then - self:broadcast_except_nick(bp, occupant_jid); + + if nb_modified_nicks > 0 then + if callback then callback(); end + end + for nick,p in pairs(modified_nicks) do + self:broadcast_except_nick(p, nick); end return true; end -- cgit v1.2.3 From 6a8358e64753fca28bcae3d85cfd6cc8560b79de Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 16 May 2018 12:17:06 +0100 Subject: MUC: Allow the number of messages sent by default to be configured (fixes #397) --- plugins/muc/muc.lib.lua | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 77ac0b87..524cc843 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -171,7 +171,7 @@ function room_mt:send_history(to, stanza) if maxchars then maxchars = math.floor(maxchars); end local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); - if not history_tag then maxstanzas = 20; end + if not history_tag then maxstanzas = self._data.default_history_messages; end local seconds = history_tag and tonumber(history_tag.attr.seconds); if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end @@ -359,6 +359,20 @@ function room_mt:set_historylength(length) self._data.history_length = length; end +-- Fix for clients who don't support XEP-0045 correctly +-- Default number of history messages the room returns +function room_mt:get_defaulthistorymessages() + return self._data.default_history_messages or default_history_length; +end +function room_mt:set_defaulthistorymessages(number) + number = math.min(tonumber(number) or default_history_length, self._data.history_length); + if number == default_history_length then + number = nil; + end + self._data.default_history_messages = number; +end + + local valid_whois = { moderators = true, anyone = true }; @@ -698,6 +712,12 @@ function room_mt:get_form_layout(actor) type = 'text-single', label = 'Maximum Number of History Messages Returned by Room', value = tostring(self:get_historylength()) + }, + { + name = 'muc#roomconfig_defaulthistorymessages', + type = 'text-single', + label = 'Default Number of History Messages Returned by Room', + value = tostring(self:get_defaulthistorymessages()) } }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; @@ -746,6 +766,7 @@ function room_mt:process_form(origin, stanza) handle_option("public", "muc#roomconfig_publicroom"); handle_option("changesubject", "muc#roomconfig_changesubject"); handle_option("historylength", "muc#roomconfig_historylength"); + handle_option("defaulthistorymessages", "muc#roomconfig_defaulthistorymessages"); handle_option("whois", "muc#roomconfig_whois", valid_whois); handle_option("password", "muc#roomconfig_roomsecret"); -- cgit v1.2.3