./ct_report/coverage/mod_roster.COVER.html

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