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