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