./ct_report/coverage/mod_muc_light.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc_light.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : MUC light support
5 %%% Created : 8 Sep 2014 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%
7 %%% Looking for documentation excerpt that was present here for 5 years?
8 %%% Now everything is moved to doc/modules/mod_muc_light.md
9 %%%
10 %%%----------------------------------------------------------------------
11
12 -module(mod_muc_light).
13 -author('piotr.nosek@erlang-solutions.com').
14
15 -include("mongoose.hrl").
16 -include("jlib.hrl").
17 -include("mod_muc_light.hrl").
18 -include("mod_roster.hrl").
19 -include("mongoose_rsm.hrl").
20 -include("mongoose_config_spec.hrl").
21
22 -behaviour(gen_mod).
23 -behaviour(mongoose_packet_handler).
24 -behaviour(mongoose_module_metrics).
25
26 %% API
27 -export([default_host/0]).
28 -export([server_host_to_muc_host/2]).
29 -export([config_schema/1]).
30
31 %% For Administration API
32 -export([try_to_create_room/3,
33 change_room_config/5,
34 delete_room/1]).
35
36 %% gen_mod callbacks
37 -export([start/2, stop/1, config_spec/0, supported_features/0, deps/2]).
38
39 %% config processing callback
40 -export([process_config_schema/1]).
41
42 %% Packet handler export
43 -export([process_packet/5]).
44
45 %% Hook handlers
46 -export([prevent_service_unavailable/4,
47 disco_local_items/1,
48 remove_user/3,
49 remove_domain/3,
50 add_rooms_to_roster/2,
51 process_iq_get/5,
52 process_iq_set/4,
53 is_muc_room_owner/4,
54 can_access_room/4,
55 acc_room_affiliations/2,
56 room_exists/3,
57 can_access_identity/4]).
58 -export([get_room_affiliations_from_acc/1]).
59
60 %% For propEr
61 -export([apply_rsm/3]).
62
63 -export([config_metrics/1]).
64 %% for mod_muc_light_codec_legacy
65 -export([subdomain_pattern/1]).
66
67 %% For tests
68 -export([default_schema/0,
69 force_clear_from_ct/1]).
70
71 -ignore_xref([
72 add_rooms_to_roster/2, apply_rsm/3, can_access_identity/4, can_access_room/4,
73 default_schema/0, disco_local_items/1, acc_room_affiliations/2, room_exists/3,
74 force_clear_from_ct/1, is_muc_room_owner/4, prevent_service_unavailable/4,
75 process_iq_get/5, process_iq_set/4, remove_domain/3, remove_user/3,
76 server_host_to_muc_host/2
77 ]).
78
79 -type muc_server() :: jid:lserver().
80 -type host_type() :: mongooseim:host_type().
81
82 %%====================================================================
83 %% API
84 %%====================================================================
85
86 -spec supported_features() -> [atom()].
87 supported_features() ->
88 59 [dynamic_domains].
89
90 -spec default_schema() -> mod_muc_light_room_config:schema().
91 default_schema() ->
92 % This list needs to be sorted
93 481 [{<<"roomname">>, <<"Untitled">>, roomname, binary},
94 {<<"subject">>, <<>>, subject, binary}].
95
96 -spec default_host() -> mongoose_subdomain_utils:subdomain_pattern().
97 default_host() ->
98 332 mongoose_subdomain_utils:make_subdomain_pattern(<<"muclight.@HOST@">>).
99
100 -spec config_schema(MUCServer :: muc_server()) -> mod_muc_light_room_config:schema().
101 config_schema(MUCServer) ->
102 336 HostType = mod_muc_light_utils:muc_host_to_host_type(MUCServer),
103 336 config_schema_for_host_type(HostType).
104
105 %% Internals
106
107 -spec config_schema_for_host_type(host_type()) -> mod_muc_light_room_config:schema().
108 config_schema_for_host_type(HostType) ->
109 477 gen_mod:get_module_opt(HostType, ?MODULE, config_schema, default_schema()).
110
111 force_clear_from_ct(HostType) ->
112 100 catch mod_muc_light_cache:force_clear(HostType),
113 100 mod_muc_light_db_backend:force_clear(HostType).
114
115 %%====================================================================
116 %% Administration API
117 %%====================================================================
118
119 -spec try_to_create_room(CreatorJid :: jid:jid(), RoomJID :: jid:jid(),
120 CreationCfg :: create_req_props()) ->
121 {ok, jid:jid(), create_req_props()} | validation_error()
122 | {error, bad_request | exists | max_occupants_reached}.
123 try_to_create_room(CreatorJid, RoomJID, #create{raw_config = RawConfig} = CreationCfg) ->
124 146 RoomUS = jid:to_lus(RoomJID),
125 146 HostType = mod_muc_light_utils:room_jid_to_host_type(RoomJID),
126 141 CfgRes = prepare_config(HostType, RawConfig),
127 141 AffRes = prepare_affs(HostType, CreatorJid, RoomUS, CreationCfg),
128 141 case {CfgRes, AffRes} of
129 {{ok, Config0}, {ok, FinalAffUsers}} ->
130 140 Version = mongoose_bin:gen_from_timestamp(),
131 140 case mod_muc_light_db_backend:create_room(
132 HostType, RoomUS, lists:sort(Config0), FinalAffUsers, Version) of
133 {ok, {FinalU, FinalS}} ->
134 135 {ok, jid:make_noprep(FinalU, FinalS, <<>>), CreationCfg#create{
135 aff_users = FinalAffUsers, version = Version}};
136 Other ->
137 5 Other
138 end;
139 {{error, _} = Error, _} ->
140
:-(
Error;
141 {_, {error, _} = Error} ->
142 1 Error
143 end.
144
145 -spec change_room_config(UserJid :: jid:jid(), RoomID :: jid:user(),
146 MUCLightDomain :: jid:server(),
147 ConfigReq :: config_req_props(),
148 Acc :: mongoose_acc:t()) ->
149 {ok, jid:jid(), config_req_props()}
150 | {error, validation_error() | bad_request | not_allowed | not_exists | item_not_found}.
151 change_room_config(UserJid, RoomID, MUCLightDomain, ConfigReq, Acc1) ->
152 11 RoomJID = jid:make(RoomID, MUCLightDomain, <<>>),
153 11 {Acc2, AffUsersRes} = get_room_affiliations_from_acc(Acc1, RoomJID),
154 11 case mod_muc_light_room:process_request(UserJid, RoomJID, {set, ConfigReq},
155 AffUsersRes, Acc2) of
156 {set, ConfigResp, _} ->
157 4 {ok, RoomJID, ConfigResp};
158 {error, _Reason} = E ->
159 7 E
160 end.
161
162 -spec delete_room(RoomUS :: jid:simple_bare_jid()) -> ok | {error, not_exists}.
163 delete_room({_, RoomS} = RoomUS) ->
164 3 HostType = mod_muc_light_utils:muc_host_to_host_type(RoomS),
165 2 mod_muc_light_db_backend:destroy_room(HostType, RoomUS).
166
167 %%====================================================================
168 %% gen_mod callbacks
169 %%====================================================================
170
171 -spec start(HostType :: host_type(), Opts :: list()) -> ok.
172 start(HostType, Opts) ->
173 59 Codec = host_type_to_codec(HostType),
174 59 mod_muc_light_db_backend:start(HostType, Opts),
175 59 mod_muc_light_codec_backend:start(HostType, [{backend, Codec}]),
176 59 ejabberd_hooks:add(hooks(HostType)),
177 %% Handler
178 59 SubdomainPattern = subdomain_pattern(HostType),
179 59 PacketHandler = mongoose_packet_handler:new(?MODULE),
180 59 mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler),
181 59 ok.
182
183 -spec stop(HostType :: host_type()) -> ok.
184 stop(HostType) ->
185 59 SubdomainPattern = subdomain_pattern(HostType),
186 59 mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern),
187 59 ejabberd_hooks:delete(hooks(HostType)),
188 59 mod_muc_light_codec_backend:stop(HostType),
189 59 mod_muc_light_db_backend:stop(HostType),
190 59 ok.
191
192 -spec deps(mongooseim:host_type(), gen_mod:module_opts()) -> gen_mod:deps_list().
193 deps(_HostType, Opts) ->
194 750 case gen_mod:get_opt(cache_affs, Opts, undefined) of
195 undefined ->
196 534 [];
197 CacheOpts ->
198 216 [{mod_muc_light_cache, CacheOpts, hard}]
199 end.
200
201 %% Init helpers
202 subdomain_pattern(HostType) ->
203 136 gen_mod:get_module_opt(HostType, ?MODULE, host, default_host()).
204
205 server_host_to_muc_host(HostType, ServerHost) ->
206 15 mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost).
207
208 host_type_to_codec(HostType) ->
209 177 case gen_mod:get_module_opt(HostType, ?MODULE, legacy_mode, ?DEFAULT_LEGACY_MODE) of
210 true ->
211 27 legacy;
212 false ->
213 150 modern
214 end.
215
216 %% Config callbacks
217 -spec config_spec() -> mongoose_config_spec:config_section().
218 config_spec() ->
219 146 #section{
220 items = #{<<"backend">> => #option{type = atom,
221 validate = {module, mod_muc_light_db}},
222 <<"cache_affs">> => mongoose_user_cache:config_spec(),
223 <<"host">> => #option{type = string,
224 validate = subdomain_template,
225 process = fun mongoose_subdomain_utils:make_subdomain_pattern/1},
226 <<"equal_occupants">> => #option{type = boolean},
227 <<"legacy_mode">> => #option{type = boolean},
228 <<"rooms_per_user">> => #option{type = int_or_infinity,
229 validate = positive},
230 <<"blocking">> => #option{type = boolean},
231 <<"all_can_configure">> => #option{type = boolean},
232 <<"all_can_invite">> => #option{type = boolean},
233 <<"max_occupants">> => #option{type = int_or_infinity,
234 validate = positive},
235 <<"rooms_per_page">> => #option{type = int_or_infinity,
236 validate = positive},
237 <<"rooms_in_rosters">> => #option{type = boolean},
238 <<"config_schema">> => #list{items = config_schema_spec(),
239 process = fun ?MODULE:process_config_schema/1}
240 }
241 }.
242
243 config_schema_spec() ->
244 146 #section{
245 items = #{<<"field">> => #option{type = binary,
246 validate = non_empty},
247 <<"string_value">> => #option{type = binary},
248 <<"integer_value">> => #option{type = integer},
249 <<"float_value">> => #option{type = float},
250 <<"internal_key">> => #option{type = atom,
251 validate = non_empty}
252 },
253 required = [<<"field">>]
254 }.
255
256 process_config_schema(Items) ->
257
:-(
lists:ukeysort(1, lists:map(fun process_config_schema_item/1, Items)).
258
259 process_config_schema_item(KVs) ->
260
:-(
{[[{field, FieldName}], InternalKeyOpts], ValueOpts} =
261 proplists:split(KVs, [field, internal_key]),
262
:-(
{Value, Type} = process_config_schema_value(ValueOpts),
263
:-(
InternalKey = proplists:get_value(internal_key, InternalKeyOpts, binary_to_atom(FieldName)),
264
:-(
{FieldName, Value, InternalKey, Type}.
265
266
:-(
process_config_schema_value([{string_value, Val}]) -> {Val, binary};
267
:-(
process_config_schema_value([{integer_value, Val}]) -> {Val, integer};
268
:-(
process_config_schema_value([{float_value, Val}]) -> {Val, float}.
269
270 hooks(HostType) ->
271 118 Codec = host_type_to_codec(HostType),
272 118 Roster = gen_mod:get_module_opt(HostType, ?MODULE, rooms_in_rosters, ?DEFAULT_ROOMS_IN_ROSTERS),
273 [{is_muc_room_owner, HostType, ?MODULE, is_muc_room_owner, 50},
274 {can_access_room, HostType, ?MODULE, can_access_room, 50},
275 {acc_room_affiliations, HostType, ?MODULE, acc_room_affiliations, 50},
276 {room_exists, HostType, ?MODULE, room_exists, 50},
277 {can_access_identity, HostType, ?MODULE, can_access_identity, 50},
278 %% Prevent sending service-unavailable on groupchat messages
279 {offline_groupchat_message_hook, HostType, ?MODULE, prevent_service_unavailable, 90},
280 {remove_user, HostType, ?MODULE, remove_user, 50},
281 {remove_domain, HostType, ?MODULE, remove_domain, 50},
282 118 {disco_local_items, HostType, ?MODULE, disco_local_items, 50}] ++
283 case Codec of
284 legacy ->
285 18 [{privacy_iq_get, HostType, ?MODULE, process_iq_get, 1},
286 {privacy_iq_set, HostType, ?MODULE, process_iq_set, 1}];
287 _ ->
288 100 []
289 end ++
290 case Roster of
291 60 false -> [];
292 58 true -> [{roster_get, HostType, ?MODULE, add_rooms_to_roster, 50}]
293 end.
294
295 %%====================================================================
296 %% Routing
297 %%====================================================================
298
299 -spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(),
300 El :: exml:element(), Extra :: map()) -> mongoose_acc:t().
301 process_packet(Acc, From, To, El, _Extra) ->
302 458 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
303 458 DecodedPacket = mod_muc_light_codec_backend:decode(From, To, El, Acc),
304 458 process_decoded_packet(HostType, From, To, Acc, El, DecodedPacket).
305
306 -spec process_decoded_packet(
307 HostType :: host_type(),
308 From :: jid:jid(), To :: jid:jid(),
309 Acc :: mongoose_acc:t(),
310 OrigPacket :: exml:element(),
311 DecodedPacket :: mod_muc_light_codec_backend:decode_result()) ->
312 mongoose_acc:t().
313 process_decoded_packet(HostType, From, To, Acc, El,
314 {ok, {set, #create{} = Create}}) ->
315 74 case not mod_muc_light_utils:room_limit_reached(From, HostType) of
316 73 true -> create_room(Acc, From, To, Create, El);
317 1 false -> make_err(From, To, El, Acc, {error, room_limit_reached})
318 end;
319 process_decoded_packet(_HostType, From, To, Acc, _El,
320 {ok, {get, #disco_info{} = DI}}) ->
321 8 handle_disco_info_get(From, To, DI, Acc);
322 process_decoded_packet(HostType, From, To, Acc, El,
323 {ok, {get, #disco_items{} = DI}}) ->
324 21 handle_disco_items_get(HostType, Acc, From, To, DI, El);
325 process_decoded_packet(HostType, From, To, Acc, El,
326 {ok, {_, #blocking{}} = Blocking}) ->
327 14 case gen_mod:get_module_opt(HostType, ?MODULE, blocking, ?DEFAULT_BLOCKING) of
328 true ->
329 12 case handle_blocking(Acc, From, To, Blocking) of
330
:-(
{error, _} = Res -> make_err(From, To, El, Acc, Res);
331 12 _ -> Acc
332 end;
333 2 false -> make_err(From, To, El, Acc, {error, blocking_disabled})
334 end;
335 process_decoded_packet(_HostType, From, To, Acc, El,
336 {ok, #iq{} = IQ}) ->
337 29 case mod_muc_iq:process_iq(To#jid.lserver, From, To, Acc, IQ) of
338 {Acc1, error} ->
339
:-(
E = {error, {feature_not_implemented, <<"mod_muc_iq returns error">>}},
340
:-(
make_err(From, To, El, Acc1, E);
341 29 _ -> Acc
342 end;
343 process_decoded_packet(HostType, From, To, Acc, El,
344 {ok, RequestToRoom})
345 when To#jid.luser =/= <<>> ->
346 308 case mongoose_hooks:room_exists(HostType, To) of
347 308 true -> mod_muc_light_room:handle_request(From, To, El, RequestToRoom, Acc);
348
:-(
false -> make_err(From, To, El, Acc, {error, item_not_found})
349 end;
350 process_decoded_packet(_HostType, From, To, Acc, El,
351 {error, _} = Err) ->
352 1 make_err(From, To, El, Acc, Err);
353 process_decoded_packet(_HostType, _From, _To, Acc, _El, ignore) ->
354 3 Acc;
355 process_decoded_packet(_HostType, From, To, Acc, El, InvalidReq) ->
356
:-(
?LOG_WARNING(#{what => muc_light_invalid_request,
357
:-(
acc => Acc, reason => InvalidReq}),
358
:-(
make_err(From, To, El, Acc, {error, bad_request}).
359
360 make_err(From, To, El, Acc, Reason) ->
361 4 mod_muc_light_codec_backend:encode_error(Reason, From, To, El, Acc).
362
363 %%====================================================================
364 %% Hook handlers
365 %%====================================================================
366
367 -spec prevent_service_unavailable(Acc :: map(), From ::jid:jid(), To ::jid:jid(),
368 Packet :: exml:element()) -> map() | {stop, map()}.
369 prevent_service_unavailable(Acc, _From, _To, Packet) ->
370 133 case xml:get_tag_attr_s(<<"type">>, Packet) of
371 133 <<"groupchat">> -> {stop, Acc};
372
:-(
_Type -> Acc
373 end.
374
375 -spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc().
376 disco_local_items(Acc = #{host_type := HostType,
377 to_jid := #jid{lserver = ServerHost},
378 node := <<>>}) ->
379 2 XMLNS = case legacy_mode(HostType) of
380 1 true -> ?NS_MUC;
381 1 false -> ?NS_MUC_LIGHT
382 end,
383 2 MUCHost = server_host_to_muc_host(HostType, ServerHost),
384 2 Items = [#{jid => MUCHost, node => XMLNS}],
385 2 mongoose_disco:add_items(Items, Acc);
386 disco_local_items(Acc) ->
387
:-(
Acc.
388
389 legacy_mode(HostType) ->
390 2 gen_mod:get_module_opt(HostType, ?MODULE, legacy_mode, ?DEFAULT_LEGACY_MODE).
391
392 -spec remove_user(Acc :: mongoose_acc:t(), User :: binary(), Server :: binary()) ->
393 mongoose_acc:t().
394 remove_user(Acc, User, Server) ->
395 200 HostType = mongoose_acc:host_type(Acc),
396 200 UserJid = jid:make(User, Server, <<>>),
397 200 Version = mongoose_bin:gen_from_timestamp(),
398 200 case mod_muc_light_db_backend:remove_user(HostType, jid:to_lus(UserJid), Version) of
399 {error, Reason} ->
400
:-(
?LOG_ERROR(#{what => muc_remove_user_failed,
401
:-(
reason => Reason, acc => Acc}),
402
:-(
Acc;
403 AffectedRooms ->
404 200 bcast_removed_user(Acc, UserJid, AffectedRooms, Version),
405 200 maybe_forget_rooms(Acc, AffectedRooms, Version),
406 200 Acc
407 end.
408
409 -spec remove_domain(mongoose_hooks:simple_acc(),
410 mongooseim:host_type(), jid:lserver()) ->
411 mongoose_hooks:simple_acc().
412 remove_domain(Acc, HostType, Domain) ->
413 4 MUCHost = server_host_to_muc_host(HostType, Domain),
414 4 mod_muc_light_db_backend:remove_domain(HostType, MUCHost, Domain),
415 4 Acc.
416
417 -spec add_rooms_to_roster(Acc :: mongoose_acc:t(), UserJID :: jid:jid()) -> mongoose_acc:t().
418 add_rooms_to_roster(Acc, UserJID) ->
419 16 Items = mongoose_acc:get(roster, items, [], Acc),
420 16 HostType = mongoose_acc:host_type(Acc),
421 16 RoomList = mod_muc_light_db_backend:get_user_rooms(HostType, jid:to_lus(UserJID), undefined),
422 16 Info = get_rooms_info(HostType, lists:sort(RoomList)),
423 16 NewItems = [make_roster_item(Item) || Item <- Info] ++ Items,
424 16 mongoose_acc:set(roster, items, NewItems, Acc).
425
426 make_roster_item({{RoomU, RoomS}, RoomName, RoomVersion}) ->
427 5 JID = jid:make_noprep(RoomU, RoomS, <<>>),
428 5 VerEl = #xmlel{ name = <<"version">>,
429 children = [#xmlcdata{ content = RoomVersion }] },
430 5 #roster{usj = {RoomU, RoomS, jid:to_lower(JID)},
431 us = {RoomU, RoomS},
432 jid = jid:to_lower(JID),
433 name = RoomName,
434 subscription = to,
435 groups = [?NS_MUC_LIGHT],
436 xs = [VerEl] }.
437
438 -spec process_iq_get(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(),
439 IQ :: jlib:iq(), ActiveList :: binary()) ->
440 {stop, mongoose_acc:t()} | mongoose_acc:t().
441 process_iq_get(Acc, #jid{lserver = FromS} = From, To, #iq{} = IQ, _ActiveList) ->
442 4 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
443 4 MUCHost = server_host_to_muc_host(HostType, FromS),
444 4 case {mod_muc_light_codec_backend:decode(From, To, IQ, Acc),
445 gen_mod:get_module_opt(HostType, ?MODULE, blocking, ?DEFAULT_BLOCKING)} of
446 {{ok, {get, #blocking{} = Blocking}}, true} ->
447 3 Items = mod_muc_light_db_backend:get_blocking(HostType, jid:to_lus(From), MUCHost),
448 3 mod_muc_light_codec_backend:encode(
449 {get, Blocking#blocking{ items = Items }}, From, To,
450 3 fun(_, _, Packet) -> put(encode_res, Packet) end,
451 Acc),
452 3 #xmlel{ children = ResponseChildren } = erase(encode_res),
453 3 Result = {result, ResponseChildren},
454 3 {stop, mongoose_acc:set(hook, result, Result, Acc)};
455 {{ok, {get, #blocking{}}}, false} ->
456 1 Result = {error, mongoose_xmpp_errors:bad_request()},
457 1 {stop, mongoose_acc:set(hook, result, Result, Acc)};
458 _ ->
459
:-(
Result = {error, mongoose_xmpp_errors:bad_request()},
460
:-(
mongoose_acc:set(hook, result, Result, Acc)
461 end.
462
463 %% Blocking is done using your local domain
464 -spec process_iq_set(Acc :: mongoose_acc:t(), From :: jid:jid(),
465 To :: jid:jid(), IQ :: jlib:iq()) ->
466 {stop, mongoose_acc:t()} | mongoose_acc:t().
467 process_iq_set(Acc, #jid{ lserver = FromS } = From, To, #iq{} = IQ) ->
468 5 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
469 5 MUCHost = server_host_to_muc_host(HostType, FromS),
470 5 case {mod_muc_light_codec_backend:decode(From, To, IQ, Acc),
471 gen_mod:get_module_opt(HostType, ?MODULE, blocking, ?DEFAULT_BLOCKING)} of
472 {{ok, {set, #blocking{ items = Items }} = Blocking}, true} ->
473 4 RouteFun = fun(_, _, Packet) -> put(encode_res, Packet) end,
474 4 ConditionFun = fun({_, _, {WhoU, WhoS}}) -> WhoU =:= <<>> orelse WhoS =:= <<>> end,
475 4 case lists:any(ConditionFun, Items) of
476 true ->
477
:-(
{stop, mongoose_acc:set(hook, result,
478 {error, mongoose_xmpp_errors:bad_request()}, Acc)};
479 false ->
480 4 ok = mod_muc_light_db_backend:set_blocking(HostType, jid:to_lus(From), MUCHost, Items),
481 4 mod_muc_light_codec_backend:encode(Blocking, From, To, RouteFun, Acc),
482 4 #xmlel{ children = ResponseChildren } = erase(encode_res),
483 4 {stop, mongoose_acc:set(hook, result, {result, ResponseChildren}, Acc)}
484 end;
485 {{ok, {set, #blocking{}}}, false} ->
486 1 {stop, mongoose_acc:set(hook, result,
487 {error, mongoose_xmpp_errors:bad_request()}, Acc)};
488 _ ->
489
:-(
mongoose_acc:set(hook, result, {error, mongoose_xmpp_errors:bad_request()}, Acc)
490 end.
491
492 -spec is_muc_room_owner(boolean(), Acc :: mongoose_acc:t(),
493 Room :: jid:jid(), User :: jid:jid()) -> boolean().
494 is_muc_room_owner(true, _Acc, _Room, _User) ->
495
:-(
true;
496 is_muc_room_owner(_, Acc, Room, User) ->
497
:-(
owner == get_affiliation(Acc, Room, User).
498
499 -spec can_access_room(boolean(), Acc :: mongoose_acc:t(),
500 Room :: jid:jid(), User :: jid:jid()) ->
501 boolean().
502 can_access_room(true, _Acc, _Room, _User) ->
503
:-(
true;
504 can_access_room(_, Acc, Room, User) ->
505 34 none =/= get_affiliation(Acc, Room, User).
506
507 -spec acc_room_affiliations(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
508 acc_room_affiliations(Acc1, Room) ->
509 353 case mongoose_acc:get(?MODULE, affiliations, {error, not_exists}, Acc1) of
510 {error, _} ->
511 325 HostType = mongoose_acc:host_type(Acc1),
512 325 case mod_muc_light_db_backend:get_aff_users(HostType, jid:to_lus(Room)) of
513 {error, not_exists} ->
514 2 Acc1;
515 {ok, _Affs, _Version} = Res ->
516 323 mongoose_acc:set(?MODULE, affiliations, Res, Acc1)
517 end;
518 _Affs ->
519 28 Acc1
520 end.
521
522 -spec room_exists(boolean(), mongooseim:host_type(), jid:jid()) -> boolean().
523 room_exists(_, HostType, RoomJid) ->
524 280 mod_muc_light_db_backend:room_exists(HostType, jid:to_lus(RoomJid)).
525
526 -spec get_room_affiliations_from_acc(mongoose_acc:t()) ->
527 {ok, aff_users(), binary()} | {error, not_exists}.
528 get_room_affiliations_from_acc(Acc) ->
529 308 mongoose_acc:get(?MODULE, affiliations, {error, not_exists}, Acc).
530
531 -spec get_room_affiliations_from_acc(mongoose_acc:t(), jid:jid()) ->
532 {mongoose_acc:t(), {ok, aff_users(), binary()} | {error, not_exists}}.
533 get_room_affiliations_from_acc(Acc1, RoomJid) ->
534 45 Acc2 = mongoose_hooks:acc_room_affiliations(Acc1, RoomJid),
535 45 {Acc2, mongoose_acc:get(?MODULE, affiliations, {error, not_exists}, Acc2)}.
536
537 -spec can_access_identity(Acc :: boolean(), HostType :: mongooseim:host_type(),
538 Room :: jid:jid(), User :: jid:jid()) ->
539 boolean().
540 can_access_identity(true, _HostType, _Room, _User) ->
541
:-(
true;
542 can_access_identity(_Acc, _HostType, _Room, _User) ->
543 %% User JIDs are explicit in MUC Light but this hook is about appending
544 %% 0045 MUC element with user identity and we don't want it
545 29 false.
546
547 %%====================================================================
548 %% Internal functions
549 %%====================================================================
550
551 prepare_config(HostType, RawConfig) ->
552 141 Schema = config_schema_for_host_type(HostType),
553 141 mod_muc_light_room_config:from_binary_kv(RawConfig, Schema).
554
555 prepare_affs(HostType, CreatorJid, RoomUS, #create{aff_users = AffUsers}) ->
556 141 CreatorUS = jid:to_lus(CreatorJid),
557 141 InitialAffUsers = mod_muc_light_utils:filter_out_prevented(HostType,
558 CreatorUS, RoomUS, AffUsers),
559 141 Res = process_create_aff_users_if_valid(HostType, CreatorUS, InitialAffUsers),
560 141 MaxOccupants = max_occupants(HostType),
561 141 case Res of
562 {ok, FinalAffUsers} when length(FinalAffUsers) > MaxOccupants ->
563 1 {error, max_occupants_reached};
564 _ ->
565 140 Res
566 end.
567
568 max_occupants(HostType) ->
569 141 gen_mod:get_module_opt(HostType, ?MODULE, max_occupants, ?DEFAULT_MAX_OCCUPANTS).
570
571 get_affiliation(Acc, Room, User) ->
572 34 case get_room_affiliations_from_acc(Acc, Room) of
573 {_, {ok, AffUsers, _}} ->
574 34 case lists:keyfind(jid:to_lus(User), 1, AffUsers) of
575 34 {_, Aff} -> Aff;
576
:-(
_ -> none
577 end;
578 _ ->
579
:-(
none
580 end.
581
582 -spec create_room(mongoose_acc:t(),
583 jid:jid(),
584 jid:jid(),
585 create_req_props(),
586 exml:element()) ->
587 mongoose_acc:t().
588 create_room(Acc, From, To, Create0, OrigPacket) ->
589 73 case try_to_create_room(From, To, Create0) of
590 {ok, FinalRoomJid, Details} ->
591 70 mod_muc_light_codec_backend:encode({set, Details, To#jid.luser == <<>>}, From,
592 FinalRoomJid, make_handler_fun(Acc), Acc);
593 {error, exists} ->
594 2 mod_muc_light_codec_backend:encode_error({error, {conflict, <<"Room already exists">>}},
595 From, To, OrigPacket,
596 Acc);
597 {error, bad_request} ->
598
:-(
mod_muc_light_codec_backend:encode_error({error, bad_request}, From, To, OrigPacket,
599 Acc);
600 {error, {_, _} = Error} ->
601
:-(
ErrorText = io_lib:format("~s:~p", tuple_to_list(Error)),
602
:-(
mod_muc_light_codec_backend:encode_error(
603 {error, bad_request, ErrorText}, From, To, OrigPacket, Acc);
604 {error, Error} ->
605 1 ErrorText = io_lib:format("~p", [Error]),
606 1 mod_muc_light_codec_backend:encode_error(
607 {error, bad_request, ErrorText}, From, To, OrigPacket, Acc)
608 end.
609
610 -spec process_create_aff_users_if_valid(HostType :: host_type(),
611 Creator :: jid:simple_bare_jid(),
612 AffUsers :: aff_users()) ->
613 {ok, aff_users()} | {error, bad_request}.
614 process_create_aff_users_if_valid(HostType, Creator, AffUsers) ->
615 141 case lists:any(fun ({User, _}) when User =:= Creator -> true;
616 80 ({_, Aff}) -> Aff =:= none end, AffUsers) of
617 false ->
618 141 process_create_aff_users(Creator, AffUsers, equal_occupants(HostType));
619 true ->
620
:-(
{error, bad_request}
621 end.
622
623 equal_occupants(HostType) ->
624 141 gen_mod:get_module_opt(HostType, ?MODULE,
625 equal_occupants, ?DEFAULT_EQUAL_OCCUPANTS).
626
627 -spec process_create_aff_users(Creator :: jid:simple_bare_jid(), AffUsers :: aff_users(),
628 EqualOccupants :: boolean()) ->
629 {ok, aff_users()} | {error, bad_request}.
630 process_create_aff_users(Creator, AffUsers, EqualOccupants) ->
631 141 case mod_muc_light_utils:change_aff_users([{Creator, creator_aff(EqualOccupants)}], AffUsers) of
632 141 {ok, FinalAffUsers, _ChangedAffUsers, _JoiningUsers, _LeavingUsers} -> {ok, FinalAffUsers};
633
:-(
Error -> Error
634 end.
635
636 -spec creator_aff(EqualOccupants :: boolean()) -> owner | member.
637 2 creator_aff(true) -> member;
638 139 creator_aff(false) -> owner.
639
640 -spec handle_disco_info_get(From ::jid:jid(),
641 To :: jid:jid(),
642 DiscoInfo :: disco_info_req_props(),
643 Acc :: mongoose_acc:t()) ->
644 mongoose_acc:t().
645 handle_disco_info_get(From, To, DiscoInfo, Acc) ->
646 8 mod_muc_light_codec_backend:encode({get, DiscoInfo}, From, To,
647 make_handler_fun(Acc), Acc).
648
649 -spec handle_disco_items_get(HostType :: host_type(),
650 Acc :: mongoose_acc:t(),
651 From ::jid:jid(), To ::jid:jid(),
652 DiscoItems :: disco_items_req_props(),
653 OrigPacket :: exml:element()) ->
654 mongoose_acc:t().
655 handle_disco_items_get(HostType, Acc, From, To, DiscoItems0, OrigPacket) ->
656 21 case catch mod_muc_light_db_backend:get_user_rooms(HostType, jid:to_lus(From), To#jid.lserver) of
657 {error, Error} ->
658
:-(
?LOG_ERROR(#{what => muc_get_user_rooms_failed,
659 text => <<"Couldn't get room list for user">>,
660
:-(
from_jid => From, reason => Error}),
661
:-(
mod_muc_light_codec_backend:encode_error(
662 {error, internal_server_error}, From, To, OrigPacket, Acc);
663 Rooms ->
664 21 RoomsInfo = get_rooms_info(HostType, lists:sort(Rooms)),
665 21 RouteFun = make_handler_fun(Acc),
666 21 RoomsPerPage = gen_mod:get_module_opt(HostType, ?MODULE, rooms_per_page, ?DEFAULT_ROOMS_PER_PAGE),
667 21 case apply_rsm(RoomsInfo, length(RoomsInfo),
668 page_service_limit(DiscoItems0#disco_items.rsm, RoomsPerPage)) of
669 {ok, RoomsInfoSlice, RSMOut} ->
670 19 DiscoItems = DiscoItems0#disco_items{ rooms = RoomsInfoSlice, rsm = RSMOut },
671 19 mod_muc_light_codec_backend:encode({get, DiscoItems},
672 From, To,
673 RouteFun, Acc);
674 {error, item_not_found} ->
675 2 mod_muc_light_codec_backend:encode_error({error, item_not_found},
676 From, To, OrigPacket, Acc)
677 end
678 end.
679
680 -spec get_rooms_info(HostType :: mongooseim:host_type(),
681 Rooms :: [jid:simple_bare_jid()]) -> [disco_room_info()].
682 get_rooms_info(_HostType, []) ->
683 37 [];
684 get_rooms_info(HostType, [{RoomU, _} = RoomUS | RRooms]) ->
685 27 {ok, Config, Version} = mod_muc_light_db_backend:get_config(HostType, RoomUS),
686 27 RoomName = case lists:keyfind(roomname, 1, Config) of
687 2 false -> RoomU;
688 25 {_, RoomName0} -> RoomName0
689 end,
690 27 [{RoomUS, RoomName, Version} | get_rooms_info(HostType, RRooms)].
691
692 -spec apply_rsm(RoomsInfo :: [disco_room_info()], RoomsInfoLen :: non_neg_integer(),
693 RSMIn :: jlib:rsm_in()) ->
694 {ok, RoomsInfoSlice :: [disco_room_info()], RSMOut :: jlib:rsm_out()} | {error, item_not_found}.
695 apply_rsm(RoomsInfo, _RoomsInfoLen, none) ->
696 2 {ok, RoomsInfo, none};
697 apply_rsm(_RoomsInfo, _RoomsInfoLen, #rsm_in{ max = Max }) when Max < 0 ->
698
:-(
{error, item_not_found};
699 apply_rsm(_RoomsInfo, RoomsInfoLen, #rsm_in{ max = 0 }) ->
700
:-(
{ok, [], #rsm_out{ count = RoomsInfoLen }};
701 apply_rsm([], 0, #rsm_in{ direction = undefined, id = undefined, index = undefined } = _RSMIn) ->
702 4 {ok, [], none};
703 apply_rsm(RoomsInfo, RoomsInfoLen, #rsm_in{ direction = undefined, id = undefined,
704 index = undefined } = RSMIn) ->
705 11 apply_rsm(RoomsInfo, RoomsInfoLen, RSMIn#rsm_in{ index = 0 });
706 apply_rsm(RoomsInfo, RoomsInfoLen, #rsm_in{ direction = before, id = <<>>, max = Max }) ->
707 2 apply_rsm(RoomsInfo, RoomsInfoLen, #rsm_in{ max = Max, index = RoomsInfoLen - Max });
708 apply_rsm(RoomsInfo, RoomsInfoLen, #rsm_in{ index = undefined, direction = Direction,
709 id = RoomUSBin, max = Max }) ->
710 2 case find_room_pos(RoomUSBin, RoomsInfo) of
711 {error, item_not_found} ->
712 2 {error, item_not_found};
713 RoomPos ->
714
:-(
FirstPos = case {Direction, RoomPos - Max} of
715
:-(
{aft, _} -> RoomPos + 1;
716
:-(
{before, TooLow} when TooLow < 1 -> 1;
717
:-(
{before, FirstPos0} -> FirstPos0
718 end,
719
:-(
[{FirstRoomUS, _, _} | _] = RoomsInfoSlice = lists:sublist(RoomsInfo, FirstPos, Max),
720
:-(
{LastRoomUS, _, _} = lists:last(RoomsInfoSlice),
721
:-(
{ok, RoomsInfoSlice, #rsm_out{ count = RoomsInfoLen,
722 index = FirstPos - 1,
723 first = jid:to_binary(FirstRoomUS),
724 last = jid:to_binary(LastRoomUS) }}
725 end;
726 apply_rsm(RoomsInfo, RoomsInfoLen, #rsm_in{ max = Max, index = Index}) when Index < RoomsInfoLen ->
727 13 [{FirstRoomUS, _, _} | _] = RoomsInfoSlice = lists:sublist(RoomsInfo, Index + 1, Max),
728 13 {LastRoomUS, _, _} = lists:last(RoomsInfoSlice),
729 13 {ok, RoomsInfoSlice, #rsm_out{ count = RoomsInfoLen,
730 index = Index,
731 first = jid:to_binary(FirstRoomUS),
732 last = jid:to_binary(LastRoomUS) }};
733 apply_rsm(_RoomsInfo, _RoomsInfoLen, _RSMIn) ->
734
:-(
{error, item_not_found}.
735
736 -spec page_service_limit(RSMIn :: jlib:rsm_in() | undefined, ServiceMax :: integer()) ->
737 jlib:rsm_in() | none.
738 2 page_service_limit(none, infinity) -> none;
739 15 page_service_limit(none, ServiceMax) -> #rsm_in{ max = ServiceMax };
740
:-(
page_service_limit(#rsm_in{ max = Max } = RSMIn, ServiceMax) when Max =< ServiceMax -> RSMIn;
741 4 page_service_limit(RSMIn, ServiceMax) -> RSMIn#rsm_in{ max = ServiceMax }.
742
743 -spec find_room_pos(RoomUSBin :: binary(), RoomsInfo :: [disco_room_info()]) ->
744 pos_integer() | {error, item_not_found}.
745 find_room_pos(RoomUSBin, RoomsInfo) ->
746 2 case jid:from_binary(RoomUSBin) of
747
:-(
error -> {error, item_not_found};
748 2 #jid{ luser = RoomU, lserver = RoomS } -> find_room_pos({RoomU, RoomS}, RoomsInfo, 1)
749 end.
750
751 -spec find_room_pos(RoomUS :: jid:simple_bare_jid(), RoomsInfo :: [disco_room_info()],
752 Pos :: pos_integer()) -> pos_integer() | {error, item_not_found}.
753
:-(
find_room_pos(RoomUS, [{RoomUS, _, _} | _], Pos) -> Pos;
754 4 find_room_pos(RoomUS, [_ | RRooms], Pos) -> find_room_pos(RoomUS, RRooms, Pos + 1);
755 2 find_room_pos(_, [], _) -> {error, item_not_found}.
756
757 -spec handle_blocking(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(),
758 BlockingReq :: {get | set, blocking_req_props()}) ->
759 {error, bad_request} | ok.
760 handle_blocking(Acc, From, To, {get, #blocking{} = Blocking}) ->
761 3 HostType = mongoose_acc:host_type(Acc),
762 3 BlockingItems = mod_muc_light_db_backend:get_blocking(HostType, jid:to_lus(From), To#jid.lserver),
763 3 mod_muc_light_codec_backend:encode({get, Blocking#blocking{ items = BlockingItems }},
764 From, To, make_handler_fun(Acc), Acc);
765 handle_blocking(Acc, From, To, {set, #blocking{ items = Items }} = BlockingReq) ->
766 9 case lists:any(fun({_, _, {WhoU, WhoS}}) -> WhoU =:= <<>> orelse WhoS =:= <<>> end, Items) of
767 true ->
768
:-(
{error, bad_request};
769 false ->
770 9 HostType = mongoose_acc:host_type(Acc),
771 9 ok = mod_muc_light_db_backend:set_blocking(HostType, jid:to_lus(From), To#jid.lserver, Items),
772 9 mod_muc_light_codec_backend:encode(
773 BlockingReq, From, To, make_handler_fun(Acc), Acc),
774 9 ok
775 end.
776
777 -spec bcast_removed_user(Acc :: mongoose_acc:t(), UserJid :: jid:jid(),
778 AffectedRooms :: mod_muc_light_db_backend:remove_user_return(),
779 Version :: binary()) -> ok.
780 bcast_removed_user(Acc, UserJid, AffectedRooms, Version) ->
781 200 bcast_removed_user(Acc, UserJid, AffectedRooms,
782 Version, mongoose_bin:gen_from_timestamp()).
783
784 -spec bcast_removed_user(Acc :: mongoose_acc:t(), UserJID :: jid:jid(),
785 AffectedRooms :: mod_muc_light_db_backend:remove_user_return(),
786 Version :: binary(),
787 PacketID :: binary()) -> ok.
788 bcast_removed_user(_Acc, _UserJID, [], _Version, _ID) ->
789 200 ok;
790 bcast_removed_user(Acc, UserJID,
791 [{{RoomU, RoomS}, {ok, OldAffUsers, NewAffUsers, AffUsersChanged, PrevVersion}}
792 | RAffected], Version, ID) ->
793 98 Affiliations = #affiliations{
794 id = ID,
795 prev_version = PrevVersion,
796 version = Version,
797 aff_users = AffUsersChanged
798 },
799 98 Cmd = {set, Affiliations, OldAffUsers, NewAffUsers},
800 98 RoomJid = jid:make_noprep(RoomU, RoomS, <<>>),
801 98 mod_muc_light_codec_backend:encode(Cmd, UserJID, RoomJid, make_handler_fun(Acc), Acc),
802 98 bcast_removed_user(Acc, UserJID, RAffected, Version, ID);
803 bcast_removed_user(Acc, UserJID, [{{RoomU, RoomS} = _RoomUS, Error} | RAffected], Version, ID) ->
804
:-(
?LOG_ERROR(#{what => muc_remove_user_failed,
805 user_jid => jid:to_binary(UserJID), room => RoomU, sub_host => RoomS,
806
:-(
reason => Error}),
807
:-(
bcast_removed_user(Acc, UserJID, RAffected, Version, ID).
808
809 -spec maybe_forget_rooms(Acc :: mongoose_acc:t(),
810 AffectedRooms :: mod_muc_light_db_backend:remove_user_return(),
811 Version :: binary()) -> ok.
812 maybe_forget_rooms(_Acc, [], _) ->
813 200 ok;
814 maybe_forget_rooms(Acc, [{RoomUS, {ok, _, NewAffUsers, _, _}} | RAffectedRooms], Version) ->
815 98 mod_muc_light_room:maybe_forget(Acc, RoomUS, NewAffUsers, Version),
816 98 maybe_forget_rooms(Acc, RAffectedRooms, Version).
817
818 make_handler_fun(Acc) ->
819 209 fun(From, To, Packet) -> ejabberd_router:route(From, To, Acc, Packet) end.
820
821 config_metrics(Host) ->
822
:-(
OptsToReport = [{backend, mnesia}], %list of tuples {option, defualt_value}
823
:-(
mongoose_module_metrics:opts_for_module(Host, ?MODULE, OptsToReport).
Line Hits Source