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