./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([deps/2]).
11 -export([supported_features/0]).
12 -export([config_spec/0]).
13
14 -export([
15 process_iq_get/5,
16 process_iq_set/4,
17 disco_local_features/1
18 ]).
19
20 -ignore_xref([disco_local_features/1, process_iq_get/5, process_iq_set/4]).
21
22 -include("jlib.hrl").
23 -include("mod_privacy.hrl").
24
25 -type listitem() :: #listitem{}.
26
27 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
28 start(HostType, Opts) when is_map(Opts) ->
29 3 ejabberd_hooks:add(hooks(HostType)).
30
31 -spec stop(mongooseim:host_type()) -> ok.
32 stop(HostType) ->
33 3 ejabberd_hooks:delete(hooks(HostType)).
34
35 deps(_HostType, Opts) ->
36 15 [{mod_privacy, Opts, hard}].
37
38 -spec supported_features() -> [atom()].
39 supported_features() ->
40
:-(
[dynamic_domains].
41
42 config_spec() ->
43 166 mod_privacy:config_spec().
44
45 hooks(HostType) ->
46 6 [{disco_local_features, HostType, ?MODULE, disco_local_features, 99},
47 {privacy_iq_get, HostType, ?MODULE, process_iq_get, 50},
48 {privacy_iq_set, HostType, ?MODULE, process_iq_set, 50}].
49
50 -spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
51 disco_local_features(Acc = #{node := <<>>}) ->
52 2 mongoose_disco:add_features([?NS_BLOCKING], Acc);
53 disco_local_features(Acc) ->
54 1 Acc.
55
56 process_iq_get(Acc, _From = #jid{luser = LUser, lserver = LServer},
57 _, #iq{xmlns = ?NS_BLOCKING}, _) ->
58 8 HostType = mongoose_acc:host_type(Acc),
59 8 Res = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of
60 {error, not_found} ->
61 1 {ok, []};
62 {ok, L} ->
63 7 {ok, L};
64 E ->
65
:-(
{error, E}
66 end,
67 8 IqRes = case Res of
68 {ok, Lst} ->
69 8 {result, blocking_query_response(Lst)};
70 {error, _} ->
71
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
72 end,
73 8 mongoose_acc:set(hook, result, IqRes, Acc);
74 process_iq_get(Val, _, _, _, _) ->
75 24 Val.
76
77 process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_BLOCKING, sub_el = SubEl}) ->
78 %% collect needed data
79 28 HostType = mongoose_acc:host_type(Acc),
80 28 #jid{luser = LUser, lserver = LServer} = From,
81 28 #xmlel{name = BType} = SubEl,
82 28 Type = parse_command_type(BType),
83 28 Usrs = exml_query:paths(SubEl, [{element, <<"item">>}, {attr, <<"jid">>}]),
84 28 CurrList = case mod_privacy_backend:get_privacy_list(HostType, LUser, LServer, <<"blocking">>) of
85 {ok, List} ->
86 12 List;
87 {error, not_found} ->
88 16 [];
89 {error, Reason} ->
90
:-(
{error, Reason}
91 end,
92 %% process
93 28 {Acc1, Res} = process_blocking_iq_set(Type, Acc, LUser, LServer, CurrList, Usrs),
94 %% respond / notify
95 28 {Acc2, Res1} = complete_iq_set(blocking_command, Acc1, LUser, LServer, Res),
96 28 mongoose_acc:set(hook, result, Res1, Acc2);
97 process_iq_set(Val, _, _, _) ->
98 60 Val.
99
100 19 parse_command_type(<<"block">>) -> block;
101 9 parse_command_type(<<"unblock">>) -> unblock.
102
103 %% @doc Set IQ must do the following:
104 %% * get / create a dedicated privacy list (we call it "blocking")
105 %% * modify the list
106 %% * set that list as a default (in backend)
107 %% * return the list so that c2s can set it as current list
108 %% * broadcast (push) message to all the user's resources
109 %% * sent 'unavailable' msg to blocked contacts, or 'available' to unblocked
110 %%
111 -spec process_blocking_iq_set(Type :: block | unblock, Acc :: mongoose_acc:t(),
112 LUser:: binary(), LServer:: binary(),
113 CurrList :: [listitem()], Users :: [binary()]) ->
114 {mongoose_acc:t(), {ok, [binary()], [listitem()], block | unblock | unblock_all} |
115 {error, exml:element()}}.
116 %% fail if current default list could not be retrieved
117 process_blocking_iq_set(_, Acc, _, _, {error, _}, _) ->
118
:-(
{Acc, {error, mongoose_xmpp_errors:internal_server_error()}};
119 %% reject block request with empty jid list
120 process_blocking_iq_set(block, Acc, _, _, _, []) ->
121 1 {Acc, {error, mongoose_xmpp_errors:bad_request()}};
122 process_blocking_iq_set(Type, Acc, LUser, LServer, CurrList, Usrs) ->
123 %% check who is being added / removed
124 27 HostType = mongoose_acc:host_type(Acc),
125 27 {NType, Changed, NewList} = blocking_list_modify(Type, Usrs, CurrList),
126 27 case mod_privacy_backend:replace_privacy_list(HostType, LUser, LServer, <<"blocking">>, NewList) of
127 {error, E} ->
128
:-(
{error, E};
129 ok ->
130 27 case mod_privacy_backend:set_default_list(HostType, LUser, LServer, <<"blocking">>) of
131 ok ->
132 27 {Acc, {ok, Changed, NewList, NType}};
133 {error, not_found} ->
134
:-(
{Acc, {error, mongoose_xmpp_errors:item_not_found()}};
135 {error, _Reason} ->
136
:-(
{Acc, {error, mongoose_xmpp_errors:internal_server_error()}}
137 end
138 end.
139
140 -spec complete_iq_set(atom(), mongoose_acc:t(), term(), term(), term()) ->
141 {mongoose_acc:t(), {error, term()} | {result, list() | {result, list(), term()}}}.
142 complete_iq_set(blocking_command, Acc, _, _, {error, Reason}) ->
143 1 {Acc, {error, Reason}};
144 complete_iq_set(blocking_command, Acc, LUser, LServer, {ok, Changed, List, Type}) ->
145 27 UserList = #userlist{name = <<"blocking">>, list = List, needdb = false},
146 % send the list to all users c2s processes (resources) to make it effective immediately
147 27 Acc1 = broadcast_blocking_command(Acc, LUser, LServer, UserList, Changed, Type),
148 % return a response here so that c2s sets the list in its state
149 27 {Acc1, {result, [], UserList}}.
150 %%complete_iq_set(blocking_command, _, _, _) ->
151 %% {result, []}.
152
153 -spec blocking_list_modify(Type :: block | unblock, New :: [binary()], Old :: [listitem()]) ->
154 {block|unblock|unblock_all, [binary()], [listitem()]}.
155 blocking_list_modify(block, Change, Old) ->
156 18 N = make_blocking_list(Change),
157 18 {_, O} = remove_from(Change, Old),
158 %% we treat all items on the "to block" list as changed becase they might have been present
159 %% on the old list with different settings
160 %% and we need to set order numbers, doesn't matter how but it has to be unique
161 18 {block, Change, set_order(N ++ O)};
162 blocking_list_modify(unblock, [], Old) ->
163 %% unblock with empty list means unblocking all contacts
164 2 Rem = [jid:to_binary(J#listitem.value) || J <- Old],
165 2 {unblock_all, Rem, []};
166 blocking_list_modify(unblock, Change, Old) ->
167 7 {Removed, O} = remove_from(Change, Old),
168 7 {unblock, Removed, O}.
169
170 set_order(L) ->
171 18 set_order(1, [], L).
172
173 set_order(_, N, []) ->
174 18 N;
175 set_order(Idx, N, [H|T]) ->
176 26 set_order(Idx + 1, [H#listitem{order = Idx}|N], T).
177
178 remove_from(ToRem, Lst) ->
179 25 remove_from(ToRem, [], [], Lst).
180
181 remove_from(_, Removed, New, []) ->
182 25 {Removed, New};
183 remove_from(ToRem, Removed, New, [H|T]) ->
184 15 Bin = jid:to_binary(H#listitem.value),
185 15 case lists:member(Bin, ToRem) of
186 true ->
187 8 remove_from(ToRem, [Bin|Removed], New, T);
188 false ->
189 7 remove_from(ToRem, Removed, [H|New], T)
190 end.
191
192 make_blocking_list(L) ->
193 18 make_blocking_list([], L).
194
195 make_blocking_list(New, []) ->
196 18 New;
197 make_blocking_list(New, [H|T]) ->
198 24 case make_blocking_list_entry(H) of
199 false ->
200
:-(
make_blocking_list(New, T);
201 Entry ->
202 24 make_blocking_list([Entry|New], T)
203 end.
204
205 make_blocking_list_entry(J) ->
206 24 case jid:from_binary(J) of
207 error ->
208
:-(
false;
209 JID ->
210 24 #listitem{type = jid,
211 match_all = true,
212 %% we have to use another action
213 %% because c2s has to respond differently based on why we deny
214 action = block,
215 value = jid:to_lower(JID)}
216 end.
217
218 %% @doc send iq confirmation to all of the user's resources
219 %% if we unblock all contacts then we don't list who's been unblocked
220 broadcast_blocking_command(Acc, LUser, LServer, UserList, _Changed, unblock_all) ->
221 2 broadcast_blocking_command(Acc, LUser, LServer, UserList, [], unblock);
222 broadcast_blocking_command(Acc, LUser, LServer, UserList, Changed, Type) ->
223 27 UserJID = jid:make_noprep(LUser, LServer, <<>>),
224 27 Bcast = {blocking, UserList, Type, Changed},
225 27 ejabberd_sm:route(UserJID, UserJID, Acc, {broadcast, Bcast}).
226
227 blocking_query_response(Lst) ->
228 8 #xmlel{
229 name = <<"blocklist">>,
230 attrs = [{<<"xmlns">>, ?NS_BLOCKING}],
231 6 children = [#xmlel{name= <<"item">>,
232 8 attrs = [{<<"jid">>, jid:to_binary(J#listitem.value)}]} || J <- Lst]}.
Line Hits Source