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