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