./ct_report/coverage/mod_blocking.COVER.html

1 -module(mod_blocking).
2
3 -xep([{xep, 191}, {version, "1.3"}]).
4
5 -behaviour(gen_mod).
6 -behaviour(mongoose_module_metrics).
7
8 -export([start/2]).
9 -export([stop/1]).
10 -export([hooks/1]).
11 -export([deps/2]).
12 -export([supported_features/0]).
13 -export([config_spec/0]).
14
15 -export([
16 process_iq_get/3,
17 process_iq_set/3,
18 disco_local_features/3,
19 user_send_iq/3,
20 foreign_event/3
21 ]).
22
23 -include("jlib.hrl").
24 -include("mod_privacy.hrl").
25
26 -type blocking_type() :: block | unblock.
27 -type listitem() :: #listitem{}.
28
29 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
30 start(_HostType, _Opts) ->
31
:-(
ok.
32
33 -spec stop(mongooseim:host_type()) -> ok.
34 stop(_HostType) ->
35
:-(
ok.
36
37 deps(_HostType, Opts) ->
38
:-(
[{mod_privacy, Opts, hard}].
39
40 -spec supported_features() -> [atom()].
41 supported_features() ->
42
:-(
[dynamic_domains].
43
44 config_spec() ->
45 8 mod_privacy:config_spec().
46
47 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
48 hooks(HostType) ->
49
:-(
[{disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 99},
50 {privacy_iq_get, HostType, fun ?MODULE:process_iq_get/3, #{}, 50},
51 {privacy_iq_set, HostType, fun ?MODULE:process_iq_set/3, #{}, 50}
52 | c2s_hooks(HostType)].
53
54 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
55 c2s_hooks(HostType) ->
56
:-(
[
57 {user_send_iq, HostType, fun ?MODULE:user_send_iq/3, #{}, 50},
58 {foreign_event, HostType, fun ?MODULE:foreign_event/3, #{}, 50}
59 ].
60
61 -spec user_send_iq(mongoose_acc:t(), mongoose_c2s_hooks:params(), map()) ->
62 mongoose_c2s_hooks:result().
63 user_send_iq(Acc, #{c2s_data := StateData}, #{host_type := HostType}) ->
64
:-(
case mongoose_iq:info(Acc) of
65 {#iq{xmlns = ?NS_BLOCKING, type = Type} = IQ, Acc1} when Type == get; Type == set ->
66
:-(
mod_privacy:do_user_send_iq(Acc1, StateData, HostType, IQ);
67 _ ->
68
:-(
{ok, Acc}
69 end.
70
71 -spec foreign_event(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
72 mongoose_c2s_hooks:result().
73 foreign_event(Acc, #{c2s_data := StateData,
74 event_type := cast,
75 event_tag := ?MODULE,
76 event_content := {blocking, UserList, Action, Changed}}, _Extra) ->
77
:-(
{stop, handle_new_blocking_command(Acc, StateData, UserList, Action, Changed)};
78 foreign_event(Acc, _Params, _Extra) ->
79
:-(
{ok, Acc}.
80
81 handle_new_blocking_command(Acc, StateData, UserList, Action, JIDs) ->
82
:-(
blocking_push_to_resources(Action, JIDs, StateData),
83
:-(
blocking_presence_to_contacts(Action, JIDs, StateData),
84
:-(
ToAcc = [{state_mod, {mod_privacy, UserList}}],
85
:-(
mongoose_c2s_acc:to_acc_many(Acc, ToAcc).
86
87 -spec blocking_push_to_resources(blocking_type(), [binary()], mongoose_c2s:data()) -> ok.
88 blocking_push_to_resources(Action, JIDs, StateData) ->
89
:-(
SubEl = case Action of
90
:-(
block -> blocking_stanza(JIDs, <<"block">>);
91
:-(
unblock -> blocking_stanza(JIDs, <<"unblock">>)
92 end,
93
:-(
PrivPushIQ = blocking_iq(SubEl),
94
:-(
T = mongoose_c2s:get_jid(StateData),
95
:-(
F = jid:to_bare(T),
96
:-(
PrivPushEl = jlib:replace_from_to(F, T, jlib:iq_to_xml(PrivPushIQ)),
97
:-(
ejabberd_router:route(F, T, PrivPushEl),
98
:-(
ok.
99
100 -spec blocking_presence_to_contacts(blocking_type(), [binary()], mongoose_c2s:data()) -> ok.
101
:-(
blocking_presence_to_contacts(_Action, [], _StateData) -> ok;
102 blocking_presence_to_contacts(Action, [Jid | JIDs], StateData) ->
103
:-(
Presences = mod_presence:maybe_get_handler(StateData),
104
:-(
Pres = case Action of
105 block ->
106
:-(
mod_presence:presence_unavailable_stanza();
107 unblock ->
108
:-(
mod_presence:get(Presences, last)
109 end,
110
:-(
T = jid:from_binary(Jid),
111
:-(
case mod_presence:is_subscribed_to_my_presence(T, jid:to_bare(T), Presences) of
112 true ->
113
:-(
F = jid:to_bare(mongoose_c2s:get_jid(StateData)),
114
:-(
ejabberd_router:route(F, T, Pres);
115 false ->
116
:-(
ok
117 end,
118
:-(
blocking_presence_to_contacts(Action, JIDs, StateData).
119
120 -spec disco_local_features(mongoose_disco:feature_acc(), map(), map()) ->
121 {ok, mongoose_disco:feature_acc()}.
122 disco_local_features(Acc = #{node := <<>>}, _, _) ->
123
:-(
{ok, mongoose_disco:add_features([?NS_BLOCKING], Acc)};
124 disco_local_features(Acc, _, _) ->
125
:-(
{ok, Acc}.
126
127 -spec process_iq_get(Acc, Params, Extra) -> {ok | stop, Acc} when
128 Acc :: mongoose_acc:t(),
129 Params :: #{from := jid:jid(), iq := jlib:iq()},
130 Extra :: gen_hook:extra().
131 process_iq_get(Acc, #{from := #jid{luser = LUser, lserver = LServer},
132 iq := #iq{xmlns = ?NS_BLOCKING}}, _) ->
133
:-(
HostType = mongoose_acc:host_type(Acc),
134
:-(
Res = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of
135 {error, not_found} ->
136
:-(
{ok, []};
137 {ok, L} ->
138
:-(
{ok, L};
139 E ->
140
:-(
{error, E}
141 end,
142
:-(
IqRes = case Res of
143 {ok, Lst} ->
144
:-(
{result, blocking_query_response(Lst)};
145 {error, _} ->
146
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
147 end,
148
:-(
{ok, mongoose_acc:set(hook, result, IqRes, Acc)};
149 process_iq_get(Acc, _, _) ->
150
:-(
{ok, Acc}.
151
152 -spec process_iq_set(Acc, Params, Extra) -> {ok | stop, Acc} when
153 Acc :: mongoose_acc:t(),
154 Params :: #{from := jid:jid(), iq := jlib:iq()},
155 Extra :: gen_hook:extra().
156 process_iq_set(Acc, #{from := From, iq := #iq{xmlns = ?NS_BLOCKING, sub_el = SubEl}}, _) ->
157 %% collect needed data
158
:-(
HostType = mongoose_acc:host_type(Acc),
159
:-(
#jid{luser = LUser, lserver = LServer} = From,
160
:-(
#xmlel{name = BType} = SubEl,
161
:-(
Type = parse_command_type(BType),
162
:-(
Usrs = exml_query:paths(SubEl, [{element, <<"item">>}, {attr, <<"jid">>}]),
163
:-(
CurrList = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of
164 {ok, List} ->
165
:-(
List;
166 {error, not_found} ->
167
:-(
[];
168 {error, Reason} ->
169
:-(
{error, Reason}
170 end,
171 %% process
172
:-(
{Acc1, Res} = process_blocking_iq_set(Type, Acc, LUser, LServer, CurrList, Usrs),
173 %% respond / notify
174
:-(
{Acc2, Res1} = complete_iq_set(blocking_command, Acc1, LUser, LServer, Res),
175
:-(
{ok, mongoose_acc:set(hook, result, Res1, Acc2)};
176 process_iq_set(Acc, _, _) ->
177
:-(
{ok, Acc}.
178
179
:-(
parse_command_type(<<"block">>) -> block;
180
:-(
parse_command_type(<<"unblock">>) -> unblock.
181
182 %% @doc Set IQ must do the following:
183 %% * get / create a dedicated privacy list (we call it "blocking")
184 %% * modify the list
185 %% * set that list as a default (in backend)
186 %% * return the list so that c2s can set it as current list
187 %% * broadcast (push) message to all the user's resources
188 %% * sent 'unavailable' msg to blocked contacts, or 'available' to unblocked
189 %%
190 -spec process_blocking_iq_set(Type :: block | unblock, Acc :: mongoose_acc:t(),
191 LUser:: binary(), LServer:: binary(),
192 CurrList :: [listitem()], Users :: [binary()]) ->
193 {mongoose_acc:t(), {ok, [binary()], [listitem()], block | unblock | unblock_all} |
194 {error, exml:element()}}.
195 %% fail if current default list could not be retrieved
196 process_blocking_iq_set(_, Acc, _, _, {error, _}, _) ->
197
:-(
{Acc, {error, mongoose_xmpp_errors:internal_server_error()}};
198 %% reject block request with empty jid list
199 process_blocking_iq_set(block, Acc, _, _, _, []) ->
200
:-(
{Acc, {error, mongoose_xmpp_errors:bad_request()}};
201 process_blocking_iq_set(Type, Acc, LUser, LServer, CurrList, Usrs) ->
202 %% check who is being added / removed
203
:-(
HostType = mongoose_acc:host_type(Acc),
204
:-(
{NType, Changed, NewList} = blocking_list_modify(Type, Usrs, CurrList),
205
:-(
case mod_privacy_backend:replace_privacy_list(HostType, LUser, LServer, <<"blocking">>, NewList) of
206 {error, E} ->
207
:-(
{error, E};
208 ok ->
209
:-(
case mod_privacy_backend:set_default_list(HostType, LUser, LServer, <<"blocking">>) of
210 ok ->
211
:-(
{Acc, {ok, Changed, NewList, NType}};
212 {error, not_found} ->
213
:-(
{Acc, {error, mongoose_xmpp_errors:item_not_found()}};
214 {error, _Reason} ->
215
:-(
{Acc, {error, mongoose_xmpp_errors:internal_server_error()}}
216 end
217 end.
218
219 -spec complete_iq_set(atom(), mongoose_acc:t(), term(), term(), term()) ->
220 {mongoose_acc:t(), {error, term()} | {result, list() | {result, list(), term()}}}.
221 complete_iq_set(blocking_command, Acc, _, _, {error, Reason}) ->
222
:-(
{Acc, {error, Reason}};
223 complete_iq_set(blocking_command, Acc, LUser, LServer, {ok, Changed, List, Type}) ->
224
:-(
UserList = #userlist{name = <<"blocking">>, list = List, needdb = false},
225 % send the list to all users c2s processes (resources) to make it effective immediately
226
:-(
broadcast_blocking_command(Acc, LUser, LServer, UserList, Changed, Type),
227 % return a response here so that c2s sets the list in its state
228
:-(
{Acc, {result, [], UserList}}.
229 %%complete_iq_set(blocking_command, _, _, _) ->
230 %% {result, []}.
231
232 -spec blocking_list_modify(Type :: block | unblock, New :: [binary()], Old :: [listitem()]) ->
233 {block|unblock|unblock_all, [binary()], [listitem()]}.
234 blocking_list_modify(block, Change, Old) ->
235
:-(
N = make_blocking_list(Change),
236
:-(
{_, O} = remove_from(Change, Old),
237 %% we treat all items on the "to block" list as changed becase they might have been present
238 %% on the old list with different settings
239 %% and we need to set order numbers, doesn't matter how but it has to be unique
240
:-(
{block, Change, set_order(N ++ O)};
241 blocking_list_modify(unblock, [], Old) ->
242 %% unblock with empty list means unblocking all contacts
243
:-(
Rem = [jid:to_binary(J#listitem.value) || J <- Old],
244
:-(
{unblock_all, Rem, []};
245 blocking_list_modify(unblock, Change, Old) ->
246
:-(
{Removed, O} = remove_from(Change, Old),
247
:-(
{unblock, Removed, O}.
248
249 set_order(L) ->
250
:-(
set_order(1, [], L).
251
252 set_order(_, N, []) ->
253
:-(
N;
254 set_order(Idx, N, [H|T]) ->
255
:-(
set_order(Idx + 1, [H#listitem{order = Idx}|N], T).
256
257 remove_from(ToRem, Lst) ->
258
:-(
remove_from(ToRem, [], [], Lst).
259
260 remove_from(_, Removed, New, []) ->
261
:-(
{Removed, New};
262 remove_from(ToRem, Removed, New, [H|T]) ->
263
:-(
Bin = jid:to_binary(H#listitem.value),
264
:-(
case lists:member(Bin, ToRem) of
265 true ->
266
:-(
remove_from(ToRem, [Bin|Removed], New, T);
267 false ->
268
:-(
remove_from(ToRem, Removed, [H|New], T)
269 end.
270
271 make_blocking_list(L) ->
272
:-(
make_blocking_list([], L).
273
274 make_blocking_list(New, []) ->
275
:-(
New;
276 make_blocking_list(New, [H|T]) ->
277
:-(
case make_blocking_list_entry(H) of
278 false ->
279
:-(
make_blocking_list(New, T);
280 Entry ->
281
:-(
make_blocking_list([Entry|New], T)
282 end.
283
284 make_blocking_list_entry(J) ->
285
:-(
case jid:from_binary(J) of
286 error ->
287
:-(
false;
288 JID ->
289
:-(
#listitem{type = jid,
290 match_all = true,
291 %% we have to use another action
292 %% because c2s has to respond differently based on why we deny
293 action = block,
294 value = jid:to_lower(JID)}
295 end.
296
297 %% @doc send iq confirmation to all of the user's resources
298 %% if we unblock all contacts then we don't list who's been unblocked
299 broadcast_blocking_command(Acc, LUser, LServer, UserList, _Changed, unblock_all) ->
300
:-(
broadcast_blocking_command(Acc, LUser, LServer, UserList, [], unblock);
301 broadcast_blocking_command(Acc, LUser, LServer, UserList, Changed, Type) ->
302
:-(
Item = {blocking, UserList, Type, Changed},
303
:-(
UserPids = ejabberd_sm:get_user_present_pids(LUser, LServer),
304
:-(
HostType = mongoose_acc:host_type(Acc),
305
:-(
mongoose_hooks:privacy_list_push(HostType, LUser, LServer, Item, length(UserPids)),
306
:-(
lists:foreach(fun({_, Pid}) -> mongoose_c2s:cast(Pid, ?MODULE, Item) end, UserPids).
307
308 -spec blocking_query_response([mod_privacy:list_name()]) -> exml:element().
309 blocking_query_response(Lst) ->
310
:-(
#xmlel{
311 name = <<"blocklist">>,
312 attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
313
:-(
children = [#xmlel{name= <<"item">>,
314
:-(
attrs = [{<<"jid">>, jid:to_binary(J#listitem.value)}]} || J <- Lst]}.
315
316 -spec blocking_stanza([binary()], binary()) -> exml:element().
317 blocking_stanza(JIDs, Name) ->
318
:-(
#xmlel{name = Name,
319 attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
320 children = lists:map(
321 fun(JID) ->
322
:-(
#xmlel{name = <<"item">>, attrs = [{<<"jid">>, JID}]}
323 end, JIDs)}.
324
325 -spec blocking_iq(exml:element()) -> jlib:iq().
326 blocking_iq(SubEl) ->
327
:-(
#iq{type = set, xmlns = ?NS_BLOCKING, id = <<"push">>, sub_el = [SubEl]}.
Line Hits Source