./ct_report/coverage/mod_muc_light_room.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc_light_room.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : Room logic for mod_muc_light
5 %%% Created : 9 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_room).
24 -author('piotr.nosek@erlang-solutions.com').
25
26 %% API
27 -export([handle_request/5, maybe_forget/4, process_request/5]).
28
29 %% Callbacks
30 -export([participant_limit_check/2]).
31
32 -include("mongoose.hrl").
33 -include("jlib.hrl").
34 -include("mod_muc_light.hrl").
35
36 -type packet_processing_result() :: muc_light_encode_request() | {error, Reason :: term()}.
37
38 %%====================================================================
39 %% API
40 %%====================================================================
41
42 -spec handle_request(From :: jid:jid(), RoomJID :: jid:jid(), OrigPacket :: exml:element(),
43 Request :: muc_light_packet(), Acc :: mongoose_acc:t()) -> mongoose_acc:t().
44 handle_request(From, RoomJID, OrigPacket, Request, Acc1) ->
45 648 {Acc2, AffUsersRes} = mod_muc_light:get_acc_room_affiliations(Acc1, RoomJID),
46 648 Response = process_request(From, RoomJID, Request, AffUsersRes, Acc2),
47 648 send_response(From, RoomJID, OrigPacket, Response, Acc2).
48
49 -spec maybe_forget(Acc :: mongoose_acc:t(),
50 RoomUS :: jid:simple_bare_jid(),
51 NewAffUsers :: aff_users(),
52 Version :: binary() ) -> any().
53 maybe_forget(Acc, {RoomU, RoomS} = RoomUS, [], _Version) ->
54 174 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
55 174 mongoose_hooks:forget_room(HostType, RoomS, RoomU),
56 174 mod_muc_light_db_backend:destroy_room(HostType, RoomUS);
57 maybe_forget(Acc, {RoomU, RoomS}, NewAffs, Version) ->
58 204 RoomJid = jid:make_noprep(RoomU, RoomS, <<>>),
59 204 mongoose_hooks:room_new_affiliations(Acc, RoomJid, NewAffs, Version),
60 204 my_room_will_go_on.
61
62 %%====================================================================
63 %% Callbacks
64 %%====================================================================
65
66 -spec participant_limit_check(RoomUS :: jid:simple_bare_jid(),
67 NewAffUsers :: aff_users()) ->
68 ok | {error, occupant_limit_exceeded}.
69 participant_limit_check({_, MUCServer} = _RoomUS, NewAffUsers) ->
70 153 HostType = mod_muc_light_utils:muc_host_to_host_type(MUCServer),
71 153 MaxOccupants = gen_mod:get_module_opt(HostType, mod_muc_light, max_occupants),
72 153 case length(NewAffUsers) > MaxOccupants of
73 1 true -> {error, occupant_limit_exceeded};
74 152 false -> ok
75 end.
76
77 %%====================================================================
78 %% Packet handling
79 %%====================================================================
80
81 -spec process_request(From :: jid:jid(),
82 RoomBareJid :: jid:jid(),
83 Request :: muc_light_packet(),
84 AffUsersRes :: {ok, aff_users(), binary()} | {error, term()},
85 Acc :: mongoose_acc:t()) ->
86 packet_processing_result().
87 process_request(_From, _RoomBareJid, _Request, {error, _} = Error, _Acc) ->
88 5 Error;
89 process_request(From, RoomBareJid, Request, {ok, AffUsers, _Ver}, Acc) ->
90 668 UserUS = jid:to_lus(From),
91 668 RoomUS = jid:to_lus(RoomBareJid),
92 668 Auth = lists:keyfind(UserUS, 1, AffUsers),
93 668 process_request(Request, From, RoomUS, Auth, AffUsers, Acc).
94
95 -spec process_request(Request :: muc_light_packet(),
96 From :: jid:jid(),
97 RoomUS :: jid:simple_bare_jid(),
98 Auth :: false | aff_user(),
99 AffUsers :: aff_users(),
100 Acc :: mongoose_acc:t()) ->
101 packet_processing_result().
102 process_request(_Request, _From, _RoomUS, false, _AffUsers, _Acc) ->
103 7 {error, item_not_found};
104 process_request(#msg{} = Msg, _From, _RoomUS, _Auth, AffUsers, _Acc) ->
105 419 {Msg, AffUsers};
106 process_request({get, #config{} = ConfigReq},
107 _From, RoomUS, _Auth, _AffUsers, Acc) ->
108 21 {_, RoomS} = RoomUS,
109 21 HostType = mongoose_acc:host_type(Acc),
110 21 {ok, Config, RoomVersion} = mod_muc_light_db_backend:get_config(HostType, RoomUS),
111 21 {ok, RawConfig} = mod_muc_light_room_config:to_binary_kv(Config, mod_muc_light:config_schema(RoomS)),
112 21 {get, ConfigReq#config{ version = RoomVersion,
113 raw_config = RawConfig }};
114 process_request({get, #affiliations{} = AffReq},
115 _From, RoomUS, _Auth, _AffUsers, Acc) ->
116 9 HostType = mongoose_acc:host_type(Acc),
117 9 {ok, AffUsers, RoomVersion} = mod_muc_light_db_backend:get_aff_users(HostType, RoomUS),
118 9 {get, AffReq#affiliations{ version = RoomVersion,
119 aff_users = AffUsers }};
120 process_request({get, #info{} = InfoReq},
121 _From, {_, RoomS} = RoomUS, _Auth, _AffUsers, Acc) ->
122 3 HostType = mongoose_acc:host_type(Acc),
123 3 {ok, Config, AffUsers, RoomVersion} = mod_muc_light_db_backend:get_info(HostType, RoomUS),
124 3 {ok, RawConfig} = mod_muc_light_room_config:to_binary_kv(Config, mod_muc_light:config_schema(RoomS)),
125 3 {get, InfoReq#info{ version = RoomVersion, aff_users = AffUsers,
126 raw_config = RawConfig }};
127 process_request({set, #config{} = ConfigReq},
128 _From, RoomUS, {_, UserAff}, AffUsers, Acc) ->
129 38 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
130 38 AllCanConfigure = all_can_configure(HostType),
131 38 process_config_set(HostType, ConfigReq, RoomUS, UserAff, AffUsers, AllCanConfigure);
132 process_request({set, #affiliations{} = AffReq},
133 From, RoomUS, {_, UserAff}, AffUsers, Acc) ->
134 164 UserUS = jid:to_lus(From),
135 164 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
136 164 OwnerUS = case lists:keyfind(owner, 2, AffUsers) of
137
:-(
false -> undefined;
138 164 {OwnerUS0, _} -> OwnerUS0
139 end,
140 164 ValidateResult
141 = case UserAff of
142 owner ->
143 84 {ok, mod_muc_light_utils:filter_out_prevented(HostType,
144 UserUS, RoomUS, AffReq#affiliations.aff_users)};
145 member ->
146 80 AllCanInvite = all_can_invite(HostType),
147 80 validate_aff_changes_by_member(
148 AffReq#affiliations.aff_users, [], UserUS, OwnerUS, RoomUS, AllCanInvite)
149 end,
150 164 process_aff_set(AffReq, RoomUS, ValidateResult, Acc);
151 process_request({set, #destroy{} = DestroyReq},
152 _From, RoomUS, {_, owner}, AffUsers, Acc) ->
153 7 HostType = mongoose_acc:host_type(Acc),
154 7 ok = mod_muc_light_db_backend:destroy_room(HostType, RoomUS),
155 7 maybe_forget(Acc, RoomUS, [], <<>>),
156 7 {set, DestroyReq, AffUsers};
157 process_request({set, #destroy{}},
158 _From, _RoomUS, _Auth, _AffUsers, _Acc) ->
159
:-(
{error, not_allowed};
160 process_request(_UnknownReq, _From, _RoomUS, _Auth, _AffUsers, _Acc) ->
161
:-(
{error, bad_request}.
162
163 all_can_invite(HostType) ->
164 80 gen_mod:get_module_opt(HostType, mod_muc_light, all_can_invite).
165
166 all_can_configure(HostType) ->
167 38 gen_mod:get_module_opt(HostType, mod_muc_light, all_can_configure).
168
169 %% --------- Config set ---------
170
171 -spec process_config_set(HostType :: mongooseim:host_type(),
172 ConfigReq :: config_req_props(),
173 RoomUS :: jid:simple_bare_jid(),
174 UserAff :: member | owner, AffUsers :: aff_users(),
175 UserAllowedToConfigure :: boolean()) ->
176 {set, config_req_props(), aff_users()} | {error, not_allowed} | validation_error().
177 process_config_set(HostType, #config{ raw_config = [{<<"subject">>, _}] } = ConfigReq, RoomUS, UserAff,
178 AffUsers, false) ->
179 % Everyone is allowed to change subject
180 7 process_config_set(HostType, ConfigReq, RoomUS, UserAff, AffUsers, true);
181 process_config_set(_HostType, _ConfigReq, _RoomUS, member, _AffUsers, false) ->
182 7 {error, not_allowed};
183 process_config_set(HostType, ConfigReq, {_, RoomS} = RoomUS, _UserAff, AffUsers, _AllCanConfigure) ->
184 31 case mod_muc_light_room_config:from_binary_kv_diff(
185 ConfigReq#config.raw_config, mod_muc_light:config_schema(RoomS)) of
186 {ok, Config} ->
187 29 NewVersion = mongoose_bin:gen_from_timestamp(),
188 29 {ok, PrevVersion} = mod_muc_light_db_backend:set_config(HostType, RoomUS, Config, NewVersion),
189 29 {set, ConfigReq#config{ prev_version = PrevVersion, version = NewVersion }, AffUsers};
190 Error ->
191 2 Error
192 end.
193
194 %% --------- Affiliation set ---------
195
196 %% Member can only add new members or leave
197 -spec validate_aff_changes_by_member(AffUsersChanges :: aff_users(),
198 AffUsersChangesAcc :: aff_users(),
199 UserUS :: jid:simple_bare_jid(),
200 OwnerUS :: jid:simple_bare_jid(),
201 RoomUS :: jid:simple_bare_jid(),
202 AllCanInvite :: boolean()) ->
203 {ok, aff_users()} | {error, not_allowed}.
204 validate_aff_changes_by_member([], Acc, _UserUS, _OwnerUS, _RoomUS, _AllCanInvite) ->
205 74 {ok, Acc};
206 validate_aff_changes_by_member([{UserUS, none} | RAffUsersChanges], Acc, UserUS, OwnerUS,
207 RoomUS, AllCanInvite) ->
208 72 validate_aff_changes_by_member(RAffUsersChanges, [{UserUS, none} | Acc], UserUS, OwnerUS,
209 RoomUS, AllCanInvite);
210 validate_aff_changes_by_member([{OwnerUS, _} | _RAffUsersChanges], _Acc, _UserUS, OwnerUS,
211 _RoomUS, _AllCanInvite) ->
212
:-(
{error, not_allowed};
213 validate_aff_changes_by_member([{_, member} = AffUserChange | RAffUsersChanges], Acc, UserUS,
214 OwnerUS, RoomUS, true) ->
215 2 validate_aff_changes_by_member(
216 RAffUsersChanges, [AffUserChange | Acc], UserUS, OwnerUS, RoomUS, true);
217 validate_aff_changes_by_member(_AffUsersChanges, _Acc, _UserUS, _OwnerUS, _RoomUS, _AllCanInvite) ->
218 6 {error, not_allowed}.
219
220 -spec process_aff_set(AffReq :: affiliations_req_props(),
221 RoomUS :: jid:simple_bare_jid(),
222 ValidateResult :: {ok, aff_users()} | {error, not_allowed},
223 Acc :: mongoose_acc:t()) ->
224 {set, affiliations_req_props(), OldAffUsers :: aff_users(), NewAffUsers :: aff_users()}
225 | {error, not_allowed}.
226 process_aff_set(AffReq, _RoomUS, {ok, []}, _Acc) -> % It seems that all users blocked this request
227 4 {set, AffReq, [], []}; % Just return result to the user, don't change or broadcast anything
228 process_aff_set(AffReq, RoomUS, {ok, FilteredAffUsers}, Acc) ->
229 154 NewVersion = mongoose_bin:gen_from_timestamp(),
230 154 HostType = mongoose_acc:host_type(Acc),
231 154 case mod_muc_light_db_backend:modify_aff_users(HostType, RoomUS, FilteredAffUsers,
232 fun ?MODULE:participant_limit_check/2, NewVersion) of
233 {ok, OldAffUsers, NewAffUsers, AffUsersChanged, OldVersion} ->
234 152 maybe_forget(Acc, RoomUS, NewAffUsers, NewVersion),
235 152 {set, AffReq#affiliations{
236 prev_version = OldVersion,
237 version = NewVersion,
238 aff_users = AffUsersChanged
239 }, OldAffUsers, NewAffUsers};
240 Error ->
241 2 Error
242 end;
243 process_aff_set(_AffReq, _RoomUS, Error, _Acc) ->
244 6 Error.
245
246 %%====================================================================
247 %% Response processing
248 %%====================================================================
249
250 -spec send_response(From :: jid:jid(), RoomJID :: jid:jid(), OrigPacket :: exml:element(),
251 Result :: packet_processing_result(), Acc :: mongoose_acc:t()) -> mongoose_acc:t().
252 send_response(From, RoomJID, OrigPacket, {error, _} = Err, Acc) ->
253 13 mod_muc_light_codec_backend:encode_error(
254 Err, From, RoomJID, OrigPacket, Acc);
255 send_response(From, RoomJID, _OriginalPacket, Response, Acc) ->
256 635 F = make_handler_fun(Acc),
257 635 mod_muc_light_codec_backend:encode(Response, From, RoomJID, F, Acc).
258
259 %%====================================================================
260 %% Internal functions
261 %%====================================================================
262 make_handler_fun(Acc) ->
263 635 fun(From, To, Packet) ->
264 1693 NewAcc0 = mongoose_acc:new(#{location => ?LOCATION,
265 lserver => From#jid.lserver,
266 element => Packet,
267 from_jid => From,
268 to_jid => To}),
269 1693 PermanentFields = mongoose_acc:get_permanent_fields(Acc),
270 1693 NewAcc = mongoose_acc:set_permanent(PermanentFields, NewAcc0),
271 1693 ejabberd_router:route(From, To, NewAcc, Packet)
272 end.
Line Hits Source