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