1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_roster.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Roster management |
5 |
|
%%% Created : 11 Dec 2002 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
%%% @doc Roster management. |
27 |
|
%%% |
28 |
|
%%% Includes support for XEP-0237: Roster Versioning. |
29 |
|
%%% The roster versioning follows an all-or-nothing strategy: |
30 |
|
%%% - If the version supplied by the client is the latest, return an empty response. |
31 |
|
%%% - If not, return the entire new roster (with updated version string). |
32 |
|
%%% Roster version is a hash digest of the entire roster. |
33 |
|
%%% No additional data is stored in DB. |
34 |
|
|
35 |
|
-module(mod_roster). |
36 |
|
-author('alexey@process-one.net'). |
37 |
|
-xep([{xep, 237}, {version, "1.3"}]). |
38 |
|
-xep([{xep, 83}, {version, "1.0"}]). |
39 |
|
-xep([{xep, 93}, {version, "1.2"}]). |
40 |
|
-behaviour(gen_mod). |
41 |
|
-behaviour(mongoose_module_metrics). |
42 |
|
|
43 |
|
-export([start/2, |
44 |
|
stop/1, |
45 |
|
config_spec/0, |
46 |
|
supported_features/0, |
47 |
|
process_iq/5, |
48 |
|
get_roster_entry/4, |
49 |
|
item_to_map/1, |
50 |
|
set_items/3, |
51 |
|
set_roster_entry/5, |
52 |
|
remove_from_roster/3, |
53 |
|
item_to_xml/1 |
54 |
|
]). |
55 |
|
|
56 |
|
% Hook handlers |
57 |
|
-export([ |
58 |
|
get_user_roster/2, |
59 |
|
in_subscription/5, |
60 |
|
out_subscription/4, |
61 |
|
get_subscription_lists/2, |
62 |
|
get_jid_info/4, |
63 |
|
remove_user/3, |
64 |
|
remove_domain/3, |
65 |
|
get_versioning_feature/2, |
66 |
|
get_personal_data/3 |
67 |
|
]). |
68 |
|
|
69 |
|
-export([transaction/2, |
70 |
|
process_subscription_t/6, |
71 |
|
get_user_rosters_length/2]). % for testing |
72 |
|
|
73 |
|
-export([config_metrics/1]). |
74 |
|
|
75 |
|
-ignore_xref([ |
76 |
|
get_jid_info/4, get_personal_data/3, get_subscription_lists/2, |
77 |
|
get_user_roster/2, get_user_rosters_length/2, get_versioning_feature/2, |
78 |
|
in_subscription/5, item_to_xml/1, out_subscription/4, process_subscription_t/6, |
79 |
|
remove_user/3, remove_domain/3, transaction/2 |
80 |
|
]). |
81 |
|
|
82 |
|
-include("mongoose.hrl"). |
83 |
|
-include("jlib.hrl"). |
84 |
|
-include("mod_roster.hrl"). |
85 |
|
-include("mongoose_config_spec.hrl"). |
86 |
|
|
87 |
|
-export_type([roster/0, sub_presence/0]). |
88 |
|
|
89 |
|
-type roster() :: #roster{}. |
90 |
|
|
91 |
|
-type sub_presence() :: subscribe | subscribed | unsubscribe | unsubscribed. |
92 |
|
|
93 |
|
-type subscription_state() :: none | from | to | both | remove. |
94 |
|
|
95 |
|
-type get_user_roster_strategy() :: db_versioning | hash_versioning | no_versioning. |
96 |
|
|
97 |
|
%% Types used in the backend API |
98 |
|
|
99 |
|
-type contact() :: jid:simple_jid(). |
100 |
|
-type transaction_state() :: in_transaction | no_transaction. |
101 |
|
-type entry_format() :: full | short. |
102 |
|
-type version() :: binary(). |
103 |
|
|
104 |
|
-export_type([contact/0, transaction_state/0, entry_format/0, version/0]). |
105 |
|
|
106 |
|
%%-------------------------------------------------------------------- |
107 |
|
%% gdpr callback |
108 |
|
%%-------------------------------------------------------------------- |
109 |
|
|
110 |
|
-spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> |
111 |
|
gdpr:personal_data(). |
112 |
|
get_personal_data(Acc, HostType, #jid{ luser = LUser, lserver = LServer }) -> |
113 |
33 |
Schema = ["jid", "name", "subscription", "ask", "groups", "askmessage", "xs"], |
114 |
33 |
Records = get_roster(HostType, LUser, LServer), |
115 |
33 |
SerializedRecords = lists:map(fun roster_record_to_gdpr_entry/1, Records), |
116 |
33 |
[{roster, Schema, SerializedRecords} | Acc]. |
117 |
|
|
118 |
|
-spec roster_record_to_gdpr_entry(roster()) -> gdpr:entry(). |
119 |
|
roster_record_to_gdpr_entry(#roster{ jid = JID, name = Name, |
120 |
|
subscription = Subscription, ask = Ask, groups = Groups, |
121 |
|
askmessage = AskMessage, xs = XS }) -> |
122 |
2 |
[ |
123 |
|
jid:to_binary(JID), |
124 |
|
Name, |
125 |
|
atom_to_binary(Subscription, utf8), |
126 |
|
atom_to_binary(Ask, utf8), |
127 |
:-( |
string:join([ unicode:characters_to_list(G) || G <- Groups ], ", "), |
128 |
|
AskMessage, |
129 |
:-( |
<< (exml:to_binary(X)) || X <- XS >> |
130 |
|
]. |
131 |
|
|
132 |
|
%%-------------------------------------------------------------------- |
133 |
|
%% mod_roster's callbacks |
134 |
|
%%-------------------------------------------------------------------- |
135 |
|
|
136 |
|
-spec start(mongooseim:host_type(), gen_mod:module_opts()) -> any(). |
137 |
|
start(HostType, Opts = #{iqdisc := IQDisc}) -> |
138 |
353 |
mod_roster_backend:init(HostType, Opts), |
139 |
353 |
ejabberd_hooks:add(hooks(HostType)), |
140 |
353 |
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm, |
141 |
|
fun ?MODULE:process_iq/5, #{}, IQDisc). |
142 |
|
|
143 |
|
-spec stop(mongooseim:host_type()) -> any(). |
144 |
|
stop(HostType) -> |
145 |
340 |
ejabberd_hooks:delete(hooks(HostType)), |
146 |
340 |
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm). |
147 |
|
|
148 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
149 |
|
config_spec() -> |
150 |
164 |
#section{ |
151 |
|
items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(), |
152 |
|
<<"versioning">> => #option{type = boolean}, |
153 |
|
<<"store_current_id">> => #option{type = boolean}, |
154 |
|
<<"backend">> => #option{type = atom, |
155 |
|
validate = {module, mod_roster}}, |
156 |
|
<<"riak">> => riak_config_spec() |
157 |
|
}, |
158 |
|
format_items = map, |
159 |
|
defaults = #{<<"iqdisc">> => one_queue, |
160 |
|
<<"versioning">> => false, |
161 |
|
<<"store_current_id">> => false, |
162 |
|
<<"backend">> => mnesia}, |
163 |
|
process = fun remove_unused_backend_opts/1 |
164 |
|
}. |
165 |
|
|
166 |
|
riak_config_spec() -> |
167 |
164 |
#section{items = #{<<"bucket_type">> => #option{type = binary, |
168 |
|
validate = non_empty}, |
169 |
|
<<"version_bucket_type">> => #option{type = binary, |
170 |
|
validate = non_empty}}, |
171 |
|
include = always, |
172 |
|
format_items = map, |
173 |
|
defaults = #{<<"bucket_type">> => <<"rosters">>, |
174 |
|
<<"version_bucket_type">> => <<"roster_versions">>} |
175 |
|
}. |
176 |
|
|
177 |
:-( |
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts; |
178 |
82 |
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts). |
179 |
|
|
180 |
146 |
supported_features() -> [dynamic_domains]. |
181 |
|
|
182 |
|
hooks(HostType) -> |
183 |
693 |
[{roster_get, HostType, ?MODULE, get_user_roster, 50}, |
184 |
|
{roster_in_subscription, HostType, ?MODULE, in_subscription, 50}, |
185 |
|
{roster_out_subscription, HostType, ?MODULE, out_subscription, 50}, |
186 |
|
{roster_get_subscription_lists, HostType, ?MODULE, get_subscription_lists, 50}, |
187 |
|
{roster_get_jid_info, HostType, ?MODULE, get_jid_info, 50}, |
188 |
|
{remove_user, HostType, ?MODULE, remove_user, 50}, |
189 |
|
{remove_domain, HostType, ?MODULE, remove_domain, 50}, |
190 |
|
{anonymous_purge_hook, HostType, ?MODULE, remove_user, 50}, |
191 |
|
{roster_get_versioning_feature, HostType, ?MODULE, get_versioning_feature, 50}, |
192 |
|
{get_personal_data, HostType, ?MODULE, get_personal_data, 50}]. |
193 |
|
|
194 |
|
-spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> |
195 |
|
{mongoose_acc:t(), jlib:iq()}. |
196 |
|
process_iq(Acc, From = #jid{lserver = LServer, luser = LUser}, |
197 |
|
To = #jid{lserver = LServer, luser = LUser}, IQ, _Extra) -> |
198 |
67 |
process_local_iq(Acc, From, To, IQ); |
199 |
|
process_iq(Acc, _From, _To, IQ = #iq{lang = Lang, sub_el = SubEl}, _Extra) -> |
200 |
|
%% Error type 'forbidden' is specified in Section 2.3.3 of RFC6121 for iq set. |
201 |
|
%% The behaviour is unspecified for iq get, but it makes sense to handle them consistently. |
202 |
2 |
ErrorMsg = <<"It is forbidden to query the roster of another user">>, |
203 |
2 |
ErrorEl = mongoose_xmpp_errors:forbidden(Lang, ErrorMsg), |
204 |
2 |
{Acc, IQ#iq{type = error, sub_el = [SubEl, ErrorEl]}}. |
205 |
|
|
206 |
|
-spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq()) -> |
207 |
|
{mongoose_acc:t(), jlib:iq()}. |
208 |
|
process_local_iq(Acc, From, To, #iq{type = Type} = IQ) -> |
209 |
67 |
HostType = mongoose_acc:host_type(Acc), |
210 |
67 |
IQReply = case Type of |
211 |
37 |
set -> process_iq_set(HostType, From, To, IQ); |
212 |
30 |
get -> process_iq_get(HostType, From, To, IQ) |
213 |
|
end, |
214 |
67 |
{Acc, IQReply}. |
215 |
|
|
216 |
|
roster_hash(Items) -> |
217 |
4 |
L = [R#roster{groups = lists:sort(Grs)} || |
218 |
4 |
R = #roster{groups = Grs} <- Items], |
219 |
4 |
sha:sha1_hex(term_to_binary(lists:sort(L))). |
220 |
|
|
221 |
|
-spec roster_versioning_enabled(mongooseim:host_type()) -> boolean(). |
222 |
|
roster_versioning_enabled(HostType) -> |
223 |
3324 |
gen_mod:get_module_opt(HostType, ?MODULE, versioning, false). |
224 |
|
|
225 |
|
-spec roster_version_on_db(mongooseim:host_type()) -> boolean(). |
226 |
|
roster_version_on_db(HostType) -> |
227 |
765 |
gen_mod:get_module_opt(HostType, ?MODULE, store_current_id, false). |
228 |
|
|
229 |
|
%% Returns a list that may contain an xmlel with the XEP-237 feature if it's enabled. |
230 |
|
get_versioning_feature(Acc, HostType) -> |
231 |
2607 |
case roster_versioning_enabled(HostType) of |
232 |
|
true -> |
233 |
4 |
Feature = #xmlel{name = <<"ver">>, |
234 |
|
attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}]}, |
235 |
4 |
[Feature | Acc]; |
236 |
2603 |
false -> [] |
237 |
|
end. |
238 |
|
|
239 |
|
roster_version(HostType, #jid{luser = LUser, lserver = LServer} = JID) -> |
240 |
2 |
case roster_version_on_db(HostType) of |
241 |
|
true -> |
242 |
1 |
case read_roster_version(HostType, LUser, LServer) of |
243 |
:-( |
error -> not_found; |
244 |
1 |
V -> V |
245 |
|
end; |
246 |
|
false -> |
247 |
1 |
R = get_roster_old(HostType, JID), |
248 |
1 |
roster_hash(R) |
249 |
|
end. |
250 |
|
|
251 |
|
%% Load roster from DB only if neccesary. |
252 |
|
%% It is neccesary if |
253 |
|
%% - roster versioning is disabled in server OR |
254 |
|
%% - roster versioning is not used by the client OR |
255 |
|
%% - roster versioning is used by server and client, |
256 |
|
%% BUT the server isn't storing versions on db OR |
257 |
|
%% - the roster version from client don't match current version. |
258 |
|
-spec process_iq_get(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq(). |
259 |
|
process_iq_get(HostType, From, To, #iq{sub_el = SubEl} = IQ) -> |
260 |
30 |
AttrVer = exml_query:attr(SubEl, <<"ver">>), %% type binary() | undefined |
261 |
30 |
VersioningRequested = is_binary(AttrVer), |
262 |
30 |
VersioningEnabled = roster_versioning_enabled(HostType), |
263 |
30 |
VersionOnDb = roster_version_on_db(HostType), |
264 |
30 |
Strategy = choose_get_user_roster_strategy(VersioningRequested, VersioningEnabled, VersionOnDb), |
265 |
30 |
{ItemsToSend, VersionToSend} = |
266 |
|
get_user_roster_based_on_version(HostType, Strategy, AttrVer, From, To), |
267 |
30 |
IQ#iq{type = result, sub_el = create_sub_el(ItemsToSend, VersionToSend)}. |
268 |
|
|
269 |
|
-spec choose_get_user_roster_strategy(VersioningRequested :: boolean(), |
270 |
|
VersioningEnabled :: boolean(), |
271 |
|
VersionOnDb :: boolean()) -> |
272 |
|
get_user_roster_strategy(). |
273 |
3 |
choose_get_user_roster_strategy(true, true, true) -> db_versioning; |
274 |
3 |
choose_get_user_roster_strategy(true, true, false) -> hash_versioning; |
275 |
24 |
choose_get_user_roster_strategy(_, _, _) -> no_versioning. |
276 |
|
|
277 |
|
get_user_roster_based_on_version(HostType, db_versioning, RequestedVersion, From, To) -> |
278 |
3 |
get_user_roster_db_versioning(HostType, RequestedVersion, From, To); |
279 |
|
get_user_roster_based_on_version(HostType, hash_versioning, RequestedVersion, From, To) -> |
280 |
3 |
get_user_roster_hash_versioning(HostType, RequestedVersion, From, To); |
281 |
|
get_user_roster_based_on_version(HostType, no_versioning, _RequestedVersion, From, To) -> |
282 |
24 |
get_user_roster_no_versioning(HostType, From, To). |
283 |
|
|
284 |
|
get_user_roster_db_versioning(HostType, RequestedVersion, From, To) |
285 |
|
when is_binary(RequestedVersion) -> |
286 |
3 |
LUser = From#jid.luser, |
287 |
3 |
LServer = From#jid.lserver, |
288 |
3 |
case read_roster_version(HostType, LUser, LServer) of |
289 |
|
error -> |
290 |
1 |
RosterVersion = write_roster_version(HostType, LUser, LServer), |
291 |
1 |
{lists:map(fun item_to_xml/1, |
292 |
|
get_roster_old(HostType, To#jid.server, From)), |
293 |
|
RosterVersion}; |
294 |
|
RequestedVersion -> |
295 |
1 |
{false, false}; |
296 |
|
NewVersion -> |
297 |
1 |
{lists:map(fun item_to_xml/1, |
298 |
|
get_roster_old(HostType, To#jid.server, From)), |
299 |
|
NewVersion} |
300 |
|
end. |
301 |
|
|
302 |
|
get_user_roster_hash_versioning(HostType, RequestedVersion, From, To) |
303 |
|
when is_binary(RequestedVersion) -> |
304 |
3 |
RosterItems = get_roster_old(HostType, To#jid.lserver, From), |
305 |
3 |
case roster_hash(RosterItems) of |
306 |
|
RequestedVersion -> |
307 |
1 |
{false, false}; |
308 |
|
New -> |
309 |
2 |
{lists:map(fun item_to_xml/1, RosterItems), New} |
310 |
|
end. |
311 |
|
|
312 |
|
get_user_roster_no_versioning(HostType, From, To) -> |
313 |
24 |
{lists:map(fun item_to_xml/1, |
314 |
|
get_roster_old(HostType, To#jid.lserver, From)), |
315 |
|
false}. |
316 |
|
|
317 |
|
create_sub_el(false, false) -> |
318 |
2 |
[]; |
319 |
|
create_sub_el(Items, false) -> |
320 |
24 |
[#xmlel{name = <<"query">>, |
321 |
|
attrs = [{<<"xmlns">>, ?NS_ROSTER}], |
322 |
|
children = Items}]; |
323 |
|
create_sub_el(Items, Version) -> |
324 |
4 |
[#xmlel{name = <<"query">>, |
325 |
|
attrs = [{<<"xmlns">>, ?NS_ROSTER}, |
326 |
|
{<<"ver">>, Version}], |
327 |
|
children = Items}]. |
328 |
|
|
329 |
|
-spec get_user_roster(mongoose_acc:t(), jid:jid() | {jid:luser(), jid:lserver()}) -> |
330 |
|
mongoose_acc:t(). |
331 |
|
get_user_roster(Acc, #jid{luser = LUser, lserver = LServer}) -> |
332 |
2392 |
HostType = mongoose_acc:host_type(Acc), |
333 |
2392 |
case mongoose_acc:get(roster, show_full_roster, false, Acc) of |
334 |
|
true -> |
335 |
51 |
Roster = get_roster(HostType, LUser, LServer), |
336 |
51 |
mongoose_acc:append(roster, items, Roster, Acc); |
337 |
|
_ -> |
338 |
2341 |
Roster = lists:filter(fun (#roster{subscription = none, ask = in}) -> |
339 |
5 |
false; |
340 |
|
(_) -> |
341 |
380 |
true |
342 |
|
end, get_roster(HostType, LUser, LServer)), |
343 |
2341 |
mongoose_acc:append(roster, items, Roster, Acc) |
344 |
|
end. |
345 |
|
|
346 |
|
item_to_xml(Item) -> |
347 |
388 |
Attrs1 = [{<<"jid">>, |
348 |
|
jid:to_binary(Item#roster.jid)}], |
349 |
388 |
Attrs2 = case Item#roster.name of |
350 |
176 |
<<"">> -> Attrs1; |
351 |
212 |
Name -> [{<<"name">>, Name} | Attrs1] |
352 |
|
end, |
353 |
388 |
Attrs3 = case Item#roster.subscription of |
354 |
193 |
none -> [{<<"subscription">>, <<"none">>} | Attrs2]; |
355 |
41 |
from -> [{<<"subscription">>, <<"from">>} | Attrs2]; |
356 |
50 |
to -> [{<<"subscription">>, <<"to">>} | Attrs2]; |
357 |
83 |
both -> [{<<"subscription">>, <<"both">>} | Attrs2]; |
358 |
21 |
remove -> [{<<"subscription">>, <<"remove">>} | Attrs2] |
359 |
|
end, |
360 |
388 |
Attrs4 = case ask_to_pending(Item#roster.ask) of |
361 |
71 |
out -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; |
362 |
44 |
both -> [{<<"ask">>, <<"subscribe">>} | Attrs3]; |
363 |
273 |
_ -> Attrs3 |
364 |
|
end, |
365 |
388 |
SubEls1 = lists:map(fun (G) -> |
366 |
211 |
#xmlel{name = <<"group">>, attrs = [], |
367 |
|
children = [{xmlcdata, G}]} |
368 |
|
end, |
369 |
|
Item#roster.groups), |
370 |
388 |
SubEls = SubEls1 ++ Item#roster.xs, |
371 |
388 |
#xmlel{name = <<"item">>, attrs = Attrs4, |
372 |
|
children = SubEls}. |
373 |
|
|
374 |
|
-spec process_iq_set(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq(). |
375 |
|
process_iq_set(HostType, From, To, #iq{sub_el = SubEl} = IQ) -> |
376 |
37 |
#xmlel{children = Els} = SubEl, |
377 |
37 |
mongoose_hooks:roster_set(HostType, From, To, SubEl), |
378 |
37 |
lists:foreach(fun(El) -> process_item_set(HostType, From, To, El) end, Els), |
379 |
37 |
IQ#iq{type = result, sub_el = []}. |
380 |
|
|
381 |
|
-spec process_item_set(mongooseim:host_type(), jid:jid(), jid:jid(), exml:element()) -> ok. |
382 |
|
process_item_set(HostType, From, To, #xmlel{attrs = Attrs} = El) -> |
383 |
37 |
JID1 = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)), |
384 |
37 |
do_process_item_set(HostType, JID1, From, To, El); |
385 |
:-( |
process_item_set(_HostType, _From, _To, _) -> ok. |
386 |
|
|
387 |
|
-spec do_process_item_set(mongooseim:host_type(), error | jid:jid(), jid:jid(), jid:jid(), |
388 |
|
exml:element()) -> ok. |
389 |
:-( |
do_process_item_set(_, error, _, _, _) -> ok; |
390 |
|
do_process_item_set(HostType, #jid{} = JID1, #jid{} = From, #jid{} = To, |
391 |
|
#xmlel{attrs = Attrs, children = Els}) -> |
392 |
37 |
MakeItem2 = fun(Item) -> |
393 |
37 |
Item1 = process_item_attrs(Item, Attrs), |
394 |
37 |
process_item_els(Item1, Els) |
395 |
|
end, |
396 |
37 |
set_roster_item(HostType, JID1, From, To, MakeItem2), |
397 |
37 |
ok. |
398 |
|
|
399 |
|
%% @doc this is run when a roster item is to be added, updated or removed |
400 |
|
%% the interface of this func could probably be a bit simpler |
401 |
|
-spec set_roster_item(mongooseim:host_type(), |
402 |
|
ContactJID :: jid:jid(), |
403 |
|
From :: jid:jid(), |
404 |
|
To :: jid:jid(), |
405 |
|
fun((roster()) -> roster())) -> ok | {error, any()}. |
406 |
|
set_roster_item(HostType, ContactJID, From, To, MakeItem) -> |
407 |
106 |
ContactLJID = jid:to_lower(ContactJID), |
408 |
106 |
F = fun() -> set_roster_item_t(HostType, From, ContactLJID, MakeItem) end, |
409 |
106 |
case transaction(HostType, F) of |
410 |
|
{atomic, does_not_exist} -> |
411 |
5 |
{error, does_not_exist}; |
412 |
|
{atomic, {OldItem, NewItem}} -> |
413 |
101 |
push_item(HostType, From, To, NewItem), |
414 |
101 |
case NewItem#roster.subscription of |
415 |
|
remove -> |
416 |
22 |
send_unsubscribing_presence(From, OldItem), |
417 |
22 |
ok; |
418 |
79 |
_ -> ok |
419 |
|
end; |
420 |
|
E -> |
421 |
:-( |
?LOG_ERROR(#{what => roster_set_item_failed, reason => E}), |
422 |
:-( |
{error, E} |
423 |
|
end. |
424 |
|
|
425 |
|
-spec set_roster_item_t(mongooseim:host_type(), jid:jid(), contact(), |
426 |
|
fun((roster()) -> roster())) -> |
427 |
|
does_not_exist | {roster(), roster()}. |
428 |
|
set_roster_item_t(HostType, UserJid = #jid{luser = LUser, lserver = LServer}, |
429 |
|
ContactLJID, MakeItem) -> |
430 |
106 |
InitialItem = get_roster_entry_t(HostType, UserJid, ContactLJID, short), |
431 |
106 |
Item1 = case InitialItem of |
432 |
74 |
does_not_exist -> new_roster_item(LUser, LServer, ContactLJID); |
433 |
32 |
Item -> Item |
434 |
|
end, |
435 |
106 |
Item2 = MakeItem(Item1), |
436 |
106 |
case {InitialItem, Item2} of |
437 |
|
{does_not_exist, #roster{subscription = remove}} -> |
438 |
|
%% Cannot remove a non-existing item |
439 |
5 |
does_not_exist; |
440 |
|
_ -> |
441 |
101 |
case Item2#roster.subscription of |
442 |
22 |
remove -> del_roster_t(HostType, LUser, LServer, ContactLJID); |
443 |
79 |
_ -> update_roster_t(HostType, Item2) |
444 |
|
end, |
445 |
101 |
Item3 = mongoose_hooks:roster_process_item(HostType, LServer, Item2), |
446 |
101 |
case roster_version_on_db(HostType) of |
447 |
1 |
true -> write_roster_version_t(HostType, LUser, LServer); |
448 |
100 |
false -> ok |
449 |
|
end, |
450 |
101 |
{Item1, Item3} |
451 |
|
end. |
452 |
|
|
453 |
|
-spec new_roster_item(jid:luser(), jid:lserver(), jid:simple_jid()) -> roster(). |
454 |
|
new_roster_item(LUser, LServer, ContactLJID) -> |
455 |
74 |
#roster{usj = {LUser, LServer, ContactLJID}, |
456 |
|
us = {LUser, LServer}, |
457 |
|
jid = ContactLJID}. |
458 |
|
|
459 |
|
process_item_attrs(Item, [{<<"jid">>, Val} | Attrs]) -> |
460 |
37 |
case jid:from_binary(Val) of |
461 |
|
error -> |
462 |
:-( |
process_item_attrs(Item, Attrs); |
463 |
|
JID1 -> |
464 |
37 |
JID = jid:to_lower(JID1), |
465 |
37 |
process_item_attrs(Item#roster{jid = JID}, Attrs) |
466 |
|
end; |
467 |
|
process_item_attrs(Item, [{<<"name">>, Val} | Attrs]) -> |
468 |
30 |
process_item_attrs(Item#roster{name = Val}, Attrs); |
469 |
|
process_item_attrs(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) -> |
470 |
7 |
process_item_attrs(Item#roster{subscription = remove}, Attrs); |
471 |
|
process_item_attrs(Item, [_ | Attrs]) -> |
472 |
:-( |
process_item_attrs(Item, Attrs); |
473 |
|
process_item_attrs(Item, []) -> |
474 |
37 |
Item. |
475 |
|
|
476 |
|
process_item_els(Item, |
477 |
|
[#xmlel{name = Name, attrs = Attrs, children = SEls} |
478 |
|
| Els]) -> |
479 |
309 |
case Name of |
480 |
|
<<"group">> -> |
481 |
309 |
Groups = [xml:get_cdata(SEls) | Item#roster.groups], |
482 |
309 |
process_item_els(Item#roster{groups = Groups}, Els); |
483 |
|
_ -> |
484 |
:-( |
case xml:get_attr_s(<<"xmlns">>, Attrs) of |
485 |
:-( |
<<"">> -> process_item_els(Item, Els); |
486 |
|
_ -> |
487 |
:-( |
XEls = [#xmlel{name = Name, attrs = Attrs, |
488 |
|
children = SEls} |
489 |
|
| Item#roster.xs], |
490 |
:-( |
process_item_els(Item#roster{xs = XEls}, Els) |
491 |
|
end |
492 |
|
end; |
493 |
|
process_item_els(Item, [{xmlcdata, _} | Els]) -> |
494 |
:-( |
process_item_els(Item, Els); |
495 |
332 |
process_item_els(Item, []) -> Item. |
496 |
|
|
497 |
|
push_item(HostType, JID, From, Item) -> |
498 |
687 |
ejabberd_sm:route(jid:make_noprep(<<>>, <<>>, <<>>), JID, |
499 |
|
{broadcast, {item, Item#roster.jid, Item#roster.subscription}}), |
500 |
687 |
case roster_versioning_enabled(HostType) of |
501 |
|
true -> |
502 |
2 |
push_item_version(JID, From, Item, roster_version(HostType, JID)); |
503 |
|
false -> |
504 |
685 |
lists:foreach(fun(Resource) -> |
505 |
342 |
push_item_without_version(HostType, JID, Resource, From, Item) |
506 |
|
end, |
507 |
|
ejabberd_sm:get_user_resources(JID)) |
508 |
|
end. |
509 |
|
|
510 |
|
push_item_without_version(HostType, JID, Resource, From, Item) -> |
511 |
342 |
mongoose_hooks:roster_push(HostType, From, Item), |
512 |
342 |
push_item_final(jid:replace_resource(JID, Resource), From, Item, not_found). |
513 |
|
|
514 |
|
push_item_version(JID, From, Item, RosterVersion) -> |
515 |
2 |
lists:foreach(fun(Resource) -> |
516 |
2 |
push_item_final(jid:replace_resource(JID, Resource), |
517 |
|
From, Item, RosterVersion) |
518 |
|
end, ejabberd_sm:get_user_resources(JID)). |
519 |
|
|
520 |
|
push_item_final(JID, From, Item, RosterVersion) -> |
521 |
344 |
ExtraAttrs = case RosterVersion of |
522 |
342 |
not_found -> []; |
523 |
2 |
_ -> [{<<"ver">>, RosterVersion}] |
524 |
|
end, |
525 |
344 |
ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, |
526 |
|
%% @doc Roster push, calculate and include the version attribute. |
527 |
|
%% TODO: don't push to those who didn't load roster |
528 |
|
id = <<"push", (mongoose_bin:gen_from_crypto())/binary>>, |
529 |
|
sub_el = |
530 |
|
[#xmlel{name = <<"query">>, |
531 |
|
attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs], |
532 |
|
children = [item_to_xml(Item)]}]}, |
533 |
344 |
ejabberd_router:route(From, JID, jlib:iq_to_xml(ResIQ)). |
534 |
|
|
535 |
|
-spec get_subscription_lists(Acc :: mongoose_acc:t(), JID :: jid:jid()) -> |
536 |
|
mongoose_acc:t(). |
537 |
|
get_subscription_lists(Acc, #jid{luser = LUser, lserver = LServer} = JID) -> |
538 |
5031 |
Items = mod_roster_backend:get_subscription_lists(Acc, LUser, LServer), |
539 |
5031 |
SubLists = fill_subscription_lists(JID, LServer, Items, [], [], []), |
540 |
5031 |
mongoose_acc:set(roster, subscription_lists, SubLists, Acc). |
541 |
|
|
542 |
|
fill_subscription_lists(JID, LServer, [#roster{} = I | Is], F, T, P) -> |
543 |
32 |
J = element(3, I#roster.usj), |
544 |
32 |
NewP = build_pending(I, JID, P), |
545 |
32 |
case I#roster.subscription of |
546 |
|
both -> |
547 |
25 |
fill_subscription_lists(JID, LServer, Is, [J | F], [J | T], NewP); |
548 |
|
from -> |
549 |
1 |
fill_subscription_lists(JID, LServer, Is, [J | F], T, NewP); |
550 |
:-( |
to -> fill_subscription_lists(JID, LServer, Is, F, [J | T], NewP); |
551 |
6 |
_ -> fill_subscription_lists(JID, LServer, Is, F, T, NewP) |
552 |
|
end; |
553 |
5031 |
fill_subscription_lists(_, _LServer, [], F, T, P) -> {F, T, P}. |
554 |
|
|
555 |
|
build_pending(#roster{ask = Ask} = I, JID, P) |
556 |
|
when Ask == in; Ask == both -> |
557 |
6 |
Status = case I#roster.askmessage of |
558 |
6 |
Message when is_binary(Message) -> Message; |
559 |
:-( |
true -> <<>> |
560 |
|
end, |
561 |
6 |
StatusEl = #xmlel{ |
562 |
|
name = <<"status">>, |
563 |
|
children = [#xmlcdata{content = Status}]}, |
564 |
6 |
El = #xmlel{ |
565 |
|
name = <<"presence">>, |
566 |
|
attrs = [{<<"from">>, jid:to_binary(I#roster.jid)}, |
567 |
|
{<<"to">>, jid:to_binary(JID)}, |
568 |
|
{<<"type">>, <<"subscribe">>}], |
569 |
|
children = [StatusEl]}, |
570 |
6 |
[El | P]; |
571 |
|
build_pending(_, _, P) -> |
572 |
26 |
P. |
573 |
|
|
574 |
:-( |
ask_to_pending(subscribe) -> out; |
575 |
:-( |
ask_to_pending(unsubscribe) -> none; |
576 |
388 |
ask_to_pending(Ask) -> Ask. |
577 |
|
|
578 |
|
-spec in_subscription(Acc :: mongoose_acc:t(), |
579 |
|
ToJID :: jid:jid(), |
580 |
|
FromJID :: jid:jid(), |
581 |
|
Type :: sub_presence(), |
582 |
|
Reason :: iodata()) -> |
583 |
|
mongoose_acc:t(). |
584 |
|
in_subscription(Acc, ToJID, FromJID, Type, Reason) -> |
585 |
514 |
HostType = mongoose_acc:host_type(Acc), |
586 |
514 |
Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason), |
587 |
514 |
mongoose_acc:set(hook, result, Res, Acc). |
588 |
|
|
589 |
|
-spec out_subscription(Acc :: mongoose_acc:t(), |
590 |
|
FromJID :: jid:jid(), |
591 |
|
ToJID :: jid:jid(), |
592 |
|
Type :: sub_presence()) -> |
593 |
|
mongoose_acc:t(). |
594 |
|
out_subscription(Acc, FromJID, ToJID, Type) -> |
595 |
146 |
HostType = mongoose_acc:host_type(Acc), |
596 |
146 |
Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>), |
597 |
146 |
mongoose_acc:set(hook, result, Res, Acc). |
598 |
|
|
599 |
|
-spec process_subscription(mongooseim:host_type(), in | out, jid:jid(), jid:jid(), |
600 |
|
sub_presence(), iodata()) -> |
601 |
|
boolean(). |
602 |
|
process_subscription(HostType, Direction, JID, ContactJID, Type, Reason) -> |
603 |
660 |
TransactionFun = |
604 |
|
fun() -> |
605 |
664 |
process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason) |
606 |
|
end, |
607 |
660 |
case transaction(HostType, TransactionFun) of |
608 |
|
{atomic, {Push, AutoReply}} -> |
609 |
660 |
case AutoReply of |
610 |
505 |
none -> ok; |
611 |
|
_ -> |
612 |
155 |
PresenceStanza = #xmlel{name = <<"presence">>, |
613 |
|
attrs = [{<<"type">>, autoreply_to_type(AutoReply)}], |
614 |
|
children = []}, |
615 |
155 |
ejabberd_router:route(JID, ContactJID, PresenceStanza) |
616 |
|
end, |
617 |
660 |
case Push of |
618 |
|
{push, #roster{subscription = none, ask = in}} -> |
619 |
46 |
true; |
620 |
|
{push, Item} -> |
621 |
586 |
push_item(HostType, JID, JID, Item), |
622 |
586 |
true; |
623 |
28 |
none -> false |
624 |
|
end; |
625 |
:-( |
_ -> false |
626 |
|
end. |
627 |
|
|
628 |
:-( |
autoreply_to_type(subscribed) -> <<"subscribed">>; |
629 |
155 |
autoreply_to_type(unsubscribed) -> <<"unsubscribed">>. |
630 |
|
|
631 |
|
-spec process_subscription_t(mongooseim:host_type(), in | out, jid:jid(), jid:jid(), |
632 |
|
sub_presence(), iodata()) -> |
633 |
|
{Push :: none | {push, roster()}, |
634 |
|
AutoReply :: none | subscribed | unsubscribed}. |
635 |
|
process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason) -> |
636 |
664 |
#jid{luser = LUser, lserver = LServer} = JID, |
637 |
664 |
ContactLJID = jid:to_lower(ContactJID), |
638 |
664 |
Item = case get_roster_entry_t(HostType, JID, ContactLJID, full) of |
639 |
|
does_not_exist -> |
640 |
66 |
#roster{usj = {LUser, LServer, ContactLJID}, |
641 |
|
us = {LUser, LServer}, |
642 |
|
jid = ContactLJID}; |
643 |
598 |
R -> R |
644 |
|
end, |
645 |
664 |
NewState = case Direction of |
646 |
|
out -> |
647 |
146 |
out_state_change(Item#roster.subscription, |
648 |
|
Item#roster.ask, Type); |
649 |
|
in -> |
650 |
518 |
in_state_change(Item#roster.subscription, |
651 |
|
Item#roster.ask, Type) |
652 |
|
end, |
653 |
664 |
AutoReply = case Direction of |
654 |
146 |
out -> none; |
655 |
|
in -> |
656 |
518 |
in_auto_reply(Item#roster.subscription, |
657 |
|
Item#roster.ask, Type) |
658 |
|
end, |
659 |
664 |
AskMessage = case NewState of |
660 |
44 |
{_, both} -> Reason; |
661 |
70 |
{_, in} -> Reason; |
662 |
550 |
_ -> <<"">> |
663 |
|
end, |
664 |
664 |
case NewState of |
665 |
23 |
none -> {none, AutoReply}; |
666 |
|
{none, none} |
667 |
|
when Item#roster.subscription == none, |
668 |
|
Item#roster.ask == in -> |
669 |
5 |
del_roster_t(HostType, LUser, LServer, ContactLJID), {none, AutoReply}; |
670 |
|
{Subscription, Pending} -> |
671 |
636 |
NewItem = Item#roster{subscription = Subscription, |
672 |
|
ask = Pending, |
673 |
|
askmessage = iolist_to_binary(AskMessage)}, |
674 |
636 |
roster_subscribe_t(HostType, NewItem), |
675 |
632 |
case roster_version_on_db(HostType) of |
676 |
:-( |
true -> write_roster_version_t(HostType, LUser, LServer); |
677 |
632 |
false -> ok |
678 |
|
end, |
679 |
632 |
{{push, NewItem}, AutoReply} |
680 |
|
end. |
681 |
|
|
682 |
|
%% in_state_change(Subscription, Pending, Type) -> NewState |
683 |
|
%% NewState = none | {NewSubscription, NewPending} |
684 |
|
-ifdef(ROSTER_GATEWAY_WORKAROUND). |
685 |
|
|
686 |
|
-define(NNSD, {to, none}). |
687 |
|
|
688 |
|
-define(NISD, {to, in}). |
689 |
|
|
690 |
|
-else. |
691 |
|
|
692 |
|
-define(NNSD, none). |
693 |
|
|
694 |
|
-define(NISD, none). |
695 |
|
|
696 |
|
-endif. |
697 |
|
|
698 |
46 |
in_state_change(none, none, subscribe) -> {none, in}; |
699 |
:-( |
in_state_change(none, none, subscribed) -> ?NNSD; |
700 |
4 |
in_state_change(none, none, unsubscribe) -> none; |
701 |
13 |
in_state_change(none, none, unsubscribed) -> none; |
702 |
22 |
in_state_change(none, out, subscribe) -> {none, both}; |
703 |
17 |
in_state_change(none, out, subscribed) -> {to, none}; |
704 |
:-( |
in_state_change(none, out, unsubscribe) -> none; |
705 |
5 |
in_state_change(none, out, unsubscribed) -> {none, none}; |
706 |
1 |
in_state_change(none, in, subscribe) -> none; |
707 |
:-( |
in_state_change(none, in, subscribed) -> ?NISD; |
708 |
:-( |
in_state_change(none, in, unsubscribe) -> {none, none}; |
709 |
:-( |
in_state_change(none, in, unsubscribed) -> none; |
710 |
:-( |
in_state_change(none, both, subscribe) -> none; |
711 |
22 |
in_state_change(none, both, subscribed) -> {to, in}; |
712 |
:-( |
in_state_change(none, both, unsubscribe) -> {none, out}; |
713 |
:-( |
in_state_change(none, both, unsubscribed) -> {none, in}; |
714 |
2 |
in_state_change(to, none, subscribe) -> {to, in}; |
715 |
:-( |
in_state_change(to, none, subscribed) -> none; |
716 |
:-( |
in_state_change(to, none, unsubscribe) -> none; |
717 |
149 |
in_state_change(to, none, unsubscribed) -> {none, none}; |
718 |
:-( |
in_state_change(to, in, subscribe) -> none; |
719 |
:-( |
in_state_change(to, in, subscribed) -> none; |
720 |
:-( |
in_state_change(to, in, unsubscribe) -> {to, none}; |
721 |
:-( |
in_state_change(to, in, unsubscribed) -> {none, in}; |
722 |
:-( |
in_state_change(from, none, subscribe) -> none; |
723 |
:-( |
in_state_change(from, none, subscribed) -> {both, none}; |
724 |
12 |
in_state_change(from, none, unsubscribe) -> {none, none}; |
725 |
:-( |
in_state_change(from, none, unsubscribed) -> none; |
726 |
:-( |
in_state_change(from, out, subscribe) -> none; |
727 |
24 |
in_state_change(from, out, subscribed) -> {both, none}; |
728 |
:-( |
in_state_change(from, out, unsubscribe) -> {none, out}; |
729 |
:-( |
in_state_change(from, out, unsubscribed) -> {from, none}; |
730 |
:-( |
in_state_change(both, none, subscribe) -> none; |
731 |
:-( |
in_state_change(both, none, subscribed) -> none; |
732 |
146 |
in_state_change(both, none, unsubscribe) -> {to, none}; |
733 |
55 |
in_state_change(both, none, unsubscribed) -> {from, none}. |
734 |
|
|
735 |
46 |
out_state_change(none, none, subscribe) -> {none, out}; |
736 |
4 |
out_state_change(none, none, subscribed) -> none; |
737 |
1 |
out_state_change(none, none, unsubscribe) -> none; |
738 |
:-( |
out_state_change(none, none, unsubscribed) -> none; |
739 |
|
out_state_change(none, out, subscribe) -> |
740 |
:-( |
{none, out}; %% We need to resend query (RFC3921, section 9.2) |
741 |
:-( |
out_state_change(none, out, subscribed) -> none; |
742 |
:-( |
out_state_change(none, out, unsubscribe) -> {none, none}; |
743 |
:-( |
out_state_change(none, out, unsubscribed) -> none; |
744 |
22 |
out_state_change(none, in, subscribe) -> {none, both}; |
745 |
15 |
out_state_change(none, in, subscribed) -> {from, none}; |
746 |
:-( |
out_state_change(none, in, unsubscribe) -> none; |
747 |
5 |
out_state_change(none, in, unsubscribed) -> {none, none}; |
748 |
:-( |
out_state_change(none, both, subscribe) -> none; |
749 |
22 |
out_state_change(none, both, subscribed) -> {from, out}; |
750 |
:-( |
out_state_change(none, both, unsubscribe) -> {none, in}; |
751 |
:-( |
out_state_change(none, both, unsubscribed) -> {none, out}; |
752 |
:-( |
out_state_change(to, none, subscribe) -> none; |
753 |
:-( |
out_state_change(to, none, subscribed) -> {both, none}; |
754 |
4 |
out_state_change(to, none, unsubscribe) -> {none, none}; |
755 |
:-( |
out_state_change(to, none, unsubscribed) -> none; |
756 |
:-( |
out_state_change(to, in, subscribe) -> none; |
757 |
24 |
out_state_change(to, in, subscribed) -> {both, none}; |
758 |
:-( |
out_state_change(to, in, unsubscribe) -> {none, in}; |
759 |
:-( |
out_state_change(to, in, unsubscribed) -> {to, none}; |
760 |
2 |
out_state_change(from, none, subscribe) -> {from, out}; |
761 |
:-( |
out_state_change(from, none, subscribed) -> none; |
762 |
:-( |
out_state_change(from, none, unsubscribe) -> none; |
763 |
:-( |
out_state_change(from, none, unsubscribed) -> {none, none}; |
764 |
:-( |
out_state_change(from, out, subscribe) -> none; |
765 |
:-( |
out_state_change(from, out, subscribed) -> none; |
766 |
:-( |
out_state_change(from, out, unsubscribe) -> {from, none}; |
767 |
:-( |
out_state_change(from, out, unsubscribed) -> {none, out}; |
768 |
:-( |
out_state_change(both, none, subscribe) -> none; |
769 |
:-( |
out_state_change(both, none, subscribed) -> none; |
770 |
:-( |
out_state_change(both, none, unsubscribe) -> {from, none}; |
771 |
1 |
out_state_change(both, none, unsubscribed) -> {to, none}. |
772 |
|
|
773 |
:-( |
in_auto_reply(from, none, subscribe) -> subscribed; |
774 |
:-( |
in_auto_reply(from, out, subscribe) -> subscribed; |
775 |
:-( |
in_auto_reply(both, none, subscribe) -> subscribed; |
776 |
:-( |
in_auto_reply(none, in, unsubscribe) -> unsubscribed; |
777 |
:-( |
in_auto_reply(none, both, unsubscribe) -> unsubscribed; |
778 |
:-( |
in_auto_reply(to, in, unsubscribe) -> unsubscribed; |
779 |
12 |
in_auto_reply(from, none, unsubscribe) -> unsubscribed; |
780 |
:-( |
in_auto_reply(from, out, unsubscribe) -> unsubscribed; |
781 |
146 |
in_auto_reply(both, none, unsubscribe) -> unsubscribed; |
782 |
360 |
in_auto_reply(_, _, _) -> none. |
783 |
|
|
784 |
|
get_user_rosters_length(HostType, JID) -> |
785 |
1 |
length(get_roster_old(HostType, JID)). |
786 |
|
|
787 |
|
-spec remove_user(mongoose_acc:t(), jid:luser(), jid:lserver()) -> mongoose_acc:t(). |
788 |
|
remove_user(Acc, LUser, LServer) -> |
789 |
2283 |
HostType = mongoose_acc:host_type(Acc), |
790 |
2283 |
JID = jid:make_noprep(LUser, LServer, <<>>), |
791 |
2283 |
Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID), |
792 |
2283 |
F = fun() -> mod_roster_backend:remove_user_t(HostType, LUser, LServer) end, |
793 |
2283 |
case transaction(HostType, F) of |
794 |
|
{atomic, ok} -> |
795 |
2283 |
ok; |
796 |
|
Result -> |
797 |
:-( |
?LOG_ERROR(#{what => remove_user_transaction_failed, reason => Result}) |
798 |
|
end, |
799 |
2283 |
Acc1. |
800 |
|
|
801 |
|
try_send_unsubscription_to_rosteritems(Acc, JID) -> |
802 |
2283 |
try |
803 |
2283 |
send_unsubscription_to_rosteritems(Acc, JID) |
804 |
|
catch |
805 |
|
E:R:S -> |
806 |
:-( |
?LOG_WARNING(#{what => roster_unsubcribe_failed, |
807 |
:-( |
class => E, reason => R, stacktrace => S}), |
808 |
:-( |
Acc |
809 |
|
end. |
810 |
|
|
811 |
|
%% For each contact with Subscription: |
812 |
|
%% Both or From, send a "unsubscribed" presence stanza; |
813 |
|
%% Both or To, send a "unsubscribe" presence stanza. |
814 |
|
send_unsubscription_to_rosteritems(Acc, JID) -> |
815 |
2283 |
Acc1 = get_user_roster(Acc, JID), |
816 |
2283 |
RosterItems = mongoose_acc:get(roster, items, [], Acc1), |
817 |
2283 |
lists:foreach(fun(RosterItem) -> |
818 |
330 |
send_unsubscribing_presence(JID, RosterItem) |
819 |
|
end, RosterItems), |
820 |
2283 |
Acc1. |
821 |
|
|
822 |
|
-spec send_unsubscribing_presence(From :: jid:jid(), Item :: roster()) -> ok | mongoose_acc:t(). |
823 |
|
send_unsubscribing_presence(From, #roster{ subscription = Subscription } = Item) -> |
824 |
352 |
BareFrom = jid:to_bare(From), |
825 |
352 |
ContactJID = jid:make_noprep(Item#roster.jid), |
826 |
352 |
IsTo = Subscription == both orelse Subscription == to, |
827 |
352 |
IsFrom = Subscription == both orelse Subscription == from, |
828 |
352 |
case IsTo of |
829 |
160 |
true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribe">>); |
830 |
192 |
_ -> ok |
831 |
|
end, |
832 |
352 |
case IsFrom of |
833 |
155 |
true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribed">>); |
834 |
197 |
_ -> ok |
835 |
|
end. |
836 |
|
|
837 |
|
send_presence_type(From, To, Type) -> |
838 |
315 |
ejabberd_router:route(From, To, |
839 |
|
#xmlel{name = <<"presence">>, |
840 |
|
attrs = [{<<"type">>, Type}], children = []}). |
841 |
|
|
842 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
843 |
|
|
844 |
|
-spec remove_domain(mongoose_hooks:simple_acc(), |
845 |
|
mongooseim:host_type(), jid:lserver()) -> |
846 |
|
mongoose_hooks:simple_acc(). |
847 |
|
remove_domain(Acc, HostType, Domain) -> |
848 |
:-( |
case backend_module:is_exported(mod_roster_backend, remove_domain_t, 2) of |
849 |
|
true -> |
850 |
:-( |
F = fun() -> mod_roster_backend:remove_domain_t(HostType, Domain) end, |
851 |
:-( |
case transaction(HostType, F) of |
852 |
|
{atomic, ok} -> |
853 |
:-( |
ok; |
854 |
|
Result -> |
855 |
:-( |
?LOG_ERROR(#{what => remove_domain_transaction_failed, |
856 |
:-( |
reason => Result}) |
857 |
|
end; |
858 |
|
false -> |
859 |
:-( |
ok |
860 |
|
end, |
861 |
:-( |
Acc. |
862 |
|
|
863 |
|
-spec set_items(mongooseim:host_type(), jid:jid(), exml:element()) -> ok | {error, any()}. |
864 |
|
set_items(HostType, #jid{luser = LUser, lserver = LServer}, SubEl) -> |
865 |
295 |
F = fun() -> set_items_t(HostType, LUser, LServer, SubEl) end, |
866 |
295 |
case transaction(HostType, F) of |
867 |
|
{atomic, _} -> |
868 |
295 |
ok; |
869 |
|
Result -> |
870 |
:-( |
{error, Result} |
871 |
|
end. |
872 |
|
|
873 |
|
set_items_t(HostType, LUser, LServer, #xmlel{children = Els}) -> |
874 |
295 |
lists:foreach(fun(El) -> |
875 |
295 |
process_item_set_t(HostType, LUser, LServer, El) |
876 |
|
end, Els). |
877 |
|
|
878 |
|
%% @doc add a contact to roster, or update |
879 |
|
-spec set_roster_entry(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), [binary()]) -> |
880 |
|
ok | {error, any()}. |
881 |
|
set_roster_entry(HostType, UserJid, ContactJid, Name, Groups) -> |
882 |
49 |
UpdateF = fun(Item) -> Item#roster{name = Name, groups = Groups} end, |
883 |
49 |
set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF). |
884 |
|
|
885 |
|
%% @doc remove from roster - in practice it means changing subscription state to 'remove' |
886 |
|
-spec remove_from_roster(mongooseim:host_type(), UserJid :: jid:jid(), ContactJid :: jid:jid()) -> |
887 |
|
ok | {error, any()}. |
888 |
|
remove_from_roster(HostType, UserJid, ContactJid) -> |
889 |
20 |
UpdateF = fun(Item) -> Item#roster{subscription = remove} end, |
890 |
20 |
set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF). |
891 |
|
|
892 |
|
process_item_set_t(HostType, LUser, LServer, |
893 |
|
#xmlel{attrs = Attrs, children = Els}) -> |
894 |
295 |
JID1 = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)), |
895 |
295 |
case JID1 of |
896 |
:-( |
error -> ok; |
897 |
|
_ -> |
898 |
295 |
JID = {JID1#jid.user, JID1#jid.server, |
899 |
|
JID1#jid.resource}, |
900 |
295 |
LJID = {JID1#jid.luser, JID1#jid.lserver, |
901 |
|
JID1#jid.lresource}, |
902 |
295 |
Item = #roster{usj = {LUser, LServer, LJID}, |
903 |
|
us = {LUser, LServer}, jid = JID}, |
904 |
295 |
Item1 = process_item_attrs_ws(Item, Attrs), |
905 |
295 |
Item2 = process_item_els(Item1, Els), |
906 |
295 |
case Item2#roster.subscription of |
907 |
14 |
remove -> del_roster_t(HostType, LUser, LServer, LJID); |
908 |
281 |
_ -> update_roster_t(HostType, Item2) |
909 |
|
end |
910 |
|
end; |
911 |
:-( |
process_item_set_t(_HostType, _LUser, _LServer, _) -> ok. |
912 |
|
|
913 |
|
process_item_attrs_ws(Item, [{<<"jid">>, Val} | Attrs]) -> |
914 |
295 |
case jid:from_binary(Val) of |
915 |
|
error -> |
916 |
:-( |
process_item_attrs_ws(Item, Attrs); |
917 |
|
JID1 -> |
918 |
295 |
JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource}, |
919 |
295 |
process_item_attrs_ws(Item#roster{jid = JID}, Attrs) |
920 |
|
end; |
921 |
|
process_item_attrs_ws(Item, [{<<"name">>, Val} | Attrs]) -> |
922 |
281 |
process_item_attrs_ws(Item#roster{name = Val}, Attrs); |
923 |
|
process_item_attrs_ws(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) -> |
924 |
14 |
process_item_attrs_ws(Item#roster{subscription = remove}, Attrs); |
925 |
|
process_item_attrs_ws(Item, [{<<"subscription">>, <<"none">>} | Attrs]) -> |
926 |
:-( |
process_item_attrs_ws(Item#roster{subscription = none}, Attrs); |
927 |
|
process_item_attrs_ws(Item, [{<<"subscription">>, <<"both">>} | Attrs]) -> |
928 |
274 |
process_item_attrs_ws(Item#roster{subscription = both}, Attrs); |
929 |
|
process_item_attrs_ws(Item, [{<<"subscription">>, <<"from">>} | Attrs]) -> |
930 |
4 |
process_item_attrs_ws(Item#roster{subscription = from}, Attrs); |
931 |
|
process_item_attrs_ws(Item, [{<<"subscription">>, <<"to">>} | Attrs]) -> |
932 |
3 |
process_item_attrs_ws(Item#roster{subscription = to}, Attrs); |
933 |
|
process_item_attrs_ws(Item, [_ | Attrs]) -> |
934 |
:-( |
process_item_attrs_ws(Item, Attrs); |
935 |
|
process_item_attrs_ws(Item, []) -> |
936 |
295 |
Item. |
937 |
|
|
938 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
939 |
|
|
940 |
|
-spec get_jid_info(HookAcc, mongooseim:host_type(), |
941 |
|
ToJID :: jid:jid(), |
942 |
|
JID :: jid:jid() | jid:ljid()) -> HookAcc |
943 |
|
when HookAcc :: {subscription_state(), [binary()]}. |
944 |
|
get_jid_info(_, HostType, ToJID, JID) -> |
945 |
67 |
case get_roster_entry(HostType, ToJID, JID, full) of |
946 |
:-( |
error -> {none, []}; |
947 |
|
does_not_exist -> |
948 |
52 |
LRJID = jid:to_bare(jid:to_lower(JID)), |
949 |
52 |
case get_roster_entry(HostType, ToJID, LRJID, full) of |
950 |
:-( |
error -> {none, []}; |
951 |
30 |
does_not_exist -> {none, []}; |
952 |
22 |
R -> {R#roster.subscription, R#roster.groups} |
953 |
|
end; |
954 |
15 |
Re -> {Re#roster.subscription, Re#roster.groups} |
955 |
|
end. |
956 |
|
|
957 |
|
get_roster_old(HostType, #jid{lserver = LServer} = JID) -> |
958 |
2 |
get_roster_old(HostType, LServer, JID). |
959 |
|
|
960 |
|
get_roster_old(HostType, DestServer, JID) -> |
961 |
31 |
A = mongoose_acc:new(#{ location => ?LOCATION, |
962 |
|
lserver => DestServer, |
963 |
|
host_type => HostType, |
964 |
|
element => undefined }), |
965 |
31 |
A2 = mongoose_hooks:roster_get(A, JID), |
966 |
31 |
mongoose_acc:get(roster, items, [], A2). |
967 |
|
|
968 |
|
-spec item_to_map(roster()) -> map(). |
969 |
|
item_to_map(#roster{} = Roster) -> |
970 |
19 |
ContactJid = jid:make_noprep(jid:to_bare(Roster#roster.jid)), |
971 |
19 |
ContactName = Roster#roster.name, |
972 |
19 |
Subs = Roster#roster.subscription, |
973 |
19 |
Groups = Roster#roster.groups, |
974 |
19 |
Ask = Roster#roster.ask, |
975 |
19 |
#{jid => ContactJid, name => ContactName, subscription => Subs, |
976 |
|
groups => Groups, ask => Ask}. |
977 |
|
|
978 |
|
-spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}]. |
979 |
|
config_metrics(HostType) -> |
980 |
227 |
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]). |
981 |
|
|
982 |
|
%% Backend API wrappers |
983 |
|
|
984 |
|
-spec transaction(mongooseim:host_type(), fun(() -> any())) -> |
985 |
|
{aborted, any()} | {atomic, any()} | {error, any()}. |
986 |
|
transaction(HostType, F) -> |
987 |
3344 |
mod_roster_backend:transaction(HostType, F). |
988 |
|
|
989 |
|
-spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
990 |
|
binary() | error. |
991 |
|
read_roster_version(HostType, LUser, LServer) -> |
992 |
4 |
mod_roster_backend:read_roster_version(HostType, LUser, LServer). |
993 |
|
|
994 |
|
-spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> version(). |
995 |
|
write_roster_version(HostType, LUser, LServer) -> |
996 |
1 |
write_roster_version(HostType, LUser, LServer, no_transaction). |
997 |
|
|
998 |
|
-spec write_roster_version_t(mongooseim:host_type(), jid:luser(), jid:lserver()) -> version(). |
999 |
|
write_roster_version_t(HostType, LUser, LServer) -> |
1000 |
1 |
write_roster_version(HostType, LUser, LServer, in_transaction). |
1001 |
|
|
1002 |
|
-spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(), |
1003 |
|
transaction_state()) -> version(). |
1004 |
|
write_roster_version(HostType, LUser, LServer, TransactionState) -> |
1005 |
2 |
Ver = sha:sha1_hex(term_to_binary(os:timestamp())), |
1006 |
2 |
mod_roster_backend:write_roster_version(HostType, LUser, LServer, TransactionState, Ver), |
1007 |
2 |
Ver. |
1008 |
|
|
1009 |
|
-spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [roster()]. |
1010 |
|
get_roster(HostType, LUser, LServer) -> |
1011 |
2425 |
mod_roster_backend:get_roster(HostType, LUser, LServer). |
1012 |
|
|
1013 |
|
-spec get_roster_entry(mongooseim:host_type(), jid:jid(), contact(), entry_format()) -> |
1014 |
|
roster() | does_not_exist | error. |
1015 |
|
get_roster_entry(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) -> |
1016 |
146 |
mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, no_transaction, Format). |
1017 |
|
|
1018 |
|
-spec get_roster_entry_t(mongooseim:host_type(), jid:jid(), contact(), entry_format()) -> |
1019 |
|
roster() | does_not_exist | error. |
1020 |
|
get_roster_entry_t(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) -> |
1021 |
770 |
mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, in_transaction, Format). |
1022 |
|
|
1023 |
|
-spec roster_subscribe_t(mongooseim:host_type(), roster()) -> ok. |
1024 |
|
roster_subscribe_t(HostType, Item) -> |
1025 |
636 |
mod_roster_backend:roster_subscribe_t(HostType, Item). |
1026 |
|
|
1027 |
|
-spec update_roster_t(mongooseim:host_type(), roster()) -> ok. |
1028 |
|
update_roster_t(HostType, Item) -> |
1029 |
360 |
mod_roster_backend:update_roster_t(HostType, Item). |
1030 |
|
|
1031 |
|
-spec del_roster_t(mongooseim:host_type(), jid:luser(), jid:lserver(), contact()) -> ok. |
1032 |
|
del_roster_t(HostType, LUser, LServer, LJID) -> |
1033 |
41 |
mod_roster_backend:del_roster_t(HostType, LUser, LServer, LJID). |