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