Comparison

plugins/muc/muc.lib.lua @ 8792:c2b99fa134b3

MUC: Import revised, more comprehensive patch for 8da11142fabf (#345)
author Matthew Wild <mwild1@gmail.com>
date Wed, 16 May 2018 12:14:21 +0100
parent 8791:8da11142fabf
child 8793:05a3275b6873
comparison
equal deleted inserted replaced
8791:8da11142fabf 8792:c2b99fa134b3
802 local affiliation = self:get_affiliation(actor); 802 local affiliation = self:get_affiliation(actor);
803 local current_nick = self._jid_nick[actor]; 803 local current_nick = self._jid_nick[actor];
804 local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); 804 local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation);
805 if type == "set" then 805 if type == "set" then
806 local at_least_one_item_provided = false; 806 local at_least_one_item_provided = false;
807 807 local callback = function() origin.send(st.reply(stanza)); end
808
809 -- Gather all changes to affiliations and roles
810 local jid_affiliation = {};
811 local jidnick_role = {};
808 for item in stanza.tags[1]:childtags("item") do 812 for item in stanza.tags[1]:childtags("item") do
809 at_least_one_item_provided = true; 813 at_least_one_item_provided = true;
810 814
811 local callback = function() origin.send(st.reply(stanza)); end
812 if item.attr.jid then -- Validate provided JID 815 if item.attr.jid then -- Validate provided JID
813 item.attr.jid = jid_prep(item.attr.jid); 816 item.attr.jid = jid_prep(item.attr.jid);
814 if not item.attr.jid then 817 if not item.attr.jid then
815 origin.send(st.error_reply(stanza, "modify", "jid-malformed")); 818 origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
819 return;
816 end 820 end
817 end 821 end
818 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation 822 if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
819 local occupant = self._occupants[self.jid.."/"..item.attr.nick]; 823 local occupant = self._occupants[self.jid.."/"..item.attr.nick];
820 if occupant then item.attr.jid = occupant.jid; end 824 if occupant then item.attr.jid = occupant.jid; end
821 elseif not item.attr.nick and item.attr.jid then 825 elseif not item.attr.nick and item.attr.jid then
822 local nick = self._jid_nick[item.attr.jid]; 826 local nick = self._jid_nick[item.attr.jid];
823 if nick then item.attr.nick = select(3, jid_split(nick)); end 827 if nick then item.attr.nick = select(3, jid_split(nick)); end
824 end 828 end
829
825 local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; 830 local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
826 if item.attr.affiliation and item.attr.jid and not item.attr.role then 831 if item.attr.affiliation and item.attr.jid and not item.attr.role then
827 local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); 832 jid_affiliation[item.attr.jid] = { ["affiliation"] = item.attr.affiliation, ["reason"] = reason };
828 if not success then origin.send(st.error_reply(stanza, errtype, err)); end
829 elseif item.attr.role and item.attr.nick and not item.attr.affiliation then 833 elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
830 local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); 834 jidnick_role[item.attr.jid.."/"..item.attr.nick] = { ["role"] = item.attr.role, ["reason"] = reason };
831 if not success then origin.send(st.error_reply(stanza, errtype, err)); end
832 else 835 else
833 origin.send(st.error_reply(stanza, "cancel", "bad-request")); 836 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
834 return; 837 return;
835 end 838 end
836 end 839 end
837 840
838 if not at_least_one_item_provided then 841 if not at_least_one_item_provided then
839 origin.send(st.error_reply(stanza, "cancel", "bad-request")); 842 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
840 return; 843 return;
844 else
845 local can_set_affiliations, errtype_aff, err_aff = self:can_set_affiliations(actor, jid_affiliation)
846 local can_set_roles, errtype_role, err_role = self:can_set_roles(actor, jidnick_role)
847
848 if can_set_affiliations and can_set_roles then
849 local nb_affiliation_changes = 0;
850 for _ in pairs(jid_affiliation) do nb_affiliation_changes = nb_affiliation_changes + 1; end
851 local nb_role_changes = 0;
852 for _ in pairs(jidnick_role) do nb_role_changes = nb_role_changes + 1; end
853
854 if nb_affiliation_changes > 0 and nb_role_changes > 0 then
855 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
856 end
857 if nb_affiliation_changes > 0 then
858 self:set_affiliations(actor, jid_affiliation, callback);
859 end
860 if nb_role_changes > 0 then
861 self:set_roles(actor, jidnick_role, callback);
862 end
863 else
864 if not can_set_affiliations then
865 origin.send(st.error_reply(stanza, errtype_aff, err_aff));
866 elseif not can_set_roles then
867 origin.send(st.error_reply(stanza, errtype_role, err_role));
868 else
869 origin.send(st.error_reply(stanza, "cancel", "bad-request"));
870 end
871
872 return;
873 end
841 end 874 end
842 elseif type == "get" then 875 elseif type == "get" then
843 local item = stanza.tags[1].tags[1]; 876 local item = stanza.tags[1].tags[1];
844 if item and item.name == "item" then 877 if item and item.name == "item" then
845 local _aff = item.attr.affiliation; 878 local _aff = item.attr.affiliation;
1007 local bare = node and node.."@"..host or host; 1040 local bare = node and node.."@"..host or host;
1008 local result = self._affiliations[bare]; -- Affiliations are granted, revoked, and maintained based on the user's bare JID. 1041 local result = self._affiliations[bare]; -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
1009 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned 1042 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned
1010 return result; 1043 return result;
1011 end 1044 end
1012 function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) 1045 --- Checks whether the given affiliation changes in jid_affiliation can be applied by actor.
1013 jid = jid_bare(jid); 1046 -- Note: Empty tables can always be applied and won't have any effect.
1014 if affiliation == "none" then affiliation = nil; end 1047 function room_mt:can_set_affiliations(actor, jid_affiliation)
1015 if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then 1048 local actor_affiliation;
1016 return nil, "modify", "not-acceptable";
1017 end
1018 if actor ~= true then 1049 if actor ~= true then
1019 local actor_affiliation = self:get_affiliation(actor); 1050 actor_affiliation = self:get_affiliation(actor);
1051 end
1052
1053 -- First let's see if there are any problems with the affiliations given
1054 -- in jid_affiliation
1055 for jid, value in pairs(jid_affiliation) do
1056 local affiliation = value["affiliation"];
1057
1058 jid = jid_bare(jid);
1059 if affiliation == "none" then affiliation = nil; end
1060 if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then
1061 return false, "modify", "not-acceptable";
1062 end
1063
1020 local target_affiliation = self:get_affiliation(jid); 1064 local target_affiliation = self:get_affiliation(jid);
1021 if target_affiliation == affiliation then -- no change, shortcut 1065 if target_affiliation == affiliation then
1022 if callback then callback(); end 1066 -- no change, no error checking necessary
1023 return true;
1024 end
1025 if actor_affiliation ~= "owner" then
1026 if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then
1027 return nil, "cancel", "not-allowed";
1028 end
1029 elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change
1030 local is_last = true;
1031 for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end
1032 if is_last then
1033 return nil, "cancel", "conflict";
1034 end
1035 end
1036 end
1037 self._affiliations[jid] = affiliation;
1038 local role = self:get_default_role(affiliation);
1039 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
1040 :tag("item", {affiliation=affiliation or "none", role=role or "none"})
1041 :tag("reason"):text(reason or ""):up()
1042 :up();
1043 local presence_type = nil;
1044 if not role then -- getting kicked
1045 presence_type = "unavailable";
1046 if affiliation == "outcast" then
1047 x:tag("status", {code="301"}):up(); -- banned
1048 else 1067 else
1049 x:tag("status", {code="321"}):up(); -- affiliation change 1068 if actor ~= true and actor_affiliation ~= "owner" then
1050 end 1069 if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then
1070 return false, "cancel", "not-allowed";
1071 end
1072 elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change
1073 local is_last = true;
1074 for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end
1075 if is_last then
1076 return false, "cancel", "conflict";
1077 end
1078 end
1079 end
1080 end
1081
1082 return true;
1083 end
1084 --- Updates the room affiliations by applying the ones given here.
1085 -- Takes the affiliations given in jid_affiliation and applies them to
1086 -- the room, overwriting a potentially existing affiliation for any given
1087 -- jid.
1088 -- @param jid_affiliation A table associating a jid with a table consisting
1089 -- of two subkeys: `affilation` and `reason`. The jids
1090 -- within must not be malformed.
1091 function room_mt:set_affiliations(actor, jid_affiliation, callback)
1092 local can_set, err_type, err_condition = self:can_set_affiliations(actor, jid_affiliation)
1093
1094 if not can_set then
1095 return false, err_type, err_condition;
1051 end 1096 end
1052 -- Your own presence should have status 110 1097 -- Your own presence should have status 110
1053 local self_x = st.clone(x);
1054 self_x:tag("status", {code="110"});
1055 local modified_nicks = {}; 1098 local modified_nicks = {};
1056 for nick, occupant in pairs(self._occupants) do 1099 local nb_modified_nicks = 0;
1057 if jid_bare(occupant.jid) == jid then 1100 -- Now we can be sure that jid_affiliation causes no problems
1058 if not role then -- getting kicked 1101 -- We can actually set them
1059 self._occupants[nick] = nil; 1102 for jid, value in pairs(jid_affiliation) do
1103 local affiliation = value["affiliation"];
1104 local reason = value["reason"];
1105
1106 self._affiliations[jid] = affiliation;
1107 local role = self:get_default_role(affiliation);
1108 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
1109 :tag("item", {affiliation=affiliation or "none", role=role or "none"})
1110 :tag("reason"):text(reason or ""):up()
1111 :up();
1112 local self_x = st.clone(x);
1113 self_x:tag("status", {code="110"});
1114 local presence_type = nil;
1115 if not role then -- getting kicked
1116 presence_type = "unavailable";
1117 if affiliation == "outcast" then
1118 x:tag("status", {code="301"}):up(); -- banned
1060 else 1119 else
1061 occupant.affiliation, occupant.role = affiliation, role; 1120 x:tag("status", {code="321"}):up(); -- affiliation change
1062 end 1121 end
1063 for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick 1122 end
1064 if not role then self._jid_nick[jid] = nil; end 1123 for nick, occupant in pairs(self._occupants) do
1065 local p = st.clone(pres); 1124 if jid_bare(occupant.jid) == jid then
1066 p.attr.from = nick; 1125 if not role then -- getting kicked
1067 p.attr.type = presence_type; 1126 self._occupants[nick] = nil;
1068 p.attr.to = jid; 1127 else
1069 if occupant.jid == jid then 1128 occupant.affiliation, occupant.role = affiliation, role;
1129 end
1130 for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick
1131 if not role then self._jid_nick[jid] = nil; end
1132 local p = st.clone(pres);
1133 p.attr.from = nick;
1134 p.attr.type = presence_type;
1135 p.attr.to = jid;
1136 self:_route_stanza(p);
1137 if occupant.jid == jid then
1070 -- Broadcast this presence to everyone else later, with the public <x> variant 1138 -- Broadcast this presence to everyone else later, with the public <x> variant
1071 local bp = st.clone(p); 1139 local bp = st.clone(p);
1072 bp:add_child(x); 1140 bp:add_child(x);
1073 modified_nicks[nick] = bp; 1141 modified_nicks[nick] = bp;
1074 end 1142 nb_modified_nicks = nb_modified_nicks + 1;
1075 p:add_child(self_x); 1143 end
1076 self:_route_stanza(p); 1144 p:add_child(self_x);
1077 end 1145 self:_route_stanza(p);
1078 end 1146 end
1079 end 1147 end
1080 if self.save then self:save(); end 1148 end
1081 if callback then callback(); end 1149 end
1150
1151 if nb_modified_nicks > 0 then
1152 if self.save then self:save(); end
1153 if callback then callback(); end
1154 end
1082 for nick,p in pairs(modified_nicks) do 1155 for nick,p in pairs(modified_nicks) do
1083 p.attr.from = nick; 1156 p.attr.from = nick;
1084 self:broadcast_except_nick(p, nick); 1157 self:broadcast_except_nick(p, nick);
1085 end 1158 end
1086 return true; 1159 return true;
1160 end
1161 function room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
1162 return self.set_affiliations(actor, { [jid] = { ["affiliation"] = affiliation, ["reason"] = reason } }, callback)
1087 end 1163 end
1088 1164
1089 function room_mt:get_role(nick) 1165 function room_mt:get_role(nick)
1090 local session = self._occupants[nick]; 1166 local session = self._occupants[nick];
1091 return session and session.role or nil; 1167 return session and session.role or nil;
1106 end 1182 end
1107 end 1183 end
1108 end 1184 end
1109 return nil, "cancel", "not-allowed"; 1185 return nil, "cancel", "not-allowed";
1110 end 1186 end
1111 function room_mt:set_role(actor, occupant_jid, role, callback, reason) 1187
1112 if role == "none" then role = nil; end 1188 --- Checks whether the given role changes in jidnick_role can be applied by actor.
1113 if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end 1189 -- Note: Empty tables can always be applied and won't have any effect.
1114 local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); 1190 function room_mt:can_set_roles(actor, jidnick_role)
1191 for jidnick, role in pairs(jidnick_role) do
1192 if role == "none" then role = nil; end
1193 if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return false, "modify", "not-acceptable"; end
1194 local can_set, err_type, err_condition = self:can_set_role(actor, jidnick, role)
1195 if not can_set then
1196 return false, err_type, err_condition;
1197 end
1198 end
1199
1200 return true;
1201 end
1202
1203 --- Updates the room roles by applying the ones given here.
1204 -- Takes the roles given in jidnick_role and applies them to
1205 -- the room, overwriting a potentially existing role for any given
1206 -- jid.
1207 -- @param jidnick_role A table associating a jid/nick with a table consisting
1208 -- of two subkeys: `role` and `reason`. The jids within
1209 -- must not be malformed.
1210 function room_mt:set_roles(actor, jidnick_role, callback)
1211 local allowed, err_type, err_condition = self:can_set_roles(actor, jidnick_role);
1115 if not allowed then return allowed, err_type, err_condition; end 1212 if not allowed then return allowed, err_type, err_condition; end
1116 local occupant = self._occupants[occupant_jid]; 1213
1117 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 1214 local modified_nicks = {};
1118 :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) 1215 local nb_modified_nicks = 0;
1119 :tag("reason"):text(reason or ""):up() 1216 for jidnick, value in pairs(jidnick_role) do
1120 :up(); 1217 local occupant_jid = jidnick;
1121 local presence_type = nil; 1218 local role = value["role"];
1122 if not role then -- kick 1219 local reason = value["reason"];
1123 presence_type = "unavailable"; 1220
1124 self._occupants[occupant_jid] = nil; 1221 local occupant = self._occupants[occupant_jid];
1125 for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick 1222 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
1126 self._jid_nick[jid] = nil; 1223 :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
1127 end 1224 :tag("reason"):text(reason or ""):up()
1128 x:tag("status", {code = "307"}):up(); 1225 :up();
1129 else 1226 local presence_type = nil;
1130 occupant.role = role; 1227 if not role then -- kick
1131 end 1228 presence_type = "unavailable";
1132 local self_x = st.clone(x); 1229 self._occupants[occupant_jid] = nil;
1133 self_x:tag("status", {code = "110"}):up(); 1230 for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
1134 local bp; 1231 self._jid_nick[jid] = nil;
1135 for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick 1232 end
1136 local p = st.clone(pres); 1233 x:tag("status", {code = "307"}):up();
1137 p.attr.from = occupant_jid; 1234 else
1138 p.attr.type = presence_type; 1235 occupant.role = role;
1139 p.attr.to = jid; 1236 end
1140 if occupant.jid == jid then 1237 local self_x = st.clone(x);
1141 bp = st.clone(p); 1238 self_x:tag("status", {code = "110"}):up();
1142 bp:add_child(x); 1239 local bp;
1143 end 1240 for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick
1144 p:add_child(self_x); 1241 local p = st.clone(pres);
1145 self:_route_stanza(p); 1242 p.attr.from = occupant_jid;
1146 end 1243 p.attr.type = presence_type;
1147 if callback then callback(); end 1244 p.attr.to = jid;
1148 if bp then 1245 self:_route_stanza(p);
1149 self:broadcast_except_nick(bp, occupant_jid); 1246 if occupant.jid == jid then
1247 bp = st.clone(p);
1248 bp:add_child(x);
1249 modified_nicks[occupant_jid] = p;
1250 nb_modified_nicks = nb_modified_nicks + 1;
1251 end
1252 p:add_child(self_x);
1253 self:route_stanza(p);
1254 end
1255 end
1256
1257 if nb_modified_nicks > 0 then
1258 if callback then callback(); end
1259 end
1260 for nick,p in pairs(modified_nicks) do
1261 self:broadcast_except_nick(p, nick);
1150 end 1262 end
1151 return true; 1263 return true;
1152 end 1264 end
1153 1265
1154 function room_mt:_route_stanza(stanza) 1266 function room_mt:_route_stanza(stanza)