Mercurial > prosody-hg
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 |
| parents | 8da11142fabf |
| children | 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) |
