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