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