./ct_report/coverage/mod_muc_light_utils.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc_light_utils.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : Stateless utilities for mod_muc_light
5 %%% Created : 8 Sep 2014 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%
7 %%% This program is free software; you can redistribute it and/or
8 %%% modify it under the terms of the GNU General Public License as
9 %%% published by the Free Software Foundation; either version 2 of the
10 %%% License, or (at your option) any later version.
11 %%%
12 %%% This program is distributed in the hope that it will be useful,
13 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
14 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 %%% General Public License for more details.
16 %%%
17 %%% You should have received a copy of the GNU General Public License
18 %%% along with this program; if not, write to the Free Software
19 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 %%%
21 %%%----------------------------------------------------------------------
22
23 -module(mod_muc_light_utils).
24 -author('piotr.nosek@erlang-solutions.com').
25
26 %% API
27 -export([change_aff_users/2]).
28 -export([b2aff/1, aff2b/1]).
29 -export([light_aff_to_muc_role/1]).
30 -export([room_limit_reached/2]).
31 -export([filter_out_prevented/4]).
32 -export([acc_to_host_type/1]).
33 -export([room_jid_to_host_type/1]).
34 -export([room_jid_to_server_host/1]).
35 -export([muc_host_to_host_type/1]).
36 -export([server_host_to_host_type/1]).
37 -export([server_host_to_muc_host/2]).
38 -export([run_forget_room_hook/1]).
39
40 -include("jlib.hrl").
41 -include("mongoose.hrl").
42 -include("mod_muc_light.hrl").
43
44 -type change_aff_success() :: {ok, NewAffUsers :: aff_users(), ChangedAffUsers :: aff_users(),
45 JoiningUsers :: [jid:simple_bare_jid()],
46 LeavingUsers :: [jid:simple_bare_jid()]}.
47
48 -type change_aff_success_without_users() :: {ok, NewAffUsers :: aff_users(),
49 ChangedAffUsers :: aff_users()}.
50
51 -type promotion_type() :: promote_old_member | promote_joined_member | promote_demoted_owner.
52
53 -export_type([change_aff_success/0]).
54
55 -type bad_request() :: bad_request | {bad_request, binary()}.
56
57 %%====================================================================
58 %% API
59 %%====================================================================
60
61 -spec change_aff_users(CurrentAffUsers :: aff_users(), AffUsersChangesAssorted :: aff_users()) ->
62 change_aff_success() | {error, bad_request()}.
63 change_aff_users(AffUsers, AffUsersChangesAssorted) ->
64 393 case {lists:keyfind(owner, 2, AffUsers), lists:keyfind(owner, 2, AffUsersChangesAssorted)} of
65 {false, false} -> % simple, no special cases
66 2 apply_aff_users_change(AffUsers, AffUsersChangesAssorted);
67 {false, {_, _}} -> % ownerless room!
68
:-(
{error, {bad_request, <<"Ownerless room">>}};
69 _ ->
70 391 lists:foldl(fun(F, Acc) -> F(Acc) end,
71 apply_aff_users_change(AffUsers, AffUsersChangesAssorted),
72 [fun maybe_demote_old_owner/1,
73 fun maybe_select_new_owner/1])
74 end.
75
76 -spec aff2b(Aff :: aff()) -> binary().
77 276 aff2b(owner) -> <<"owner">>;
78 382 aff2b(member) -> <<"member">>;
79 339 aff2b(none) -> <<"none">>.
80
81 -spec b2aff(AffBin :: binary()) -> aff().
82 4 b2aff(<<"owner">>) -> owner;
83 196 b2aff(<<"member">>) -> member;
84 43 b2aff(<<"none">>) -> none.
85
86 -spec light_aff_to_muc_role(aff()) -> mod_muc:role().
87 211 light_aff_to_muc_role(owner) -> moderator;
88 89 light_aff_to_muc_role(member) -> participant;
89
:-(
light_aff_to_muc_role(none) -> none.
90
91 -spec room_limit_reached(UserJid :: jid:jid(), HostType :: mongooseim:host_type()) ->
92 boolean().
93 room_limit_reached(UserJid, HostType) ->
94 117 check_room_limit_reached(jid:to_lus(UserJid), HostType, rooms_per_user(HostType)).
95
96 rooms_per_user(HostType) ->
97 371 gen_mod:get_module_opt(HostType, mod_muc_light, rooms_per_user).
98
99 -spec filter_out_prevented(HostType :: mongooseim:host_type(),
100 FromUS :: jid:simple_bare_jid(),
101 RoomUS :: jid:simple_bare_jid(),
102 AffUsers :: aff_users()) -> aff_users().
103 filter_out_prevented(HostType, FromUS, {RoomU, MUCServer} = RoomUS, AffUsers) ->
104 254 RoomsPerUser = rooms_per_user(HostType),
105 254 BlockingEnabled = gen_mod:get_module_opt(HostType, mod_muc_light, blocking),
106 254 BlockingQuery = case {BlockingEnabled, RoomU} of
107 65 {true, <<>>} -> [{user, FromUS}];
108 189 {true, _} -> [{user, FromUS}, {room, RoomUS}];
109
:-(
{false, _} -> undefined
110 end,
111 254 case BlockingQuery == undefined andalso RoomsPerUser == infinity of
112
:-(
true -> AffUsers;
113 254 false -> filter_out_loop(HostType, FromUS, MUCServer,
114 BlockingQuery, RoomsPerUser, AffUsers)
115 end.
116
117 %%====================================================================
118 %% Internal functions
119 %%====================================================================
120
121 %% ---------------- Checks ----------------
122
123 -spec check_room_limit_reached(UserUS :: jid:simple_bare_jid(),
124 HostType :: mongooseim:host_type(),
125 RoomsPerUser :: infinity | pos_integer()) ->
126 boolean().
127 check_room_limit_reached(_UserUS, _HostType, infinity) ->
128 322 false;
129 check_room_limit_reached(UserUS, HostType, RoomsPerUser) ->
130 3 mod_muc_light_db_backend:get_user_rooms_count(HostType, UserUS) >= RoomsPerUser.
131
132 %% ---------------- Filter for blocking ----------------
133
134 -spec filter_out_loop(HostType :: mongooseim:host_type(),
135 FromUS :: jid:simple_bare_jid(),
136 MUCServer :: jid:lserver(),
137 BlockingQuery :: [{blocking_what(), jid:simple_bare_jid()}],
138 RoomsPerUser :: rooms_per_user(),
139 AffUsers :: aff_users()) -> aff_users().
140 filter_out_loop(HostType, FromUS, MUCServer, BlockingQuery, RoomsPerUser,
141 [{UserUS, _} = AffUser | RAffUsers]) ->
142 212 NotBlocked = case (BlockingQuery == undefined orelse UserUS =:= FromUS) of
143 false -> mod_muc_light_db_backend:get_blocking(
144 203 HostType, UserUS, MUCServer, BlockingQuery) == allow;
145 9 true -> true
146 end,
147 212 case NotBlocked andalso not check_room_limit_reached(FromUS, HostType, RoomsPerUser) of
148 true ->
149 207 [AffUser | filter_out_loop(HostType, FromUS, MUCServer,
150 BlockingQuery, RoomsPerUser, RAffUsers)];
151 false ->
152 5 filter_out_loop(HostType, FromUS, MUCServer,
153 BlockingQuery, RoomsPerUser, RAffUsers)
154 end;
155 filter_out_loop(_HostType, _FromUS, _MUCServer, _BlockingQuery, _RoomsPerUser, []) ->
156 254 [].
157
158 %% ---------------- Affiliations manipulation ----------------
159
160 -spec maybe_select_new_owner(ChangeResult :: change_aff_success() | {error, bad_request()}) ->
161 change_aff_success() | {error, bad_request()}.
162 maybe_select_new_owner({ok, AU, AUC, JoiningUsers, LeavingUsers} = _AffRes) ->
163 390 {AffUsers, AffUsersChanged} =
164 390 case is_new_owner_needed(AU) andalso find_new_owner(AU, AUC, JoiningUsers) of
165 {NewOwner, PromotionType} ->
166 45 NewAU = lists:keyreplace(NewOwner, 1, AU, {NewOwner, owner}),
167 45 NewAUC = update_auc(PromotionType, NewOwner, AUC),
168 45 {NewAU, NewAUC};
169 false ->
170 345 {AU, AUC}
171 end,
172 390 {ok, AffUsers, AffUsersChanged, JoiningUsers, LeavingUsers};
173 maybe_select_new_owner(Error) ->
174 1 Error.
175
176 update_auc(promote_old_member, NewOwner, AUC) ->
177 43 [{NewOwner, owner} | AUC];
178 update_auc(promote_joined_member, NewOwner, AUC) ->
179
:-(
lists:keyreplace(NewOwner, 1, AUC, {NewOwner, owner});
180 update_auc(promote_demoted_owner, NewOwner, AUC) ->
181 2 lists:keydelete(NewOwner, 1, AUC).
182
183 is_new_owner_needed(AU) ->
184 390 case lists:keyfind(owner, 2, AU) of
185 124 false -> true;
186 266 _ -> false
187 end.
188
189
190 -spec find_new_owner(aff_users(), aff_users(), [jid:simple_bare_jid()]) ->
191 {jid:simple_bare_jid(), promotion_type()} | false.
192 find_new_owner(AU, AUC, JoiningUsers) ->
193 124 AllMembers = [U || {U, member} <- (AU)],
194 124 NewMembers = [U || {U, member} <- (AUC)],
195 124 OldMembers = AllMembers -- NewMembers,
196 124 DemotedOwners = NewMembers -- JoiningUsers,
197 124 select_promotion(OldMembers, JoiningUsers, DemotedOwners).
198
199 %% @doc try to select the new owner from:
200 %% 1) old unchanged room members
201 %% 2) new just joined room members
202 %% 3) demoted room owners
203 select_promotion([U | _], _JoiningUsers, _DemotedOwners) ->
204 43 {U, promote_old_member};
205 select_promotion(_OldMembers, [U | _], _DemotedOwners) ->
206
:-(
{U, promote_joined_member};
207 select_promotion(_OldMembers, _JoiningUsers, [U | _]) ->
208 2 {U, promote_demoted_owner};
209 select_promotion(_, _, _) ->
210 79 false.
211
212 -spec maybe_demote_old_owner(ChangeResult :: change_aff_success() | {error, bad_request()}) ->
213 change_aff_success() | {error, bad_request()}.
214 maybe_demote_old_owner({ok, AU, AUC, JoiningUsers, LeavingUsers}) ->
215 390 Owners = [U || {U, owner} <- AU],
216 390 PromotedOwners = [U || {U, owner} <- AUC],
217 390 OldOwners = Owners -- PromotedOwners,
218 390 case {Owners, OldOwners} of
219 _ when length(Owners) =< 1 ->
220 390 {ok, AU, AUC, JoiningUsers, LeavingUsers};
221 {[_, _], [OldOwner]} ->
222
:-(
NewAU = lists:keyreplace(OldOwner, 1, AU, {OldOwner, member}),
223
:-(
NewAUC = [{OldOwner, member} | AUC],
224
:-(
{ok, NewAU, NewAUC, JoiningUsers, LeavingUsers};
225 _ ->
226
:-(
{error, {bad_request, <<"Failed to demote old owner">>}}
227 end;
228 maybe_demote_old_owner(Error) ->
229 1 Error.
230
231 -spec apply_aff_users_change(AffUsers :: aff_users(),
232 AffUsersChanges :: aff_users()) ->
233 change_aff_success() | {error, bad_request()}.
234 apply_aff_users_change(AU, AUC) ->
235 393 JoiningUsers = proplists:get_keys(AUC) -- proplists:get_keys(AU),
236 393 AffAndNewUsers = lists:sort(AU ++ [{U, none} || U <- JoiningUsers]),
237 393 AffChanges = lists:sort(AUC),
238 393 LeavingUsers = [U || {U, none} <- AUC],
239 393 case apply_aff_users_change(AffAndNewUsers, [], AffChanges, []) of
240 {ok, NewAffUsers, ChangesDone} ->
241 392 {ok, NewAffUsers, ChangesDone, JoiningUsers, LeavingUsers};
242 1 Error -> Error
243 end.
244
245
246 -spec apply_aff_users_change(AffUsers :: aff_users(),
247 NewAffUsers :: aff_users(),
248 AffUsersChanges :: aff_users(),
249 ChangesDone :: aff_users()) ->
250 change_aff_success_without_users() | {error, bad_request()}.
251 apply_aff_users_change([], NAU, [], CD) ->
252 %% User list must be sorted ascending but acc is currently sorted descending
253 392 {ok, lists:reverse(NAU), CD};
254 apply_aff_users_change(_AU, _NAU, [{User, _}, {User, _} | _RAUC], _CD) ->
255
:-(
{error, {bad_request, <<"Cannot change affiliation for the same user "
256 "twice in the same request">>}};
257 apply_aff_users_change([AffUser | _], _NAU, [AffUser | _], _CD) ->
258 1 {error, {bad_request, <<"Meaningless change">>}};
259 apply_aff_users_change([{User, _} | RAU], NAU, [{User, none} | RAUC], CD) ->
260 %% removing user from the room
261 161 apply_aff_users_change(RAU, NAU, RAUC, [{User, none} | CD]);
262
263 apply_aff_users_change([{User, none} | RAU], NAU, [{User, _} = NewUser | RAUC], CD) ->
264 %% Adding new member to a room
265 182 apply_aff_users_change(RAU, [NewUser | NAU], RAUC, [NewUser | CD]);
266
267 apply_aff_users_change([{User, _} | RAU], NAU, [{User, NewAff} | RAUC], CD) ->
268 %% Changing affiliation, owner -> member or member -> owner
269 6 apply_aff_users_change(RAU, [{User, NewAff} | NAU], RAUC, [{User, NewAff} | CD]);
270
271 apply_aff_users_change([OldUser | RAU], NAU, AUC, CD) ->
272 %% keep user affiliation unchanged
273 342 apply_aff_users_change(RAU, [OldUser | NAU], AUC, CD).
274
275 -spec acc_to_host_type(mongoose_acc:t()) -> mongooseim:host_type().
276 acc_to_host_type(Acc) ->
277 2173 case mongoose_acc:host_type(Acc) of
278 undefined ->
279
:-(
MucHost = mongoose_acc:lserver(Acc),
280
:-(
muc_host_to_host_type(MucHost);
281 HostType ->
282 2173 HostType
283 end.
284
285 -spec room_jid_to_host_type(jid:jid()) -> mongooseim:host_type().
286 room_jid_to_host_type(#jid{lserver = MucHost}) ->
287 1886 muc_host_to_host_type(MucHost).
288
289 -spec room_jid_to_server_host(jid:jid()) -> jid:lserver().
290 room_jid_to_server_host(#jid{lserver = MucHost}) ->
291 2216 case mongoose_domain_api:get_subdomain_info(MucHost) of
292 {ok, #{parent_domain := ServerHost}} when is_binary(ServerHost) ->
293 2214 ServerHost;
294 Other ->
295 2 error({room_jid_to_server_host_failed, MucHost, Other})
296 end.
297
298 server_host_to_host_type(LServer) ->
299 184 case mongoose_domain_api:get_domain_host_type(LServer) of
300 {ok, HostType} ->
301 184 HostType;
302 Other ->
303
:-(
error({server_host_to_host_type_failed, LServer, Other})
304 end.
305
306 muc_host_to_host_type(MucHost) ->
307 2485 case mongoose_domain_api:get_subdomain_host_type(MucHost) of
308 {ok, HostType} ->
309 2479 HostType;
310 Other ->
311 6 error({muc_host_to_host_type_failed, MucHost, Other})
312 end.
313
314 subdomain_pattern(HostType) ->
315 194 gen_mod:get_module_opt(HostType, mod_muc_light, host).
316
317 server_host_to_muc_host(HostType, ServerHost) ->
318 194 mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost).
319
320 run_forget_room_hook({Room, MucHost}) ->
321 38 case mongoose_domain_api:get_subdomain_host_type(MucHost) of
322 {ok, HostType} ->
323 38 mongoose_hooks:forget_room(HostType, MucHost, Room);
324 _Other ->
325 %% MUC light is not started probably
326
:-(
?LOG_ERROR(#{what => run_forget_room_hook_skipped,
327
:-(
room => Room, muc_host => MucHost})
328 end.
Line Hits Source