./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_muc_host/2]).
37 -export([run_forget_room_hook/1]).
38
39 -include("jlib.hrl").
40 -include("mongoose.hrl").
41 -include("mod_muc_light.hrl").
42
43 -type change_aff_success() :: {ok, NewAffUsers :: aff_users(), ChangedAffUsers :: aff_users(),
44 JoiningUsers :: [jid:simple_bare_jid()],
45 LeavingUsers :: [jid:simple_bare_jid()]}.
46
47 -type change_aff_success_without_users() :: {ok, NewAffUsers :: aff_users(),
48 ChangedAffUsers :: aff_users()}.
49
50 -type promotion_type() :: promote_old_member | promote_joined_member | promote_demoted_owner.
51
52 -export_type([change_aff_success/0]).
53
54 -type bad_request() :: bad_request | {bad_request, binary()}.
55
56 %%====================================================================
57 %% API
58 %%====================================================================
59
60 -spec change_aff_users(CurrentAffUsers :: aff_users(), AffUsersChangesAssorted :: aff_users()) ->
61 change_aff_success() | {error, bad_request()}.
62 change_aff_users(AffUsers, AffUsersChangesAssorted) ->
63 684 case {lists:keyfind(owner, 2, AffUsers), lists:keyfind(owner, 2, AffUsersChangesAssorted)} of
64 {false, false} -> % simple, no special cases
65 2 apply_aff_users_change(AffUsers, AffUsersChangesAssorted);
66 {false, {_, _}} -> % ownerless room!
67
:-(
{error, {bad_request, <<"Ownerless room">>}};
68 _ ->
69 682 lists:foldl(fun(F, Acc) -> F(Acc) end,
70 apply_aff_users_change(AffUsers, AffUsersChangesAssorted),
71 [fun maybe_demote_old_owner/1,
72 fun maybe_select_new_owner/1])
73 end.
74
75 -spec aff2b(Aff :: aff()) -> binary().
76 390 aff2b(owner) -> <<"owner">>;
77 548 aff2b(member) -> <<"member">>;
78 639 aff2b(none) -> <<"none">>.
79
80 -spec b2aff(AffBin :: binary()) -> aff().
81 4 b2aff(<<"owner">>) -> owner;
82 279 b2aff(<<"member">>) -> member;
83 94 b2aff(<<"none">>) -> none.
84
85 -spec light_aff_to_muc_role(aff()) -> mod_muc:role().
86 280 light_aff_to_muc_role(owner) -> moderator;
87 139 light_aff_to_muc_role(member) -> participant;
88
:-(
light_aff_to_muc_role(none) -> none.
89
90 -spec room_limit_reached(UserJid :: jid:jid(), HostType :: mongooseim:host_type()) ->
91 boolean().
92 room_limit_reached(UserJid, HostType) ->
93 169 check_room_limit_reached(jid:to_lus(UserJid), HostType, rooms_per_user(HostType)).
94
95 rooms_per_user(HostType) ->
96 563 gen_mod:get_module_opt(HostType, mod_muc_light, rooms_per_user).
97
98 -spec filter_out_prevented(HostType :: mongooseim:host_type(),
99 FromUS :: jid:simple_bare_jid(),
100 RoomUS :: jid:simple_bare_jid(),
101 AffUsers :: aff_users()) -> aff_users().
102 filter_out_prevented(HostType, FromUS, {RoomU, MUCServer} = RoomUS, AffUsers) ->
103 394 RoomsPerUser = rooms_per_user(HostType),
104 394 BlockingEnabled = gen_mod:get_module_opt(HostType, mod_muc_light, blocking),
105 394 BlockingQuery = case {BlockingEnabled, RoomU} of
106 129 {true, <<>>} -> [{user, FromUS}];
107 265 {true, _} -> [{user, FromUS}, {room, RoomUS}];
108
:-(
{false, _} -> undefined
109 end,
110 394 case BlockingQuery == undefined andalso RoomsPerUser == infinity of
111
:-(
true -> AffUsers;
112 394 false -> filter_out_loop(HostType, FromUS, MUCServer,
113 BlockingQuery, RoomsPerUser, AffUsers)
114 end.
115
116 %%====================================================================
117 %% Internal functions
118 %%====================================================================
119
120 %% ---------------- Checks ----------------
121
122 -spec check_room_limit_reached(UserUS :: jid:simple_bare_jid(),
123 HostType :: mongooseim:host_type(),
124 RoomsPerUser :: infinity | pos_integer()) ->
125 boolean().
126 check_room_limit_reached(_UserUS, _HostType, infinity) ->
127 457 false;
128 check_room_limit_reached(UserUS, HostType, RoomsPerUser) ->
129 3 mod_muc_light_db_backend:get_user_rooms_count(HostType, UserUS) >= RoomsPerUser.
130
131 %% ---------------- Filter for blocking ----------------
132
133 -spec filter_out_loop(HostType :: mongooseim:host_type(),
134 FromUS :: jid:simple_bare_jid(),
135 MUCServer :: jid:lserver(),
136 BlockingQuery :: [{blocking_what(), jid:simple_bare_jid()}],
137 RoomsPerUser :: rooms_per_user(),
138 AffUsers :: aff_users()) -> aff_users().
139 filter_out_loop(HostType, FromUS, MUCServer, BlockingQuery, RoomsPerUser,
140 [{UserUS, _} = AffUser | RAffUsers]) ->
141 295 NotBlocked = case (BlockingQuery == undefined orelse UserUS =:= FromUS) of
142 false -> mod_muc_light_db_backend:get_blocking(
143 286 HostType, UserUS, MUCServer, BlockingQuery) == allow;
144 9 true -> true
145 end,
146 295 case NotBlocked andalso not check_room_limit_reached(FromUS, HostType, RoomsPerUser) of
147 true ->
148 290 [AffUser | filter_out_loop(HostType, FromUS, MUCServer,
149 BlockingQuery, RoomsPerUser, RAffUsers)];
150 false ->
151 5 filter_out_loop(HostType, FromUS, MUCServer,
152 BlockingQuery, RoomsPerUser, RAffUsers)
153 end;
154 filter_out_loop(_HostType, _FromUS, _MUCServer, _BlockingQuery, _RoomsPerUser, []) ->
155 394 [].
156
157 %% ---------------- Affiliations manipulation ----------------
158
159 -spec maybe_select_new_owner(ChangeResult :: change_aff_success() | {error, bad_request()}) ->
160 change_aff_success() | {error, bad_request()}.
161 maybe_select_new_owner({ok, AU, AUC, JoiningUsers, LeavingUsers} = _AffRes) ->
162 681 {AffUsers, AffUsersChanged} =
163 681 case is_new_owner_needed(AU) andalso find_new_owner(AU, AUC, JoiningUsers) of
164 {NewOwner, PromotionType} ->
165 55 NewAU = lists:keyreplace(NewOwner, 1, AU, {NewOwner, owner}),
166 55 NewAUC = update_auc(PromotionType, NewOwner, AUC),
167 55 {NewAU, NewAUC};
168 false ->
169 626 {AU, AUC}
170 end,
171 681 {ok, AffUsers, AffUsersChanged, JoiningUsers, LeavingUsers};
172 maybe_select_new_owner(Error) ->
173 1 Error.
174
175 update_auc(promote_old_member, NewOwner, AUC) ->
176 53 [{NewOwner, owner} | AUC];
177 update_auc(promote_joined_member, NewOwner, AUC) ->
178
:-(
lists:keyreplace(NewOwner, 1, AUC, {NewOwner, owner});
179 update_auc(promote_demoted_owner, NewOwner, AUC) ->
180 2 lists:keydelete(NewOwner, 1, AUC).
181
182 is_new_owner_needed(AU) ->
183 681 case lists:keyfind(owner, 2, AU) of
184 223 false -> true;
185 458 _ -> false
186 end.
187
188
189 -spec find_new_owner(aff_users(), aff_users(), [jid:simple_bare_jid()]) ->
190 {jid:simple_bare_jid(), promotion_type()} | false.
191 find_new_owner(AU, AUC, JoiningUsers) ->
192 223 AllMembers = [U || {U, member} <- (AU)],
193 223 NewMembers = [U || {U, member} <- (AUC)],
194 223 OldMembers = AllMembers -- NewMembers,
195 223 DemotedOwners = NewMembers -- JoiningUsers,
196 223 select_promotion(OldMembers, JoiningUsers, DemotedOwners).
197
198 %% @doc try to select the new owner from:
199 %% 1) old unchanged room members
200 %% 2) new just joined room members
201 %% 3) demoted room owners
202 select_promotion([U | _], _JoiningUsers, _DemotedOwners) ->
203 53 {U, promote_old_member};
204 select_promotion(_OldMembers, [U | _], _DemotedOwners) ->
205
:-(
{U, promote_joined_member};
206 select_promotion(_OldMembers, _JoiningUsers, [U | _]) ->
207 2 {U, promote_demoted_owner};
208 select_promotion(_, _, _) ->
209 168 false.
210
211 -spec maybe_demote_old_owner(ChangeResult :: change_aff_success() | {error, bad_request()}) ->
212 change_aff_success() | {error, bad_request()}.
213 maybe_demote_old_owner({ok, AU, AUC, JoiningUsers, LeavingUsers}) ->
214 681 Owners = [U || {U, owner} <- AU],
215 681 PromotedOwners = [U || {U, owner} <- AUC],
216 681 OldOwners = Owners -- PromotedOwners,
217 681 case {Owners, OldOwners} of
218 _ when length(Owners) =< 1 ->
219 681 {ok, AU, AUC, JoiningUsers, LeavingUsers};
220 {[_, _], [OldOwner]} ->
221
:-(
NewAU = lists:keyreplace(OldOwner, 1, AU, {OldOwner, member}),
222
:-(
NewAUC = [{OldOwner, member} | AUC],
223
:-(
{ok, NewAU, NewAUC, JoiningUsers, LeavingUsers};
224 _ ->
225
:-(
{error, {bad_request, <<"Failed to demote old owner">>}}
226 end;
227 maybe_demote_old_owner(Error) ->
228 1 Error.
229
230 -spec apply_aff_users_change(AffUsers :: aff_users(),
231 AffUsersChanges :: aff_users()) ->
232 change_aff_success() | {error, bad_request()}.
233 apply_aff_users_change(AU, AUC) ->
234 684 JoiningUsers = proplists:get_keys(AUC) -- proplists:get_keys(AU),
235 684 AffAndNewUsers = lists:sort(AU ++ [{U, none} || U <- JoiningUsers]),
236 684 AffChanges = lists:sort(AUC),
237 684 LeavingUsers = [U || {U, none} <- AUC],
238 684 case apply_aff_users_change(AffAndNewUsers, [], AffChanges, []) of
239 {ok, NewAffUsers, ChangesDone} ->
240 683 {ok, NewAffUsers, ChangesDone, JoiningUsers, LeavingUsers};
241 1 Error -> Error
242 end.
243
244
245 -spec apply_aff_users_change(AffUsers :: aff_users(),
246 NewAffUsers :: aff_users(),
247 AffUsersChanges :: aff_users(),
248 ChangesDone :: aff_users()) ->
249 change_aff_success_without_users() | {error, bad_request()}.
250 apply_aff_users_change([], NAU, [], CD) ->
251 %% User list must be sorted ascending but acc is currently sorted descending
252 683 {ok, lists:reverse(NAU), CD};
253 apply_aff_users_change(_AU, _NAU, [{User, _}, {User, _} | _RAUC], _CD) ->
254
:-(
{error, {bad_request, <<"Cannot change affiliation for the same user "
255 "twice in the same request">>}};
256 apply_aff_users_change([AffUser | _], _NAU, [AffUser | _], _CD) ->
257 1 {error, {bad_request, <<"Meaningless change">>}};
258 apply_aff_users_change([{User, _} | RAU], NAU, [{User, none} | RAUC], CD) ->
259 %% removing user from the room
260 312 apply_aff_users_change(RAU, NAU, RAUC, [{User, none} | CD]);
261
262 apply_aff_users_change([{User, none} | RAU], NAU, [{User, _} = NewUser | RAUC], CD) ->
263 %% Adding new member to a room
264 265 apply_aff_users_change(RAU, [NewUser | NAU], RAUC, [NewUser | CD]);
265
266 apply_aff_users_change([{User, _} | RAU], NAU, [{User, NewAff} | RAUC], CD) ->
267 %% Changing affiliation, owner -> member or member -> owner
268 6 apply_aff_users_change(RAU, [{User, NewAff} | NAU], RAUC, [{User, NewAff} | CD]);
269
270 apply_aff_users_change([OldUser | RAU], NAU, AUC, CD) ->
271 %% keep user affiliation unchanged
272 568 apply_aff_users_change(RAU, [OldUser | NAU], AUC, CD).
273
274 -spec acc_to_host_type(mongoose_acc:t()) -> mongooseim:host_type().
275 acc_to_host_type(Acc) ->
276 3214 case mongoose_acc:host_type(Acc) of
277 undefined ->
278
:-(
MucHost = mongoose_acc:lserver(Acc),
279
:-(
muc_host_to_host_type(MucHost);
280 HostType ->
281 3214 HostType
282 end.
283
284 -spec room_jid_to_host_type(jid:jid()) -> mongooseim:host_type().
285 room_jid_to_host_type(#jid{lserver = MucHost}) ->
286 3417 muc_host_to_host_type(MucHost).
287
288 -spec room_jid_to_server_host(jid:jid()) -> jid:lserver().
289 room_jid_to_server_host(#jid{lserver = MucHost}) ->
290 2670 case mongoose_domain_api:get_subdomain_info(MucHost) of
291 {ok, #{parent_domain := ServerHost}} when is_binary(ServerHost) ->
292 2670 ServerHost;
293 Other ->
294
:-(
error({room_jid_to_server_host_failed, MucHost, Other})
295 end.
296
297 muc_host_to_host_type(MucHost) ->
298 4323 case mongoose_domain_api:get_subdomain_host_type(MucHost) of
299 {ok, HostType} ->
300 4323 HostType;
301 Other ->
302
:-(
error({muc_host_to_host_type_failed, MucHost, Other})
303 end.
304
305 subdomain_pattern(HostType) ->
306 149 gen_mod:get_module_opt(HostType, mod_muc_light, host).
307
308 server_host_to_muc_host(HostType, ServerHost) ->
309 149 mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost).
310
311 run_forget_room_hook({Room, MucHost}) ->
312
:-(
case mongoose_domain_api:get_subdomain_host_type(MucHost) of
313 {ok, HostType} ->
314
:-(
mongoose_hooks:forget_room(HostType, MucHost, Room);
315 _Other ->
316 %% MUC light is not started probably
317
:-(
?LOG_ERROR(#{what => run_forget_room_hook_skipped,
318
:-(
room => Room, muc_host => MucHost})
319 end.
Line Hits Source