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 |
49 |
Schema = ["jid", "name", "subscription", "ask", "groups", "askmessage", "xs"], |
116 |
49 |
Records = get_roster(HostType, LUser, LServer), |
117 |
49 |
SerializedRecords = lists:map(fun roster_record_to_gdpr_entry/1, Records), |
118 |
49 |
{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 |
436 |
mod_roster_backend:init(HostType, Opts), |
141 |
436 |
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 |
426 |
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 |
202 |
#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 |
198 |
supported_features() -> [dynamic_domains]. |
164 |
|
|
165 |
|
hooks(HostType) -> |
166 |
862 |
[{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 |
52 |
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 |
52 |
HostType = mongoose_acc:host_type(Acc), |
193 |
52 |
IQReply = case Type of |
194 |
30 |
set -> process_iq_set(HostType, From, To, IQ); |
195 |
22 |
get -> process_iq_get(HostType, From, To, IQ) |
196 |
|
end, |
197 |
52 |
{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 |
3886 |
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 |
759 |
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 |
3202 |
NewAcc = case roster_versioning_enabled(HostType) of |
219 |
|
true -> |
220 |
4 |
Feature = #xmlel{name = <<"ver">>, |
221 |
|
attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}]}, |
222 |
4 |
[Feature | Acc]; |
223 |
3198 |
false -> [] |
224 |
|
end, |
225 |
3202 |
{ok, NewAcc}. |
226 |
|
|
227 |
|
roster_version(HostType, #jid{luser = LUser, lserver = LServer} = JID) -> |
228 |
2 |
case roster_version_on_db(HostType) of |
229 |
|
true -> |
230 |
1 |
case read_roster_version(HostType, LUser, LServer) of |
231 |
:-( |
error -> not_found; |
232 |
1 |
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 |
22 |
AttrVer = exml_query:attr(SubEl, <<"ver">>), %% type binary() | undefined |
249 |
22 |
VersioningRequested = is_binary(AttrVer), |
250 |
22 |
VersioningEnabled = roster_versioning_enabled(HostType), |
251 |
22 |
VersionOnDb = roster_version_on_db(HostType), |
252 |
22 |
Strategy = choose_get_user_roster_strategy(VersioningRequested, VersioningEnabled, VersionOnDb), |
253 |
22 |
{ItemsToSend, VersionToSend} = |
254 |
|
get_user_roster_based_on_version(HostType, Strategy, AttrVer, From, To), |
255 |
22 |
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 |
16 |
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 |
16 |
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 |
16 |
{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 |
16 |
[#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 |
54 |
NewItems = do_get_user_roster(HostType, JID), |
323 |
54 |
{ok, Items ++ NewItems}; |
324 |
|
get_user_roster(Items, #{show_full_roster := true, jid := JID}, #{host_type := HostType}) -> |
325 |
75 |
NewItems = do_get_user_full_roster(HostType, JID), |
326 |
75 |
{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 |
75 |
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 |
3043 |
Roster = get_roster(HostType, LUser, LServer), |
335 |
3043 |
Fun = fun(#roster{subscription = S, ask = A}) -> not (none =:= S andalso in =:= A) end, |
336 |
3043 |
lists:filter(Fun, Roster). |
337 |
|
|
338 |
|
-spec item_to_xml(roster()) -> exml:element(). |
339 |
|
item_to_xml(Item) -> |
340 |
577 |
Attrs0 = [{<<"jid">>, jid:to_binary(Item#roster.jid)}, |
341 |
|
{<<"subscription">>, subs_to_binary(Item#roster.subscription)}], |
342 |
577 |
Attrs1 = maybe_append_ask(Attrs0, Item), |
343 |
577 |
Attrs2 = maybe_append_name(Attrs1, Item), |
344 |
577 |
Fold = fun(G, Acc) -> [group_el(G) | Acc] end, |
345 |
577 |
SubEls = lists:foldl(Fold, Item#roster.xs, Item#roster.groups), |
346 |
577 |
#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 |
243 |
Attrs; |
351 |
|
maybe_append_name(Attrs, #roster{name = Name}) -> |
352 |
334 |
[{<<"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 |
214 |
[{<<"ask">>, <<"subscribe">>} | Attrs]; |
357 |
|
maybe_append_ask(Attrs, _) -> |
358 |
363 |
Attrs. |
359 |
|
|
360 |
|
-spec group_el(binary()) -> exml:element(). |
361 |
|
group_el(Name) -> |
362 |
243 |
#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 |
30 |
#xmlel{children = Els} = SubEl, |
367 |
30 |
mongoose_hooks:roster_set(HostType, From, To, SubEl), |
368 |
30 |
lists:foreach(fun(El) -> process_item_set(HostType, From, To, El) end, Els), |
369 |
30 |
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 |
30 |
Jid = jid:from_binary(exml_query:attr(El, <<"jid">>)), |
374 |
30 |
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 |
30 |
MakeItem2 = fun(Item) -> |
381 |
30 |
Item1 = process_item_attrs(Item, Attrs), |
382 |
30 |
process_item_els(Item1, Els) |
383 |
|
end, |
384 |
30 |
set_roster_item(HostType, JID1, From, To, MakeItem2), |
385 |
30 |
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 |
151 |
ContactLJID = jid:to_lower(ContactJID), |
396 |
151 |
F = fun() -> set_roster_item_t(HostType, From, ContactLJID, MakeItem) end, |
397 |
151 |
case transaction(HostType, F) of |
398 |
|
{atomic, does_not_exist} -> |
399 |
10 |
{error, does_not_exist}; |
400 |
|
{atomic, {OldItem, NewItem}} -> |
401 |
141 |
push_item(HostType, From, To, NewItem), |
402 |
141 |
case NewItem#roster.subscription of |
403 |
|
remove -> |
404 |
21 |
send_unsubscribing_presence(From, OldItem), |
405 |
21 |
ok; |
406 |
120 |
_ -> 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 |
151 |
InitialItem = get_roster_entry_t(HostType, UserJid, ContactLJID, short), |
419 |
151 |
Item1 = case InitialItem of |
420 |
121 |
does_not_exist -> new_roster_item(UserJid, ContactLJID); |
421 |
30 |
Item -> Item |
422 |
|
end, |
423 |
151 |
Item2 = MakeItem(Item1), |
424 |
151 |
case {InitialItem, Item2} of |
425 |
|
{does_not_exist, #roster{subscription = remove}} -> |
426 |
|
%% Cannot remove a non-existing item |
427 |
10 |
does_not_exist; |
428 |
|
_ -> |
429 |
141 |
case Item2#roster.subscription of |
430 |
21 |
remove -> del_roster_t(HostType, UserJid, ContactLJID); |
431 |
120 |
_ -> update_roster_t(HostType, Item2) |
432 |
|
end, |
433 |
141 |
Item3 = mongoose_hooks:roster_process_item(HostType, LServer, Item2), |
434 |
141 |
case roster_version_on_db(HostType) of |
435 |
1 |
true -> write_roster_version_t(HostType, UserJid); |
436 |
140 |
false -> ok |
437 |
|
end, |
438 |
141 |
{Item1, Item3} |
439 |
|
end. |
440 |
|
|
441 |
|
-spec new_roster_item(jid:jid(), jid:simple_jid()) -> roster(). |
442 |
|
new_roster_item(UserJid, ContactLJID) -> |
443 |
121 |
#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 |
30 |
case jid:from_binary(Val) of |
449 |
|
error -> |
450 |
:-( |
process_item_attrs(Item, Attrs); |
451 |
|
JID1 -> |
452 |
30 |
JID = jid:to_lower(JID1), |
453 |
30 |
process_item_attrs(Item#roster{jid = JID}, Attrs) |
454 |
|
end; |
455 |
|
process_item_attrs(Item, [{<<"name">>, Val} | Attrs]) -> |
456 |
28 |
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 |
30 |
Item. |
463 |
|
|
464 |
|
process_item_els(Item, [#xmlel{name = Name} = El | Els]) -> |
465 |
26 |
case Name of |
466 |
|
<<"group">> -> |
467 |
26 |
Groups = [exml_query:cdata(El) | Item#roster.groups], |
468 |
26 |
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 |
30 |
process_item_els(Item, []) -> Item. |
480 |
|
|
481 |
|
push_item(HostType, JID, From, Item) -> |
482 |
662 |
broadcast_item(JID, Item#roster.jid, Item#roster.subscription), |
483 |
662 |
case roster_versioning_enabled(HostType) of |
484 |
|
true -> |
485 |
2 |
push_item_version(JID, From, Item, roster_version(HostType, JID)); |
486 |
|
false -> |
487 |
660 |
lists:foreach(fun(Resource) -> |
488 |
566 |
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 |
662 |
Item = {item, ContactJid, Subscription}, |
496 |
662 |
UserPids = ejabberd_sm:get_user_present_pids(LUser, LServer), |
497 |
662 |
lists:foreach(fun({_, Pid}) -> mongoose_c2s:cast(Pid, ?MODULE, Item) end, UserPids). |
498 |
|
|
499 |
|
push_item_without_version(HostType, JID, Resource, From, Item) -> |
500 |
566 |
mongoose_hooks:roster_push(HostType, From, Item), |
501 |
566 |
push_item_final(jid:replace_resource(JID, Resource), From, Item, not_found). |
502 |
|
|
503 |
|
push_item_version(JID, From, Item, RosterVersion) -> |
504 |
2 |
lists:foreach(fun(Resource) -> |
505 |
2 |
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 |
568 |
ExtraAttrs = case RosterVersion of |
511 |
566 |
not_found -> []; |
512 |
2 |
_ -> [{<<"ver">>, RosterVersion}] |
513 |
|
end, |
514 |
568 |
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 |
568 |
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 |
3050 |
Items = mod_roster_backend:get_subscription_lists(Acc, LUser, LServer), |
530 |
3050 |
SubLists = fill_subscription_lists(JID, LServer, Items, [], [], []), |
531 |
3050 |
{ok, mongoose_acc:set(roster, subscription_lists, SubLists, Acc)}. |
532 |
|
|
533 |
|
fill_subscription_lists(JID, LServer, [#roster{} = I | Is], F, T, P) -> |
534 |
11 |
J = I#roster.jid, |
535 |
11 |
NewP = build_pending(I, JID, P), |
536 |
11 |
case I#roster.subscription of |
537 |
|
both -> |
538 |
7 |
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 |
3050 |
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 |
8 |
P. |
564 |
|
|
565 |
|
-spec subs_to_binary(subscription_state()) -> binary(). |
566 |
313 |
subs_to_binary(none) -> <<"none">>; |
567 |
70 |
subs_to_binary(from) -> <<"from">>; |
568 |
77 |
subs_to_binary(to) -> <<"to">>; |
569 |
96 |
subs_to_binary(both) -> <<"both">>; |
570 |
21 |
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 |
364 |
Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason), |
583 |
364 |
{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 |
250 |
Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>), |
595 |
250 |
{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 |
614 |
TransactionFun = |
602 |
|
fun() -> |
603 |
633 |
process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason) |
604 |
|
end, |
605 |
614 |
case transaction(HostType, TransactionFun) of |
606 |
|
{atomic, {Push, AutoReply}} -> |
607 |
614 |
case AutoReply of |
608 |
556 |
none -> ok; |
609 |
|
_ -> |
610 |
58 |
PresenceStanza = #xmlel{name = <<"presence">>, |
611 |
|
attrs = [{<<"type">>, autoreply_to_type(AutoReply)}], |
612 |
|
children = []}, |
613 |
58 |
ejabberd_router:route(JID, ContactJID, PresenceStanza) |
614 |
|
end, |
615 |
614 |
case Push of |
616 |
|
{push, #roster{subscription = none, ask = in}} -> |
617 |
73 |
true; |
618 |
|
{push, Item} -> |
619 |
521 |
push_item(HostType, JID, JID, Item), |
620 |
521 |
true; |
621 |
20 |
none -> false |
622 |
|
end; |
623 |
:-( |
_ -> false |
624 |
|
end. |
625 |
|
|
626 |
:-( |
autoreply_to_type(subscribed) -> <<"subscribed">>; |
627 |
58 |
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 |
633 |
US = jid:to_lus(JID), |
635 |
633 |
ContactLJID = jid:to_lower(ContactJID), |
636 |
633 |
Item = case get_roster_entry_t(HostType, JID, ContactLJID, full) of |
637 |
|
does_not_exist -> |
638 |
73 |
#roster{usj = {US, ContactLJID}, |
639 |
|
us = US, |
640 |
|
jid = ContactLJID}; |
641 |
559 |
R -> R |
642 |
|
end, |
643 |
632 |
NewState = case Direction of |
644 |
|
out -> |
645 |
250 |
out_state_change(Item#roster.subscription, |
646 |
|
Item#roster.ask, Type); |
647 |
|
in -> |
648 |
382 |
in_state_change(Item#roster.subscription, |
649 |
|
Item#roster.ask, Type) |
650 |
|
end, |
651 |
632 |
AutoReply = case Direction of |
652 |
250 |
out -> none; |
653 |
|
in -> |
654 |
382 |
in_auto_reply(Item#roster.subscription, |
655 |
|
Item#roster.ask, Type) |
656 |
|
end, |
657 |
632 |
AskMessage = case NewState of |
658 |
92 |
{_, both} -> Reason; |
659 |
121 |
{_, in} -> Reason; |
660 |
419 |
_ -> <<"">> |
661 |
|
end, |
662 |
632 |
case NewState of |
663 |
14 |
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 |
612 |
NewItem = Item#roster{subscription = Subscription, |
670 |
|
ask = Pending, |
671 |
|
askmessage = iolist_to_binary(AskMessage)}, |
672 |
612 |
roster_subscribe_t(HostType, NewItem), |
673 |
594 |
case roster_version_on_db(HostType) of |
674 |
:-( |
true -> write_roster_version_t(HostType, JID); |
675 |
594 |
false -> ok |
676 |
|
end, |
677 |
594 |
{{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 |
73 |
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 |
10 |
in_state_change(none, none, unsubscribed) -> none; |
700 |
46 |
in_state_change(none, out, subscribe) -> {none, both}; |
701 |
19 |
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 |
46 |
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 |
2 |
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 |
60 |
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 |
14 |
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 |
48 |
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 |
55 |
in_state_change(both, none, unsubscribe) -> {to, none}; |
731 |
2 |
in_state_change(both, none, unsubscribed) -> {from, none}. |
732 |
|
|
733 |
73 |
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 |
46 |
out_state_change(none, in, subscribe) -> {none, both}; |
743 |
19 |
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 |
46 |
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 |
48 |
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 |
2 |
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 |
2 |
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 |
14 |
in_auto_reply(from, none, unsubscribe) -> unsubscribed; |
778 |
:-( |
in_auto_reply(from, out, unsubscribe) -> unsubscribed; |
779 |
55 |
in_auto_reply(both, none, unsubscribe) -> unsubscribed; |
780 |
313 |
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 |
2989 |
Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID), |
791 |
2989 |
F = fun() -> mod_roster_backend:remove_user_t(HostType, LUser, LServer) end, |
792 |
2989 |
case transaction(HostType, F) of |
793 |
|
{atomic, ok} -> |
794 |
2989 |
ok; |
795 |
|
Result -> |
796 |
:-( |
?LOG_ERROR(#{what => remove_user_transaction_failed, reason => Result}) |
797 |
|
end, |
798 |
2989 |
{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 |
2989 |
try |
804 |
2989 |
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 |
2989 |
RosterItems = do_get_user_roster(mongoose_acc:host_type(Acc), JID), |
818 |
2989 |
lists:foreach(fun(RosterItem) -> |
819 |
146 |
send_unsubscribing_presence(JID, RosterItem) |
820 |
|
end, RosterItems), |
821 |
2989 |
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 |
167 |
BareFrom = jid:to_bare(From), |
826 |
167 |
ContactJID = jid:make_noprep(Item#roster.jid), |
827 |
167 |
IsTo = Subscription == both orelse Subscription == to, |
828 |
167 |
IsFrom = Subscription == both orelse Subscription == from, |
829 |
167 |
case IsTo of |
830 |
53 |
true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribe">>); |
831 |
114 |
_ -> ok |
832 |
|
end, |
833 |
167 |
case IsFrom of |
834 |
53 |
true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribed">>); |
835 |
114 |
_ -> ok |
836 |
|
end. |
837 |
|
|
838 |
|
send_presence_type(From, To, Type) -> |
839 |
106 |
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 |
1 |
case mongoose_lib:is_exported(mod_roster_backend, remove_domain_t, 2) of |
851 |
|
true -> |
852 |
1 |
F = fun() -> mod_roster_backend:remove_domain_t(HostType, Domain) end, |
853 |
1 |
case transaction(HostType, F) of |
854 |
|
{atomic, ok} -> |
855 |
1 |
ok; |
856 |
|
Result -> |
857 |
:-( |
?LOG_ERROR(#{what => remove_domain_transaction_failed, |
858 |
:-( |
reason => Result}) |
859 |
|
end; |
860 |
|
false -> |
861 |
:-( |
ok |
862 |
|
end, |
863 |
1 |
{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 |
92 |
UpdateF = fun(Item) -> Item#roster{name = Name, groups = Groups} end, |
885 |
92 |
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 |
29 |
UpdateF = fun(Item) -> Item#roster{subscription = remove} end, |
892 |
29 |
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 |
57 |
ToRosterEntry = get_roster_entry(HostType, ToJID, JID, full), |
944 |
57 |
RemoteRosterEntryGetter = fun() -> get_roster_entry(HostType, ToJID, jid:to_bare(jid:to_lower(JID)), full) end, |
945 |
57 |
NewAcc = determine_subscription_state(ToRosterEntry, RemoteRosterEntryGetter), |
946 |
57 |
{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 |
25 |
determine_subscription_state(does_not_exist, undefined) -> {none, []}; |
954 |
47 |
determine_subscription_state(does_not_exist, Getter) -> determine_subscription_state(Getter(), undefined); |
955 |
32 |
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 |
23 |
A = mongoose_acc:new(#{ location => ?LOCATION, |
962 |
|
lserver => DestServer, |
963 |
|
host_type => HostType, |
964 |
|
element => undefined }), |
965 |
23 |
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 |
225 |
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 |
3755 |
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 |
4 |
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 |
1 |
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 |
2 |
Ver = mongoose_bin:encode_crypto(term_to_binary(os:timestamp())), |
1005 |
2 |
mod_roster_backend:write_roster_version(HostType, LUser, LServer, TransactionState, Ver), |
1006 |
2 |
Ver. |
1007 |
|
|
1008 |
|
-spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [roster()]. |
1009 |
|
get_roster(HostType, LUser, LServer) -> |
1010 |
3167 |
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 |
142 |
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 |
784 |
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 |
612 |
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 |
120 |
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 |
27 |
mod_roster_backend:del_roster_t(HostType, LUser, LServer, LJID). |