From 1e060e4ce88d96e56c4ed81ebddacb5a368f959f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Thu, 10 Nov 2022 13:03:56 +0100 Subject: [PATCH 01/44] Refactored hook handlers in mod_muc --- src/mod_muc.erl | 141 ++++++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 63 deletions(-) diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 4174a1cc70..956b7357a3 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -61,12 +61,12 @@ -export([process_packet/5]). %% Hooks handlers --export([is_muc_room_owner/4, - can_access_room/4, +-export([is_muc_room_owner/3, + can_access_room/3, remove_domain/3, - acc_room_affiliations/2, - can_access_identity/4, - disco_local_items/1]). + acc_room_affiliations/3, + can_access_identity/3, + disco_local_items/3]). %% Stats -export([online_rooms_number/0]). @@ -75,9 +75,9 @@ -export([config_metrics/1]). -ignore_xref([ - broadcast_service_message/2, can_access_identity/4, can_access_room/4, acc_room_affiliations/2, - create_instant_room/6, disco_local_items/1, hibernated_rooms_number/0, is_muc_room_owner/4, - remove_domain/3, online_rooms_number/0, register_room/4, restore_room/3, start_link/2]). + broadcast_service_message/2, create_instant_room/6, hibernated_rooms_number/0, + online_rooms_number/0, register_room/4, restore_room/3, start_link/2 +]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -478,7 +478,7 @@ init({HostType, Opts}) -> hibernated_room_check_interval = CheckInterval, hibernated_room_timeout = HibernatedTimeout}, %% Hooks - ejabberd_hooks:add(hooks(HostType)), + gen_hook:add_handlers(hooks(HostType)), %% Handler PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}), case SubdomainPattern of @@ -509,7 +509,7 @@ set_persistent_rooms_timer(#muc_state{hibernated_room_check_interval = Timeout}) timer:send_after(Timeout, stop_hibernated_persistent_rooms). handle_call(stop, _From, State) -> - ejabberd_hooks:delete(hooks(State#muc_state.host_type)), + gen_hook:delete_handlers(hooks(State#muc_state.host_type)), {stop, normal, ok, State}; handle_call({create_instant, ServerHost, MucHost, Room, From, Nick, Opts}, _From, @@ -1259,56 +1259,81 @@ clean_table_from_bad_node(Node, HostType) -> %% Hooks handlers %%==================================================================== --spec is_muc_room_owner(Acc :: boolean(), Acc :: mongoose_acc:t(), - Room :: jid:jid(), User :: jid:jid()) -> boolean(). -is_muc_room_owner(true, _Acc, _Room, _User) -> - true; -is_muc_room_owner(_, _Acc, Room, User) -> - mod_muc_room:is_room_owner(Room, User) =:= {ok, true}. - --spec can_access_room(Acc :: boolean(), Acc :: mongoose_acc:t(), - Room :: jid:jid(), User :: jid:jid()) -> - boolean(). -can_access_room(true, _Acc, _Room, _User) -> - true; -can_access_room(_, _Acc, Room, User) -> - case mod_muc_room:can_access_room(Room, User) of +-spec is_muc_room_owner(Acc, Params, Extra) -> {ok, Acc} when + Acc :: boolean(), + Params :: #{room := jid:jid(), user := jid:jid()}, + Extra :: gen_hook:extra(). +is_muc_room_owner(true, _, _) -> + {ok, true}; +is_muc_room_owner(_, #{room := Room, user := User}, _) -> + Result = mod_muc_room:is_room_owner(Room, User) =:= {ok, true}, + {ok, Result}. + +-spec can_access_room(Acc, Params, Extra) -> {ok, Acc} when + Acc :: boolean(), + Params :: #{room := jid:jid(), user := jid:jid()}, + Extra :: gen_hook:extra(). +can_access_room(true, _, _) -> + {ok, true}; +can_access_room(_, #{room := Room, user := User}, _) -> + Result = case mod_muc_room:can_access_room(Room, User) of {error, _} -> false; {ok, CanAccess} -> CanAccess - end. + end, + {ok, Result}. --spec remove_domain(mongoose_hooks:simple_acc(), - mongooseim:host_type(), jid:lserver()) -> - mongoose_hooks:simple_acc(). -remove_domain(Acc, HostType, Domain) -> +-spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_hooks:simple_acc(), + Params :: #{domain := jid:lserver()}, + Extra :: gen_hook:extra(). + remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> MUCHost = server_host_to_muc_host(HostType, Domain), mod_muc_backend:remove_domain(HostType, MUCHost, Domain), - Acc. - --spec acc_room_affiliations(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t(). -acc_room_affiliations(Acc1, Room) -> - case mongoose_acc:get(?MODULE, {affiliations, Room}, {error, not_found}, Acc1) of + {ok, Acc}. + +-spec acc_room_affiliations(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{room := jid:jid()}, + Extra :: gen_hook:extra(). +acc_room_affiliations(Acc, #{room := Room}, _) -> + NewAcc = case mongoose_acc:get(?MODULE, {affiliations, Room}, {error, not_found}, Acc) of {error, _} -> case mod_muc_room:get_room_users(Room) of {error, not_found} -> - Acc1; + Acc; {ok, _Affs} = Res -> - mongoose_acc:set(?MODULE, {affiliations, Room}, Res, Acc1) + mongoose_acc:set(?MODULE, {affiliations, Room}, Res, Acc) end; _Affs -> - Acc1 - end. - --spec can_access_identity(Acc :: boolean(), HostType :: mongooseim:host_type(), - Room :: jid:jid(), User :: jid:jid()) -> - boolean(). -can_access_identity(true, _HostType, _Room, _User) -> - true; -can_access_identity(_, _HostType, Room, User) -> - case mod_muc_room:can_access_identity(Room, User) of + Acc + end, + {ok, NewAcc}. + +-spec can_access_identity(Acc, Params, Extra) -> {ok, Acc} when + Acc :: boolean(), + Params :: #{room := jid:jid(), user := jid:jid()}, + Extra :: gen_hook:extra(). +can_access_identity(true, _, _) -> + {ok, true}; +can_access_identity(_, #{room := Room, user := User}, _) -> + Result = case mod_muc_room:can_access_identity(Room, User) of {error, _} -> false; {ok, CanAccess} -> CanAccess - end. + end, + {ok, Result}. + +-spec disco_local_items(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_disco:item_acc(), + Params :: map(), + Extra :: gen_hook:extra(). +disco_local_items(Acc = #{host_type := HostType, + to_jid := #jid{lserver = ServerHost}, + node := <<>>}, _, _) -> + MUCHost = server_host_to_muc_host(HostType, ServerHost), + Items = [#{jid => MUCHost, node => ?NS_MUC}], + {ok, mongoose_disco:add_items(Items, Acc)}; +disco_local_items(Acc, _, _) -> + {ok, Acc}. online_rooms_number() -> lists:sum([online_rooms_number(HostType) @@ -1368,12 +1393,12 @@ config_metrics(HostType) -> mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]). hooks(HostType) -> - [{is_muc_room_owner, HostType, ?MODULE, is_muc_room_owner, 50}, - {can_access_room, HostType, ?MODULE, can_access_room, 50}, - {remove_domain, HostType, ?MODULE, remove_domain, 50}, - {acc_room_affiliations, HostType, ?MODULE, acc_room_affiliations, 50}, - {can_access_identity, HostType, ?MODULE, can_access_identity, 50}, - {disco_local_items, HostType, ?MODULE, disco_local_items, 250}]. + [{is_muc_room_owner, HostType, fun ?MODULE:is_muc_room_owner/3, #{}, 50}, + {can_access_room, HostType, fun ?MODULE:can_access_room/3, #{}, 50}, + {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}, + {acc_room_affiliations, HostType, fun ?MODULE:acc_room_affiliations/3, #{}, 50}, + {can_access_identity, HostType, fun ?MODULE:can_access_identity/3, #{}, 50}, + {disco_local_items, HostType, fun ?MODULE:disco_local_items/3, #{}, 250}]. subdomain_pattern(HostType) -> gen_mod:get_module_opt(HostType, ?MODULE, host). @@ -1381,16 +1406,6 @@ subdomain_pattern(HostType) -> server_host_to_muc_host(HostType, ServerHost) -> mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost). --spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). -disco_local_items(Acc = #{host_type := HostType, - to_jid := #jid{lserver = ServerHost}, - node := <<>>}) -> - MUCHost = server_host_to_muc_host(HostType, ServerHost), - Items = [#{jid => MUCHost, node => ?NS_MUC}], - mongoose_disco:add_items(Items, Acc); -disco_local_items(Acc) -> - Acc. - make_server_host(To, #muc_state{host_type = HostType, subdomain_pattern = SubdomainPattern}) -> case SubdomainPattern of From cda7dbd695477bb7344484d9ddd36f66fc3e7f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Thu, 10 Nov 2022 11:15:44 +0100 Subject: [PATCH 02/44] Refactored hook handlers in mod_last --- src/auth/ejabberd_auth_anonymous.erl | 2 +- src/mod_last.erl | 80 +++++++++++++++------------- src/mongoose_hooks.erl | 5 +- test/mongoose_cleanup_SUITE.erl | 4 +- 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/auth/ejabberd_auth_anonymous.erl b/src/auth/ejabberd_auth_anonymous.erl index 214943e284..1b3e4883d9 100644 --- a/src/auth/ejabberd_auth_anonymous.erl +++ b/src/auth/ejabberd_auth_anonymous.erl @@ -180,7 +180,7 @@ purge_hook(true, HostType, LUser, LServer) -> Acc :: mongoose_acc:t(), Params :: map(), Extra :: map(). -session_cleanup(Acc, #{sid := SID, user := LUser, server := LServer}, _Extra) -> +session_cleanup(Acc, #{sid := SID, jid := #jid{luser = LUser,lserver = LServer}}, _Extra) -> remove_connection(SID, LUser, LServer), {ok, Acc}. diff --git a/src/mod_last.erl b/src/mod_last.erl index 24db316853..398f651b33 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -42,8 +42,8 @@ -export([process_local_iq/5, process_sm_iq/5, remove_user/3, - on_presence_update/5, - session_cleanup/5, + on_presence_update/3, + session_cleanup/3, remove_domain/3, remove_unused_backend_opts/1]). @@ -54,11 +54,6 @@ -export([config_metrics/1]). --ignore_xref([ - behaviour_info/1, on_presence_update/5, process_local_iq/4, - process_sm_iq/4, remove_user/3, session_cleanup/5, remove_domain/3 -]). - -include("mongoose.hrl"). -include("mongoose_config_spec.hrl"). @@ -74,15 +69,14 @@ -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok. start(HostType, #{iqdisc := IQDisc} = Opts) -> - mod_last_backend:init(HostType, Opts), [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_LAST, Component, Fn, #{}, IQDisc) || {Component, Fn} <- iq_handlers()], - ejabberd_hooks:add(hooks(HostType)). + gen_hook:add_handlers(hooks(HostType)). -spec stop(mongooseim:host_type()) -> ok. stop(HostType) -> - ejabberd_hooks:delete(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_LAST, Component) || {Component, _Fn} <- iq_handlers()], ok. @@ -91,12 +85,13 @@ iq_handlers() -> [{ejabberd_local, fun ?MODULE:process_local_iq/5}, {ejabberd_sm, fun ?MODULE:process_sm_iq/5}]. +-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). hooks(HostType) -> - [{remove_user, HostType, ?MODULE, remove_user, 50}, - {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50}, - {unset_presence_hook, HostType, ?MODULE, on_presence_update, 50}, - {session_cleanup, HostType, ?MODULE, session_cleanup, 50}, - {remove_domain, HostType, ?MODULE, remove_domain, 50}]. + [{remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {anonymous_purge_hook, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {unset_presence_hook, HostType, fun ?MODULE:on_presence_update/3, #{}, 50}, + {session_cleanup, HostType, fun ?MODULE:session_cleanup/3, #{}, 50}, + {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}]. %%% %%% config_spec @@ -229,31 +224,40 @@ get_last_info(HostType, LUser, LServer) -> Res -> Res end. --spec remove_user(mongoose_acc:t(), jid:user(), jid:server()) -> mongoose_acc:t(). -remove_user(Acc, User, Server) -> - HostType = mongoose_acc:host_type(Acc), - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - R = mod_last_backend:remove_user(HostType, LUser, LServer), - mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}), - Acc. +%%% +%%% Hook handlers +%%% --spec remove_domain(mongoose_hooks:simple_acc(), mongooseim:host_type(), jid:lserver()) -> - mongoose_hooks:simple_acc(). -remove_domain(Acc, HostType, Domain) -> +-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> + R = mod_last_backend:remove_user(HostType, LUser, LServer), + mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, LUser, LServer}), + {ok, Acc}. + +-spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_hooks:simple_acc(), + Params :: #{domain := jid:lserver()}, + Extra :: gen_hook:extra(). +remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> mod_last_backend:remove_domain(HostType, Domain), - Acc. - --spec on_presence_update(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(), status()) -> - mongoose_acc:t(). -on_presence_update(Acc, LUser, LServer, _Resource, Status) -> - store_last_info(Acc, LUser, LServer, Status). - --spec session_cleanup(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(), - ejabberd_sm:sid()) -> - mongoose_acc:t(). -session_cleanup(Acc, LUser, LServer, _LResource, _SID) -> - store_last_info(Acc, LUser, LServer, <<>>). + {ok, Acc}. + +-spec on_presence_update(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid(), status := binary()}, + Extra :: gen_hook:extra(). +on_presence_update(Acc, #{jid := #jid{luser = LUser, lserver = LServer}, status := Status}, _) -> + {ok, store_last_info(Acc, LUser, LServer, Status)}. + +-spec session_cleanup(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +session_cleanup(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> + {ok, store_last_info(Acc, LUser, LServer, <<>>)}. -spec store_last_info(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t(). store_last_info(Acc, LUser, LServer, Status) -> diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 4f20076214..1dcbb58105 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -416,7 +416,8 @@ resend_offline_messages_hook(Acc, JID) -> SID :: ejabberd_sm:sid(), Result :: mongoose_acc:t(). session_cleanup(Server, Acc, User, Resource, SID) -> - Params = #{user => User, server => Server, resource => Resource, sid => SID}, + JID = jid:make(User, Server, Resource), + Params = #{jid => JID, sid => SID}, Args = [User, Server, Resource, SID], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), @@ -838,7 +839,7 @@ sm_remove_connection_hook(Acc, SID, JID, Info, Reason) -> Result :: mongoose_acc:t(). unset_presence_hook(Acc, JID, Status) -> #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, - Params = #{jid => JID}, + Params = #{jid => JID, status => Status}, Args = [LUser, LServer, LResource, Status], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), diff --git a/test/mongoose_cleanup_SUITE.erl b/test/mongoose_cleanup_SUITE.erl index 870b597334..02bd853c56 100644 --- a/test/mongoose_cleanup_SUITE.erl +++ b/test/mongoose_cleanup_SUITE.erl @@ -104,11 +104,11 @@ auth_anonymous(_Config) -> last(_Config) -> HostType = host_type(), - {U, S, R, _JID, SID} = get_fake_session(), + {U, S, R, JID, SID} = get_fake_session(), mod_last:start(HostType, config_parser_helper:mod_config(mod_last, #{iqdisc => no_queue})), not_found = mod_last:get_last_info(HostType, U, S), Status1 = <<"status1">>, - #{} = mod_last:on_presence_update(new_acc(S), U, S, R, Status1), + {ok, #{}} = mod_last:on_presence_update(new_acc(S), #{jid => JID, status => Status1}, #{}), {ok, TS1, Status1} = mod_last:get_last_info(HostType, U, S), async_helper:wait_until( fun() -> From 775f7062f0e6df1d8b59b7bcde651bc1b970bdd8 Mon Sep 17 00:00:00 2001 From: Mikhail Uvarov Date: Wed, 16 Nov 2022 11:19:25 +0100 Subject: [PATCH 03/44] Do not store message sent to yourself --- big_tests/tests/mam_SUITE.erl | 16 ++++++++++++++++ src/mam/mod_mam_pm.erl | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/big_tests/tests/mam_SUITE.erl b/big_tests/tests/mam_SUITE.erl index e01478545d..8408908030 100644 --- a/big_tests/tests/mam_SUITE.erl +++ b/big_tests/tests/mam_SUITE.erl @@ -36,6 +36,7 @@ muc_service_discovery/1, easy_archive_request/1, easy_archive_request_for_the_receiver/1, + message_sent_to_yourself/1, text_search_query_fails_if_disabled/1, pagination_simple_enforced/1, text_search_is_not_available/1, @@ -383,6 +384,7 @@ mam_cases() -> [mam_service_discovery, easy_archive_request, easy_archive_request_for_the_receiver, + message_sent_to_yourself, range_archive_request, range_archive_request_not_empty, limit_archive_request, @@ -1226,6 +1228,20 @@ easy_archive_request_for_the_receiver(Config) -> end, escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). +message_sent_to_yourself(Config) -> + P = ?config(props, Config), + F = fun(Alice) -> + escalus:send(Alice, escalus_stanza:chat_to(Alice, <<"OH, HAI!">>)), + escalus:wait_for_stanza(Alice), %% Receive that message + mam_helper:wait_for_archive_size(Alice, 1), + escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), + Res = wait_archive_respond(Alice), + assert_respond_size(1, Res), + assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res)), + ok + end, + escalus_fresh:story(Config, [{alice, 1}], F). + text_search_is_not_available(Config) -> P = ?config(props, Config), F = fun(Alice) -> diff --git a/src/mam/mod_mam_pm.erl b/src/mam/mod_mam_pm.erl index 6610236b8d..0e0bd94f96 100644 --- a/src/mam/mod_mam_pm.erl +++ b/src/mam/mod_mam_pm.erl @@ -449,7 +449,8 @@ handle_package(Dir, ReturnMessID, LocJID = #jid{}, RemJID = #jid{}, SrcJID = #jid{}, Packet, Acc) -> HostType = acc_to_host_type(Acc), case is_archivable_message(HostType, Dir, Packet) - andalso should_archive_if_groupchat(HostType, exml_query:attr(Packet, <<"type">>)) of + andalso should_archive_if_groupchat(HostType, exml_query:attr(Packet, <<"type">>)) + andalso should_archive_if_sent_to_yourself(LocJID, RemJID, Dir) of true -> ArcID = archive_id_int(HostType, LocJID), OriginID = mod_mam_utils:get_origin_id(Packet), @@ -479,6 +480,12 @@ should_archive_if_groupchat(HostType, <<"groupchat">>) -> should_archive_if_groupchat(_, _) -> true. +%% Only store messages sent to yourself in user_send_packet. +should_archive_if_sent_to_yourself(LocJID, RemJID, incoming) -> + not jid:are_bare_equal(LocJID, RemJID); +should_archive_if_sent_to_yourself(LocJID, RemJID, _Dir) -> + true. + -spec return_external_message_id_if_ok(ReturnMessID :: boolean(), ArchivingResult :: ok | any(), MessID :: integer()) -> binary() | undefined. From 4c7f6ffff976c8419ceaa29c2e9fbbc88bed53a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Tue, 15 Nov 2022 13:38:05 +0100 Subject: [PATCH 04/44] Get rid of the nested 'wait_until' In case of failure it would result in very long execution time. Pass the optional Check function to the inner loop. --- big_tests/tests/inbox_helper.erl | 57 +++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/big_tests/tests/inbox_helper.erl b/big_tests/tests/inbox_helper.erl index 02e9d87119..c7294c4928 100644 --- a/big_tests/tests/inbox_helper.erl +++ b/big_tests/tests/inbox_helper.erl @@ -106,6 +106,9 @@ Expected :: binary(), AttrName :: binary()) -> any() | no_return()). +-type inbox_result() :: #{respond_iq := exml:element(), + respond_messages := [exml:element()]}. + -type conv() :: #conv{}. %% --------------------------------------------------------- @@ -177,43 +180,41 @@ foreach_check_inbox(Users, Unread, SenderJid, Msg) -> check_inbox(U, [#conv{unread = Unread, from = SenderJid, to = UserJid, content = Msg}]) end || U <- Users]. --spec check_inbox(Client :: escalus:client(), Convs :: [conv()]) -> ok | no_return(). +-spec check_inbox(Client :: escalus:client(), Convs :: [conv()]) -> inbox_result(). check_inbox(Client, Convs) -> check_inbox(Client, Convs, #{}, #{}). --spec check_inbox(escalus:client(), [conv()], inbox_query_params()) -> ok | no_return(). +-spec check_inbox(escalus:client(), [conv()], inbox_query_params()) -> inbox_result(). check_inbox(Client, Convs, QueryOpts) -> check_inbox(Client, Convs, QueryOpts, #{}). -spec check_inbox(Client :: escalus:client(), Convs :: [conv()], QueryOpts :: inbox_query_params(), - CheckOpts :: inbox_check_params()) -> ok | no_return(). + CheckOpts :: inbox_check_params()) -> inbox_result(). check_inbox(Client, Convs, QueryOpts, CheckOpts) -> - {ok, Res} = mongoose_helper:wait_until( - fun() -> do_check_inbox(Client, Convs, QueryOpts, CheckOpts) end, - ok, #{name => inbox_size, validator => fun(_) -> true end}), - Res. - -do_check_inbox(Client, Convs, QueryOpts, CheckOpts) -> ExpectedSortedConvs = case maps:get(order, QueryOpts, undefined) of asc -> lists:reverse(Convs); _ -> Convs end, - Inbox = #{respond_messages := ResultStanzas} - = get_inbox(Client, QueryOpts, #{count => length(ExpectedSortedConvs)}), + F = fun(#{respond_messages := ResultStanzas} = InboxResult) -> + do_check_inbox(Client, QueryOpts, CheckOpts, ResultStanzas, ExpectedSortedConvs), + InboxResult + end, + get_inbox(Client, QueryOpts, #{count => length(ExpectedSortedConvs)}, F). + +do_check_inbox(Client, QueryOpts, CheckOpts, ResultStanzas, ExpectedSortedConvs) -> try - check_inbox_result(Client, QueryOpts, CheckOpts, ResultStanzas, ExpectedSortedConvs), - Inbox + check_inbox_result(Client, QueryOpts, CheckOpts, ResultStanzas, ExpectedSortedConvs) catch _:Reason:StackTrace -> - ct:fail(#{ reason => inbox_mismatch, - inbox_items => lists:map(fun exml:to_binary/1, ResultStanzas), - expected_items => lists:map(fun pretty_conv/1, ExpectedSortedConvs), - query_params => QueryOpts, - check_params => CheckOpts, - error => Reason, - stacktrace => StackTrace }) + error(#{ reason => inbox_mismatch, + inbox_items => lists:map(fun exml:to_binary/1, ResultStanzas), + expected_items => lists:map(fun pretty_conv/1, ExpectedSortedConvs), + query_params => QueryOpts, + check_params => CheckOpts, + error => Reason, + stacktrace => StackTrace }) end. check_inbox_result(Client, QueryOpts, CheckOpts, ResultStanzas, MsgCheckList) -> @@ -252,21 +253,23 @@ maybe_check_queryid(Children, #{queryid := QueryId}) -> maybe_check_queryid(_Children, #{}) -> ok. --spec get_inbox(Client :: escalus:client(), - ExpectedResult :: inbox_result_params()) -> - #{respond_iq := exml:element(), respond_messages := [exml:element()]}. +-spec get_inbox(escalus:client(), inbox_result_params()) -> inbox_result(). get_inbox(Client, ExpectedResult) -> get_inbox(Client, #{}, ExpectedResult). +-spec get_inbox(escalus:client(), inbox_query_params(), inbox_result_params()) -> inbox_result(). +get_inbox(Client, GetParams, ExpectedResult) -> + get_inbox(Client, GetParams, ExpectedResult, fun(R) -> R end). + -spec get_inbox(Client :: escalus:client(), GetParams :: inbox_query_params(), - ExpectedResult :: inbox_result_params()) -> - #{respond_iq := exml:element(), respond_messages := [exml:element()]}. -get_inbox(Client, GetParams, #{count := ExpectedCount} = ExpectedResult) -> + ExpectedResult :: inbox_result_params(), + Check :: fun((inbox_result()) -> inbox_result())) -> inbox_result(). +get_inbox(Client, GetParams, #{count := ExpectedCount} = ExpectedResult, Check) -> GetInbox = make_inbox_stanza(GetParams), Validator = fun(#{respond_messages := Val}) -> ExpectedCount =:= length(Val) end, {ok, Inbox} = mongoose_helper:wait_until( - fun() -> do_get_inbox(Client, GetInbox) end, + fun() -> Check(do_get_inbox(Client, GetInbox)) end, ExpectedCount, #{validator => Validator, name => inbox_size}), #{respond_iq := ResIQ} = Inbox, ?assert(escalus_pred:is_iq_result(GetInbox, ResIQ)), From 1088b09d35f7a32c3f74e5a69d70e179e36ae0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Tue, 15 Nov 2022 13:38:52 +0100 Subject: [PATCH 05/44] Test the cases when the domain is not string-prepped --- big_tests/tests/common_helper.erl | 2 ++ big_tests/tests/graphql_account_SUITE.erl | 23 +++++++++----- big_tests/tests/graphql_gdpr_SUITE.erl | 13 ++++++-- big_tests/tests/graphql_http_upload_SUITE.erl | 13 ++++++-- big_tests/tests/graphql_inbox_SUITE.erl | 17 ++++++++--- big_tests/tests/graphql_last_SUITE.erl | 15 ++++++---- big_tests/tests/graphql_muc_SUITE.erl | 30 +++++++++++++++++-- big_tests/tests/graphql_muc_light_SUITE.erl | 30 ++++++++++++++++++- big_tests/tests/graphql_offline_SUITE.erl | 21 +++++++++---- 9 files changed, 134 insertions(+), 30 deletions(-) diff --git a/big_tests/tests/common_helper.erl b/big_tests/tests/common_helper.erl index 565e166ea5..6cfb55112e 100644 --- a/big_tests/tests/common_helper.erl +++ b/big_tests/tests/common_helper.erl @@ -6,3 +6,5 @@ get_bjid(UserSpec) -> Server = proplists:get_value(server, UserSpec), <>. +unprep(Bin) when is_binary(Bin) -> + list_to_binary(string:titlecase(binary_to_list(Bin))). diff --git a/big_tests/tests/graphql_account_SUITE.erl b/big_tests/tests/graphql_account_SUITE.erl index cc808ab689..93793e3aa6 100644 --- a/big_tests/tests/graphql_account_SUITE.erl +++ b/big_tests/tests/graphql_account_SUITE.erl @@ -4,6 +4,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). -import(graphql_helper, [execute_command/4, execute_user_command/5, get_listener_port/1, get_listener_config/1, get_ok_value/2, get_err_msg/1, @@ -199,9 +200,11 @@ admin_list_users(Config) -> Domain = domain_helper:domain(), Username = jid:nameprep(escalus_users:get_username(Config, alice)), JID = <>, - Resp2 = list_users(Domain, Config), - Users = get_ok_value([data, account, listUsers], Resp2), - ?assert(lists:member(JID, Users)). + Resp1 = list_users(Domain, Config), + Users = get_ok_value([data, account, listUsers], Resp1), + ?assert(lists:member(JID, Users)), + Resp2 = list_users(unprep(Domain), Config), + ?assertEqual(Users, get_ok_value([data, account, listUsers], Resp2)). admin_list_users_unknown_domain(Config) -> Resp = list_users(<<"unknown-domain">>, Config), @@ -210,8 +213,11 @@ admin_list_users_unknown_domain(Config) -> admin_count_users(Config) -> % A domain with at least one user Domain = domain_helper:domain(), - Resp2 = count_users(Domain, Config), - ?assert(0 < get_ok_value([data, account, countUsers], Resp2)). + Resp1 = count_users(Domain, Config), + Count = get_ok_value([data, account, countUsers], Resp1), + ?assert(0 < Count), + Resp2 = count_users(unprep(Domain), Config), + ?assertEqual(Count, get_ok_value([data, account, countUsers], Resp2)). admin_count_users_unknown_domain(Config) -> Resp = count_users(<<"unknown-domain">>, Config), @@ -306,9 +312,12 @@ admin_register_user(Config) -> % Try to register a user with existing name Resp2 = register_user(Domain, Username, Password, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)), + % Try again, this time with a domain name that is not stringprepped + Resp3 = register_user(unprep(Domain), Username, Password, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"already registered">>)), % Try to register a user without any name - Resp3 = register_user(Domain, <<>>, Password, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"Invalid JID">>)). + Resp4 = register_user(Domain, <<>>, Password, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp4), <<"Invalid JID">>)). admin_register_random_user(Config) -> Password = <<"my_password">>, diff --git a/big_tests/tests/graphql_gdpr_SUITE.erl b/big_tests/tests/graphql_gdpr_SUITE.erl index b1f7e65ee5..e876c34149 100644 --- a/big_tests/tests/graphql_gdpr_SUITE.erl +++ b/big_tests/tests/graphql_gdpr_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(domain_helper, [host_type/0, domain/0]). -import(distributed_helper, [mim/0, rpc/4, require_rpc_nodes/1]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, @@ -25,6 +26,7 @@ groups() -> admin_gdpr_tests() -> [admin_retrieve_user_data, + admin_retrieve_user_data_for_unprepped_domain, admin_gdpr_no_user_test, admin_gdpr_empty_filename_test, admin_gdpr_access_denied_erofs, @@ -34,6 +36,7 @@ admin_gdpr_tests() -> domain_admin_gdpr_tests() -> [admin_retrieve_user_data, + admin_retrieve_user_data_for_unprepped_domain, admin_gdpr_no_user_test, domain_admin_retrieve_user_data_no_permission]. @@ -65,11 +68,17 @@ end_per_testcase(CaseName, Config) -> % Admin test cases admin_retrieve_user_data(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_retrieve_user_data/2). + Config1 = [{domain, domain()} | Config], + escalus:fresh_story_with_config(Config1, [{alice, 1}], fun admin_retrieve_user_data/2). + +admin_retrieve_user_data_for_unprepped_domain(Config) -> + Config1 = [{domain, unprep(domain())} | Config], + escalus:fresh_story_with_config(Config1, [{alice, 1}], fun admin_retrieve_user_data/2). admin_retrieve_user_data(Config, Alice) -> Filename = random_filename(Config), - Res = admin_retrieve_personal_data(escalus_client:username(Alice), escalus_client:server(Alice), + Domain = ?config(domain, Config), + Res = admin_retrieve_personal_data(escalus_client:username(Alice), Domain, list_to_binary(Filename), Config), ParsedResult = get_ok_value([data, gdpr, retrievePersonalData], Res), ?assertEqual(<<"Data retrieved">>, ParsedResult), diff --git a/big_tests/tests/graphql_http_upload_SUITE.erl b/big_tests/tests/graphql_http_upload_SUITE.erl index ca35bee7ea..eb0cba9aca 100644 --- a/big_tests/tests/graphql_http_upload_SUITE.erl +++ b/big_tests/tests/graphql_http_upload_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(domain_helper, [host_type/0, domain/0, secondary_domain/0]). -import(graphql_helper, [execute_user_command/5, execute_command/4, get_ok_value/2, @@ -190,7 +191,11 @@ user_http_upload_not_configured(Config, Alice) -> % Admin test cases admin_get_url_test(Config) -> - Result = admin_get_url(domain(), <<"test">>, 123, <<"Test">>, 123, Config), + admin_get_url_test(Config, domain()), + admin_get_url_test(Config, unprep(domain())). + +admin_get_url_test(Config, Domain) -> + Result = admin_get_url(Domain, <<"test">>, 123, <<"Test">>, 123, Config), ParsedResult = get_ok_value([data, httpUpload, getUrl], Result), #{<<"PutUrl">> := PutURL, <<"GetUrl">> := GetURL, <<"Header">> := _Headers} = ParsedResult, ?assertMatch({_, _}, binary:match(PutURL, [?S3_HOSTNAME])), @@ -217,7 +222,11 @@ admin_get_url_no_domain(Config) -> ?assertEqual(<<"domain does not exist">>, get_err_msg(Result)). admin_http_upload_not_configured(Config) -> - Result = admin_get_url(domain(), <<"test">>, 123, <<"Test">>, 123, Config), + admin_http_upload_not_configured(Config, domain()), + admin_http_upload_not_configured(Config, unprep(domain())). + +admin_http_upload_not_configured(Config, Domain) -> + Result = admin_get_url(Domain, <<"test">>, 123, <<"Test">>, 123, Config), ?assertEqual(<<"deps_not_loaded">>, get_err_code(Result)), ?assertEqual(<<"Some of required modules or services are not loaded">>, get_err_msg(Result)). diff --git a/big_tests/tests/graphql_inbox_SUITE.erl b/big_tests/tests/graphql_inbox_SUITE.erl index 011d81d35e..c4568d7a3b 100644 --- a/big_tests/tests/graphql_inbox_SUITE.erl +++ b/big_tests/tests/graphql_inbox_SUITE.erl @@ -2,12 +2,14 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). -import(domain_helper, [host_type/0, domain/0]). -import(graphql_helper, [execute_user_command/5, execute_command/4, user_to_bin/1, get_ok_value/2, get_err_msg/1, get_err_code/1, get_not_loaded/1, get_unauthorized/1]). +-include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("inbox.hrl"). @@ -55,6 +57,7 @@ admin_inbox_tests() -> admin_try_flush_nonexistent_user_bin, admin_try_flush_user_bin_nonexistent_domain, admin_flush_domain_bin, + admin_flush_unprepped_domain_bin, admin_try_flush_nonexistent_domain_bin, admin_flush_global_bin, admin_flush_global_bin_after_days, @@ -155,13 +158,18 @@ admin_try_flush_user_bin_nonexistent_domain(Config) -> ?assertErrCode(Res, domain_not_found). admin_flush_domain_bin(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {kate, 1}], + Config1 = [{domain, domain()} | Config], + escalus:fresh_story_with_config(Config1, [{alice, 1}, {alice_bis, 1}, {kate, 1}], + fun admin_flush_domain_bin/4). + +admin_flush_unprepped_domain_bin(Config) -> + Config1 = [{domain, unprep(domain())} | Config], + escalus:fresh_story_with_config(Config1, [{alice, 1}, {alice_bis, 1}, {kate, 1}], fun admin_flush_domain_bin/4). admin_flush_domain_bin(Config, Alice, AliceBis, Kate) -> RoomBinJID = create_room_and_make_users_leave(Alice, AliceBis, Kate), - Domain = domain_helper:domain(), - Res = flush_domain_bin(Domain, Config), + Res = flush_domain_bin(?config(domain, Config), Config), NumOfRows = get_ok_value(p(flushDomainBin), Res), ?assertEqual(1, NumOfRows), inbox_helper:check_inbox(Kate, [], #{box => bin}), @@ -213,7 +221,8 @@ admin_flush_user_bin_inbox_not_configured(Config, Alice) -> get_not_loaded(flush_user_bin(Alice, Config)). admin_flush_domain_bin_inbox_not_configured(Config) -> - get_not_loaded(flush_domain_bin(domain(), Config)). + get_not_loaded(flush_domain_bin(domain(), Config)), + get_not_loaded(flush_domain_bin(unprep(domain()), Config)). admin_flush_global_bin_inbox_not_configured(Config) -> get_not_loaded(flush_global_bin(host_type(), 10, Config)). diff --git a/big_tests/tests/graphql_last_SUITE.erl b/big_tests/tests/graphql_last_SUITE.erl index 8d936187ae..3ac6d07ea1 100644 --- a/big_tests/tests/graphql_last_SUITE.erl +++ b/big_tests/tests/graphql_last_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, user_to_jid/1, get_ok_value/2, get_err_msg/1, get_err_code/1, get_unauthorized/1, @@ -311,6 +312,8 @@ admin_count_active_users_story(Config, Alice, Bob) -> set_last(Bob, now_dt_with_offset(10), Config), Res = admin_count_active_users(Domain, null, Config), ?assertEqual(2, get_ok_value(p(countActiveUsers), Res)), + Res1 = admin_count_active_users(unprep(Domain), null, Config), + ?assertEqual(2, get_ok_value(p(countActiveUsers), Res1)), Res2 = admin_count_active_users(Domain, now_dt_with_offset(30), Config), ?assertEqual(0, get_ok_value(p(countActiveUsers), Res2)). @@ -561,13 +564,13 @@ admin_get_last_not_configured_story(Config, Alice) -> admin_count_active_users_last_not_configured(Config) -> Domain = domain_helper:domain(), - Res = admin_count_active_users(Domain, null, Config), - get_not_loaded(Res). + get_not_loaded(admin_count_active_users(Domain, null, Config)), + get_not_loaded(admin_count_active_users(unprep(Domain), null, Config)). admin_remove_old_users_domain_last_not_configured(Config) -> Domain = domain_helper:domain(), - Res = admin_remove_old_users(Domain, now_dt_with_offset(150), Config), - get_not_loaded(Res). + get_not_loaded(admin_remove_old_users(Domain, now_dt_with_offset(150), Config)), + get_not_loaded(admin_remove_old_users(unprep(Domain), now_dt_with_offset(150), Config)). admin_remove_old_users_global_last_not_configured(Config) -> Res = admin_remove_old_users(null, now_dt_with_offset(150), Config), @@ -575,8 +578,8 @@ admin_remove_old_users_global_last_not_configured(Config) -> admin_list_old_users_domain_last_not_configured(Config) -> Domain = domain_helper:domain(), - Res = admin_list_old_users(Domain, now_dt_with_offset(150), Config), - get_not_loaded(Res). + get_not_loaded(admin_list_old_users(Domain, now_dt_with_offset(150), Config)), + get_not_loaded(admin_list_old_users(unprep(Domain), now_dt_with_offset(150), Config)). admin_list_old_users_global_last_not_configured(Config) -> Res = admin_list_old_users(null, now_dt_with_offset(150), Config), diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index 4c722127fc..7aaaf7efe6 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). -import(graphql_helper, [execute_command/4, execute_user_command/5, get_ok_value/2, get_err_msg/1, get_coercion_err_msg/1, user_to_bin/1, user_to_full_bin/1, user_to_jid/1, @@ -40,6 +41,7 @@ admin_groups() -> user_muc_tests() -> [user_create_and_delete_room, + user_create_room_with_unprepped_domain, user_try_delete_nonexistent_room, user_try_delete_room_by_not_owner, user_try_create_instant_room_with_nonexistent_domain, @@ -97,6 +99,7 @@ user_muc_not_configured_tests() -> admin_muc_tests() -> [admin_create_and_delete_room, + admin_create_room_with_unprepped_domain, admin_try_create_instant_room_with_nonexistent_domain, admin_try_create_instant_room_with_nonexistent_user, admin_try_delete_nonexistent_room, @@ -151,6 +154,7 @@ admin_muc_not_configured_tests() -> domain_admin_muc_tests() -> [admin_create_and_delete_room, + admin_create_room_with_unprepped_domain, admin_try_create_instant_room_with_nonexistent_domain, admin_try_delete_nonexistent_room, domain_admin_create_and_delete_room_no_permission, @@ -287,8 +291,10 @@ admin_list_rooms_story(Config, Alice, Bob) -> BobRoom = rand_name(), muc_helper:create_instant_room(AliceRoom, AliceJID, <<"Ali">>, []), muc_helper:create_instant_room(BobRoom, BobJID, <<"Bob">>, [{public_list, false}]), - Res = list_rooms(muc_helper:muc_host(), Alice, null, null, Config), - #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res), + Res1 = list_rooms(muc_helper:muc_host(), Alice, null, null, Config), + #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res1), + Res2 = list_rooms(unprep(muc_helper:muc_host()), Alice, null, null, Config), + #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res2), ?assert(contain_room(AliceRoom, Rooms)), ?assert(contain_room(BobRoom, Rooms)). @@ -312,6 +318,15 @@ admin_create_and_delete_room_story(Config, Alice) -> Res4 = list_rooms(MUCServer, Alice, null, null, Config), ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))). +admin_create_room_with_unprepped_domain(Config) -> + FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceJid = escalus_users:get_jid(FreshConfig, alice), + Name = rand_name(), + MUCServer = unprep(muc_helper:muc_host()), + Res = create_instant_room(MUCServer, Name, AliceJid, <<"Ali">>, FreshConfig), + ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, + get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)). + admin_try_create_instant_room_with_nonexistent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_try_create_instant_room_with_nonexistent_domain_story/2). @@ -975,6 +990,17 @@ user_create_and_delete_room_story(Config, Alice) -> Res4 = user_list_rooms(Alice, MUCServer, null, null, Config), ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))). +user_create_room_with_unprepped_domain(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_create_room_with_unprepped_domain_story/2). + +user_create_room_with_unprepped_domain_story(Config, Alice) -> + Name = rand_name(), + MUCServer = unprep(muc_helper:muc_host()), + Res = user_create_instant_room(Alice, MUCServer, Name, <<"Ali">>, Config), + ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, + get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)). + user_try_create_instant_room_with_nonexistent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_try_create_instant_room_with_nonexistent_domain_story/2). diff --git a/big_tests/tests/graphql_muc_light_SUITE.erl b/big_tests/tests/graphql_muc_light_SUITE.erl index 6b98374ea0..7e09b0517d 100644 --- a/big_tests/tests/graphql_muc_light_SUITE.erl +++ b/big_tests/tests/graphql_muc_light_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). -import(graphql_helper, [execute_user_command/5, execute_command/4, get_listener_port/1, get_listener_config/1, get_ok_value/2, get_err_msg/1, @@ -48,7 +49,7 @@ groups() -> {admin_cli, [], admin_groups()}, {user_muc_light, [parallel], user_muc_light_tests()}, {user_muc_light_not_configured, [], user_muc_light_not_configured_tests()}, - {admin_muc_light, [parallel], admin_muc_light_tests()}, + {admin_muc_light, [], admin_muc_light_tests()}, {domain_admin_muc_light, [], domain_admin_muc_light_tests()}, {admin_muc_light_not_configured, [], admin_muc_light_not_configured_tests()}]. @@ -62,6 +63,7 @@ admin_groups() -> user_muc_light_tests() -> [user_create_room, + user_create_room_with_unprepped_domain, user_create_room_with_custom_fields, user_create_identified_room, user_change_room_config, @@ -94,6 +96,7 @@ user_muc_light_not_configured_tests() -> admin_muc_light_tests() -> [admin_create_room, + admin_create_room_with_unprepped_domain, admin_create_room_with_custom_fields, admin_create_identified_room, admin_change_room_config, @@ -122,6 +125,7 @@ admin_muc_light_tests() -> domain_admin_muc_light_tests() -> [admin_create_room, + admin_create_room_with_unprepped_domain, admin_create_room_with_custom_fields, domain_admin_create_room_no_permission, admin_create_identified_room, @@ -258,6 +262,19 @@ user_create_room_story(Config, Alice) -> Res2 = user_create_room(Alice, ?UNKNOWN_DOMAIN, Name, Subject, null, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). +user_create_room_with_unprepped_domain(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_create_room_with_unprepped_domain_story/2). + +user_create_room_with_unprepped_domain_story(Config, Alice) -> + MucServer = ?config(muc_light_host, Config), + Name = <<"room with unprepped domain">>, + Subject = <<"testing">>, + Res = user_create_room(Alice, unprep(MucServer), Name, Subject, null, Config), + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = + get_ok_value(?CREATE_ROOM_PATH, Res), + ?assertMatch(#jid{lserver = MucServer}, jid:from_binary_noprep(JID)). + user_create_room_with_custom_fields(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_create_room_with_custom_fields_story/2). @@ -931,6 +948,17 @@ admin_create_room_story(Config, Alice) -> Res2 = create_room(?UNKNOWN_DOMAIN, Name, AliceBin, Subject, null, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). +admin_create_room_with_unprepped_domain(Config) -> + FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceBin = escalus_users:get_jid(FreshConfig, alice), + MucServer = ?config(muc_light_host, Config), + Name = <<"room with unprepped domain">>, + Subject = <<"testing">>, + Res = create_room(unprep(MucServer), Name, AliceBin, Subject, null, Config), + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = + get_ok_value(?CREATE_ROOM_PATH, Res), + ?assertMatch(#jid{lserver = MucServer}, jid:from_binary_noprep(JID)). + admin_create_room_with_custom_fields(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_room_with_custom_fields_story/2). diff --git a/big_tests/tests/graphql_offline_SUITE.erl b/big_tests/tests/graphql_offline_SUITE.erl index def5d6253b..a5379cb1df 100644 --- a/big_tests/tests/graphql_offline_SUITE.erl +++ b/big_tests/tests/graphql_offline_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(domain_helper, [host_type/0, domain/0]). -import(graphql_helper, [execute_command/4, get_ok_value/2, get_err_code/1, user_to_bin/1, @@ -129,9 +130,13 @@ admin_delete_expired_messages2_test(Config) -> fun admin_delete_expired_messages2_test/3). admin_delete_expired_messages2_test(Config, JidMike, JidKate) -> + admin_delete_expired_messages2(Config, JidMike, JidKate, domain()), + admin_delete_expired_messages2(Config, JidMike, JidKate, unprep(domain())). + +admin_delete_expired_messages2(Config, JidMike, JidKate, Domain) -> generate_message(JidMike, JidKate, 2, 1), generate_message(JidMike, JidKate, 5, -1), % not expired yet - Result = delete_expired_messages(domain(), Config), + Result = delete_expired_messages(Domain, Config), ParsedResult = get_ok_value([data, offline, deleteExpiredMessages], Result), ?assertEqual(<<"Removed 1 messages">>, ParsedResult). @@ -140,10 +145,14 @@ admin_delete_old_messages2_test(Config) -> fun admin_delete_old_messages2_test/3). admin_delete_old_messages2_test(Config, JidMike, JidKate) -> + admin_delete_old_messages2(Config, JidMike, JidKate, domain()), + admin_delete_old_messages2(Config, JidMike, JidKate, unprep(domain())). + +admin_delete_old_messages2(Config, JidMike, JidKate, Domain) -> generate_message(JidMike, JidKate, 2, 1), % not old enough generate_message(JidMike, JidKate, 5, -1), generate_message(JidMike, JidKate, 7, 5), - Result = delete_old_messages(domain(), 3, Config), + Result = delete_old_messages(Domain, 3, Config), ParsedResult = get_ok_value([data, offline, deleteOldMessages], Result), ?assertEqual(<<"Removed 2 messages">>, ParsedResult). @@ -156,12 +165,12 @@ admin_delete_old_messages_no_domain_test(Config) -> ?assertEqual(<<"domain_not_found">>, get_err_code(Result)). admin_delete_expired_messages_offline_not_configured_test(Config) -> - Result = delete_expired_messages(domain(), Config), - get_not_loaded(Result). + get_not_loaded(delete_expired_messages(domain(), Config)), + get_not_loaded(delete_expired_messages(unprep(domain()), Config)). admin_delete_old_messages_offline_not_configured_test(Config) -> - Result = delete_old_messages(domain(), 2, Config), - get_not_loaded(Result). + get_not_loaded(delete_old_messages(domain(), 2, Config)), + get_not_loaded(delete_old_messages(unprep(domain()), 2, Config)). %% Domain admin test cases From 41609300af8fae96534b368f282446b17f6ec09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Tue, 15 Nov 2022 13:39:33 +0100 Subject: [PATCH 06/44] Use DomainType to stringprep the domain --- priv/graphql/schemas/admin/account.gql | 6 +++--- priv/graphql/schemas/admin/admin_auth_status.gql | 2 +- priv/graphql/schemas/admin/gdpr.gql | 2 +- priv/graphql/schemas/admin/http_upload.gql | 2 +- priv/graphql/schemas/admin/inbox.gql | 2 +- priv/graphql/schemas/admin/last.gql | 6 +++--- priv/graphql/schemas/admin/muc.gql | 4 ++-- priv/graphql/schemas/admin/muc_light.gql | 2 +- priv/graphql/schemas/admin/offline.gql | 4 ++-- priv/graphql/schemas/user/muc.gql | 4 ++-- priv/graphql/schemas/user/muc_light.gql | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/priv/graphql/schemas/admin/account.gql b/priv/graphql/schemas/admin/account.gql index 43a8715f32..e7f83a3481 100644 --- a/priv/graphql/schemas/admin/account.gql +++ b/priv/graphql/schemas/admin/account.gql @@ -3,10 +3,10 @@ Allow admin to get information about accounts. """ type AccountAdminQuery @protected{ "List users per domain" - listUsers(domain: String!): [String!] + listUsers(domain: DomainName!): [String!] @protected(type: DOMAIN, args: ["domain"]) "Get number of users per domain" - countUsers(domain: String!): Int + countUsers(domain: DomainName!): Int @protected(type: DOMAIN, args: ["domain"]) "Check if a password is correct" checkPassword(user: JID!, password: String!): CheckPasswordPayload @@ -24,7 +24,7 @@ Allow admin to manage user accounts. """ type AccountAdminMutation @protected{ "Register a user. Username will be generated when skipped" - registerUser(domain: String!, username: String, password: String!): UserPayload + registerUser(domain: DomainName!, username: String, password: String!): UserPayload @protected(type: DOMAIN, args: ["domain"]) "Remove the user's account along with all the associated personal data" removeUser(user: JID!): UserPayload diff --git a/priv/graphql/schemas/admin/admin_auth_status.gql b/priv/graphql/schemas/admin/admin_auth_status.gql index 799ab3aa0a..e69e630851 100644 --- a/priv/graphql/schemas/admin/admin_auth_status.gql +++ b/priv/graphql/schemas/admin/admin_auth_status.gql @@ -1,7 +1,7 @@ "Information about user request authorization" type AdminAuthInfo{ "Authorized for a domain" - domain: String + domain: DomainName "Authorization status" authStatus: AuthStatus! "Authorization as a " diff --git a/priv/graphql/schemas/admin/gdpr.gql b/priv/graphql/schemas/admin/gdpr.gql index eef5b5a317..829b4f35f3 100644 --- a/priv/graphql/schemas/admin/gdpr.gql +++ b/priv/graphql/schemas/admin/gdpr.gql @@ -1,6 +1,6 @@ "Retrieve user's presonal data" type GdprAdminQuery @protected{ "Retrieves all personal data from MongooseIM for a given user" - retrievePersonalData(username: String!, domain: String!, resultFilepath: String!): String + retrievePersonalData(username: String!, domain: DomainName!, resultFilepath: String!): String @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/admin/http_upload.gql b/priv/graphql/schemas/admin/http_upload.gql index fccffa1dca..22b38c1f79 100644 --- a/priv/graphql/schemas/admin/http_upload.gql +++ b/priv/graphql/schemas/admin/http_upload.gql @@ -3,6 +3,6 @@ Allow admin to generate upload/download URL for a file on user's behalf". """ type HttpUploadAdminMutation @use(modules: ["mod_http_upload"]) @protected{ "Allow admin to generate upload/download URLs for a file on user's behalf" - getUrl(domain: String!, filename: String!, size: Int!, contentType: String!, timeout: Int!): FileUrls + getUrl(domain: DomainName!, filename: String!, size: Int!, contentType: String!, timeout: Int!): FileUrls @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/admin/inbox.gql b/priv/graphql/schemas/admin/inbox.gql index d34096ab23..2517cf3834 100644 --- a/priv/graphql/schemas/admin/inbox.gql +++ b/priv/graphql/schemas/admin/inbox.gql @@ -13,7 +13,7 @@ type InboxAdminMutation @use(modules: ["mod_inbox"]) @protected{ "Flush the whole domain bin and return the number of deleted rows" flushDomainBin( "Domain to be cleared" - domain: String!, + domain: DomainName!, "Remove older than given days or all if null" days: PosInt ): Int @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) diff --git a/priv/graphql/schemas/admin/last.gql b/priv/graphql/schemas/admin/last.gql index ac003c1709..563c1868aa 100644 --- a/priv/graphql/schemas/admin/last.gql +++ b/priv/graphql/schemas/admin/last.gql @@ -6,13 +6,13 @@ type LastAdminQuery @use(modules: ["mod_last"]) @protected{ getLast(user: JID!): LastActivity @use(arg: "user") @protected(type: DOMAIN, args: ["user"]) "Get the number of users active from the given timestamp" - countActiveUsers(domain: String!, timestamp: DateTime): Int + countActiveUsers(domain: DomainName!, timestamp: DateTime): Int @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) """ List users that didn't log in the last days or have never logged in. Globally or for a specified domain """ - listOldUsers(domain: String, timestamp: DateTime!): [OldUser!] + listOldUsers(domain: DomainName, timestamp: DateTime!): [OldUser!] @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) } @@ -27,7 +27,7 @@ type LastAdminMutation @use(modules: ["mod_last"]) @protected{ Delete users that didn't log in the last days or have never logged in. Globally or for a specified domain. Please use listOldUsers to check which users will be deleted """ - removeOldUsers(domain: String, timestamp: DateTime!): [OldUser!] + removeOldUsers(domain: DomainName, timestamp: DateTime!): [OldUser!] @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/admin/muc.gql b/priv/graphql/schemas/admin/muc.gql index 78e788e652..2679e5f686 100644 --- a/priv/graphql/schemas/admin/muc.gql +++ b/priv/graphql/schemas/admin/muc.gql @@ -4,7 +4,7 @@ Allow admin to manage Multi-User Chat rooms. type MUCAdminMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: String!, name: String!, owner: JID!, nick: String!): MUCRoomDesc + createInstantRoom(mucDomain: DomainName!, name: String!, owner: JID!, nick: String!): MUCRoomDesc @protected(type: DOMAIN, args: ["owner"]) "Invite a user to a MUC room" inviteUser(room: JID!, sender: JID!, recipient: JID!, reason: String): String @@ -44,7 +44,7 @@ Allow admin to get information about Multi-User Chat rooms. type MUCAdminQuery @protected @use(modules: ["mod_muc"]){ "Get MUC rooms under the given MUC domain" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - listRooms(mucDomain: String!, from: JID!, limit: Int, index: Int): MUCRoomsPayload! + listRooms(mucDomain: DomainName!, from: JID!, limit: Int, index: Int): MUCRoomsPayload! @protected(type: DOMAIN, args: ["from"]) "Get configuration of the MUC room" getRoomConfig(room: JID!): MUCRoomConfig diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index ae148a6e63..4f6a81adc8 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -4,7 +4,7 @@ Allow admin to manage Multi-User Chat Light rooms. type MUCLightAdminMutation @use(modules: ["mod_muc_light"]) @protected{ "Create a MUC light room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createRoom(mucDomain: String!, name: String!, owner: JID!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room + createRoom(mucDomain: DomainName!, name: String!, owner: JID!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room @protected(type: DOMAIN, args: ["owner"]) "Change configuration of a MUC Light room" changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room diff --git a/priv/graphql/schemas/admin/offline.gql b/priv/graphql/schemas/admin/offline.gql index ca212fd5f2..a21fd1b49d 100644 --- a/priv/graphql/schemas/admin/offline.gql +++ b/priv/graphql/schemas/admin/offline.gql @@ -3,9 +3,9 @@ Allow admin to delete offline messages from specified domain """ type OfflineAdminMutation @protected @use(modules: ["mod_offline"]){ "Delete offline messages whose date has expired" - deleteExpiredMessages(domain: String!): String @use(arg: "domain") + deleteExpiredMessages(domain: DomainName!): String @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) "Delete messages at least as old as the number of days specified in the parameter" - deleteOldMessages(domain: String!, days: Int!): String + deleteOldMessages(domain: DomainName!, days: Int!): String @protected(type: DOMAIN, args: ["domain"]) @use(arg: "domain") } diff --git a/priv/graphql/schemas/user/muc.gql b/priv/graphql/schemas/user/muc.gql index 1430afbe9c..d6dd3732c4 100644 --- a/priv/graphql/schemas/user/muc.gql +++ b/priv/graphql/schemas/user/muc.gql @@ -4,7 +4,7 @@ Allow user to manage Multi-User Chat rooms. type MUCUserMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: String!, name: String!, nick: String!): MUCRoomDesc + createInstantRoom(mucDomain: DomainName!, name: String!, nick: String!): MUCRoomDesc "Invite a user to a MUC room" inviteUser(room: JID!, recipient: JID!, reason: String): String @use(arg: "room") "Kick a user from a MUC room" @@ -33,7 +33,7 @@ Allow user to get information about Multi-User Chat rooms. type MUCUserQuery @protected @use(modules: ["mod_muc"]){ "Get MUC rooms under the given MUC domain" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - listRooms(mucDomain: String!, limit: Int, index: Int): MUCRoomsPayload! + listRooms(mucDomain: DomainName!, limit: Int, index: Int): MUCRoomsPayload! "Get configuration of the MUC room" getRoomConfig(room: JID!): MUCRoomConfig @use(arg: "room") "Get the user list of a given MUC room" diff --git a/priv/graphql/schemas/user/muc_light.gql b/priv/graphql/schemas/user/muc_light.gql index 3263fa692f..34ae3495b1 100644 --- a/priv/graphql/schemas/user/muc_light.gql +++ b/priv/graphql/schemas/user/muc_light.gql @@ -4,7 +4,7 @@ Allow user to manage Multi-User Chat Light rooms. type MUCLightUserMutation @protected @use(modules: ["mod_muc_light"]){ "Create a MUC light room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createRoom(mucDomain: String!, name: String!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room + createRoom(mucDomain: DomainName!, name: String!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room "Change configuration of a MUC Light room" changeRoomConfiguration(room: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room @use(arg: "room") "Invite a user to a MUC Light room" From a4577fbf21491eb0e4040db8e74df699144dcabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Wed, 16 Nov 2022 15:25:39 +0100 Subject: [PATCH 07/44] Update the expected printout in tests --- big_tests/tests/mongooseimctl_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/big_tests/tests/mongooseimctl_SUITE.erl b/big_tests/tests/mongooseimctl_SUITE.erl index 72a27be9c2..ed57643e9f 100644 --- a/big_tests/tests/mongooseimctl_SUITE.erl +++ b/big_tests/tests/mongooseimctl_SUITE.erl @@ -1227,7 +1227,7 @@ expect_existing_commands(Res) -> ?assertMatch({match, _}, re:run(Res, "countUsers")). expect_command_arguments(Res) -> - ?assertMatch({match, _}, re:run(Res, "domain\s+String!")). + ?assertMatch({match, _}, re:run(Res, "domain\s+DomainName!")). %%----------------------------------------------------------------- %% Help tests From 2c8ac74ee73749ea22a67718390f6c0c57d5a79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 18:40:38 +0100 Subject: [PATCH 08/44] Add tests for unprepped domains in session and stat commands Also: add a test verifying that the stats are counted only for the given domain, and not globally. --- big_tests/tests/graphql_session_SUITE.erl | 70 +++++++++++------------ big_tests/tests/graphql_stats_SUITE.erl | 44 ++++++++++---- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/big_tests/tests/graphql_session_SUITE.erl b/big_tests/tests/graphql_session_SUITE.erl index f7ff33905e..d67d4af521 100644 --- a/big_tests/tests/graphql_session_SUITE.erl +++ b/big_tests/tests/graphql_session_SUITE.erl @@ -4,7 +4,9 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). +-import(domain_helper, [domain/0]). -import(graphql_helper, [execute_user_command/5, execute_command/4, get_listener_port/1, get_listener_config/1, get_ok_value/2, get_err_msg/1, get_unauthorized/1]). @@ -224,7 +226,6 @@ domain_admin_list_users_with_status(Config) -> domain_admin_list_users_with_status_story(Config, Alice, _AliceB) -> AliceJID = escalus_client:full_jid(Alice), - Path = [data, session, listUsersWithStatus], AwayStatus = <<"away">>, AwayPresence = escalus_stanza:presence_show(AwayStatus), DndStatus = <<"dnd">>, @@ -234,10 +235,8 @@ domain_admin_list_users_with_status_story(Config, Alice, _AliceB) -> Res = list_users_with_status(null, AwayStatus, Config), get_unauthorized(Res), % List users with away status for a domain - Res2 = list_users_with_status(domain_helper:domain(), AwayStatus, Config), - StatusUsers = get_ok_value(Path, Res2), - ?assertEqual(1, length(StatusUsers)), - check_users([AliceJID], StatusUsers), + assert_list_users_with_status([AliceJID], domain(), AwayStatus, Config), + assert_list_users_with_status([AliceJID], unprep(domain()), AwayStatus, Config), % List users with away status for an external domain Res3 = list_users_with_status(domain_helper:secondary_domain(), AwayStatus, Config), get_unauthorized(Res3), @@ -246,10 +245,7 @@ domain_admin_list_users_with_status_story(Config, Alice, _AliceB) -> Res4 = list_users_with_status(null, DndStatus, Config), get_unauthorized(Res4), % List users with dnd status for a domain - Res5 = list_users_with_status(domain_helper:domain(), DndStatus, Config), - StatusUsers2 = get_ok_value(Path, Res5), - ?assertEqual(1, length(StatusUsers2)), - check_users([AliceJID], StatusUsers2), + assert_list_users_with_status([AliceJID], domain(), DndStatus, Config), % List users with dnd status for an external domain Res6 = list_users_with_status(domain_helper:secondary_domain(), AwayStatus, Config), get_unauthorized(Res6). @@ -259,7 +255,6 @@ domain_admin_count_users_with_status(Config) -> fun domain_admin_count_users_with_status_story/3). domain_admin_count_users_with_status_story(Config, Alice, _AliceB) -> - Path = [data, session, countUsersWithStatus], AwayStatus = <<"away">>, AwayPresence = escalus_stanza:presence_show(AwayStatus), DndStatus = <<"dnd">>, @@ -269,15 +264,14 @@ domain_admin_count_users_with_status_story(Config, Alice, _AliceB) -> Res = count_users_with_status(null, AwayStatus, Config), get_unauthorized(Res), % Count users with away status for a domain - Res2 = count_users_with_status(domain_helper:domain(), AwayStatus, Config), - ?assertEqual(1, get_ok_value(Path, Res2)), + assert_count_users_with_status(1, domain_helper:domain(), AwayStatus, Config), + assert_count_users_with_status(1, unprep(domain_helper:domain()), AwayStatus, Config), % Count users with dnd status globally escalus_client:send(Alice, DndPresence), Res3 = count_users_with_status(null, DndStatus, Config), get_unauthorized(Res3), % Count users with dnd status for a domain - Res4 = count_users_with_status(domain_helper:domain(), DndStatus, Config), - ?assertEqual(1, get_ok_value(Path, Res4)). + assert_count_users_with_status(1, domain_helper:domain(), DndStatus, Config). admin_list_sessions(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {bob, 1}], @@ -293,7 +287,10 @@ admin_list_sessions_story(Config, _Alice, AliceB, _Bob) -> % List sessions for a domain Res2 = list_sessions(BisDomain, Config), Sessions2 = get_ok_value(Path, Res2), - ?assertEqual(1, length(Sessions2)). + ?assertEqual(1, length(Sessions2)), + Res3 = list_sessions(unprep(BisDomain), Config), + Sessions3 = get_ok_value(Path, Res3), + ?assertEqual(1, length(Sessions3)). admin_count_sessions(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {bob, 1}], @@ -308,8 +305,9 @@ admin_count_sessions_story(Config, _Alice, AliceB, _Bob) -> ?assertEqual(3, Number), % Count sessions for a domain Res2 = count_sessions(BisDomain, Config), - Number2 = get_ok_value(Path, Res2), - ?assertEqual(1, Number2). + ?assertEqual(1, get_ok_value(Path, Res2)), + Res3 = count_sessions(unprep(BisDomain), Config), + ?assertEqual(1, get_ok_value(Path, Res3)). admin_list_user_sessions(Config) -> escalus:fresh_story_with_config(Config, [{alice, 2}, {bob, 1}], @@ -352,7 +350,6 @@ admin_count_users_with_status(Config) -> fun admin_count_users_with_status_story/3). admin_count_users_with_status_story(Config, Alice, AliceB) -> - Path = [data, session, countUsersWithStatus], AwayStatus = <<"away">>, AwayPresence = escalus_stanza:presence_show(AwayStatus), DndStatus = <<"dnd">>, @@ -360,15 +357,13 @@ admin_count_users_with_status_story(Config, Alice, AliceB) -> % Count users with away status globally escalus_client:send(Alice, AwayPresence), escalus_client:send(AliceB, AwayPresence), - Res = count_users_with_status(null, AwayStatus, Config), - ?assertEqual(2, get_ok_value(Path, Res)), + assert_count_users_with_status(2, null, AwayStatus, Config), % Count users with away status for a domain - Res2 = count_users_with_status(domain_helper:domain(), AwayStatus, Config), - ?assertEqual(1, get_ok_value(Path, Res2)), + assert_count_users_with_status(1, domain_helper:domain(), AwayStatus, Config), + assert_count_users_with_status(1, unprep(domain_helper:domain()), AwayStatus, Config), % Count users with dnd status globally escalus_client:send(AliceB, DndPresence), - Res3 = count_users_with_status(null, DndStatus, Config), - ?assertEqual(1, get_ok_value(Path, Res3)). + assert_count_users_with_status(1, null, DndStatus, Config). admin_list_users_with_status(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], @@ -377,7 +372,6 @@ admin_list_users_with_status(Config) -> admin_list_users_with_status_story(Config, Alice, AliceB) -> AliceJID = escalus_client:full_jid(Alice), AliceBJID = escalus_client:full_jid(AliceB), - Path = [data, session, listUsersWithStatus], AwayStatus = <<"away">>, AwayPresence = escalus_stanza:presence_show(AwayStatus), DndStatus = <<"dnd">>, @@ -385,21 +379,13 @@ admin_list_users_with_status_story(Config, Alice, AliceB) -> % List users with away status globally escalus_client:send(Alice, AwayPresence), escalus_client:send(AliceB, AwayPresence), - Res = list_users_with_status(null, AwayStatus, Config), - StatusUsers = get_ok_value(Path, Res), - ?assertEqual(2, length(StatusUsers)), - check_users([AliceJID, AliceBJID], StatusUsers), + assert_list_users_with_status([AliceJID, AliceBJID], null, AwayStatus, Config), % List users with away status for a domain - Res2 = list_users_with_status(domain_helper:domain(), AwayStatus, Config), - StatusUsers2 = get_ok_value(Path, Res2), - ?assertEqual(1, length(StatusUsers2)), - check_users([AliceJID], StatusUsers2), + assert_list_users_with_status([AliceJID], domain(), AwayStatus, Config), + assert_list_users_with_status([AliceJID], unprep(domain()), AwayStatus, Config), % List users with dnd status globally escalus_client:send(AliceB, DndPresence), - Res3 = list_users_with_status(null, DndStatus, Config), - StatusUsers3 = get_ok_value(Path, Res3), - ?assertEqual(1, length(StatusUsers3)), - check_users([AliceBJID], StatusUsers3). + assert_list_users_with_status([AliceBJID], null, DndStatus, Config). admin_kick_session(Config) -> escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_kick_session_story/3). @@ -525,6 +511,16 @@ set_presence(JID, Type, Show, Status, Priority, Config) -> %% Helpers +assert_list_users_with_status(ExpectedUsers, Domain, Status, Config) -> + Res = list_users_with_status(Domain, Status, Config), + Users = get_ok_value([data, session, listUsersWithStatus], Res), + check_users(ExpectedUsers, Users). + +assert_count_users_with_status(ExpectedCount, Domain, Status, Config) -> + Res = count_users_with_status(Domain, Status, Config), + Count = get_ok_value([data, session, countUsersWithStatus], Res), + ?assertEqual(ExpectedCount, Count). + -spec check_users([jid:literal_jid()], [#{user := jid:literal_jid()}]) -> boolean(). check_users(Expected, ActualUsers) -> ActualJIDs = [JID || #{<<"user">> := JID} <- ActualUsers], diff --git a/big_tests/tests/graphql_stats_SUITE.erl b/big_tests/tests/graphql_stats_SUITE.erl index 9d17f95335..36f23a0cf8 100644 --- a/big_tests/tests/graphql_stats_SUITE.erl +++ b/big_tests/tests/graphql_stats_SUITE.erl @@ -2,11 +2,13 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(domain_helper, [host_type/0, domain/0, secondary_domain/0]). -import(graphql_helper, [execute_command/4, get_ok_value/2, get_unauthorized/1]). -import(mongooseimctl_helper, [mongooseimctl/3, rpc_call/3]). +-include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). suite() -> @@ -26,16 +28,19 @@ admin_stats_tests() -> [admin_stats_global_test, admin_stats_global_with_users_test, admin_stats_domain_test, - admin_stats_domain_with_users_test]. + admin_stats_domain_with_users_test, + admin_stats_domain_without_users_test]. domain_admin_tests() -> [domain_admin_stats_global_test, admin_stats_domain_test, + admin_stats_domain_with_users_test, domain_admin_stats_domain_no_permission_test]. init_per_suite(Config) -> Config1 = ejabberd_node_utils:init(mim(), Config), - escalus:init_per_suite(Config1). + Config2 = [{auth_mods, mongoose_helper:auth_modules()} | Config1], + escalus:init_per_suite(Config2). end_per_suite(Config) -> escalus:end_per_suite(Config). @@ -51,6 +56,13 @@ end_per_group(_, _Config) -> graphql_helper:clean(), escalus_fresh:clean(). +init_per_testcase(CaseName = admin_stats_domain_without_users_test, Config) -> + case lists:member(ejabberd_auth_ldap, ?config(auth_mods, Config)) of + true -> + {skip, not_supported_with_ldap}; + false -> + escalus:init_per_testcase(CaseName, Config) + end; init_per_testcase(CaseName, Config) -> escalus:init_per_testcase(CaseName, Config). @@ -88,19 +100,28 @@ admin_stats_global_with_users_test(Config, _Alice) -> ?assert(is_not_negative_integer(OutgoingS2S)). admin_stats_domain_test(Config) -> - Result = get_ok_value([data, stat, domainStats], get_domain_stats(domain(), Config)), - #{<<"registeredUsers">> := RegisteredUsers, <<"onlineUsers">> := OnlineUsers} = Result, - ?assertEqual(0, RegisteredUsers), - ?assertEqual(0, OnlineUsers). + Result1 = get_ok_value([data, stat, domainStats], get_domain_stats(domain(), Config)), + ?assertMatch(#{<<"registeredUsers">> := 0, <<"onlineUsers">> := 0}, Result1), + Result2 = get_ok_value([data, stat, domainStats], get_domain_stats(unprep(domain()), Config)), + ?assertMatch(#{<<"registeredUsers">> := 0, <<"onlineUsers">> := 0}, Result2). admin_stats_domain_with_users_test(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_stats_domain_with_users_test/2). admin_stats_domain_with_users_test(Config, _Alice) -> - Result = get_ok_value([data, stat, domainStats], get_domain_stats(domain(), Config)), - #{<<"registeredUsers">> := RegisteredUsers, <<"onlineUsers">> := OnlineUsers} = Result, - ?assertEqual(1, RegisteredUsers), - ?assertEqual(1, OnlineUsers). + Result1 = get_ok_value([data, stat, domainStats], get_domain_stats(domain(), Config)), + ?assertMatch(#{<<"registeredUsers">> := 1, <<"onlineUsers">> := 1}, Result1), + Result2 = get_ok_value([data, stat, domainStats], get_domain_stats(unprep(domain()), Config)), + ?assertMatch(#{<<"registeredUsers">> := 1, <<"onlineUsers">> := 1}, Result2). + +admin_stats_domain_without_users_test(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun admin_stats_domain_without_users_test/2). + +admin_stats_domain_without_users_test(Config, _Alice) -> + %% Alice's session is at domain, not secondary_domain + Result = get_ok_value([data, stat, domainStats], get_domain_stats(secondary_domain(), Config)), + ?assertMatch(#{<<"registeredUsers">> := 0, <<"onlineUsers">> := 0}, Result). % Domain admin test cases @@ -108,7 +129,8 @@ domain_admin_stats_global_test(Config) -> get_unauthorized(get_stats(Config)). domain_admin_stats_domain_no_permission_test(Config) -> - get_unauthorized(get_domain_stats(secondary_domain(), Config)). + get_unauthorized(get_domain_stats(secondary_domain(), Config)), + get_unauthorized(get_domain_stats(unprep(secondary_domain()), Config)). % Commands From b2007a7974a3138712c394749a88d7dce8f27145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 18:42:52 +0100 Subject: [PATCH 09/44] Use DomainName in the remaining GraphQL commands --- priv/graphql/schemas/admin/domain.gql | 2 +- priv/graphql/schemas/admin/session.gql | 8 ++++---- priv/graphql/schemas/admin/stats.gql | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/priv/graphql/schemas/admin/domain.gql b/priv/graphql/schemas/admin/domain.gql index 56f8b435cf..2638ecbbb0 100644 --- a/priv/graphql/schemas/admin/domain.gql +++ b/priv/graphql/schemas/admin/domain.gql @@ -1,6 +1,6 @@ type DomainAdminQuery @use(services: ["service_domain_db"]) @protected{ "Get all enabled domains by hostType. Only for global admin" - domainsByHostType(hostType: String!): [String!] + domainsByHostType(hostType: String!): [DomainName!] @protected(type: GLOBAL) @use "Get information about the domain" domainDetails(domain: DomainName!): Domain diff --git a/priv/graphql/schemas/admin/session.gql b/priv/graphql/schemas/admin/session.gql index 3e6724c97d..1a528a9455 100644 --- a/priv/graphql/schemas/admin/session.gql +++ b/priv/graphql/schemas/admin/session.gql @@ -3,10 +3,10 @@ Allow admin to get information about sessions. """ type SessionAdminQuery @protected{ "Get the list of established sessions for a specified domain or globally" - listSessions(domain: String): [Session!] + listSessions(domain: DomainName): [Session!] @protected(type: DOMAIN, args: ["domain"]) "Get the number of established sessions for a specified domain or globally" - countSessions(domain: String): Int + countSessions(domain: DomainName): Int @protected(type: DOMAIN, args: ["domain"]) "Get information about all sessions of a user" listUserSessions(user: JID!): [Session!] @@ -18,10 +18,10 @@ type SessionAdminQuery @protected{ getUserResource(user: JID!, number: Int): String @protected(type: DOMAIN, args: ["user"]) "Get the list of logged users with this status for a specified domain or globally" - listUsersWithStatus(domain: String, status: String!): [UserStatus!] + listUsersWithStatus(domain: DomainName, status: String!): [UserStatus!] @protected(type: DOMAIN, args: ["domain"]) "Get the number of logged users with this status for a specified domain or globally" - countUsersWithStatus(domain: String, status: String!): Int + countUsersWithStatus(domain: DomainName, status: String!): Int @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/admin/stats.gql b/priv/graphql/schemas/admin/stats.gql index a0dfecd78b..24d7dd72c6 100644 --- a/priv/graphql/schemas/admin/stats.gql +++ b/priv/graphql/schemas/admin/stats.gql @@ -4,7 +4,7 @@ type StatsAdminQuery @protected{ globalStats: GlobalStats @protected(type: GLOBAL) "Get statistics from a specific domain" - domainStats(domain: String!): DomainStats + domainStats(domain: DomainName!): DomainStats @protected(type: DOMAIN, args: ["domain"]) } From bbfa690c4377624f9ce4c1de26ffa57fe9cf17fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 12:18:38 +0100 Subject: [PATCH 10/44] Test string-prepping of user names in GraphQL API --- big_tests/tests/graphql_account_SUITE.erl | 9 ++++++--- big_tests/tests/graphql_gdpr_SUITE.erl | 24 +++++++++++------------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/big_tests/tests/graphql_account_SUITE.erl b/big_tests/tests/graphql_account_SUITE.erl index 93793e3aa6..b0f8c1dd99 100644 --- a/big_tests/tests/graphql_account_SUITE.erl +++ b/big_tests/tests/graphql_account_SUITE.erl @@ -312,12 +312,15 @@ admin_register_user(Config) -> % Try to register a user with existing name Resp2 = register_user(Domain, Username, Password, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)), - % Try again, this time with a domain name that is not stringprepped - Resp3 = register_user(unprep(Domain), Username, Password, Config), + % Try again, this time with a name that is not stringprepped + Resp3 = register_user(unprep(Domain), unprep(Username), Password, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"already registered">>)), % Try to register a user without any name Resp4 = register_user(Domain, <<>>, Password, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp4), <<"Invalid JID">>)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Resp4), <<"empty_user_name">>)), + % Try to register a user with an invalid name + Resp5 = register_user(Domain, <<"@invalid">>, Password, Config), + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Resp5), <<"failed_to_parse_user_name">>)). admin_register_random_user(Config) -> Password = <<"my_password">>, diff --git a/big_tests/tests/graphql_gdpr_SUITE.erl b/big_tests/tests/graphql_gdpr_SUITE.erl index e876c34149..89f054c074 100644 --- a/big_tests/tests/graphql_gdpr_SUITE.erl +++ b/big_tests/tests/graphql_gdpr_SUITE.erl @@ -26,7 +26,7 @@ groups() -> admin_gdpr_tests() -> [admin_retrieve_user_data, - admin_retrieve_user_data_for_unprepped_domain, + admin_retrieve_user_data_with_unprepped_name, admin_gdpr_no_user_test, admin_gdpr_empty_filename_test, admin_gdpr_access_denied_erofs, @@ -36,7 +36,7 @@ admin_gdpr_tests() -> domain_admin_gdpr_tests() -> [admin_retrieve_user_data, - admin_retrieve_user_data_for_unprepped_domain, + admin_retrieve_user_data_with_unprepped_name, admin_gdpr_no_user_test, domain_admin_retrieve_user_data_no_permission]. @@ -68,22 +68,22 @@ end_per_testcase(CaseName, Config) -> % Admin test cases admin_retrieve_user_data(Config) -> - Config1 = [{domain, domain()} | Config], - escalus:fresh_story_with_config(Config1, [{alice, 1}], fun admin_retrieve_user_data/2). + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + User = escalus_users:get_username(Config1, alice), + admin_retrieve_user_data(Config1, User, domain()). -admin_retrieve_user_data_for_unprepped_domain(Config) -> - Config1 = [{domain, unprep(domain())} | Config], - escalus:fresh_story_with_config(Config1, [{alice, 1}], fun admin_retrieve_user_data/2). +admin_retrieve_user_data_with_unprepped_name(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + User = escalus_users:get_username(Config1, alice), + admin_retrieve_user_data(Config1, unprep(User), unprep(domain())). -admin_retrieve_user_data(Config, Alice) -> +admin_retrieve_user_data(Config, User, Domain) -> Filename = random_filename(Config), - Domain = ?config(domain, Config), - Res = admin_retrieve_personal_data(escalus_client:username(Alice), Domain, - list_to_binary(Filename), Config), + Res = admin_retrieve_personal_data(User, Domain, list_to_binary(Filename), Config), ParsedResult = get_ok_value([data, gdpr, retrievePersonalData], Res), ?assertEqual(<<"Data retrieved">>, ParsedResult), FullPath = get_mim_cwd() ++ "/" ++ Filename, - Dir = make_dir_name(Filename, escalus_client:username(Alice)), + Dir = make_dir_name(Filename, User), ct:log("extracting logs ~s", [Dir]), ?assertMatch({ok, _}, zip:extract(FullPath, [{cwd, Dir}])). From 32b7bb3a620b1e5951415eec26d2f5603352c3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 12:21:00 +0100 Subject: [PATCH 11/44] Add UserName GraphQL type --- priv/graphql/schemas/admin/account.gql | 2 +- priv/graphql/schemas/admin/gdpr.gql | 2 +- priv/graphql/schemas/global/scalar_types.gql | 3 ++- priv/graphql/schemas/user/user_auth_status.gql | 2 +- src/graphql/mongoose_graphql_scalar.erl | 12 ++++++++++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/priv/graphql/schemas/admin/account.gql b/priv/graphql/schemas/admin/account.gql index e7f83a3481..d749c7f7c2 100644 --- a/priv/graphql/schemas/admin/account.gql +++ b/priv/graphql/schemas/admin/account.gql @@ -24,7 +24,7 @@ Allow admin to manage user accounts. """ type AccountAdminMutation @protected{ "Register a user. Username will be generated when skipped" - registerUser(domain: DomainName!, username: String, password: String!): UserPayload + registerUser(domain: DomainName!, username: UserName, password: String!): UserPayload @protected(type: DOMAIN, args: ["domain"]) "Remove the user's account along with all the associated personal data" removeUser(user: JID!): UserPayload diff --git a/priv/graphql/schemas/admin/gdpr.gql b/priv/graphql/schemas/admin/gdpr.gql index 829b4f35f3..db2422aaab 100644 --- a/priv/graphql/schemas/admin/gdpr.gql +++ b/priv/graphql/schemas/admin/gdpr.gql @@ -1,6 +1,6 @@ "Retrieve user's presonal data" type GdprAdminQuery @protected{ "Retrieves all personal data from MongooseIM for a given user" - retrievePersonalData(username: String!, domain: DomainName!, resultFilepath: String!): String + retrievePersonalData(username: UserName!, domain: DomainName!, resultFilepath: String!): String @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/global/scalar_types.gql b/priv/graphql/schemas/global/scalar_types.gql index 66f95b575a..8a6ee7b9ff 100644 --- a/priv/graphql/schemas/global/scalar_types.gql +++ b/priv/graphql/schemas/global/scalar_types.gql @@ -6,7 +6,8 @@ scalar Stanza @spectaql(options: [{ key: "example", value: "Hi!" }]) scalar JID @spectaql(options: [{ key: "example", value: "alice@localhost" }]) "The JID with resource" scalar FullJID @spectaql(options: [{ key: "example", value: "alice@localhost/res1" }]) -"XMPP Domain name (domain part of a JID)" +"XMPP user name (local part of a JID)" +scalar UserName @spectaql(options: [{ key: "example", value: "alice" }]) scalar DomainName @spectaql(options: [{ key: "example", value: "localhost" }]) "String that contains at least one character" scalar NonEmptyString @spectaql(options: [{ key: "example", value: "xyz789" }]) diff --git a/priv/graphql/schemas/user/user_auth_status.gql b/priv/graphql/schemas/user/user_auth_status.gql index 0f15e6a9d6..3f1e8f6967 100644 --- a/priv/graphql/schemas/user/user_auth_status.gql +++ b/priv/graphql/schemas/user/user_auth_status.gql @@ -1,7 +1,7 @@ "Inforamtion about user request authorization" type UserAuthInfo{ "Authorized as user with name" - username: String + username: UserName "Authorization status" authStatus: AuthStatus } diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index 305e07ddc0..516c806402 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -13,6 +13,7 @@ input(<<"DateTime">>, DT) -> binary_to_microseconds(DT); input(<<"Stanza">>, Value) -> exml:parse(Value); input(<<"JID">>, Jid) -> jid_from_binary(Jid); +input(<<"UserName">>, User) -> user_from_binary(User); input(<<"DomainName">>, Domain) -> domain_from_binary(Domain); input(<<"FullJID">>, Jid) -> full_jid_from_binary(Jid); input(<<"NonEmptyString">>, Value) -> non_empty_string_to_binary(Value); @@ -30,6 +31,7 @@ input(Ty, V) -> output(<<"DateTime">>, DT) -> {ok, microseconds_to_binary(DT)}; output(<<"Stanza">>, Elem) -> {ok, exml:to_binary(Elem)}; output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)}; +output(<<"UserName">>, User) -> {ok, User}; output(<<"DomainName">>, Domain) -> {ok, Domain}; output(<<"NonEmptyString">>, Value) -> binary_to_non_empty_string(Value); output(<<"PosInt">>, Value) -> validate_pos_integer(Value); @@ -45,6 +47,16 @@ jid_from_binary(Value) -> {ok, Jid} end. +user_from_binary(<<>>) -> + {error, empty_user_name}; +user_from_binary(Value) -> + case jid:nodeprep(Value) of + error -> + {error, failed_to_parse_user_name}; + User -> + {ok, User} + end. + domain_from_binary(<<>>) -> {error, empty_domain_name}; domain_from_binary(Value) -> From e9917ce07dce6f4824555662aa3344deb8f117e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 12:24:33 +0100 Subject: [PATCH 12/44] Test string-prepping of room names in GraphQL API --- big_tests/tests/graphql_muc_SUITE.erl | 37 ++++++++++++--------- big_tests/tests/graphql_muc_light_SUITE.erl | 33 ++++++++++++++++-- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index 7aaaf7efe6..eb2cb981e7 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -41,7 +41,7 @@ admin_groups() -> user_muc_tests() -> [user_create_and_delete_room, - user_create_room_with_unprepped_domain, + user_create_room_with_unprepped_name, user_try_delete_nonexistent_room, user_try_delete_room_by_not_owner, user_try_create_instant_room_with_nonexistent_domain, @@ -99,7 +99,7 @@ user_muc_not_configured_tests() -> admin_muc_tests() -> [admin_create_and_delete_room, - admin_create_room_with_unprepped_domain, + admin_create_room_with_unprepped_name, admin_try_create_instant_room_with_nonexistent_domain, admin_try_create_instant_room_with_nonexistent_user, admin_try_delete_nonexistent_room, @@ -154,7 +154,7 @@ admin_muc_not_configured_tests() -> domain_admin_muc_tests() -> [admin_create_and_delete_room, - admin_create_room_with_unprepped_domain, + admin_create_room_with_unprepped_name, admin_try_create_instant_room_with_nonexistent_domain, admin_try_delete_nonexistent_room, domain_admin_create_and_delete_room_no_permission, @@ -318,14 +318,16 @@ admin_create_and_delete_room_story(Config, Alice) -> Res4 = list_rooms(MUCServer, Alice, null, null, Config), ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))). -admin_create_room_with_unprepped_domain(Config) -> +admin_create_room_with_unprepped_name(Config) -> FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), AliceJid = escalus_users:get_jid(FreshConfig, alice), - Name = rand_name(), - MUCServer = unprep(muc_helper:muc_host()), - Res = create_instant_room(MUCServer, Name, AliceJid, <<"Ali">>, FreshConfig), + Name = <<$a, (rand_name())/binary>>, % make it start with a letter + MUCServer = muc_helper:muc_host(), + Res = create_instant_room(unprep(MUCServer), unprep(Name), AliceJid, <<"Ali">>, FreshConfig), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, - get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)). + get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)), + Res2 = list_rooms(MUCServer, AliceJid, null, null, Config), + ?assert(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res2))). admin_try_create_instant_room_with_nonexistent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -990,14 +992,14 @@ user_create_and_delete_room_story(Config, Alice) -> Res4 = user_list_rooms(Alice, MUCServer, null, null, Config), ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))). -user_create_room_with_unprepped_domain(Config) -> +user_create_room_with_unprepped_name(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], - fun user_create_room_with_unprepped_domain_story/2). + fun user_create_room_with_unprepped_name_story/2). -user_create_room_with_unprepped_domain_story(Config, Alice) -> - Name = rand_name(), - MUCServer = unprep(muc_helper:muc_host()), - Res = user_create_instant_room(Alice, MUCServer, Name, <<"Ali">>, Config), +user_create_room_with_unprepped_name_story(Config, Alice) -> + Name = <<$a, (rand_name())/binary>>, % make it start with a letter + MUCServer = muc_helper:muc_host(), + Res = user_create_instant_room(Alice, unprep(MUCServer), unprep(Name), <<"Ali">>, Config), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)). @@ -1015,7 +1017,7 @@ user_try_create_instant_room_with_invalid_name(Config) -> user_try_create_instant_room_with_invalid_name_story(Config, Alice) -> Res = user_create_instant_room(Alice, muc_helper:muc_host(), <<"test room">>, <<"Ali">>, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"Room name or domain is invalid">>)). + assert_coercion_err(Res, <<"failed_to_parse_room_name">>). user_try_delete_nonexistent_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -1655,8 +1657,11 @@ user_list_room_affiliations_muc_not_configured_story(Config, Alice) -> %% Helpers +assert_coercion_err(Res, Code) -> + ?assertNotEqual(nomatch, binary:match(get_coercion_err_msg(Res), Code)). + assert_no_full_jid(Res) -> - ?assertNotEqual(nomatch, binary:match(get_coercion_err_msg(Res), <<"jid_without_resource">>)). + assert_coercion_err(Res, <<"jid_without_resource">>). assert_no_permission(Res) -> ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not have permission">>)). diff --git a/big_tests/tests/graphql_muc_light_SUITE.erl b/big_tests/tests/graphql_muc_light_SUITE.erl index 7e09b0517d..8093464ce8 100644 --- a/big_tests/tests/graphql_muc_light_SUITE.erl +++ b/big_tests/tests/graphql_muc_light_SUITE.erl @@ -66,6 +66,7 @@ user_muc_light_tests() -> user_create_room_with_unprepped_domain, user_create_room_with_custom_fields, user_create_identified_room, + user_create_room_with_unprepped_id, user_change_room_config, user_change_room_config_errors, user_invite_user, @@ -99,6 +100,7 @@ admin_muc_light_tests() -> admin_create_room_with_unprepped_domain, admin_create_room_with_custom_fields, admin_create_identified_room, + admin_create_room_with_unprepped_id, admin_change_room_config, admin_change_room_config_with_custom_fields, admin_change_room_config_errors, @@ -129,6 +131,7 @@ domain_admin_muc_light_tests() -> admin_create_room_with_custom_fields, domain_admin_create_room_no_permission, admin_create_identified_room, + admin_create_room_with_unprepped_id, domain_admin_create_identified_room_no_permission, admin_change_room_config, admin_change_room_config_with_custom_fields, @@ -313,7 +316,21 @@ user_create_identified_room_story(Config, Alice) -> ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)), % Try with an empty string passed as ID Res4 = user_create_room(Alice, MucServer, <<"name">>, Subject, <<>>, Config), - ?assertNotEqual(nomatch, binary:match(get_coercion_err_msg(Res4), <<"Given string is empty">>)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Res4), <<"empty_room_name">>)). + +user_create_room_with_unprepped_id(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_create_room_with_unprepped_id_story/2). + +user_create_room_with_unprepped_id_story(Config, Alice) -> + MucServer = ?config(muc_light_host, Config), + Name = <<"first room">>, + Subject = <<"testing">>, + Id = <<"user_room_with_unprepped_id">>, + Res = user_create_room(Alice, unprep(MucServer), Name, Subject, unprep(Id), Config), + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = + get_ok_value(?CREATE_ROOM_PATH, Res), + ?assertMatch(#jid{luser = Id, lserver = MucServer}, jid:from_binary_noprep(JID)). user_change_room_config(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_change_room_config_story/2). @@ -1001,7 +1018,19 @@ admin_create_identified_room_story(Config, Alice) -> ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)), % Try with an empty string passed as ID Res4 = create_room(MucServer, <<"name">>, AliceBin, Subject, <<>>, Config), - ?assertNotEqual(nomatch, binary:match(get_coercion_err_msg(Res4), <<"Given string is empty">>)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Res4), <<"empty_room_name">>)). + +admin_create_room_with_unprepped_id(Config) -> + FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceBin = escalus_users:get_jid(FreshConfig, alice), + MucServer = ?config(muc_light_host, FreshConfig), + Name = <<"first room">>, + Subject = <<"testing">>, + Id = <<"room_with_unprepped_id">>, + Res = create_room(unprep(MucServer), Name, AliceBin, Subject, unprep(Id), FreshConfig), + #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject} = + get_ok_value(?CREATE_ROOM_PATH, Res), + ?assertMatch(#jid{luser = Id, lserver = MucServer}, jid:from_binary_noprep(JID)). admin_change_room_config(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_change_room_config_story/2). From 90dd50e649936a86f7541cfc0dfbf6454ef9c583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 17 Nov 2022 12:26:17 +0100 Subject: [PATCH 13/44] Add RoomName GraphQL type --- priv/graphql/schemas/admin/muc.gql | 2 +- priv/graphql/schemas/admin/muc_light.gql | 2 +- priv/graphql/schemas/global/scalar_types.gql | 3 +++ priv/graphql/schemas/user/muc.gql | 2 +- priv/graphql/schemas/user/muc_light.gql | 2 +- src/graphql/mongoose_graphql_scalar.erl | 12 ++++++++++++ 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/priv/graphql/schemas/admin/muc.gql b/priv/graphql/schemas/admin/muc.gql index 2679e5f686..63114dabb1 100644 --- a/priv/graphql/schemas/admin/muc.gql +++ b/priv/graphql/schemas/admin/muc.gql @@ -4,7 +4,7 @@ Allow admin to manage Multi-User Chat rooms. type MUCAdminMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: DomainName!, name: String!, owner: JID!, nick: String!): MUCRoomDesc + createInstantRoom(mucDomain: DomainName!, name: RoomName!, owner: JID!, nick: String!): MUCRoomDesc @protected(type: DOMAIN, args: ["owner"]) "Invite a user to a MUC room" inviteUser(room: JID!, sender: JID!, recipient: JID!, reason: String): String diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index 4f6a81adc8..aae9ae3359 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -4,7 +4,7 @@ Allow admin to manage Multi-User Chat Light rooms. type MUCLightAdminMutation @use(modules: ["mod_muc_light"]) @protected{ "Create a MUC light room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createRoom(mucDomain: DomainName!, name: String!, owner: JID!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room + createRoom(mucDomain: DomainName!, name: String!, owner: JID!, subject: String!, id: RoomName, options: [RoomConfigDictEntryInput!]): Room @protected(type: DOMAIN, args: ["owner"]) "Change configuration of a MUC Light room" changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room diff --git a/priv/graphql/schemas/global/scalar_types.gql b/priv/graphql/schemas/global/scalar_types.gql index 8a6ee7b9ff..c5a93b4e0a 100644 --- a/priv/graphql/schemas/global/scalar_types.gql +++ b/priv/graphql/schemas/global/scalar_types.gql @@ -8,6 +8,9 @@ scalar JID @spectaql(options: [{ key: "example", value: "alice@localhost" }]) scalar FullJID @spectaql(options: [{ key: "example", value: "alice@localhost/res1" }]) "XMPP user name (local part of a JID)" scalar UserName @spectaql(options: [{ key: "example", value: "alice" }]) +"XMPP room name (local part of a JID)" +scalar RoomName @spectaql(options: [{ key: "example", value: "my-chat-room" }]) +"XMPP domain name (domain part of a JID)" scalar DomainName @spectaql(options: [{ key: "example", value: "localhost" }]) "String that contains at least one character" scalar NonEmptyString @spectaql(options: [{ key: "example", value: "xyz789" }]) diff --git a/priv/graphql/schemas/user/muc.gql b/priv/graphql/schemas/user/muc.gql index d6dd3732c4..18511bb0ae 100644 --- a/priv/graphql/schemas/user/muc.gql +++ b/priv/graphql/schemas/user/muc.gql @@ -4,7 +4,7 @@ Allow user to manage Multi-User Chat rooms. type MUCUserMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: DomainName!, name: String!, nick: String!): MUCRoomDesc + createInstantRoom(mucDomain: DomainName!, name: RoomName!, nick: String!): MUCRoomDesc "Invite a user to a MUC room" inviteUser(room: JID!, recipient: JID!, reason: String): String @use(arg: "room") "Kick a user from a MUC room" diff --git a/priv/graphql/schemas/user/muc_light.gql b/priv/graphql/schemas/user/muc_light.gql index 34ae3495b1..4d0c246fd6 100644 --- a/priv/graphql/schemas/user/muc_light.gql +++ b/priv/graphql/schemas/user/muc_light.gql @@ -4,7 +4,7 @@ Allow user to manage Multi-User Chat Light rooms. type MUCLightUserMutation @protected @use(modules: ["mod_muc_light"]){ "Create a MUC light room under the given XMPP hostname" #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createRoom(mucDomain: DomainName!, name: String!, subject: String!, id: NonEmptyString, options: [RoomConfigDictEntryInput!]): Room + createRoom(mucDomain: DomainName!, name: String!, subject: String!, id: RoomName, options: [RoomConfigDictEntryInput!]): Room "Change configuration of a MUC Light room" changeRoomConfiguration(room: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room @use(arg: "room") "Invite a user to a MUC Light room" diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index 516c806402..faacc599ea 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -14,6 +14,7 @@ input(<<"DateTime">>, DT) -> binary_to_microseconds(DT); input(<<"Stanza">>, Value) -> exml:parse(Value); input(<<"JID">>, Jid) -> jid_from_binary(Jid); input(<<"UserName">>, User) -> user_from_binary(User); +input(<<"RoomName">>, Room) -> room_from_binary(Room); input(<<"DomainName">>, Domain) -> domain_from_binary(Domain); input(<<"FullJID">>, Jid) -> full_jid_from_binary(Jid); input(<<"NonEmptyString">>, Value) -> non_empty_string_to_binary(Value); @@ -32,6 +33,7 @@ output(<<"DateTime">>, DT) -> {ok, microseconds_to_binary(DT)}; output(<<"Stanza">>, Elem) -> {ok, exml:to_binary(Elem)}; output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)}; output(<<"UserName">>, User) -> {ok, User}; +output(<<"RoomName">>, Room) -> {ok, Room}; output(<<"DomainName">>, Domain) -> {ok, Domain}; output(<<"NonEmptyString">>, Value) -> binary_to_non_empty_string(Value); output(<<"PosInt">>, Value) -> validate_pos_integer(Value); @@ -57,6 +59,16 @@ user_from_binary(Value) -> {ok, User} end. +room_from_binary(<<>>) -> + {error, empty_room_name}; +room_from_binary(Value) -> + case jid:nodeprep(Value) of + error -> + {error, failed_to_parse_room_name}; + Room -> + {ok, Room} + end. + domain_from_binary(<<>>) -> {error, empty_domain_name}; domain_from_binary(Value) -> From d8842e1b6de051717d8e0f40c31b5df80b635243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Fri, 18 Nov 2022 18:22:27 +0100 Subject: [PATCH 14/44] Test resource prepping in GraphQL --- big_tests/tests/graphql_muc_SUITE.erl | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index eb2cb981e7..4a89aefa15 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -74,6 +74,7 @@ user_muc_tests() -> user_participant_set_user_role, user_try_set_nonexistent_room_role, user_can_enter_room, + user_cannot_enter_room_with_invalid_resource, user_can_enter_room_with_password, user_can_exit_room, user_list_room_affiliations, @@ -1118,8 +1119,8 @@ user_send_message_to_room_with_specified_res_story(Config, _Alice, Bob, Bob2) -> BobNick = <<"Bobek">>, enter_room(RoomJID, Bob2, BobNick), escalus:wait_for_stanza(Bob2), - % Send message - Res = user_send_message_to_room(Bob, RoomJID, Message, <<"res2">>, Config), + % Send message, the resource should be normalized to "res2" + Res = user_send_message_to_room(Bob, RoomJID, Message, <<"res₂"/utf8>>, Config), ?assertNotEqual(nomatch, binary:match(get_ok_value(?SEND_MESSAGE_PATH, Res), <<"successfully">>)), assert_is_message_correct(RoomJID, BobNick, <<"groupchat">>, Message, @@ -1155,8 +1156,8 @@ user_send_private_message_with_specified_res(Config, Alice, Alice2, Bob) -> enter_room(RoomJID, Bob, BobNick), enter_room(RoomJID, Alice2, AliceNick), escalus:wait_for_stanzas(Bob, 2), - % Send message - Res = user_send_private_message(Alice, RoomJID, Message, BobNick, <<"res2">>, Config), + % Send message, the resource should be normalized to "res2" + Res = user_send_private_message(Alice, RoomJID, Message, BobNick, <<"res₂"/utf8>>, Config), assert_success(?SEND_PRIV_MESG_PATH, Res), assert_is_message_correct(RoomJID, AliceNick, <<"chat">>, Message, escalus:wait_for_stanza(Bob)). @@ -1451,11 +1452,23 @@ user_can_enter_room(Config, Alice) -> RoomJID = jid:from_binary(?config(room_jid, Config)), Nick = <<"ali">>, JID = jid:from_binary(escalus_utils:jid_to_lower(escalus_client:full_jid(Alice))), - Resource = escalus_client:resource(Alice), - Res = user_enter_room(Alice, RoomJID, Nick, Resource, null, Config), + % Resource should be normalized to "res1", which is Alice's connected resource + Res = user_enter_room(Alice, RoomJID, Nick, <<"res₁"/utf8>>, null, Config), assert_success(?ENTER_ROOM_PATH, Res), ?assertMatch([#{nick := Nick, jid := JID}], get_room_users(RoomJID)). +user_cannot_enter_room_with_invalid_resource(Config) -> + muc_helper:story_with_room(Config, [], [{alice, 1}], + fun user_cannot_enter_room_with_invalid_resource/2). + +user_cannot_enter_room_with_invalid_resource(Config, Alice) -> + RoomJID = jid:from_binary(?config(room_jid, Config)), + Nick = <<"ali">>, + Res1 = user_enter_room(Alice, RoomJID, Nick, <<"\n">>, null, Config), + assert_coercion_err(Res1, <<"failed_to_parse_resource_name">>), + Res2 = user_enter_room(Alice, RoomJID, Nick, <<>>, null, Config), + assert_coercion_err(Res2, <<"empty_resource_name">>). + user_can_enter_room_with_password(Config) -> muc_helper:story_with_room(Config, [{password_protected, true}, {password, ?PASSWORD}], [{alice, 1}, {bob, 1}], @@ -1486,10 +1499,10 @@ user_can_exit_room(Config) -> user_can_exit_room(Config, Alice) -> RoomJID = jid:from_binary(?config(room_jid, Config)), Nick = <<"ali">>, - Resource = escalus_client:resource(Alice), enter_room(RoomJID, Alice, Nick), ?assertMatch([_], get_room_users(RoomJID)), - Res = user_exit_room(Alice, RoomJID, Nick, Resource, Config), + % Resource should be normalized to "res1", which is Alice's connected resource + Res = user_exit_room(Alice, RoomJID, Nick, <<"res₁"/utf8>>, Config), assert_success(?EXIT_ROOM_PATH, Res), ?assertMatch([], get_room_users(RoomJID)). From 5af8b3c1cdd95e50b029543773caf57f67fee791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Fri, 18 Nov 2022 18:28:27 +0100 Subject: [PATCH 15/44] Add ResourceName GraphQL type --- priv/graphql/schemas/admin/session.gql | 2 +- priv/graphql/schemas/global/scalar_types.gql | 2 ++ priv/graphql/schemas/user/muc.gql | 8 ++++---- priv/graphql/schemas/user/session.gql | 2 +- src/graphql/mongoose_graphql_scalar.erl | 12 ++++++++++++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/priv/graphql/schemas/admin/session.gql b/priv/graphql/schemas/admin/session.gql index 1a528a9455..5782e4bd07 100644 --- a/priv/graphql/schemas/admin/session.gql +++ b/priv/graphql/schemas/admin/session.gql @@ -15,7 +15,7 @@ type SessionAdminQuery @protected{ countUserResources(user: JID!): Int @protected(type: DOMAIN, args: ["user"]) "Get the resource string of the n-th session of a user" - getUserResource(user: JID!, number: Int): String + getUserResource(user: JID!, number: Int): ResourceName @protected(type: DOMAIN, args: ["user"]) "Get the list of logged users with this status for a specified domain or globally" listUsersWithStatus(domain: DomainName, status: String!): [UserStatus!] diff --git a/priv/graphql/schemas/global/scalar_types.gql b/priv/graphql/schemas/global/scalar_types.gql index c5a93b4e0a..a9490e3929 100644 --- a/priv/graphql/schemas/global/scalar_types.gql +++ b/priv/graphql/schemas/global/scalar_types.gql @@ -12,6 +12,8 @@ scalar UserName @spectaql(options: [{ key: "example", value: "alice" }]) scalar RoomName @spectaql(options: [{ key: "example", value: "my-chat-room" }]) "XMPP domain name (domain part of a JID)" scalar DomainName @spectaql(options: [{ key: "example", value: "localhost" }]) +"XMPP resource name (resource part of a JID)" +scalar ResourceName @spectaql(options: [{ key: "example", value: "res1" }]) "String that contains at least one character" scalar NonEmptyString @spectaql(options: [{ key: "example", value: "xyz789" }]) "Integer that has a value above zero" diff --git a/priv/graphql/schemas/user/muc.gql b/priv/graphql/schemas/user/muc.gql index 18511bb0ae..0435ce42b0 100644 --- a/priv/graphql/schemas/user/muc.gql +++ b/priv/graphql/schemas/user/muc.gql @@ -10,9 +10,9 @@ type MUCUserMutation @protected @use(modules: ["mod_muc"]){ "Kick a user from a MUC room" kickUser(room: JID!, nick: String!, reason: String): String @use(arg: "room") "Send a message to a MUC room" - sendMessageToRoom(room: JID!, body: String!, resource: String): String @use(arg: "room") + sendMessageToRoom(room: JID!, body: String!, resource: ResourceName): String @use(arg: "room") "Send a private message to a MUC room user from the given resource" - sendPrivateMessage(room: JID!, toNick: String!, body: String!, resource: String): String @use(arg: "room") + sendPrivateMessage(room: JID!, toNick: String!, body: String!, resource: ResourceName): String @use(arg: "room") "Remove a MUC room" deleteRoom(room: JID!, reason: String): String @use(arg: "room") "Change configuration of a MUC room" @@ -22,9 +22,9 @@ type MUCUserMutation @protected @use(modules: ["mod_muc"]){ "Change a user affiliation" setUserAffiliation(room: JID!, user: JID!, affiliation: MUCAffiliation!): String @use(arg: "room") "Enter the room with given resource and nick" - enterRoom(room: JID!, nick: String!, resource: String!, password: String): String @use(arg: "room") + enterRoom(room: JID!, nick: String!, resource: ResourceName!, password: String): String @use(arg: "room") "Exit the room with given resource and nick" - exitRoom(room: JID!, nick: String!, resource: String!): String @use(arg: "room") + exitRoom(room: JID!, nick: String!, resource: ResourceName!): String @use(arg: "room") } """ diff --git a/priv/graphql/schemas/user/session.gql b/priv/graphql/schemas/user/session.gql index d351a83ac6..a958a1029a 100644 --- a/priv/graphql/schemas/user/session.gql +++ b/priv/graphql/schemas/user/session.gql @@ -3,7 +3,7 @@ Allow user to get information about sessions. """ type SessionUserQuery @protected{ "List connected resources" - listResources: [String!] + listResources: [ResourceName!] "Count connected resources" countResources: Int "Get information about all sessions" diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index faacc599ea..175e0cfcb6 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -16,6 +16,7 @@ input(<<"JID">>, Jid) -> jid_from_binary(Jid); input(<<"UserName">>, User) -> user_from_binary(User); input(<<"RoomName">>, Room) -> room_from_binary(Room); input(<<"DomainName">>, Domain) -> domain_from_binary(Domain); +input(<<"ResourceName">>, Res) -> resource_from_binary(Res); input(<<"FullJID">>, Jid) -> full_jid_from_binary(Jid); input(<<"NonEmptyString">>, Value) -> non_empty_string_to_binary(Value); input(<<"PosInt">>, Value) -> validate_pos_integer(Value); @@ -35,6 +36,7 @@ output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)}; output(<<"UserName">>, User) -> {ok, User}; output(<<"RoomName">>, Room) -> {ok, Room}; output(<<"DomainName">>, Domain) -> {ok, Domain}; +output(<<"ResourceName">>, Res) -> {ok, Res}; output(<<"NonEmptyString">>, Value) -> binary_to_non_empty_string(Value); output(<<"PosInt">>, Value) -> validate_pos_integer(Value); output(Ty, V) -> @@ -79,6 +81,16 @@ domain_from_binary(Value) -> {ok, Domain} end. +resource_from_binary(<<>>) -> + {error, empty_resource_name}; +resource_from_binary(Value) -> + case jid:resourceprep(Value) of + error -> + {error, failed_to_parse_resource_name}; + Res -> + {ok, Res} + end. + full_jid_from_binary(Value) -> case jid_from_binary(Value) of {ok, #jid{lresource = <<>>}} -> From 59c7fa2d67683ee5a5f0dd05c66f8e8233193e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= <37941643+pawlooss1@users.noreply.github.com> Date: Mon, 21 Nov 2022 08:39:07 +0100 Subject: [PATCH 16/44] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kamil Wąż --- src/auth/ejabberd_auth_anonymous.erl | 2 +- src/mod_last.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/ejabberd_auth_anonymous.erl b/src/auth/ejabberd_auth_anonymous.erl index 1b3e4883d9..d3b4d123c1 100644 --- a/src/auth/ejabberd_auth_anonymous.erl +++ b/src/auth/ejabberd_auth_anonymous.erl @@ -180,7 +180,7 @@ purge_hook(true, HostType, LUser, LServer) -> Acc :: mongoose_acc:t(), Params :: map(), Extra :: map(). -session_cleanup(Acc, #{sid := SID, jid := #jid{luser = LUser,lserver = LServer}}, _Extra) -> +session_cleanup(Acc, #{sid := SID, jid := #jid{luser = LUser, lserver = LServer}}, _Extra) -> remove_connection(SID, LUser, LServer), {ok, Acc}. diff --git a/src/mod_last.erl b/src/mod_last.erl index 398f651b33..342ee8bb74 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -247,7 +247,7 @@ remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> -spec on_presence_update(Acc, Params, Extra) -> {ok, Acc} when Acc :: mongoose_acc:t(), - Params :: #{jid := jid:jid(), status := binary()}, + Params :: #{jid := jid:jid(), status := status()}, Extra :: gen_hook:extra(). on_presence_update(Acc, #{jid := #jid{luser = LUser, lserver = LServer}, status := Status}, _) -> {ok, store_last_info(Acc, LUser, LServer, Status)}. From 1d4a7ffae3e3a19aa1c1002615820d2063cf56ff Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Thu, 17 Nov 2022 15:46:53 +0100 Subject: [PATCH 17/44] Improve error handling in offline --- src/ejabberd_admin.erl | 8 ++++---- src/offline/mod_offline_api.erl | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index e422b1f257..346f5b49ed 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -334,16 +334,16 @@ do_register(List) -> %%% Purge DB %%% --spec delete_expired_messages(jid:lserver()) -> {ok, iolist()} | {error, iolist()}. +-spec delete_expired_messages(binary()) -> {ok, iolist()} | {error, iolist()}. delete_expired_messages(Domain) -> - case mod_offline_api:delete_expired_messages(Domain) of + case mod_offline_api:delete_expired_messages(jid:nameprep(Domain)) of {ok, _} = Result -> Result; {_, Message} -> {error, Message} end. --spec delete_old_messages(jid:lserver(), Days :: integer()) -> {ok, iolist()} | {error, iolist()}. +-spec delete_old_messages(binary(), Days :: integer()) -> {ok, iolist()} | {error, iolist()}. delete_old_messages(Domain, Days) -> - case mod_offline_api:delete_old_messages(Domain, Days) of + case mod_offline_api:delete_old_messages(jid:nameprep(Domain), Days) of {ok, _} = Result -> Result; {_, Message} -> {error, Message} end. diff --git a/src/offline/mod_offline_api.erl b/src/offline/mod_offline_api.erl index 9c76fc5ee1..12bed44108 100644 --- a/src/offline/mod_offline_api.erl +++ b/src/offline/mod_offline_api.erl @@ -2,25 +2,25 @@ -export([delete_expired_messages/1, delete_old_messages/2]). --spec delete_expired_messages(jid:lserver()) -> - {ok | domain_not_found | server_error, iolist()}. +-type(api_result()) :: {ok, string()} | {domain_not_found | server_error, string()}. + +-spec delete_expired_messages(jid:lserver()) -> api_result(). delete_expired_messages(Domain) -> - call_for_loaded_module(Domain, fun remove_expired_messages/2, {Domain}). + call_for_loaded_module(Domain, fun remove_expired_messages/2, [Domain]). --spec delete_old_messages(jid:lserver(), Days :: integer()) -> - {ok | domain_not_found | server_error, iolist()}. +-spec delete_old_messages(jid:lserver(), Days :: integer()) -> api_result(). delete_old_messages(Domain, Days) -> - call_for_loaded_module(Domain, fun remove_old_messages/2, {Domain, Days}). + call_for_loaded_module(Domain, fun remove_old_messages/3, [Domain, Days]). call_for_loaded_module(Domain, Function, Args) -> case mongoose_domain_api:get_domain_host_type(Domain) of {ok, HostType} -> - Function(Args, HostType); + apply(Function, [HostType | Args]); {error, not_found} -> {domain_not_found, "Unknown domain"} end. -remove_old_messages({Domain, Days}, HostType) -> +remove_old_messages(HostType, Domain, Days) -> case mod_offline:remove_old_messages(HostType, Domain, Days) of {ok, C} -> {ok, io_lib:format("Removed ~p messages", [C])}; @@ -28,7 +28,7 @@ remove_old_messages({Domain, Days}, HostType) -> {server_error, io_lib:format("Can't remove old messages: ~n~p", [Reason])} end. -remove_expired_messages({Domain}, HostType) -> +remove_expired_messages(HostType, Domain) -> case mod_offline:remove_expired_messages(HostType, Domain) of {ok, C} -> {ok, io_lib:format("Removed ~p messages", [C])}; From 3717f25d4b6047a4bcca2421b5c5833b560f889d Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Thu, 17 Nov 2022 11:55:48 +0100 Subject: [PATCH 18/44] Improve error handling in token --- big_tests/tests/graphql_token_SUITE.erl | 32 +++++++++++++++--- big_tests/tests/oauth_SUITE.erl | 4 +-- .../mongoose_graphql_token_admin_mutation.erl | 4 +-- .../mongoose_graphql_last_user_mutation.erl | 2 +- .../user/mongoose_graphql_last_user_query.erl | 2 +- .../mongoose_graphql_token_user_mutation.erl | 6 ++-- src/mod_auth_token.erl | 7 ++-- src/mod_auth_token_api.erl | 33 ++++++++----------- 8 files changed, 54 insertions(+), 36 deletions(-) diff --git a/big_tests/tests/graphql_token_SUITE.erl b/big_tests/tests/graphql_token_SUITE.erl index aa49c37bb0..906141f9c0 100644 --- a/big_tests/tests/graphql_token_SUITE.erl +++ b/big_tests/tests/graphql_token_SUITE.erl @@ -2,6 +2,7 @@ -compile([export_all, nowarn_export_all]). +-import(common_helper, [unprep/1]). -import(distributed_helper, [require_rpc_nodes/1, mim/0]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, get_ok_value/2, get_err_code/1, get_unauthorized/1, get_not_loaded/1]). @@ -46,17 +47,21 @@ user_token_not_configured_tests() -> domain_admin_tests() -> [admin_request_token_test, + admin_request_token_test_unprep, domain_admin_request_token_no_permission_test, domain_admin_revoke_token_no_permission_test, admin_revoke_token_no_token_test, - admin_revoke_token_test]. + admin_revoke_token_test, + admin_revoke_token_test_unprep]. admin_tests() -> [admin_request_token_test, + admin_request_token_test_unprep, admin_request_token_no_user_test, admin_revoke_token_no_user_test, admin_revoke_token_no_token_test, - admin_revoke_token_test]. + admin_revoke_token_test, + admin_revoke_token_test_unprep]. admin_token_not_configured_tests() -> [admin_request_token_test_not_configured, @@ -155,7 +160,7 @@ user_revoke_token_test(Config, Alice) -> user_request_token(Alice, Config), Res2 = user_revoke_token(Alice, Config), ParsedRes = get_ok_value([data, token, revokeToken], Res2), - ?assertEqual(<<"Revoked.">>, ParsedRes). + ?assertEqual(<<"Revoked">>, ParsedRes). % User test cases mod_token not configured @@ -213,6 +218,16 @@ admin_request_token_test(Config, Alice) -> ?assert(is_binary(Refresh)), ?assert(is_binary(Access)). +admin_request_token_test_unprep(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_request_token_test_unprep/2). + +admin_request_token_test_unprep(Config, Alice) -> + Res = admin_request_token(unprep(user_to_bin(Alice)), Config), + #{<<"refresh">> := Refresh, <<"access">> := Access} = + get_ok_value([data, token, requestToken], Res), + ?assert(is_binary(Refresh)), + ?assert(is_binary(Access)). + admin_request_token_no_user_test(Config) -> Res = admin_request_token(<<"AAAAA">>, Config), ?assertEqual(<<"not_found">>, get_err_code(Res)). @@ -235,7 +250,16 @@ admin_revoke_token_test(Config, Alice) -> admin_request_token(user_to_bin(Alice), Config), Res2 = admin_revoke_token(user_to_bin(Alice), Config), ParsedRes = get_ok_value([data, token, revokeToken], Res2), - ?assertEqual(<<"Revoked.">>, ParsedRes). + ?assertEqual(<<"Revoked">>, ParsedRes). + +admin_revoke_token_test_unprep(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_revoke_token_test_unprep/2). + +admin_revoke_token_test_unprep(Config, Alice) -> + admin_request_token(unprep(user_to_bin(Alice)), Config), + Res2 = admin_revoke_token(unprep(user_to_bin(Alice)), Config), + ParsedRes = get_ok_value([data, token, revokeToken], Res2), + ?assertEqual(<<"Revoked">>, ParsedRes). % Admin test cases token not configured diff --git a/big_tests/tests/oauth_SUITE.erl b/big_tests/tests/oauth_SUITE.erl index 24cf4515f8..527e107e57 100644 --- a/big_tests/tests/oauth_SUITE.erl +++ b/big_tests/tests/oauth_SUITE.erl @@ -291,7 +291,7 @@ revoke_token_cmd_when_no_token(Config) -> %% when revoking token R = mimctl(Config, ["revoke_token", escalus_users:get_jid(Config, bob)]), %% then no token was found - "Error: \"User or token not found.\"\n" = R. + "Error: \"User or token not found\"\n" = R. revoke_token_cmd(Config) -> %% given existing user and token present in the database @@ -299,7 +299,7 @@ revoke_token_cmd(Config) -> %% when R = mimctl(Config, ["revoke_token", escalus_users:get_jid(Config, bob)]), %% then - "Revoked.\n" = R. + "Revoked\n" = R. token_removed_on_user_removal(Config) -> %% given existing user with token and XMPP (de)registration available diff --git a/src/graphql/admin/mongoose_graphql_token_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_token_admin_mutation.erl index cb6929a44d..9c1f4ecfe5 100644 --- a/src/graphql/admin/mongoose_graphql_token_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_token_admin_mutation.erl @@ -20,12 +20,12 @@ request_token(JID) -> case mod_auth_token_api:create_token(JID) of {ok, _} = Result -> Result; - {error, Error} -> make_error(Error, #{user => JID}) + Error -> make_error(Error, #{user => JID}) end. -spec revoke_token(jid:jid()) -> {ok, string()} | {error, resolver_error()}. revoke_token(JID) -> case mod_auth_token_api:revoke_token_command(JID) of {ok, _} = Result -> Result; - {error, Error} -> make_error(Error, #{user => JID}) + Error -> make_error(Error, #{user => JID}) end. diff --git a/src/graphql/user/mongoose_graphql_last_user_mutation.erl b/src/graphql/user/mongoose_graphql_last_user_mutation.erl index 21e082943d..8bc70ad1c0 100644 --- a/src/graphql/user/mongoose_graphql_last_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_last_user_mutation.erl @@ -11,7 +11,7 @@ -type last_info() :: map(). -type args() :: mongoose_graphql:args(). --type ctx() :: mongoose_graphql:ctx(). +-type ctx() :: mongoose_graphql:context(). execute(Ctx, last, <<"setLast">>, Args) -> set_last(Ctx, Args). diff --git a/src/graphql/user/mongoose_graphql_last_user_query.erl b/src/graphql/user/mongoose_graphql_last_user_query.erl index d3655e075d..11cc710d2c 100644 --- a/src/graphql/user/mongoose_graphql_last_user_query.erl +++ b/src/graphql/user/mongoose_graphql_last_user_query.erl @@ -11,7 +11,7 @@ -type last_info() :: mongoose_graphql_last_helper:last_info(). -type args() :: mongoose_graphql:args(). --type ctx() :: mongoose_graphql:ctx(). +-type ctx() :: mongoose_graphql:context(). execute(Ctx, last, <<"getLast">>, Args) -> get_last(Ctx, Args). diff --git a/src/graphql/user/mongoose_graphql_token_user_mutation.erl b/src/graphql/user/mongoose_graphql_token_user_mutation.erl index e2ee3c0702..1be13f2d92 100644 --- a/src/graphql/user/mongoose_graphql_token_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_token_user_mutation.erl @@ -11,7 +11,7 @@ -type token_info() :: map(). -type args() :: mongoose_graphql:args(). - -type ctx() :: mongoose_graphql:ctx(). + -type ctx() :: mongoose_graphql:context(). execute(Ctx, token, <<"requestToken">>, Args) -> request_token(Ctx, Args); @@ -22,12 +22,12 @@ request_token(#{user := JID}, #{}) -> case mod_auth_token_api:create_token(JID) of {ok, _} = Result -> Result; - {error, Error} -> make_error(Error, #{user => JID}) + Error -> make_error(Error, #{user => JID}) end. -spec revoke_token(ctx(), args()) -> {ok, string()} | {error, resolver_error()}. revoke_token(#{user := JID}, #{}) -> case mod_auth_token_api:revoke_token_command(JID) of {ok, _} = Result -> Result; - {error, Error} -> make_error(Error, #{user => JID}) + Error -> make_error(Error, #{user => JID}) end. diff --git a/src/mod_auth_token.erl b/src/mod_auth_token.erl index 11161c83ab..b90851939e 100644 --- a/src/mod_auth_token.erl +++ b/src/mod_auth_token.erl @@ -416,13 +416,14 @@ key_name(refresh) -> token_secret; key_name(provision) -> provision_pre_shared. -spec revoke_token_command(Owner) -> ResTuple when - Owner :: jid:jid(), + Owner :: binary(), ResCode :: ok | not_found | error, ResTuple :: {ResCode, string()}. revoke_token_command(Owner) -> - case mod_auth_token_api:revoke_token_command(Owner) of + JID = jid:from_binary(Owner), + case mod_auth_token_api:revoke_token_command(JID) of {ok, _} = Result -> Result; - {error, Error} -> Error + Error -> Error end. -spec clean_tokens(Acc, Params, Extra) -> {ok, Acc} when diff --git a/src/mod_auth_token_api.erl b/src/mod_auth_token_api.erl index be4305de10..7734116c14 100644 --- a/src/mod_auth_token_api.erl +++ b/src/mod_auth_token_api.erl @@ -10,47 +10,40 @@ -spec revoke_token_command(User) -> Result when User :: jid:jid(), - Reason :: {not_found | internal_server_error, string()}, - Result :: {ok, string()} | {error, Reason}. + Result :: {ok, string()} | {not_found | internal_server_error, string()}. revoke_token_command(User) -> - #jid{lserver = LServer} = Jid = convert_user(User), + #jid{lserver = LServer} = User, case mongoose_domain_api:get_domain_host_type(LServer) of {ok, HostType} -> - try mod_auth_token:revoke(HostType, Jid) of + try mod_auth_token:revoke(HostType, User) of not_found -> - {error, {not_found, "User or token not found."}}; + {not_found, "User or token not found"}; ok -> - {ok, "Revoked."}; + {ok, "Revoked"}; error -> - {error, {internal_server_error, "Internal server error."}} + {internal_server_error, "Internal server error"} catch Class:Reason:Stacktrace -> ?LOG_ERROR(#{what => auth_token_revoke_failed, class => Class, reason => Reason, stacktrace => Stacktrace}), - {error, {internal_server_error, "Internal server error."}} + {internal_server_error, "Internal server error"} end; _ -> - {error, {not_found, "Unknown domain"}} + {not_found, "Unknown domain"} end. -spec create_token(User) -> Result when User :: jid:jid(), - Reason :: {not_found | internal_server_error, string()}, - Result :: {ok, #{binary() => string()}} | {error, Reason}. + Result :: {ok, #{binary() => string()}} | {not_found | internal_server_error, string()}. create_token(User) -> - #jid{lserver = LServer} = Jid = convert_user(User), + #jid{lserver = LServer} = User, case mongoose_domain_api:get_domain_host_type(LServer) of {ok, HostType} -> - case {token(HostType, Jid, access), token(HostType, Jid, refresh)} of + case {token(HostType, User, access), token(HostType, User, refresh)} of {#token{} = AccessToken, #token{} = RefreshToken} -> {ok, #{<<"access">> => serialize(AccessToken), <<"refresh">> => serialize(RefreshToken)}}; - _ -> {error, {internal_server_error, "Internal server errror."}} + _ -> {internal_server_error, "Internal server error"} end; _ -> - {error, {not_found, "Unknown domain"}} + {not_found, "Unknown domain"} end. - -convert_user(User) when is_binary(User) -> - jid:from_binary(User); -convert_user(User) -> - User. From 9cee7b5fae6495da5626b37cfe8ef52c3ed9a61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Mon, 21 Nov 2022 09:07:01 +0100 Subject: [PATCH 19/44] Refactored hook handlers in mod_private --- src/mod_private.erl | 55 ++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/mod_private.erl b/src/mod_private.erl index 60f4ebdb60..efc75ea868 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -42,10 +42,6 @@ -export([config_metrics/1]). --ignore_xref([ - behaviour_info/1, get_personal_data/3, remove_user/3, remove_domain/3 -]). - -include("mongoose.hrl"). -include("jlib.hrl"). -include("mongoose_config_spec.hrl"). @@ -55,17 +51,21 @@ %% gdpr callback %%-------------------------------------------------------------------- --spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data(). -get_personal_data(Acc, HostType, #jid{ luser = LUser, lserver = LServer }) -> +-spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when + Acc :: gdpr:personal_data(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> Schema = ["ns", "xml"], NSs = mod_private_backend:get_all_nss(HostType, LUser, LServer), Entries = lists:map( fun(NS) -> Data = mod_private_backend:multi_get_data( HostType, LUser, LServer, [{NS, default}]), - { NS, exml:to_binary(Data) } + {NS, exml:to_binary(Data)} end, NSs), - [{private, Schema, Entries} | Acc]. + NewAcc = [{private, Schema, Entries} | Acc], + {ok, NewAcc}. %% ------------------------------------------------------------------ %% gen_mod callbacks @@ -73,22 +73,23 @@ get_personal_data(Acc, HostType, #jid{ luser = LUser, lserver = LServer }) -> -spec start(HostType :: mongooseim:host_type(), Opts :: gen_mod:module_opts()) -> ok | {error, atom()}. start(HostType, #{iqdisc := IQDisc} = Opts) -> mod_private_backend:init(HostType, Opts), - ejabberd_hooks:add(hooks(HostType)), + gen_hook:add_handlers(hooks(HostType)), gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_PRIVATE, ejabberd_sm, fun ?MODULE:process_iq/5, #{}, IQDisc). -spec stop(HostType :: mongooseim:host_type()) -> ok | {error, not_registered}. stop(HostType) -> - ejabberd_hooks:delete(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_PRIVATE, ejabberd_sm). supported_features() -> [dynamic_domains]. +-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). hooks(HostType) -> - [{remove_user, HostType, ?MODULE, remove_user, 50}, - {remove_domain, HostType, ?MODULE, remove_domain, 50}, - {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50}, - {get_personal_data, HostType, ?MODULE, get_personal_data, 50}]. + [{remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}, + {anonymous_purge_hook, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50}]. config_spec() -> #section{ @@ -116,20 +117,22 @@ riak_config_spec() -> %% ------------------------------------------------------------------ %% Handlers -remove_user(Acc, User, Server) -> - HostType = mongoose_acc:host_type(Acc), - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), +-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> R = mod_private_backend:remove_user(HostType, LUser, LServer), - mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}), - Acc. - --spec remove_domain(mongoose_hooks:simple_acc(), - mongooseim:host_type(), jid:lserver()) -> - mongoose_hooks:simple_acc(). -remove_domain(Acc, HostType, Domain) -> + mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, LUser, LServer}), + {ok, Acc}. + +-spec remove_domain(Acc, Params, Extra) -> {ok , Acc} when + Acc :: mongoose_domain_api:remove_domain_acc(), + Params :: #{domain := jid:lserver()}, + Extra :: gen_hook:extra(). +remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> mod_private_backend:remove_domain(HostType, Domain), - Acc. + {ok, Acc}. process_iq(Acc, From = #jid{lserver = LServer, luser = LUser}, From 166834dddbff6ecdfe4178732af4313fe5f505d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Mon, 21 Nov 2022 10:26:00 +0100 Subject: [PATCH 20/44] Refactored hook handlers in mod_push_service_mongoosepush --- src/mod_push_service_mongoosepush.erl | 26 ++++++++++++++++---------- src/mongoose_hooks.erl | 3 +-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/mod_push_service_mongoosepush.erl b/src/mod_push_service_mongoosepush.erl index 5f423f6e83..ae0f057c91 100644 --- a/src/mod_push_service_mongoosepush.erl +++ b/src/mod_push_service_mongoosepush.erl @@ -25,13 +25,13 @@ -export([start/2, stop/1, config_spec/0]). %% Hooks and IQ handlers --export([push_notifications/4]). +-export([push_notifications/3]). -export([http_notification/5]). -export([config_metrics/1]). --ignore_xref([http_notification/5, push_notifications/4]). +-ignore_xref([http_notification/5]). %%-------------------------------------------------------------------- %% Module callbacks @@ -42,7 +42,7 @@ start(Host, Opts) -> ?LOG_INFO(#{what => push_service_starting, server => Host}), start_pool(Host, Opts), %% Hooks - ejabberd_hooks:add(push_notifications, Host, ?MODULE, push_notifications, 10), + gen_hook:add_handlers(hooks(Host)), ok. -spec start_pool(mongooseim:host_type(), gen_mod:module_opts()) -> term(). @@ -56,7 +56,7 @@ pool_opts(#{max_http_connections := MaxHTTPConnections}) -> -spec stop(Host :: jid:server()) -> ok. stop(Host) -> - ejabberd_hooks:delete(push_notifications, Host, ?MODULE, push_notifications, 10), + gen_hook:delete_handlers(hooks(Host)), mongoose_wpool:stop(generic, Host, mongoosepush_service), ok. @@ -79,12 +79,18 @@ config_spec() -> %% Hooks %%-------------------------------------------------------------------- +-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). +hooks(HostType) -> + [{push_notifications, HostType, fun ?MODULE:push_notifications/3, #{}, 10}]. + %% Hook 'push_notifications' --spec push_notifications(AccIn :: ok | mongoose_acc:t(), Host :: jid:server(), - Notifications :: [#{binary() => binary()}], - Options :: #{binary() => binary()}) -> - ok | {error, Reason :: term()}. -push_notifications(AccIn, Host, Notifications, Options = #{<<"device_id">> := DeviceId}) -> +-spec push_notifications(Acc, Params, Extra) -> {ok, ok | {error, Reason :: term()}} when + Acc :: ok | mongoose_acc:t(), + Params :: #{notification_forms := [#{atom() => binary()}], options := #{binary() => binary()}}, + Extra :: gen_hook:extra(). +push_notifications(AccIn, + #{notification_forms := Notifications, options := Options = #{<<"device_id">> := DeviceId}}, + #{host_type := Host}) -> ?LOG_DEBUG(#{what => push_notifications, notifications => Notifications, opts => Options, acc => AccIn}), @@ -97,7 +103,7 @@ push_notifications(AccIn, Host, Notifications, Options = #{<<"device_id">> := De Payload = jiffy:encode(JSON), call(Host, ?MODULE, http_notification, [Host, post, Path, ReqHeaders, Payload]) end, - send_push_notifications(Notifications, Fun, ok). + {ok, send_push_notifications(Notifications, Fun, ok)}. send_push_notifications([], _, Result) -> Result; diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 7582c88696..810891372d 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -353,8 +353,7 @@ presence_probe_hook(HostType, Acc, From, To, Pid) -> Options :: #{atom() => binary()}, Result :: ok | {error, any()}. push_notifications(HostType, Acc, NotificationForms, Options) -> - Params = #{host_type => HostType, options => Options, - notification_forms => NotificationForms}, + Params = #{options => Options, notification_forms => NotificationForms}, Args = [HostType, NotificationForms, Options], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), run_hook_for_host_type(push_notifications, HostType, Acc, ParamsWithLegacyArgs). From e141077dd1e587a9e6c9f469031ba4c85e812b7b Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Mon, 21 Nov 2022 12:01:32 +0100 Subject: [PATCH 21/44] Apply review comments --- big_tests/tests/graphql_offline_SUITE.erl | 12 +++++++++++- priv/graphql/schemas/admin/offline.gql | 2 +- src/offline/mod_offline_api.erl | 10 +++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/big_tests/tests/graphql_offline_SUITE.erl b/big_tests/tests/graphql_offline_SUITE.erl index a5379cb1df..23b82e7afa 100644 --- a/big_tests/tests/graphql_offline_SUITE.erl +++ b/big_tests/tests/graphql_offline_SUITE.erl @@ -6,7 +6,7 @@ -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(domain_helper, [host_type/0, domain/0]). -import(graphql_helper, [execute_command/4, get_ok_value/2, get_err_code/1, user_to_bin/1, - get_unauthorized/1, get_not_loaded/1]). + get_unauthorized/1, get_not_loaded/1, get_coercion_err_msg/1]). -import(config_parser_helper, [mod_config/2]). -import(mongooseimctl_helper, [mongooseimctl/3, rpc_call/3]). @@ -44,6 +44,7 @@ admin_offline_tests() -> admin_delete_old_messages_test, admin_delete_expired_messages2_test, admin_delete_old_messages2_test, + admin_delete_old_messages_invalid_days, admin_delete_expired_messages_no_domain_test, admin_delete_old_messages_no_domain_test]. @@ -56,6 +57,7 @@ domain_admin_offline_tests() -> admin_delete_old_messages_test, admin_delete_expired_messages2_test, admin_delete_old_messages2_test, + admin_delete_old_messages_invalid_days, domain_admin_delete_expired_messages_no_permission_test, domain_admin_delete_old_messages_no_permission_test]. @@ -148,6 +150,14 @@ admin_delete_old_messages2_test(Config, JidMike, JidKate) -> admin_delete_old_messages2(Config, JidMike, JidKate, domain()), admin_delete_old_messages2(Config, JidMike, JidKate, unprep(domain())). +admin_delete_old_messages_invalid_days(Config) -> + Result = delete_old_messages(domain(), -1, Config), + ParsedResult = get_coercion_err_msg(Result), + ?assertMatch({_, _}, binary:match(ParsedResult, <<"Value is not a positive integer">>)), + Result2 = delete_old_messages(domain(), 0, Config), + ParsedResult2 = get_coercion_err_msg(Result2), + ?assertMatch({_, _}, binary:match(ParsedResult2, <<"Value is not a positive integer">>)). + admin_delete_old_messages2(Config, JidMike, JidKate, Domain) -> generate_message(JidMike, JidKate, 2, 1), % not old enough generate_message(JidMike, JidKate, 5, -1), diff --git a/priv/graphql/schemas/admin/offline.gql b/priv/graphql/schemas/admin/offline.gql index a21fd1b49d..a2aa2e4c13 100644 --- a/priv/graphql/schemas/admin/offline.gql +++ b/priv/graphql/schemas/admin/offline.gql @@ -6,6 +6,6 @@ type OfflineAdminMutation @protected @use(modules: ["mod_offline"]){ deleteExpiredMessages(domain: DomainName!): String @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) "Delete messages at least as old as the number of days specified in the parameter" - deleteOldMessages(domain: DomainName!, days: Int!): String + deleteOldMessages(domain: DomainName!, days: PosInt!): String @protected(type: DOMAIN, args: ["domain"]) @use(arg: "domain") } diff --git a/src/offline/mod_offline_api.erl b/src/offline/mod_offline_api.erl index 12bed44108..171c7f76ec 100644 --- a/src/offline/mod_offline_api.erl +++ b/src/offline/mod_offline_api.erl @@ -2,17 +2,17 @@ -export([delete_expired_messages/1, delete_old_messages/2]). --type(api_result()) :: {ok, string()} | {domain_not_found | server_error, string()}. +-type(api_result()) :: {ok, string()} | {domain_not_found | server_error, iolist()}. -spec delete_expired_messages(jid:lserver()) -> api_result(). delete_expired_messages(Domain) -> - call_for_loaded_module(Domain, fun remove_expired_messages/2, [Domain]). + call_for_host_type(Domain, fun remove_expired_messages/2, [Domain]). --spec delete_old_messages(jid:lserver(), Days :: integer()) -> api_result(). +-spec delete_old_messages(jid:lserver(), Days :: pos_integer()) -> api_result(). delete_old_messages(Domain, Days) -> - call_for_loaded_module(Domain, fun remove_old_messages/3, [Domain, Days]). + call_for_host_type(Domain, fun remove_old_messages/3, [Domain, Days]). -call_for_loaded_module(Domain, Function, Args) -> +call_for_host_type(Domain, Function, Args) -> case mongoose_domain_api:get_domain_host_type(Domain) of {ok, HostType} -> apply(Function, [HostType | Args]); From 4341bc211fac4a121b2f03189f69a6c5387f8675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20W=C4=85=C5=BC?= Date: Mon, 21 Nov 2022 15:07:09 +0100 Subject: [PATCH 22/44] Update src/offline/mod_offline_api.erl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Chrząszcz --- src/offline/mod_offline_api.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/offline/mod_offline_api.erl b/src/offline/mod_offline_api.erl index 171c7f76ec..a0895a02fb 100644 --- a/src/offline/mod_offline_api.erl +++ b/src/offline/mod_offline_api.erl @@ -2,7 +2,7 @@ -export([delete_expired_messages/1, delete_old_messages/2]). --type(api_result()) :: {ok, string()} | {domain_not_found | server_error, iolist()}. +-type(api_result()) :: {ok | domain_not_found | server_error, iolist()}. -spec delete_expired_messages(jid:lserver()) -> api_result(). delete_expired_messages(Domain) -> From e90c45779eabdd16f89038f965e17e45c4e00495 Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Mon, 21 Nov 2022 09:02:39 +0100 Subject: [PATCH 23/44] Apply review comments --- big_tests/tests/graphql_muc_SUITE.erl | 2 +- big_tests/tests/graphql_private_SUITE.erl | 8 +++--- big_tests/tests/graphql_token_SUITE.erl | 26 +++++++++--------- big_tests/tests/graphql_vcard_SUITE.erl | 32 +++++++++++------------ src/graphql/mongoose_graphql_scalar.erl | 2 ++ src/vcard/mod_vcard_api.erl | 4 +-- 6 files changed, 38 insertions(+), 36 deletions(-) diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index 7aaaf7efe6..d1d17502c0 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -1615,7 +1615,7 @@ user_owner_set_user_affiliation_muc_not_configured(Config) -> fun user_owner_set_user_affiliation_muc_not_configured_story/2). user_owner_set_user_affiliation_muc_not_configured_story(Config, Alice) -> - Res = user_set_user_affiliation(Alice, get_room_name(), <<"ali">>, member, Config), + Res = user_set_user_affiliation(Alice, get_room_name(), <<"eddie@localhost">>, member, Config), get_not_loaded(Res). user_moderator_set_user_role_muc_not_configured(Config) -> diff --git a/big_tests/tests/graphql_private_SUITE.erl b/big_tests/tests/graphql_private_SUITE.erl index 530d9a78d1..787ed16240 100644 --- a/big_tests/tests/graphql_private_SUITE.erl +++ b/big_tests/tests/graphql_private_SUITE.erl @@ -201,11 +201,11 @@ admin_get_private(Config, Alice) -> no_user_error_set(Config) -> ElemStr = exml:to_binary(private_input()), - Result = admin_set_private(<<"AAAAA">>, ElemStr, Config), + Result = admin_set_private(<<"eddie@otherhost">>, ElemStr, Config), ?assertEqual(<<"not_found">>, get_err_code(Result)). no_user_error_get(Config) -> - Result = admin_get_private(<<"AAAAA">>, <<"my_element">>, <<"alice:private:ns">>, Config), + Result = admin_get_private(<<"eddie@otherhost">>, <<"my_element">>, <<"alice:private:ns">>, Config), ?assertEqual(<<"not_found">>, get_err_code(Result)). private_input() -> @@ -241,7 +241,7 @@ domain_admin_user_set_private_no_permission(Config, AliceBis) -> ElemStr = exml:to_binary(private_input()), Result = admin_set_private(user_to_bin(AliceBis), ElemStr, Config), get_unauthorized(Result), - Result2 = admin_set_private(<<"AAAAA">>, ElemStr, Config), + Result2 = admin_set_private(<<"eddie@otherhost">>, ElemStr, Config), get_unauthorized(Result2). domain_admin_user_get_private_no_permission(Config) -> @@ -252,7 +252,7 @@ domain_admin_user_get_private_no_permission(Config, AliceBis) -> AliceBisBin = user_to_bin(AliceBis), Result = admin_get_private(AliceBisBin, <<"my_element">>, <<"alice:private:ns">>, Config), get_unauthorized(Result), - Result2 = admin_get_private(<<"AAAAA">>, <<"my_element">>, <<"alice:private:ns">>, Config), + Result2 = admin_get_private(<<"eddie@otherhost">>, <<"my_element">>, <<"alice:private:ns">>, Config), get_unauthorized(Result2). %% Commands diff --git a/big_tests/tests/graphql_token_SUITE.erl b/big_tests/tests/graphql_token_SUITE.erl index 906141f9c0..c417ce957f 100644 --- a/big_tests/tests/graphql_token_SUITE.erl +++ b/big_tests/tests/graphql_token_SUITE.erl @@ -5,7 +5,7 @@ -import(common_helper, [unprep/1]). -import(distributed_helper, [require_rpc_nodes/1, mim/0]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, - get_ok_value/2, get_err_code/1, get_unauthorized/1, get_not_loaded/1]). + get_ok_value/2, get_err_code/1, get_err_msg/1, get_unauthorized/1, get_not_loaded/1]). -include_lib("eunit/include/eunit.hrl"). @@ -57,8 +57,8 @@ domain_admin_tests() -> admin_tests() -> [admin_request_token_test, admin_request_token_test_unprep, - admin_request_token_no_user_test, - admin_revoke_token_no_user_test, + admin_request_token_no_host_test, + admin_revoke_token_no_host_test, admin_revoke_token_no_token_test, admin_revoke_token_test, admin_revoke_token_test_unprep]. @@ -190,8 +190,8 @@ domain_admin_request_token_no_permission_test(Config, AliceBis) -> % External domain user Res = admin_request_token(user_to_bin(AliceBis), Config), get_unauthorized(Res), - % Non-existing user - Res2 = admin_request_token(<<"AAAAA">>, Config), + % Non-existing domain + Res2 = admin_request_token(<<"eddie@otherhost">>, Config), get_unauthorized(Res2). domain_admin_revoke_token_no_permission_test(Config) -> @@ -202,8 +202,8 @@ domain_admin_revoke_token_no_permission_test(Config, AliceBis) -> % External domain user Res = admin_revoke_token(user_to_bin(AliceBis), Config), get_unauthorized(Res), - % Non-existing user - Res2 = admin_revoke_token(<<"AAAAA">>, Config), + % Non-existing domain + Res2 = admin_revoke_token(<<"eddie@otherhost">>, Config), get_unauthorized(Res2). % Admin tests @@ -228,13 +228,13 @@ admin_request_token_test_unprep(Config, Alice) -> ?assert(is_binary(Refresh)), ?assert(is_binary(Access)). -admin_request_token_no_user_test(Config) -> - Res = admin_request_token(<<"AAAAA">>, Config), - ?assertEqual(<<"not_found">>, get_err_code(Res)). +admin_request_token_no_host_test(Config) -> + Res = admin_request_token(<<"eddie@otherhost">>, Config), + ?assertEqual(<<"Unknown domain">>, get_err_msg(Res)). -admin_revoke_token_no_user_test(Config) -> - Res = admin_revoke_token(<<"AAAAA">>, Config), - ?assertEqual(<<"not_found">>, get_err_code(Res)). +admin_revoke_token_no_host_test(Config) -> + Res = admin_revoke_token(<<"eddie@otherhost">>, Config), + ?assertEqual(<<"Unknown domain">>, get_err_msg(Res)). admin_revoke_token_no_token_test(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_revoke_token_no_token_test/2). diff --git a/big_tests/tests/graphql_vcard_SUITE.erl b/big_tests/tests/graphql_vcard_SUITE.erl index 1781c02607..a1afbdf33d 100644 --- a/big_tests/tests/graphql_vcard_SUITE.erl +++ b/big_tests/tests/graphql_vcard_SUITE.erl @@ -53,20 +53,20 @@ user_vcard_not_configured_tests() -> domain_admin_vcard_tests() -> [admin_set_vcard, admin_set_vcard_incomplete_fields, + domain_admin_set_vcard_no_host, domain_admin_set_vcard_no_permission, - domain_admin_set_vcard_no_user, admin_get_vcard, admin_get_vcard_no_vcard, - domain_admin_get_vcard_no_user, + domain_admin_get_vcard_no_host, domain_admin_get_vcard_no_permission]. admin_vcard_tests() -> [admin_set_vcard, admin_set_vcard_incomplete_fields, - admin_set_vcard_no_user, + admin_set_vcard_no_host, admin_get_vcard, admin_get_vcard_no_vcard, - admin_get_vcard_no_user]. + admin_get_vcard_no_host]. admin_vcard_not_configured_tests() -> [admin_set_vcard_not_configured, @@ -199,8 +199,8 @@ user_get_others_vcard_no_user(Config) -> fun user_get_others_vcard_no_user/2). user_get_others_vcard_no_user(Config, Alice) -> - Result = user_get_vcard(Alice, <<"AAAAA">>, Config), - ?assertEqual(<<"User does not exist">>, get_err_msg(Result)). + Result = user_get_vcard(Alice, <<"eddie@otherhost">>, Config), + ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). % User VCard not configured test cases @@ -231,12 +231,12 @@ user_get_their_vcard_not_configured(Config, Alice) -> %% Domain admin test cases -domain_admin_set_vcard_no_user(Config) -> +domain_admin_set_vcard_no_host(Config) -> Vcard = complete_vcard_input(), - get_unauthorized(set_vcard(Vcard, <<"AAAAA">>, Config)). + get_unauthorized(set_vcard(Vcard, <<"eddie@otherhost">>, Config)). -domain_admin_get_vcard_no_user(Config) -> - get_unauthorized(get_vcard(<<"AAAAA">>, Config)). +domain_admin_get_vcard_no_host(Config) -> + get_unauthorized(get_vcard(<<"eddie@otherhost">>, Config)). domain_admin_get_vcard_no_permission(Config) -> escalus:fresh_story_with_config(Config, [{alice_bis, 1}], @@ -276,10 +276,10 @@ admin_set_vcard(Config, Alice, _Bob) -> ParsedResultGet = get_ok_value([data, vcard, getVcard], ResultGet), ?assertEqual(Vcard, skip_null_fields(ParsedResultGet)). -admin_set_vcard_no_user(Config) -> +admin_set_vcard_no_host(Config) -> Vcard = complete_vcard_input(), - Result = set_vcard(Vcard, <<"AAAAA">>, Config), - ?assertEqual(<<"User does not exist">>, get_err_msg(Result)). + Result = set_vcard(Vcard, <<"eddie@otherhost">>, Config), + ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). admin_get_vcard(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], @@ -308,9 +308,9 @@ admin_get_vcard_no_vcard(Config, Alice) -> Result = get_vcard(user_to_bin(Alice), Config), ?assertEqual(<<"Vcard for user not found">>, get_err_msg(Result)). -admin_get_vcard_no_user(Config) -> - Result = get_vcard(<<"AAAAA">>, Config), - ?assertEqual(<<"User does not exist">>, get_err_msg(Result)). +admin_get_vcard_no_host(Config) -> + Result = get_vcard(<<"eddie@otherhost">>, Config), + ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). %% Admin VCard not configured test cases diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index 305e07ddc0..4731fe74df 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -41,6 +41,8 @@ jid_from_binary(Value) -> case jid:from_binary(Value) of error -> {error, failed_to_parse_jid}; + #jid{luser = <<>>} -> + {error, jid_without_user}; Jid -> {ok, Jid} end. diff --git a/src/vcard/mod_vcard_api.erl b/src/vcard/mod_vcard_api.erl index 8f0401e813..17a8d73941 100644 --- a/src/vcard/mod_vcard_api.erl +++ b/src/vcard/mod_vcard_api.erl @@ -24,7 +24,7 @@ set_vcard(#jid{luser = LUser, lserver = LServer} = UserJID, Vcard) -> {internal, "Internal server error"} end; _ -> - {not_found, "User does not exist"} + {not_found, "Host does not exist"} end. -spec get_vcard(jid:jid()) -> @@ -40,7 +40,7 @@ get_vcard(#jid{luser = LUser, lserver = LServer}) -> {vcard_not_configured_error, "Mod_vcard is not loaded for this host"} end; _ -> - {not_found, "User does not exist"} + {not_found, "Host does not exist"} end. set_vcard(HostType, UserJID, Vcard) -> From 60e2a052b75c2c90004677491cc157d66a34d0c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Tue, 22 Nov 2022 10:58:25 +0100 Subject: [PATCH 24/44] Refactored hook handlers in mod_register --- src/mod_register.erl | 47 ++++++++++++++++++++++++------------------ src/mongoose_hooks.erl | 5 ++++- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/mod_register.erl b/src/mod_register.erl index e711d35256..db90383727 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -37,7 +37,7 @@ %% IQ and hook handlers -export([c2s_stream_features/3, - unauthenticated_iq_register/5, + unauthenticated_iq_register/3, process_iq/5]). %% API @@ -45,7 +45,7 @@ process_ip_access/1, process_welcome_message/1]). --ignore_xref([c2s_stream_features/3, process_iq/5, try_register/6, unauthenticated_iq_register/5]). +-ignore_xref([process_iq/5, try_register/6]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -55,7 +55,7 @@ start(HostType, #{iqdisc := IQDisc}) -> [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_REGISTER, Component, Fn, #{}, IQDisc) || {Component, Fn} <- iq_handlers()], - ejabberd_hooks:add(hooks(HostType)), + gen_hook:add_handlers(hooks(HostType)), mnesia:create_table(mod_register_ip, [{ram_copies, [node()]}, @@ -66,7 +66,7 @@ start(HostType, #{iqdisc := IQDisc}) -> -spec stop(mongooseim:host_type()) -> ok. stop(HostType) -> - ejabberd_hooks:delete(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_REGISTER, Component) || {Component, _Fn} <- iq_handlers()], ok. @@ -75,8 +75,8 @@ iq_handlers() -> [{ejabberd_local, fun ?MODULE:process_iq/5}, {ejabberd_sm, fun ?MODULE:process_iq/5}]. hooks(HostType) -> - [{c2s_stream_features, HostType, ?MODULE, c2s_stream_features, 50}, - {c2s_unauthenticated_iq, HostType, ?MODULE, unauthenticated_iq_register, 50}]. + [{c2s_stream_features, HostType, fun ?MODULE:c2s_stream_features/3, #{}, 50}, + {c2s_unauthenticated_iq, HostType, fun ?MODULE:unauthenticated_iq_register/3, #{}, 50}]. %%% %%% config_spec @@ -134,17 +134,24 @@ process_welcome_message(#{subject := Subject, body := Body}) -> %%% Hooks and IQ handlers %%% --spec c2s_stream_features([exml:element()], mongooseim:host_type(), jid:lserver()) -> - [exml:element()]. -c2s_stream_features(Acc, _HostType, _LServer) -> - [#xmlel{name = <<"register">>, - attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}]} | Acc]. - --spec unauthenticated_iq_register(exml:element() | empty, mongooseim:host_type(), - jid:server(), jlib:iq(), - {inet:ip_address(), inet:port_number()} | undefined) -> - exml:element() | empty. -unauthenticated_iq_register(_Acc, HostType, Server, #iq{xmlns = ?NS_REGISTER} = IQ, IP) -> +-spec c2s_stream_features(Acc, Params, Extra) -> {ok, Acc} when + Acc :: [exml:element()], + Params :: map(), + Extra :: gen_hook:extra(). +c2s_stream_features(Acc, _, _) -> + NewAcc = [#xmlel{name = <<"register">>, + attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}]} | Acc], + {ok, NewAcc}. + +-spec unauthenticated_iq_register(Acc, Params, Extra) -> {ok, Acc} when + Acc :: exml:element() | empty, + Params :: #{server := jid:server(), + iq := jlib:iq(), + ip := {inet:ip_address(), inet:port_number()} | undefined}, + Extra :: gen_hook:extra(). +unauthenticated_iq_register(_Acc, + #{server := Server, iq := #iq{xmlns = ?NS_REGISTER} = IQ, ip := IP}, + #{host_type := HostType}) -> Address = case IP of {A, _Port} -> A; _ -> undefined @@ -158,9 +165,9 @@ unauthenticated_iq_register(_Acc, HostType, Server, #iq{xmlns = ?NS_REGISTER} = make_host_only_jid(Server), IQ, Address), - set_sender(jlib:iq_to_xml(ResIQ), make_host_only_jid(Server)); -unauthenticated_iq_register(Acc, _HostType, _Server, _IQ, _IP) -> - Acc. + {ok, set_sender(jlib:iq_to_xml(ResIQ), make_host_only_jid(Server))}; +unauthenticated_iq_register(Acc, _, _) -> + {ok, Acc}. %% Clients must register before being able to authenticate. process_unauthenticated_iq(HostType, From, To, #iq{type = set} = IQ, IPAddr) -> diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 7582c88696..3fab23bc13 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -648,7 +648,10 @@ c2s_stream_features(HostType, LServer) -> IP :: {inet:ip_address(), inet:port_number()} | undefined, Result :: exml:element() | empty. c2s_unauthenticated_iq(HostType, Server, IQ, IP) -> - run_hook_for_host_type(c2s_unauthenticated_iq, HostType, empty, [HostType, Server, IQ, IP]). + Params = #{server => Server, iq => IQ, ip => IP}, + Args = [HostType, Server, IQ, IP], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(c2s_unauthenticated_iq, HostType, empty, ParamsWithLegacyArgs). -spec c2s_update_presence(HostType, Acc) -> Result when HostType :: mongooseim:host_type(), From 5469765beb64e6f0370ada91b1b73fe719365981 Mon Sep 17 00:00:00 2001 From: Gustaw Lippa Date: Tue, 22 Nov 2022 12:08:38 +0100 Subject: [PATCH 25/44] Fix tabbed content in the documentation --- mkdocs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index dc30d25008..a541cd811f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,7 +37,8 @@ markdown_extensions: linenums: true - pymdownx.superfences - admonition - - pymdownx.tabbed + - pymdownx.tabbed: + alternate_style: true nav: - 'Home': 'index.md' From d9c087e40c7386db983de007cc8e7b47c06b012e Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Tue, 22 Nov 2022 11:51:55 +0100 Subject: [PATCH 26/44] Improve error handling in httpUpload --- big_tests/tests/graphql_http_upload_SUITE.erl | 62 +++++++++++++++---- priv/graphql/schemas/admin/http_upload.gql | 2 +- priv/graphql/schemas/global/http_upload.gql | 16 ++++- priv/graphql/schemas/user/http_upload.gql | 2 +- src/http_upload/mod_http_upload_api.erl | 55 +++++++++------- 5 files changed, 97 insertions(+), 40 deletions(-) diff --git a/big_tests/tests/graphql_http_upload_SUITE.erl b/big_tests/tests/graphql_http_upload_SUITE.erl index eb0cba9aca..2310e53a55 100644 --- a/big_tests/tests/graphql_http_upload_SUITE.erl +++ b/big_tests/tests/graphql_http_upload_SUITE.erl @@ -6,7 +6,7 @@ -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(domain_helper, [host_type/0, domain/0, secondary_domain/0]). -import(graphql_helper, [execute_user_command/5, execute_command/4, get_ok_value/2, - get_err_msg/1, get_err_code/1, get_unauthorized/1]). + get_err_msg/1, get_err_code/1, get_coercion_err_msg/1, get_unauthorized/1]). -include_lib("eunit/include/eunit.hrl"). @@ -47,6 +47,8 @@ domain_admin_groups() -> user_http_upload_tests() -> [user_get_url_test, + user_get_url_test_no_content_type, + user_get_url_test_empty_filename, user_get_url_zero_size, user_get_url_too_large_size, user_get_url_zero_timeout]. @@ -56,6 +58,8 @@ user_http_upload_not_configured_tests() -> admin_http_upload_tests() -> [admin_get_url_test, + admin_get_url_test_no_content_type, + admin_get_url_test_empty_filename, admin_get_url_zero_size, admin_get_url_too_large_size, admin_get_url_zero_timeout, @@ -66,6 +70,8 @@ admin_http_upload_not_configured_tests() -> domain_admin_http_upload_tests() -> [admin_get_url_test, + admin_get_url_test_no_content_type, + admin_get_url_test_empty_filename, admin_get_url_zero_size, admin_get_url_too_large_size, admin_get_url_zero_timeout, @@ -148,18 +154,40 @@ user_get_url_test(Config) -> user_get_url_test(Config, Alice) -> Result = user_get_url(<<"test">>, 123, <<"Test">>, 123, Alice, Config), ParsedResult = get_ok_value([data, httpUpload, getUrl], Result), - #{<<"PutUrl">> := PutURL, <<"GetUrl">> := GetURL, <<"Header">> := _Headers} = ParsedResult, + #{<<"putUrl">> := PutURL, <<"getUrl">> := GetURL, <<"headers">> := _Headers} = ParsedResult, ?assertMatch({_, _}, binary:match(PutURL, [?S3_HOSTNAME])), ?assertMatch({_, _}, binary:match(GetURL, [?S3_HOSTNAME])). +user_get_url_test_no_content_type(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_get_url_test_no_content_type/2). + +user_get_url_test_no_content_type(Config, Alice) -> + user_get_url_test_no_content_type(Config, Alice, null), + user_get_url_test_no_content_type(Config, Alice, <<"">>). + +user_get_url_test_no_content_type(Config, Alice, ContentType) -> + Result = user_get_url(<<"test">>, 123, ContentType, 123, Alice, Config), + ParsedResult = get_ok_value([data, httpUpload, getUrl], Result), + #{<<"putUrl">> := PutURL, <<"getUrl">> := GetURL, <<"headers">> := _Headers} = ParsedResult, + ?assertMatch({_, _}, binary:match(PutURL, [?S3_HOSTNAME])), + ?assertMatch({_, _}, binary:match(GetURL, [?S3_HOSTNAME])). + +user_get_url_test_empty_filename(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_get_url_test_empty_filename/2). + +user_get_url_test_empty_filename(Config, Alice) -> + Result = user_get_url(<<"">>, 123, <<"Test">>, 123, Alice, Config), + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Given string is empty">>)). + user_get_url_zero_size(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_get_url_zero_size/2). user_get_url_zero_size(Config, Alice) -> Result = user_get_url(<<"test">>, 0, <<"Test">>, 123, Alice, Config), - ?assertEqual(<<"size_error">>, get_err_code(Result)), - ?assertEqual(<<"size must be positive integer">>, get_err_msg(Result)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Value is not a positive integer">>)). user_get_url_too_large_size(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -176,8 +204,7 @@ user_get_url_zero_timeout(Config) -> user_get_url_zero_timeout(Config, Alice) -> Result = user_get_url(<<"test">>, 123, <<"Test">>, 0, Alice, Config), - ?assertEqual(<<"timeout_error">>, get_err_code(Result)), - ?assertEqual(<<"timeout must be positive integer">>, get_err_msg(Result)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Value is not a positive integer">>)). user_http_upload_not_configured(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -197,14 +224,28 @@ admin_get_url_test(Config) -> admin_get_url_test(Config, Domain) -> Result = admin_get_url(Domain, <<"test">>, 123, <<"Test">>, 123, Config), ParsedResult = get_ok_value([data, httpUpload, getUrl], Result), - #{<<"PutUrl">> := PutURL, <<"GetUrl">> := GetURL, <<"Header">> := _Headers} = ParsedResult, + #{<<"putUrl">> := PutURL, <<"getUrl">> := GetURL, <<"headers">> := _Headers} = ParsedResult, + ?assertMatch({_, _}, binary:match(PutURL, [?S3_HOSTNAME])), + ?assertMatch({_, _}, binary:match(GetURL, [?S3_HOSTNAME])). + +admin_get_url_test_no_content_type(Config) -> + admin_get_url_test_no_content_type(Config, null), + admin_get_url_test_no_content_type(Config, <<"">>). + +admin_get_url_test_no_content_type(Config, ContentType) -> + Result = admin_get_url(domain(), <<"test">>, 123, ContentType, 123, Config), + ParsedResult = get_ok_value([data, httpUpload, getUrl], Result), + #{<<"putUrl">> := PutURL, <<"getUrl">> := GetURL, <<"headers">> := _Headers} = ParsedResult, ?assertMatch({_, _}, binary:match(PutURL, [?S3_HOSTNAME])), ?assertMatch({_, _}, binary:match(GetURL, [?S3_HOSTNAME])). +admin_get_url_test_empty_filename(Config) -> + Result = admin_get_url(domain(), <<"">>, 123, <<"Test">>, 123, Config), + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Given string is empty">>)). + admin_get_url_zero_size(Config) -> Result = admin_get_url(domain(), <<"test">>, 0, <<"Test">>, 123, Config), - ?assertEqual(<<"size_error">>, get_err_code(Result)), - ?assertEqual(<<"size must be positive integer">>, get_err_msg(Result)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Value is not a positive integer">>)). admin_get_url_too_large_size(Config) -> Result = admin_get_url(domain(), <<"test">>, 100000, <<"Test">>, 123, Config), @@ -213,8 +254,7 @@ admin_get_url_too_large_size(Config) -> admin_get_url_zero_timeout(Config) -> Result = admin_get_url(domain(), <<"test">>, 123, <<"Test">>, 0, Config), - ?assertEqual(<<"timeout_error">>, get_err_code(Result)), - ?assertEqual(<<"timeout must be positive integer">>, get_err_msg(Result)). + ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Result), <<"Value is not a positive integer">>)). admin_get_url_no_domain(Config) -> Result = admin_get_url(<<"AAAAA">>, <<"test">>, 123, <<"Test">>, 123, Config), diff --git a/priv/graphql/schemas/admin/http_upload.gql b/priv/graphql/schemas/admin/http_upload.gql index 22b38c1f79..f5c8ce74ba 100644 --- a/priv/graphql/schemas/admin/http_upload.gql +++ b/priv/graphql/schemas/admin/http_upload.gql @@ -3,6 +3,6 @@ Allow admin to generate upload/download URL for a file on user's behalf". """ type HttpUploadAdminMutation @use(modules: ["mod_http_upload"]) @protected{ "Allow admin to generate upload/download URLs for a file on user's behalf" - getUrl(domain: DomainName!, filename: String!, size: Int!, contentType: String!, timeout: Int!): FileUrls + getUrl(domain: DomainName!, filename: NonEmptyString!, size: PosInt!, contentType: String, timeout: PosInt!): FileUrls @use(arg: "domain") @protected(type: DOMAIN, args: ["domain"]) } diff --git a/priv/graphql/schemas/global/http_upload.gql b/priv/graphql/schemas/global/http_upload.gql index ee9dafb48d..4ac2c47084 100644 --- a/priv/graphql/schemas/global/http_upload.gql +++ b/priv/graphql/schemas/global/http_upload.gql @@ -3,9 +3,19 @@ Type containing put url and get url for the file """ type FileUrls { "Url to put the file" - PutUrl: String + putUrl: String "Url to get the file" - GetUrl: String + getUrl: String "Http headers" - Header: String + headers: [Header] +} + +""" +Type containing a HTTP header +""" +type Header { + "Name" + name: String + "Value" + value: String } diff --git a/priv/graphql/schemas/user/http_upload.gql b/priv/graphql/schemas/user/http_upload.gql index 3a4c141eea..ebf61d11f5 100644 --- a/priv/graphql/schemas/user/http_upload.gql +++ b/priv/graphql/schemas/user/http_upload.gql @@ -3,5 +3,5 @@ Allow user to generate upload/download URL for a file". """ type HttpUploadUserMutation @use(modules: ["mod_http_upload"]) @protected{ "Allow user to generate upload/download URLs for a file" - getUrl(filename: String!, size: Int!, contentType: String!, timeout: Int!): FileUrls @use + getUrl(filename: NonEmptyString!, size: PosInt!, contentType: String, timeout: PosInt!): FileUrls @use } diff --git a/src/http_upload/mod_http_upload_api.erl b/src/http_upload/mod_http_upload_api.erl index e490d5993f..88940c39ab 100644 --- a/src/http_upload/mod_http_upload_api.erl +++ b/src/http_upload/mod_http_upload_api.erl @@ -7,40 +7,45 @@ -spec get_urls_mongooseimctl(Domain :: jid:lserver(), Filename :: binary(), Size :: pos_integer(), ContentType :: binary() | undefined, Timeout :: pos_integer()) -> {ok | error, string()}. +get_urls_mongooseimctl(_Domain, _Filename, Size, _ContentType, _Timeout) when Size =< 0 -> + {error, "size must be positive integer"}; +get_urls_mongooseimctl(_Domain, _Filename, _Size, _ContentType, Timeout) when Timeout =< 0 -> + {error, "timeout must be positive integer"}; get_urls_mongooseimctl(Domain, Filename, Size, ContentType, Timeout) -> case get_urls(Domain, Filename, Size, ContentType, Timeout) of - {ok, #{<<"PutUrl">> := PutURL, <<"GetUrl">> := GetURL, <<"Header">> := Header}} -> - {ok, generate_output_message(PutURL, GetURL, Header)}; + {ok, #{<<"putUrl">> := PutURL, <<"getUrl">> := GetURL, <<"headers">> := Headers}} -> + {ok, generate_output_message(PutURL, GetURL, Headers)}; {_, Message} -> {error, Message} end. --spec get_urls(Domain :: jid:lserver(), Filename :: binary(), Size :: pos_integer(), - ContentType :: binary() | undefined, Timeout :: pos_integer()) -> - {ok | size_error | timeout_error | module_not_loaded_error | domain_not_found | - file_too_large_error, string()} | {ok, #{binary() => binary() | string()}}. -get_urls(_Domain, _Filename, Size, _ContentType, _Timeout) when Size =< 0 -> - {size_error, "size must be positive integer"}; -get_urls(_Domain, _Filename, _Size, _ContentType, Timeout) when Timeout =< 0 -> - {timeout_error, "timeout must be positive integer"}; -get_urls(Domain, Filename, Size, <<>>, Timeout) -> - get_urls(Domain, Filename, Size, undefined, Timeout); +-spec get_urls(Domain :: jid:lserver(), Filename :: nonempty_binary(), Size :: pos_integer(), + ContentType :: binary() | null | undefined, Timeout :: pos_integer()) -> + {ok, #{binary() => term()}} + | {size_error | timeout_error | module_not_loaded_error | domain_not_found | + file_too_large_error, string()}. get_urls(Domain, Filename, Size, ContentType, Timeout) -> + ContentType1 = content_type(ContentType), case mongoose_domain_api:get_domain_host_type(Domain) of {ok, HostType} -> - check_module_and_get_urls(HostType, Filename, Size, ContentType, Timeout); + check_module_and_get_urls(HostType, Filename, Size, ContentType1, Timeout); _ -> {domain_not_found, "domain does not exist"} end. +content_type(null) -> undefined; +content_type(<<>>) -> undefined; +content_type(Binary) -> Binary. + check_module_and_get_urls(HostType, Filename, Size, ContentType, Timeout) -> - %The check if the module is loaded is needed by one test in mongooseimctl_SUITE case gen_mod:is_loaded(HostType, mod_http_upload) of true -> case mod_http_upload:get_urls(HostType, Filename, Size, ContentType, Timeout) of - {PutURL, GetURL, Header} -> - {ok, #{<<"PutUrl">> => PutURL, <<"GetUrl">> => GetURL, - <<"Header">> => header_output(Header)}}; + {PutURL, GetURL, Headers} -> + Headers1 = lists:map(fun({Name, Value}) -> {ok, #{<<"name">> => Name, <<"value">> => Value}} end, + maps:to_list(Headers)), + {ok, #{<<"putUrl">> => PutURL, <<"getUrl">> => GetURL, + <<"headers">> => Headers1}}; file_too_large_error -> {file_too_large_error, "Declared file size exceeds the host's maximum file size."} @@ -49,16 +54,18 @@ check_module_and_get_urls(HostType, Filename, Size, ContentType, Timeout) -> {module_not_loaded_error, "mod_http_upload is not loaded for this host"} end. --spec generate_output_message(PutURL :: binary(), GetURL :: binary(), - Header :: string()) -> string(). -generate_output_message(PutURL, GetURL, Header) -> +-spec generate_output_message(PutURL :: binary(), + GetURL :: binary(), + Headers :: [{ok, map()}]) -> string(). +generate_output_message(PutURL, GetURL, Headers) -> PutURLOutput = url_output("PutURL:", PutURL), GetURLOutput = url_output("GetURL:", GetURL), - lists:flatten([PutURLOutput, GetURLOutput, Header]). + HeadersOutput = headers_output(Headers), + lists:flatten([PutURLOutput, GetURLOutput, HeadersOutput]). url_output(Name, Url) -> io_lib:format("~s ~s~n", [Name, Url]). -header_output(Header) when Header =:= #{} -> []; -header_output(Header) -> - io_lib:format("Header: ~p~n", [maps:to_list(Header)]). +headers_output(Headers) -> + List = [{Name, Value} || {ok, #{<<"name">> := Name, <<"value">> := Value}} <- Headers], + io_lib:format("Header: ~p~n", [List]). From d8b17a97f40faf699024b964d130d4986ea01b77 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Thu, 24 Nov 2022 08:54:41 +0100 Subject: [PATCH 27/44] Fixing errors in mod_roster --- priv/graphql/schemas/admin/roster.gql | 2 +- priv/graphql/schemas/user/roster.gql | 2 +- src/mod_roster_api.erl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/priv/graphql/schemas/admin/roster.gql b/priv/graphql/schemas/admin/roster.gql index 5b68d2c7bb..ce8708edb8 100644 --- a/priv/graphql/schemas/admin/roster.gql +++ b/priv/graphql/schemas/admin/roster.gql @@ -9,7 +9,7 @@ type RosterAdminMutation @protected{ addContacts(user: JID!, contacts: [ContactInput!]!) : [String]! @protected(type: DOMAIN, args: ["user"]) "Manage the user's subscription to the contact" - subscription(user: JID!, contact: JID!, action: SubAction): String + subscription(user: JID!, contact: JID!, action: SubAction!): String @protected(type: DOMAIN, args: ["user"]) "Delete user's contact" deleteContact(user: JID!, contact: JID!): String diff --git a/priv/graphql/schemas/user/roster.gql b/priv/graphql/schemas/user/roster.gql index 38ecb5777d..94f6df5dd9 100644 --- a/priv/graphql/schemas/user/roster.gql +++ b/priv/graphql/schemas/user/roster.gql @@ -19,7 +19,7 @@ Allow user to get information about user roster/contacts. """ type RosterUserQuery @protected{ "Get the user's roster/contacts" - listContacts: [Contact!] + listContacts: [Contact!] "Get the user's contact" getContact(contact: JID!): Contact } diff --git a/src/mod_roster_api.erl b/src/mod_roster_api.erl index ddcf027f57..c29b5b4ee8 100644 --- a/src/mod_roster_api.erl +++ b/src/mod_roster_api.erl @@ -107,7 +107,7 @@ subscription(#jid{lserver = LServer} = CallerJID, ContactJID, Type) -> lserver => LServer, element => El }), Acc2 = mongoose_hooks:roster_out_subscription(Acc1, CallerJID, ContactJID, Type), - ejabberd_router:route(CallerJID, ContactJID, Acc2), + ejabberd_router:route(CallerJID, jid:to_bare(ContactJID), Acc2), {ok, io_lib:format("Subscription stanza with type ~s sent successfully", [StanzaType])}; {error, not_found} -> ?UNKNOWN_DOMAIN_RESULT From 453fc747b16ddb238d51dce6a60745ddb270ca4e Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Tue, 22 Nov 2022 12:57:57 +0100 Subject: [PATCH 28/44] Improve error handling in stat --- src/admin_extra/service_admin_extra_stats.erl | 8 +-- .../admin/mongoose_graphql_stats_domain.erl | 10 ++- .../admin/mongoose_graphql_stats_global.erl | 18 +++-- src/stats_api.erl | 70 +++++++++++-------- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/admin_extra/service_admin_extra_stats.erl b/src/admin_extra/service_admin_extra_stats.erl index aeec4fdcec..be4bb0c397 100644 --- a/src/admin_extra/service_admin_extra_stats.erl +++ b/src/admin_extra/service_admin_extra_stats.erl @@ -44,13 +44,13 @@ commands() -> #ejabberd_commands{name = stats, tags = [stats], desc = "Get statistical value:" " registeredusers onlineusers onlineusersnode uptimeseconds", - module = stats_api, function = stats, + module = stats_api, function = stats_mongooseimctl, args = [{name, binary}], - result = {stat, integer}}, + result = {res, restuple}}, #ejabberd_commands{name = stats_host, tags = [stats], desc = "Get statistical value for this host:" " registeredusers onlineusers", - module = stats_api, function = stats, + module = stats_api, function = stats_mongooseimctl, args = [{name, binary}, {host, binary}], - result = {stat, integer}} + result = {res, restuple}} ]. diff --git a/src/graphql/admin/mongoose_graphql_stats_domain.erl b/src/graphql/admin/mongoose_graphql_stats_domain.erl index ea7616175a..c904a3348c 100644 --- a/src/graphql/admin/mongoose_graphql_stats_domain.erl +++ b/src/graphql/admin/mongoose_graphql_stats_domain.erl @@ -12,6 +12,12 @@ -include("jlib.hrl"). execute(_Ctx, Domain, <<"registeredUsers">>, _Args) -> - {ok, stats_api:stats(<<"registeredusers">>, Domain)}; + domainStats(<<"registeredusers">>, Domain); execute(_Ctx, Domain, <<"onlineUsers">>, _Args) -> - {ok, stats_api:stats(<<"onlineusers">>, Domain)}. + domainStats(<<"onlineusers">>, Domain). + +domainStats(Name, Domain) -> + case stats_api:stats(Name, Domain) of + {ok, _} = Result -> Result; + Error -> make_error(Error, #{domain => Domain}) + end. diff --git a/src/graphql/admin/mongoose_graphql_stats_global.erl b/src/graphql/admin/mongoose_graphql_stats_global.erl index 0d2500e2ce..9a884f8628 100644 --- a/src/graphql/admin/mongoose_graphql_stats_global.erl +++ b/src/graphql/admin/mongoose_graphql_stats_global.erl @@ -12,14 +12,20 @@ -include("jlib.hrl"). execute(_Ctx, globalStats, <<"uptimeSeconds">>, _Args) -> - {ok, stats_api:stats(<<"uptimeseconds">>)}; + globalStats(<<"uptimeseconds">>); execute(_Ctx, globalStats, <<"registeredUsers">>, _Args) -> - {ok, stats_api:stats(<<"registeredusers">>)}; + globalStats(<<"registeredusers">>); execute(_Ctx, globalStats, <<"onlineUsersNode">>, _Args) -> - {ok, stats_api:stats(<<"onlineusersnode">>)}; + globalStats(<<"onlineusersnode">>); execute(_Ctx, globalStats, <<"onlineUsers">>, _Args) -> - {ok, stats_api:stats(<<"onlineusers">>)}; + globalStats(<<"onlineusers">>); execute(_Ctx, globalStats, <<"incomingS2S">>, _Args) -> - {ok, stats_api:incoming_s2s_number()}; + stats_api:incoming_s2s_number(); execute(_Ctx, globalStats, <<"outgoingS2S">>, _Args) -> - {ok, stats_api:outgoing_s2s_number()}. + stats_api:outgoing_s2s_number(). + +globalStats(Name) -> + case stats_api:stats(Name) of + {ok, _} = Result -> Result; + Error -> make_error(Error, #{}) + end. diff --git a/src/stats_api.erl b/src/stats_api.erl index c23196374a..9881e2ecc1 100644 --- a/src/stats_api.erl +++ b/src/stats_api.erl @@ -1,42 +1,52 @@ -module(stats_api). -export([incoming_s2s_number/0, outgoing_s2s_number/0, stats/1, stats/2]). +-export([stats_mongooseimctl/1, stats_mongooseimctl/2]). + +-ignore_xref([stats_mongooseimctl/1, stats_mongooseimctl/2]). -include("mongoose.hrl"). --include("ejabberd_commands.hrl"). --spec incoming_s2s_number() -> non_neg_integer(). +-spec incoming_s2s_number() -> {ok, non_neg_integer()}. incoming_s2s_number() -> - length(supervisor:which_children(ejabberd_s2s_in_sup)). + {ok, length(supervisor:which_children(ejabberd_s2s_in_sup))}. --spec outgoing_s2s_number() -> non_neg_integer(). +-spec outgoing_s2s_number() -> {ok, non_neg_integer()}. outgoing_s2s_number() -> - length(supervisor:which_children(ejabberd_s2s_out_sup)). - --spec stats(binary()) -> integer() | {error, string()}. -stats(Name) -> - case Name of - <<"uptimeseconds">> -> - trunc(element(1, erlang:statistics(wall_clock))/1000); - <<"registeredusers">> -> - Domains = lists:flatmap(fun mongoose_domain_api:get_domains_by_host_type/1, - ?ALL_HOST_TYPES), - lists:sum([ejabberd_auth:get_vh_registered_users_number(Domain) || Domain <- Domains]); - <<"onlineusersnode">> -> - ejabberd_sm:get_node_sessions_number(); - <<"onlineusers">> -> - ejabberd_sm:get_total_sessions_number(); - _ -> - {error, "Wrong command name."} + {ok, length(supervisor:which_children(ejabberd_s2s_out_sup))}. + +-spec stats(binary()) -> {ok, integer()} | {not_found, string()}. +stats(<<"uptimeseconds">>) -> + {ok, trunc(element(1, erlang:statistics(wall_clock)) / 1000)}; +stats(<<"registeredusers">>) -> + Domains = lists:flatmap(fun mongoose_domain_api:get_domains_by_host_type/1, + ?ALL_HOST_TYPES), + {ok, lists:sum([ejabberd_auth:get_vh_registered_users_number(Domain) || Domain <- Domains])}; +stats(<<"onlineusersnode">>) -> + {ok, ejabberd_sm:get_node_sessions_number()}; +stats(<<"onlineusers">>) -> + {ok, ejabberd_sm:get_total_sessions_number()}; +stats(_Name) -> + {not_found, "Stats not found"}. + +-spec stats(binary(), jid:server()) -> {ok, integer()} | {not_found, string()}. +stats(<<"registeredusers">>, Host) -> + {ok, ejabberd_auth:get_vh_registered_users_number(Host)}; +stats(<<"onlineusers">>, Host) -> + {ok, ejabberd_sm:get_vh_session_number(Host)}; +stats(_Name, _Host) -> + {not_found, "Stats not found"}. + +-spec stats_mongooseimctl(binary()) -> {ok | not_found, string()}. +stats_mongooseimctl(Name) -> + case stats(Name) of + {ok, Stat} -> {ok, integer_to_list(Stat)}; + Error -> Error end. --spec stats(binary(), jid:server()) -> integer() | {error, string()}. -stats(Name, Host) -> - case Name of - <<"registeredusers">> -> - ejabberd_auth:get_vh_registered_users_number(Host); - <<"onlineusers">> -> - ejabberd_sm:get_vh_session_number(Host); - _ -> - {error, "Wrong command name."} +-spec stats_mongooseimctl(binary(), binary()) -> {ok | not_found, string()}. +stats_mongooseimctl(Name, Host) -> + case stats(Name, Host) of + {ok, Stat} -> {ok, integer_to_list(Stat)}; + Error -> Error end. From 6ffea97a20e36d10625ecc8a95d04677d8c68fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Thu, 24 Nov 2022 13:12:19 +0100 Subject: [PATCH 29/44] Refactored hook handlers in mod_shared_roster_ldap --- src/mod_shared_roster_ldap.erl | 120 ++++++++++++++++++--------------- src/mongoose_hooks.erl | 12 +++- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index a2c5778a47..8667f4f887 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -39,12 +39,12 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([get_user_roster/2, get_subscription_lists/2, - get_jid_info/4, process_item/2, in_subscription/5, - out_subscription/4]). +%% Hook handlers +-export([get_user_roster/3, get_subscription_lists/3, + get_jid_info/3, process_item/3, in_subscription/3, + out_subscription/3]). --ignore_xref([config_change/4, get_jid_info/4, get_subscription_lists/2, get_user_roster/2, - in_subscription/5, out_subscription/4, process_item/2, start_link/2]). +-ignore_xref([start_link/2]). -include("jlib.hrl"). -include("mod_roster.hrl"). @@ -149,7 +149,11 @@ process_ldap_options(Opts = #{groupattr := GroupAttr}) -> %%-------------------------------------------------------------------- %% Hooks %%-------------------------------------------------------------------- -get_user_roster(Acc, #jid{luser = U, lserver = S} = JID) -> +-spec get_user_roster(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_user_roster(Acc, #{jid := #jid{luser = U, lserver = S} = JID}, _) -> US = jid:to_lus(JID), Items = mongoose_acc:get(roster, items, [], Acc), SRUsers = get_user_to_groups_map(US, true), @@ -172,16 +176,20 @@ get_user_roster(Acc, #jid{luser = U, lserver = S} = JID) -> name = get_user_name(U1, S1), subscription = both, ask = none, groups = GroupNames} || {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)], - mongoose_acc:set(roster, items, SRItems ++ NewItems1, Acc). + {ok, mongoose_acc:set(roster, items, SRItems ++ NewItems1, Acc)}. %% This function in use to rewrite the roster entries when moving or renaming %% them in the user contact list. -process_item(RosterItem, _Host) -> +-spec process_item(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mod_roster:roster(), + Params :: map(), + Extra :: gen_hook:extra(). +process_item(RosterItem, _, _) -> USFrom = RosterItem#roster.us, {User, Server, _Resource} = RosterItem#roster.jid, USTo = {User, Server}, Map = get_user_to_groups_map(USFrom, false), - case dict:find(USTo, Map) of + NewRosterItem = case dict:find(USTo, Map) of error -> RosterItem; {ok, []} -> RosterItem; {ok, GroupNames} @@ -189,9 +197,14 @@ process_item(RosterItem, _Host) -> RosterItem#roster{subscription = both, ask = none, groups = GroupNames}; _ -> RosterItem#roster{subscription = both, ask = none} - end. - -get_subscription_lists(Acc, #jid{lserver = LServer} = JID) -> + end, + {ok, NewRosterItem}. + +-spec get_subscription_lists(Acc, Params, Extra) -> {ok, Acc} when + Acc ::mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_subscription_lists(Acc, #{jid := #jid{lserver = LServer} = JID}, _) -> {F, T, P} = mongoose_acc:get(roster, subscription_lists, {[], [], []}, Acc), US = jid:to_lus(JID), DisplayedGroups = get_user_displayed_groups(US), @@ -201,13 +214,17 @@ get_subscription_lists(Acc, #jid{lserver = LServer} = JID) -> DisplayedGroups)), SRJIDs = [{U1, S1, <<>>} || {U1, S1} <- SRUsers], NewLists = {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T), P}, - mongoose_acc:set(roster, subscription_lists, NewLists, Acc). + {ok, mongoose_acc:set(roster, subscription_lists, NewLists, Acc)}. -get_jid_info({Subscription, Groups}, _HostType, ToJID, JID) -> +-spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when + Acc :: {mod_roster:subscription_state(), [binary()]}, + Params :: #{to_jid := jid:jid(), remote_jid := jid:jid() | jid:simple_jid()}, + Extra :: gen_hook:extra(). +get_jid_info({Subscription, Groups}, #{to_jid := ToJID, remote_jid := JID}, _) -> ToUS = jid:to_lus(ToJID), US1 = jid:to_lus(JID), SRUsers = get_user_to_groups_map(ToUS, false), - case dict:find(US1, SRUsers) of + NewAcc = case dict:find(US1, SRUsers) of {ok, GroupNames} -> NewGroups = case Groups of [] -> GroupNames; @@ -215,35 +232,37 @@ get_jid_info({Subscription, Groups}, _HostType, ToJID, JID) -> end, {both, NewGroups}; error -> {Subscription, Groups} - end. - --spec in_subscription(Acc :: mongoose_acc:t(), - ToJID :: jid:jid(), - FromJID :: jid:jid(), - Type :: mod_roster:sub_presence(), - _Reason :: any()) -> - mongoose_acc:t() | {stop, mongoose_acc:t()}. -in_subscription(Acc, ToJID, FromJID, Type, _Reason) -> + end, + {ok, NewAcc}. + +-spec in_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to_jid := jid:jid(), + from_jid := jid:jid(), + type := mod_roster:sub_presence()}, + Extra :: gen_hook:extra(). +in_subscription(Acc, #{to_jid := ToJID, from_jid := FromJID, type := Type}, _) -> case process_subscription(in, ToJID, FromJID, Type) of stop -> {stop, Acc}; {stop, false} -> {stop, mongoose_acc:set(hook, result, false, Acc)}; - _ -> Acc + _ -> {ok, Acc} end. --spec out_subscription(Acc :: mongoose_acc:t(), - FromJID :: jid:jid(), - ToJID :: jid:jid(), - Type :: mod_roster:sub_presence()) -> - mongoose_acc:t() | {stop, mongoose_acc:t()}. -out_subscription(Acc, FromJID, ToJID, Type) -> +-spec out_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to_jid := jid:jid(), + from_jid := jid:jid(), + type := mod_roster:sub_presence()}, + Extra :: gen_hook:extra(). + out_subscription(Acc, #{to_jid := ToJID, from_jid := FromJID, type := Type}, _) -> case process_subscription(out, FromJID, ToJID, Type) of stop -> {stop, Acc}; {stop, false} -> {stop, Acc}; - false -> Acc + false -> {ok, Acc} end. process_subscription(Direction, #jid{luser = LUser, lserver = LServer}, ToJID, _Type) -> @@ -278,18 +297,7 @@ init([Host, Opts]) -> cache_tab:new(shared_roster_ldap_group, [{max_size, State#state.group_cache_size}, {lru, false}, {life_time, State#state.group_cache_validity}]), - ejabberd_hooks:add(roster_get, Host, ?MODULE, - get_user_roster, 70), - ejabberd_hooks:add(roster_in_subscription, Host, ?MODULE, - in_subscription, 30), - ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, - out_subscription, 30), - ejabberd_hooks:add(roster_get_subscription_lists, Host, ?MODULE, - get_subscription_lists, 70), - ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, - get_jid_info, 70), - ejabberd_hooks:add(roster_process_item, Host, ?MODULE, - process_item, 50), + gen_hook:add_handlers(hooks(Host)), {ok, State}. handle_call(get_state, _From, State) -> @@ -303,21 +311,21 @@ handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, State) -> Host = State#state.host, - ejabberd_hooks:delete(roster_get, Host, ?MODULE, - get_user_roster, 70), - ejabberd_hooks:delete(roster_in_subscription, Host, - ?MODULE, in_subscription, 30), - ejabberd_hooks:delete(roster_out_subscription, Host, - ?MODULE, out_subscription, 30), - ejabberd_hooks:delete(roster_get_subscription_lists, - Host, ?MODULE, get_subscription_lists, 70), - ejabberd_hooks:delete(roster_get_jid_info, Host, - ?MODULE, get_jid_info, 70), - ejabberd_hooks:delete(roster_process_item, Host, - ?MODULE, process_item, 50). + gen_hook:delete_handlers(hooks(Host)). code_change(_OldVsn, State, _Extra) -> {ok, State}. +-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). +hooks(HostType) -> + [ + {roster_get, HostType, fun ?MODULE:get_user_roster/3, #{}, 70}, + {roster_in_subscription, HostType, fun ?MODULE:in_subscription/3, #{}, 70}, + {roster_out_subscription, HostType, fun ?MODULE:out_subscription/3, #{}, 70}, + {roster_get_subscription_lists, HostType, fun ?MODULE:get_subscription_lists/3, #{}, 70}, + {roster_get_jid_info, HostType, fun ?MODULE:get_jid_info/3, #{}, 70}, + {roster_process_item, HostType, fun ?MODULE:process_item/3, #{}, 70} + ]. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index c7e8da4951..fa8259b9a5 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -904,8 +904,13 @@ roster_get_jid_info(HostType, ToJID, RemBareJID) -> JID :: jid:jid(), Result :: mongoose_acc:t(). roster_get_subscription_lists(HostType, Acc, JID) -> + BareJID = jid:to_bare(JID), + Params = #{jid => BareJID}, + Args = [BareJID], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + HostType = mongoose_acc:host_type(Acc), run_hook_for_host_type(roster_get_subscription_lists, HostType, Acc, - [jid:to_bare(JID)]). + ParamsWithLegacyArgs). %%% @doc The `roster_get_versioning_feature' hook is %%% called to determine if roster versioning is enabled. @@ -952,7 +957,10 @@ roster_out_subscription(Acc, From, To, Type) -> Item :: mod_roster:roster(), Result :: mod_roster:roster(). roster_process_item(HostType, LServer, Item) -> - run_hook_for_host_type(roster_process_item, HostType, Item, [LServer]). + Params = #{lserver => LServer}, + Args = [LServer], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(roster_process_item, HostType, Item, ParamsWithLegacyArgs). %%% @doc The `roster_push' hook is called when a roster item is %%% being pushed and roster versioning is not enabled. From 9f7a0480f4a5ecbbf94f0caaf68cc1b8ca836017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 24 Nov 2022 15:02:02 +0100 Subject: [PATCH 30/44] Add scalar types and checks - Add a new type for BareJID. - All JID types require localpart (it is written 'local part' to be more understandable). It is renamed from 'user' to 'local part' because it can be a room name. - Unused output conversions are removed. - There is a new NonNegInteger type. --- priv/graphql/schemas/global/scalar_types.gql | 8 +++- src/graphql/mongoose_graphql_commands.erl | 5 ++- src/graphql/mongoose_graphql_scalar.erl | 45 +++++++++++++++----- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/priv/graphql/schemas/global/scalar_types.gql b/priv/graphql/schemas/global/scalar_types.gql index a9490e3929..e84ddbb733 100644 --- a/priv/graphql/schemas/global/scalar_types.gql +++ b/priv/graphql/schemas/global/scalar_types.gql @@ -2,9 +2,11 @@ scalar DateTime "Body of a data structure exchanged by XMPP entities in XML streams" scalar Stanza @spectaql(options: [{ key: "example", value: "Hi!" }]) -"Unique identifier in the form of **node@domain**" +"Unique XMPP identifier in the form of **node@domain** or **node@domain/resource" scalar JID @spectaql(options: [{ key: "example", value: "alice@localhost" }]) -"The JID with resource" +"JID without a resource" +scalar BareJID @spectaql(options: [{ key: "example", value: "alice@localhost" }]) +"JID with a resource" scalar FullJID @spectaql(options: [{ key: "example", value: "alice@localhost/res1" }]) "XMPP user name (local part of a JID)" scalar UserName @spectaql(options: [{ key: "example", value: "alice" }]) @@ -18,3 +20,5 @@ scalar ResourceName @spectaql(options: [{ key: "example", value: "res1" }]) scalar NonEmptyString @spectaql(options: [{ key: "example", value: "xyz789" }]) "Integer that has a value above zero" scalar PosInt @spectaql(options: [{ key: "example", value: "2" }]) +"Integer that has a value above or equal to zero" +scalar NonNegInt @spectaql(options: [{ key: "example", value: "0" }]) diff --git a/src/graphql/mongoose_graphql_commands.erl b/src/graphql/mongoose_graphql_commands.erl index d83a10544e..42fee3191a 100644 --- a/src/graphql/mongoose_graphql_commands.erl +++ b/src/graphql/mongoose_graphql_commands.erl @@ -155,8 +155,9 @@ parse_arg(Value, ArgSpec = #{type := Type}) -> end. %% Used input types that are not parsed from binaries should be handled here -convert_input_type(<<"Int">>, Value) -> binary_to_integer(Value); -convert_input_type(<<"PosInt">>, Value) -> binary_to_integer(Value); +convert_input_type(Type, Value) when Type =:= <<"Int">>; + Type =:= <<"PosInt">>; + Type =:= <<"NonNegInt">> -> binary_to_integer(Value); convert_input_type(_, Value) -> Value. %% Complex argument values should be provided in JSON diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index 918b550715..6a58bcc00a 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -13,13 +13,15 @@ input(<<"DateTime">>, DT) -> binary_to_microseconds(DT); input(<<"Stanza">>, Value) -> exml:parse(Value); input(<<"JID">>, Jid) -> jid_from_binary(Jid); +input(<<"BareJID">>, Jid) -> bare_jid_from_binary(Jid); +input(<<"FullJID">>, Jid) -> full_jid_from_binary(Jid); input(<<"UserName">>, User) -> user_from_binary(User); input(<<"RoomName">>, Room) -> room_from_binary(Room); input(<<"DomainName">>, Domain) -> domain_from_binary(Domain); input(<<"ResourceName">>, Res) -> resource_from_binary(Res); -input(<<"FullJID">>, Jid) -> full_jid_from_binary(Jid); input(<<"NonEmptyString">>, Value) -> non_empty_string_to_binary(Value); input(<<"PosInt">>, Value) -> validate_pos_integer(Value); +input(<<"NonNegInt">>, Value) -> validate_non_neg_integer(Value); input(Ty, V) -> error_logger:info_report({coercing_generic_scalar, Ty, V}), {ok, V}. @@ -34,11 +36,11 @@ output(<<"DateTime">>, DT) -> {ok, microseconds_to_binary(DT)}; output(<<"Stanza">>, Elem) -> {ok, exml:to_binary(Elem)}; output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)}; output(<<"UserName">>, User) -> {ok, User}; -output(<<"RoomName">>, Room) -> {ok, Room}; output(<<"DomainName">>, Domain) -> {ok, Domain}; output(<<"ResourceName">>, Res) -> {ok, Res}; output(<<"NonEmptyString">>, Value) -> binary_to_non_empty_string(Value); output(<<"PosInt">>, Value) -> validate_pos_integer(Value); +output(<<"NonNegInt">>, Value) -> validate_non_neg_integer(Value); output(Ty, V) -> error_logger:info_report({output_generic_scalar, Ty, V}), {ok, V}. @@ -48,7 +50,31 @@ jid_from_binary(Value) -> error -> {error, failed_to_parse_jid}; #jid{luser = <<>>} -> - {error, jid_without_user}; + {error, jid_without_local_part}; + Jid -> + {ok, Jid} + end. + +bare_jid_from_binary(Value) -> + case jid:from_binary(Value) of + error -> + {error, failed_to_parse_jid}; + #jid{luser = <<>>} -> + {error, jid_without_local_part}; + Jid = #jid{lresource = <<>>} -> + {ok, Jid}; + #jid{} -> + {error, jid_with_resource} + end. + +full_jid_from_binary(Value) -> + case jid:from_binary(Value) of + error -> + {error, failed_to_parse_jid}; + #jid{luser = <<>>} -> + {error, jid_without_local_part}; + #jid{lresource = <<>>} -> + {error, jid_without_resource}; Jid -> {ok, Jid} end. @@ -93,14 +119,6 @@ resource_from_binary(Value) -> {ok, Res} end. -full_jid_from_binary(Value) -> - case jid_from_binary(Value) of - {ok, #jid{lresource = <<>>}} -> - {error, jid_without_resource}; - Result -> - Result - end. - binary_to_microseconds(DT) -> case mod_mam_utils:maybe_microseconds(DT) of undefined -> @@ -124,6 +142,11 @@ validate_pos_integer(PosInt) when is_integer(PosInt), PosInt > 0 -> validate_pos_integer(_Value) -> {error, "Value is not a positive integer"}. +validate_non_neg_integer(NonNegInt) when is_integer(NonNegInt), NonNegInt >= 0 -> + {ok, NonNegInt}; +validate_non_neg_integer(_Value) -> + {error, "Value is not a non-negative integer"}. + microseconds_to_binary(Microseconds) -> Opts = [{offset, "Z"}, {unit, microsecond}], list_to_binary(calendar:system_time_to_rfc3339(Microseconds, Opts)). From 118920b4bc20bd131fc2497d9d44f5f1ae8b58da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 24 Nov 2022 15:04:30 +0100 Subject: [PATCH 31/44] Amend MUC GraphQL schema - Room JID is always bare. - Nick has to be a valid resource name, because it is used as such. - Room creation takes a bare JID instead of two args. - Positive (and non-negative) integers are checked. - MAM-related queries require mod_mam_muc. - The @use directive is valid for domain names - so it is added. --- priv/graphql/schemas/admin/muc.gql | 40 +++++++++++------------ priv/graphql/schemas/global/muc.gql | 10 +++--- priv/graphql/schemas/user/muc.gql | 50 ++++++++++++++++++----------- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/priv/graphql/schemas/admin/muc.gql b/priv/graphql/schemas/admin/muc.gql index 63114dabb1..d437db33a4 100644 --- a/priv/graphql/schemas/admin/muc.gql +++ b/priv/graphql/schemas/admin/muc.gql @@ -3,38 +3,37 @@ Allow admin to manage Multi-User Chat rooms. """ type MUCAdminMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" - #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: DomainName!, name: RoomName!, owner: JID!, nick: String!): MUCRoomDesc - @protected(type: DOMAIN, args: ["owner"]) + createInstantRoom(room: BareJID!, owner: JID!, nick: ResourceName!): MUCRoomDesc + @protected(type: DOMAIN, args: ["owner"]) @use(arg: "room") "Invite a user to a MUC room" - inviteUser(room: JID!, sender: JID!, recipient: JID!, reason: String): String + inviteUser(room: BareJID!, sender: JID!, recipient: JID!, reason: String): String @protected(type: DOMAIN, args: ["sender"]) @use(arg: "room") "Kick a user from a MUC room" - kickUser(room: JID!, nick: String!, reason: String): String + kickUser(room: BareJID!, nick: ResourceName!, reason: String): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Send a message to a MUC room" - sendMessageToRoom(room: JID!, from: FullJID!, body: String!): String + sendMessageToRoom(room: BareJID!, from: FullJID!, body: String!): String @protected(type: DOMAIN, args: ["from"]) @use(arg: "room") "Send a private message to a MUC room user" - sendPrivateMessage(room: JID!, from: FullJID!, toNick: String!, body: String!): String + sendPrivateMessage(room: BareJID!, from: FullJID!, toNick: ResourceName!, body: String!): String @protected(type: DOMAIN, args: ["from"]) @use(arg: "room") "Remove a MUC room" - deleteRoom(room: JID!, reason: String): String + deleteRoom(room: BareJID!, reason: String): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Change configuration of a MUC room" - changeRoomConfiguration(room: JID!, config: MUCRoomConfigInput!): MUCRoomConfig + changeRoomConfiguration(room: BareJID!, config: MUCRoomConfigInput!): MUCRoomConfig @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Change a user role" - setUserRole(room: JID!, nick: String!, role: MUCRole!): String + setUserRole(room: BareJID!, nick: ResourceName!, role: MUCRole!): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Change a user affiliation" - setUserAffiliation(room: JID!, user: JID!, affiliation: MUCAffiliation!): String + setUserAffiliation(room: BareJID!, user: JID!, affiliation: MUCAffiliation!): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Make a user enter the room with a given nick" - enterRoom(room: JID!, user: FullJID!, nick: String!, password: String): String + enterRoom(room: BareJID!, user: FullJID!, nick: ResourceName!, password: String): String @protected(type: DOMAIN, args: ["user"]) @use(arg: "room") "Make a user with the given nick exit the room" - exitRoom(room: JID!, user: FullJID!, nick: String!): String + exitRoom(room: BareJID!, user: FullJID!, nick: ResourceName!): String @protected(type: DOMAIN, args: ["user"]) @use(arg: "room") } @@ -43,19 +42,18 @@ Allow admin to get information about Multi-User Chat rooms. """ type MUCAdminQuery @protected @use(modules: ["mod_muc"]){ "Get MUC rooms under the given MUC domain" - #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - listRooms(mucDomain: DomainName!, from: JID!, limit: Int, index: Int): MUCRoomsPayload! - @protected(type: DOMAIN, args: ["from"]) + listRooms(mucDomain: DomainName!, from: JID!, limit: PosInt, index: NonNegInt): MUCRoomsPayload + @protected(type: DOMAIN, args: ["from"]) @use(arg: "mucDomain") "Get configuration of the MUC room" - getRoomConfig(room: JID!): MUCRoomConfig + getRoomConfig(room: BareJID!): MUCRoomConfig @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Get the user list of a given MUC room" - listRoomUsers(room: JID!): [MUCRoomUser!] + listRoomUsers(room: BareJID!): [MUCRoomUser!] @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Get the affiliation list of given MUC room" - listRoomAffiliations(room: JID!, affiliation: MUCAffiliation): [MUCRoomAffiliation!] + listRoomAffiliations(room: BareJID!, affiliation: MUCAffiliation): [MUCRoomAffiliation!] @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Get the MUC room archived messages" - getRoomMessages(room: JID!, pageSize: Int, before: DateTime): StanzasPayload - @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") + getRoomMessages(room: BareJID!, pageSize: PosInt, before: DateTime): StanzasPayload + @protected(type: DOMAIN, args: ["room"]) @use(arg: "room", modules: ["mod_mam_muc"]) } diff --git a/priv/graphql/schemas/global/muc.gql b/priv/graphql/schemas/global/muc.gql index 6448258c6d..2a20ca6910 100644 --- a/priv/graphql/schemas/global/muc.gql +++ b/priv/graphql/schemas/global/muc.gql @@ -49,7 +49,7 @@ type MUCRoomDesc{ "Is the room private?" private: Boolean "Number of the users" - usersNumber: Int + usersNumber: NonNegInt } "MUC room configuration" @@ -93,7 +93,7 @@ type MUCRoomConfig{ "Array of roles and/or privileges that enable retrieving the room's member list" mayGetMemberList: [String!]! "Maximum number of users in the room" - maxUsers: Int, + maxUsers: PosInt, "Does the room enabled logging events to a file on the disk?" logging: Boolean!, } @@ -139,7 +139,7 @@ input MUCRoomConfigInput{ "Array of roles and/or privileges that enable retrieving the room's member list" mayGetMemberList: [String!], "Maximum number of users in the room" - maxUsers: Int + maxUsers: PosInt "Does the room enabled logging events to a file on the disk?" logging: Boolean, } @@ -149,9 +149,9 @@ type MUCRoomsPayload{ "List of rooms descriptions" rooms: [MUCRoomDesc!] "Number of the rooms" - count: Int + count: NonNegInt "Index of the room" - index: Int + index: NonNegInt "First room title" first: String "Last room title" diff --git a/priv/graphql/schemas/user/muc.gql b/priv/graphql/schemas/user/muc.gql index 0435ce42b0..a5ac659e23 100644 --- a/priv/graphql/schemas/user/muc.gql +++ b/priv/graphql/schemas/user/muc.gql @@ -3,28 +3,38 @@ Allow user to manage Multi-User Chat rooms. """ type MUCUserMutation @protected @use(modules: ["mod_muc"]){ "Create a MUC room under the given XMPP hostname" - #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - createInstantRoom(mucDomain: DomainName!, name: RoomName!, nick: String!): MUCRoomDesc + createInstantRoom(room: BareJID!, nick: ResourceName!): MUCRoomDesc + @use(arg: "room") "Invite a user to a MUC room" - inviteUser(room: JID!, recipient: JID!, reason: String): String @use(arg: "room") + inviteUser(room: BareJID!, recipient: JID!, reason: String): String + @use(arg: "room") "Kick a user from a MUC room" - kickUser(room: JID!, nick: String!, reason: String): String @use(arg: "room") + kickUser(room: BareJID!, nick: ResourceName!, reason: String): String + @use(arg: "room") "Send a message to a MUC room" - sendMessageToRoom(room: JID!, body: String!, resource: ResourceName): String @use(arg: "room") + sendMessageToRoom(room: BareJID!, body: String!, resource: ResourceName): String + @use(arg: "room") "Send a private message to a MUC room user from the given resource" - sendPrivateMessage(room: JID!, toNick: String!, body: String!, resource: ResourceName): String @use(arg: "room") + sendPrivateMessage(room: BareJID!, toNick: ResourceName!, body: String!, resource: ResourceName): String + @use(arg: "room") "Remove a MUC room" - deleteRoom(room: JID!, reason: String): String @use(arg: "room") + deleteRoom(room: BareJID!, reason: String): String + @use(arg: "room") "Change configuration of a MUC room" - changeRoomConfiguration(room: JID!, config: MUCRoomConfigInput!): MUCRoomConfig @use(arg: "room") + changeRoomConfiguration(room: BareJID!, config: MUCRoomConfigInput!): MUCRoomConfig + @use(arg: "room") "Change a user role" - setUserRole(room: JID!, nick: String!, role: MUCRole!): String @use(arg: "room") + setUserRole(room: BareJID!, nick: ResourceName!, role: MUCRole!): String + @use(arg: "room") "Change a user affiliation" - setUserAffiliation(room: JID!, user: JID!, affiliation: MUCAffiliation!): String @use(arg: "room") + setUserAffiliation(room: BareJID!, user: JID!, affiliation: MUCAffiliation!): String + @use(arg: "room") "Enter the room with given resource and nick" - enterRoom(room: JID!, nick: String!, resource: ResourceName!, password: String): String @use(arg: "room") + enterRoom(room: BareJID!, nick: ResourceName!, resource: ResourceName!, password: String): String + @use(arg: "room") "Exit the room with given resource and nick" - exitRoom(room: JID!, nick: String!, resource: ResourceName!): String @use(arg: "room") + exitRoom(room: BareJID!, nick: ResourceName!, resource: ResourceName!): String + @use(arg: "room") } """ @@ -32,14 +42,18 @@ Allow user to get information about Multi-User Chat rooms. """ type MUCUserQuery @protected @use(modules: ["mod_muc"]){ "Get MUC rooms under the given MUC domain" - #There is no @use directive because it is currently impossible to get HostType from mucDomain in directive code - listRooms(mucDomain: DomainName!, limit: Int, index: Int): MUCRoomsPayload! + listRooms(mucDomain: DomainName!, limit: PosInt, index: NonNegInt): MUCRoomsPayload + @use(arg: "mucDomain") "Get configuration of the MUC room" - getRoomConfig(room: JID!): MUCRoomConfig @use(arg: "room") + getRoomConfig(room: BareJID!): MUCRoomConfig + @use(arg: "room") "Get the user list of a given MUC room" - listRoomUsers(room: JID!): [MUCRoomUser!] @use(arg: "room") + listRoomUsers(room: BareJID!): [MUCRoomUser!] + @use(arg: "room") "Get the affiliation list of given MUC room" - listRoomAffiliations(room: JID!, affiliation: MUCAffiliation): [MUCRoomAffiliation!] @use(arg: "room") + listRoomAffiliations(room: BareJID!, affiliation: MUCAffiliation): [MUCRoomAffiliation!] + @use(arg: "room") "Get the MUC room archived messages" - getRoomMessages(room: JID!, pageSize: Int, before: DateTime): StanzasPayload @use(arg: "room") + getRoomMessages(room: BareJID!, pageSize: PosInt, before: DateTime): StanzasPayload + @use(arg: "room", modules: ["mod_mam_muc"]) } From 89a91d26761b22d2d9193c371fc064328b8b4c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 24 Nov 2022 15:07:52 +0100 Subject: [PATCH 32/44] Amend the MUC API module - 'get_rooms' returns an error if the domain is unknown - Room creation takes a JID as an argument. --- src/mod_muc_api.erl | 36 ++++++++++--------- .../mongoose_admin_api_muc.erl | 5 ++- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/mod_muc_api.erl b/src/mod_muc_api.erl index 5290d8bd17..2cd59ffa76 100644 --- a/src/mod_muc_api.erl +++ b/src/mod_muc_api.erl @@ -12,7 +12,7 @@ get_room_users/2, get_room_affiliations/2, get_room_affiliations/3, - create_instant_room/4, + create_instant_room/3, invite_to_room/4, send_message_to_room/3, send_private_message/4, @@ -65,11 +65,16 @@ -define(SET_ROLE_SUCC_RESULT, {ok, "Role set successfully"}). -spec get_rooms(jid:lserver(), jid:jid(), non_neg_integer() | undefined, - non_neg_integer()) -> get_rooms_result(). + non_neg_integer()) -> {ok, get_rooms_result()} | {muc_server_not_found, iolist()}. get_rooms(MUCServer, From, Limit, Index) -> - {Rooms, RSM} = mod_muc:get_vh_rooms(MUCServer, #rsm_in{max = Limit, index = Index}), - Rooms2 = lists:filtermap(fun(R) -> room_to_item(R, MUCServer, From) end, Rooms), - {Rooms2, RSM}. + case mongoose_domain_api:get_subdomain_host_type(MUCServer) of + {ok, _HostType} -> + {Rooms, RSM} = mod_muc:get_vh_rooms(MUCServer, #rsm_in{max = Limit, index = Index}), + Rooms2 = lists:filtermap(fun(R) -> room_to_item(R, MUCServer, From) end, Rooms), + {ok, {Rooms2, RSM}}; + {error, not_found} -> + ?MUC_SERVER_NOT_FOUND_RESULT + end. -spec get_room_config(jid:jid(), jid:jid()) -> {ok, mod_muc_room:config()} | {not_allowed | room_not_found, iolist()}. @@ -93,14 +98,14 @@ get_room_config(RoomJID) -> ?ROOM_NOT_FOUND_RESULT end. --spec create_instant_room(jid:lserver(), binary(), jid:jid(), binary()) -> +-spec create_instant_room(jid:jid(), jid:jid(), binary()) -> {ok, short_room_desc()} | {internal | user_not_found | room_not_found, iolist()}. -create_instant_room(MUCDomain, Name, OwnerJID, Nick) -> +create_instant_room(BareRoomJID = #jid{luser = Name, lresource = <<>>}, OwnerJID, Nick) -> %% Because these stanzas are sent on the owner's behalf through %% the HTTP API, they will certainly receive stanzas as a %% consequence, even if their client(s) did not initiate this. - case {ejabberd_auth:does_user_exist(OwnerJID), jid:make_bare(Name, MUCDomain)} of - {true, BareRoomJID = #jid{}} -> + case ejabberd_auth:does_user_exist(OwnerJID) of + true -> UserRoomJID = jid:replace_resource(BareRoomJID, Nick), %% Send presence to create a room. ejabberd_router:route(OwnerJID, UserRoomJID, @@ -114,9 +119,7 @@ create_instant_room(MUCDomain, Name, OwnerJID, Nick) -> Error -> Error end; - {true, error} -> - {invalid_input, "Room name or domain is invalid"}; - {false, _} -> + false -> ?USER_NOT_FOUND_RESULT end. @@ -233,6 +236,7 @@ delete_room(RoomJID, OwnerJID, Reason) -> {error, not_found} -> ?DELETE_NONEXISTENT_ROOM_RESULT end. + -spec get_room_users(jid:jid()) -> {ok, [user_map()]} | {room_not_found, iolist()}. get_room_users(RoomJID) -> case mod_muc_room:get_room_users(RoomJID) of @@ -286,7 +290,7 @@ get_room_messages(RoomJID, UserJID, PageSize, Before) -> ?MUC_SERVER_NOT_FOUND_RESULT end. --spec get_room_affiliations(jid:jid(), jid:jid(), mod_muc:affiliation() | undefined) -> +-spec get_room_affiliations(jid:jid(), jid:jid(), mod_muc:affiliation() | undefined) -> {ok, [aff_item()]} | {not_allowed | room_not_found, iolist()}. get_room_affiliations(RoomJID, UserJID, AffType) -> case mod_muc_room:can_access_room(RoomJID, UserJID) of @@ -298,7 +302,7 @@ get_room_affiliations(RoomJID, UserJID, AffType) -> ?ROOM_NOT_FOUND_RESULT end. --spec get_room_affiliations(jid:jid(), mod_muc:affiliation() | undefined) -> +-spec get_room_affiliations(jid:jid(), mod_muc:affiliation() | undefined) -> {ok, [aff_item()]} | {room_not_found, iolist()}. get_room_affiliations(RoomJID, AffType) -> case room_users_aff(RoomJID) of @@ -536,8 +540,8 @@ data_submission() -> {<<"type">>, <<"submit">>}]}]). address_attributes(Sender, Recipient) -> - [{<<"from">>, jid:to_binary(jid:to_lower(Sender))}, - {<<"to">>, jid:to_binary(jid:to_lower(Recipient))}]. + [{<<"from">>, jid:to_binary(Sender)}, + {<<"to">>, jid:to_binary(Recipient)}]. room_moderator(RoomJID) -> case mod_muc_room:get_room_users(RoomJID) of diff --git a/src/mongoose_admin_api/mongoose_admin_api_muc.erl b/src/mongoose_admin_api/mongoose_admin_api_muc.erl index 6c8c384bee..13e2c5726b 100644 --- a/src/mongoose_admin_api/mongoose_admin_api_muc.erl +++ b/src/mongoose_admin_api/mongoose_admin_api_muc.erl @@ -84,9 +84,8 @@ handle_post(Req, State, #{domain := Domain}) -> RoomName = get_room_name(Args), OwnerJid = get_owner_jid(Args), Nick = get_nick(Args), - %% TODO This check should be done in the API module to work for GraphQL as well - #jid{lserver = MUCDomain} = make_room_jid(RoomName, get_muc_domain(Domain)), - case mod_muc_api:create_instant_room(MUCDomain, RoomName, OwnerJid, Nick) of + RoomJid = make_room_jid(RoomName, get_muc_domain(Domain)), + case mod_muc_api:create_instant_room(RoomJid, OwnerJid, Nick) of {ok, #{title := R}} -> Path = [cowboy_req:uri(Req), "/", R], resource_created(Req, State, Path, R); From 91bbacef0d5d903a7949a04ae0acb49df6748088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 24 Nov 2022 15:12:05 +0100 Subject: [PATCH 33/44] Update GraphQL resolvers for MUC - Creation requires a JID - Listing rooms can return an error - Room descriptions contained "undefined" strings - fixed to return nulls --- src/graphql/admin/mongoose_graphql_muc_admin_mutation.erl | 8 +++----- src/graphql/admin/mongoose_graphql_muc_admin_query.erl | 8 ++++++-- src/graphql/mongoose_graphql_helper.erl | 5 ++++- src/graphql/mongoose_graphql_muc_helper.erl | 7 +++++-- src/graphql/user/mongoose_graphql_muc_user_mutation.erl | 7 +++---- src/graphql/user/mongoose_graphql_muc_user_query.erl | 8 ++++++-- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/graphql/admin/mongoose_graphql_muc_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_muc_admin_mutation.erl index d7ec585169..cba4e7905f 100644 --- a/src/graphql/admin/mongoose_graphql_muc_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_muc_admin_mutation.erl @@ -34,14 +34,12 @@ execute(_Ctx, _Obj, <<"exitRoom">>, Args) -> exit_room(Args). -spec create_instant_room(map()) -> {ok, map()} | {error, resolver_error()}. -create_instant_room(#{<<"mucDomain">> := MUCDomain, <<"name">> := Name, - <<"owner">> := OwnerJID, <<"nick">> := Nick}) -> - case mod_muc_api:create_instant_room(MUCDomain, Name, OwnerJID, Nick) of +create_instant_room(#{<<"room">> := RoomJID, <<"owner">> := OwnerJID, <<"nick">> := Nick}) -> + case mod_muc_api:create_instant_room(RoomJID, OwnerJID, Nick) of {ok, Room} -> {ok, mongoose_graphql_muc_helper:make_room_desc(Room)}; Error -> - make_error(Error, #{mucDomain => MUCDomain, - owner => jid:to_binary(OwnerJID)}) + make_error(Error, #{room => jid:to_binary(RoomJID), owner => jid:to_binary(OwnerJID)}) end. -spec invite_user(map()) -> {ok, binary()} | {error, resolver_error()}. diff --git a/src/graphql/admin/mongoose_graphql_muc_admin_query.erl b/src/graphql/admin/mongoose_graphql_muc_admin_query.erl index d9b8fe3715..46997293ef 100644 --- a/src/graphql/admin/mongoose_graphql_muc_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_muc_admin_query.erl @@ -26,8 +26,12 @@ list_rooms(#{<<"mucDomain">> := MUCDomain, <<"from">> := FromJID, <<"limit">> := Limit, <<"index">> := Index}) -> Limit2 = null_to_undefined(Limit), Index2 = null_to_undefined(Index), - {Rooms, RSM} = mod_muc_api:get_rooms(MUCDomain, FromJID, Limit2, Index2), - {ok, mongoose_graphql_muc_helper:make_rooms_payload(Rooms, RSM)}. + case mod_muc_api:get_rooms(MUCDomain, FromJID, Limit2, Index2) of + {ok, {Rooms, RSM}} -> + {ok, mongoose_graphql_muc_helper:make_rooms_payload(Rooms, RSM)}; + Error -> + make_error(Error, #{mucDomain => MUCDomain}) + end. -spec list_room_users(map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}. list_room_users(#{<<"room">> := RoomJID}) -> diff --git a/src/graphql/mongoose_graphql_helper.erl b/src/graphql/mongoose_graphql_helper.erl index 1e6988f36b..78853e2115 100644 --- a/src/graphql/mongoose_graphql_helper.erl +++ b/src/graphql/mongoose_graphql_helper.erl @@ -1,6 +1,6 @@ -module(mongoose_graphql_helper). --export([null_to_default/2, null_to_undefined/1]). +-export([null_to_default/2, null_to_undefined/1, undefined_to_null/1]). -export([format_result/2, make_error/2, make_error/3]). @@ -32,3 +32,6 @@ null_to_default(Value, _Default) -> null_to_undefined(null) -> undefined; null_to_undefined(V) -> V. + +undefined_to_null(undefined) -> null; +undefined_to_null(V) -> V. diff --git a/src/graphql/mongoose_graphql_muc_helper.erl b/src/graphql/mongoose_graphql_muc_helper.erl index cd8ba77c8d..447d65a053 100644 --- a/src/graphql/mongoose_graphql_muc_helper.erl +++ b/src/graphql/mongoose_graphql_muc_helper.erl @@ -7,6 +7,8 @@ -ignore_xref(format_user/1). +-import(mongoose_graphql_helper, [undefined_to_null/1]). + -include("mongoose_rsm.hrl"). -include("mod_muc_room.hrl"). @@ -24,8 +26,9 @@ add_user_resource(JID, Resource) -> make_rooms_payload(Rooms, #rsm_out{count = Count, index = Index, first = First, last = Last}) -> Rooms2 = [{ok, make_room_desc(R)} || R <- Rooms], - #{<<"rooms">> => Rooms2, <<"count">> => Count, <<"index">> => Index, - <<"first">> => First, <<"last">> => Last}. + #{<<"rooms">> => Rooms2, + <<"count">> => undefined_to_null(Count), <<"index">> => undefined_to_null(Index), + <<"first">> => undefined_to_null(First), <<"last">> => undefined_to_null(Last)}. make_room_desc(#{jid := JID, title := Title} = Room) -> Private = maps:get(private, Room, null), diff --git a/src/graphql/user/mongoose_graphql_muc_user_mutation.erl b/src/graphql/user/mongoose_graphql_muc_user_mutation.erl index c12c94c894..81d59162bb 100644 --- a/src/graphql/user/mongoose_graphql_muc_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_muc_user_mutation.erl @@ -51,13 +51,12 @@ exit_room(#{user := UserJID}, #{<<"room">> := RoomJID, <<"nick">> := Nick, format_result(Res, #{room => jid:to_binary(RoomJID)}). -spec create_instant_room(map(), map()) -> {ok, map()} | {error, resolver_error()}. -create_instant_room(#{user := UserJID}, #{<<"mucDomain">> := MUCDomain, <<"name">> := Name, - <<"nick">> := Nick}) -> - case mod_muc_api:create_instant_room(MUCDomain, Name, UserJID, Nick) of +create_instant_room(#{user := UserJID}, #{<<"room">> := RoomJID, <<"nick">> := Nick}) -> + case mod_muc_api:create_instant_room(RoomJID, UserJID, Nick) of {ok, Room} -> {ok, mongoose_graphql_muc_helper:make_room_desc(Room)}; Error -> - make_error(Error, #{mucDomain => MUCDomain}) + make_error(Error, #{room => jid:to_binary(RoomJID)}) end. -spec invite_user(map(), map()) -> {ok, binary()} | {error, resolver_error()}. diff --git a/src/graphql/user/mongoose_graphql_muc_user_query.erl b/src/graphql/user/mongoose_graphql_muc_user_query.erl index 048f4dcb27..8210aab420 100644 --- a/src/graphql/user/mongoose_graphql_muc_user_query.erl +++ b/src/graphql/user/mongoose_graphql_muc_user_query.erl @@ -26,8 +26,12 @@ list_rooms(#{user := UserJID}, #{<<"mucDomain">> := MUCDomain, <<"limit">> := Li <<"index">> := Index}) -> Limit2 = null_to_undefined(Limit), Index2 = null_to_undefined(Index), - {Rooms, RSM} = mod_muc_api:get_rooms(MUCDomain, UserJID, Limit2, Index2), - {ok, mongoose_graphql_muc_helper:make_rooms_payload(Rooms, RSM)}. + case mod_muc_api:get_rooms(MUCDomain, UserJID, Limit2, Index2) of + {ok, {Rooms, RSM}} -> + {ok, mongoose_graphql_muc_helper:make_rooms_payload(Rooms, RSM)}; + Error -> + make_error(Error, #{mucDomain => MUCDomain}) + end. -spec list_room_users(map(), map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}. list_room_users(#{user := UserJID}, #{<<"room">> := RoomJID}) -> From 2a96fa9167d2703fd4bc66e6ba996e78489d7eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Thu, 24 Nov 2022 15:14:13 +0100 Subject: [PATCH 34/44] Update graphql_muc_SUITE - Add separate tests for the case when MAM is not configured - Configure MAM with the correct MUC host (it was MUC Light before). The fact that the tests passed before is because of a bug, that needs to be fixed separately. - Handle more error cases for new GraphQL checks. The goal is to check various cases, but not all possibilities are covered because of the amount of them. The focus is on basic commands - mostly room creation and listing. - Check that the nulls are present in the room list. --- big_tests/tests/graphql_muc_SUITE.erl | 266 +++++++++++++++++++------- big_tests/tests/mam_helper.erl | 1 - 2 files changed, 193 insertions(+), 74 deletions(-) diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index e8251de7a7..8ba52a93d3 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -3,7 +3,7 @@ -compile([export_all, nowarn_export_all]). -import(common_helper, [unprep/1]). --import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). +-import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4, subhost_pattern/1]). -import(graphql_helper, [execute_command/4, execute_user_command/5, get_ok_value/2, get_err_msg/1, get_coercion_err_msg/1, user_to_bin/1, user_to_full_bin/1, user_to_jid/1, get_unauthorized/1, get_not_loaded/1]). @@ -11,6 +11,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("exml/include/exml.hrl"). +-include_lib("jid/include/jid.hrl"). suite() -> require_rpc_nodes([mim]) ++ escalus:suite(). @@ -26,17 +27,21 @@ groups() -> {admin_http, [], admin_groups()}, {admin_cli, [], admin_groups()}, {admin_muc_configured, [], admin_muc_tests()}, + {admin_muc_and_mam_configured, [], admin_muc_with_mam_tests()}, {admin_muc_not_configured, [], admin_muc_not_configured_tests()}, - {user_muc_not_configured, [parallel], user_muc_not_configured_tests()}, {user_muc_configured, [parallel], user_muc_tests()}, + {user_muc_and_mam_configured, [parallel], user_muc_with_mam_tests()}, + {user_muc_not_configured, [parallel], user_muc_not_configured_tests()}, {domain_admin_muc, [], domain_admin_muc_tests()}]. user_groups() -> [{group, user_muc_configured}, + {group, user_muc_and_mam_configured}, {group, user_muc_not_configured}]. admin_groups() -> [{group, admin_muc_configured}, + {group, admin_muc_and_mam_configured}, {group, admin_muc_not_configured}]. user_muc_tests() -> @@ -45,8 +50,9 @@ user_muc_tests() -> user_try_delete_nonexistent_room, user_try_delete_room_by_not_owner, user_try_create_instant_room_with_nonexistent_domain, - user_try_create_instant_room_with_invalid_name, + user_try_create_instant_room_with_invalid_args, user_list_rooms, + user_try_list_rooms_for_nonexistent_domain, user_list_room_users, user_list_room_users_without_anonymous_mode, user_try_list_room_users_without_permission, @@ -63,9 +69,6 @@ user_muc_tests() -> user_send_message_to_room_with_specified_res, user_send_private_message, user_without_session_send_message_to_room, - user_get_room_messages, - user_try_get_nonexistent_room_messages, - user_try_get_room_messages_without_permission, user_owner_set_user_affiliation, user_admin_set_user_affiliation, user_member_set_user_affiliation, @@ -79,9 +82,15 @@ user_muc_tests() -> user_can_exit_room, user_list_room_affiliations, user_try_list_room_affiliations_without_permission, - user_try_list_nonexistent_room_affiliations + user_try_list_nonexistent_room_affiliations, + user_get_room_messages_muc_or_mam_not_configured ]. +user_muc_with_mam_tests() -> + [user_get_room_messages, + user_try_get_nonexistent_room_messages, + user_try_get_room_messages_without_permission]. + user_muc_not_configured_tests() -> [user_delete_room_muc_not_configured, user_list_room_users_muc_not_configured, @@ -91,7 +100,7 @@ user_muc_not_configured_tests() -> user_kick_user_muc_not_configured, user_send_message_to_room_muc_not_configured, user_send_private_message_muc_not_configured, - user_get_room_messages_muc_not_configured, + user_get_room_messages_muc_or_mam_not_configured, user_owner_set_user_affiliation_muc_not_configured, user_moderator_set_user_role_muc_not_configured, user_can_enter_room_muc_not_configured, @@ -99,13 +108,16 @@ user_muc_not_configured_tests() -> user_list_room_affiliations_muc_not_configured]. admin_muc_tests() -> - [admin_create_and_delete_room, + [admin_list_rooms, + admin_list_rooms_with_invalid_args, + admin_create_and_delete_room, admin_create_room_with_unprepped_name, admin_try_create_instant_room_with_nonexistent_domain, admin_try_create_instant_room_with_nonexistent_user, + admin_try_create_instant_room_with_invalid_args, admin_try_delete_nonexistent_room, admin_try_delete_room_with_nonexistent_domain, - admin_list_rooms, + admin_try_list_rooms_for_nonexistent_domain, admin_list_room_users, admin_try_list_users_from_nonexistent_room, admin_change_room_config, @@ -120,8 +132,6 @@ admin_muc_tests() -> admin_try_kick_user_from_room_without_moderators, admin_send_message_to_room, admin_send_private_message, - admin_get_room_messages, - admin_try_get_nonexistent_room_messages, admin_set_user_affiliation, admin_try_set_nonexistent_room_user_affiliation, admin_set_user_role, @@ -134,9 +144,14 @@ admin_muc_tests() -> admin_make_user_exit_room, admin_make_user_exit_room_bare_jid, admin_list_room_affiliations, - admin_try_list_nonexistent_room_affiliations + admin_try_list_nonexistent_room_affiliations, + admin_get_room_messages_muc_or_mam_not_configured ]. +admin_muc_with_mam_tests() -> + [admin_get_room_messages, + admin_try_get_nonexistent_room_messages]. + admin_muc_not_configured_tests() -> [admin_delete_room_muc_not_configured, admin_list_room_users_muc_not_configured, @@ -146,7 +161,7 @@ admin_muc_not_configured_tests() -> admin_kick_user_muc_not_configured, admin_send_message_to_room_muc_not_configured, admin_send_private_message_muc_not_configured, - admin_get_room_messages_muc_not_configured, + admin_get_room_messages_muc_or_mam_not_configured, admin_set_user_affiliation_muc_not_configured, admin_set_user_role_muc_not_configured, admin_make_user_enter_room_muc_not_configured, @@ -154,12 +169,12 @@ admin_muc_not_configured_tests() -> admin_list_room_affiliations_muc_not_configured]. domain_admin_muc_tests() -> - [admin_create_and_delete_room, + [admin_list_rooms, + admin_create_and_delete_room, admin_create_room_with_unprepped_name, admin_try_create_instant_room_with_nonexistent_domain, admin_try_delete_nonexistent_room, domain_admin_create_and_delete_room_no_permission, - admin_list_rooms, domain_admin_list_rooms_no_permission, admin_list_room_users, domain_admin_list_room_users_no_permission, @@ -203,11 +218,10 @@ init_per_suite(Config) -> Config2 = escalus:init_per_suite(Config), Config3 = dynamic_modules:save_modules(HostType, Config2), Config4 = dynamic_modules:save_modules(SecondaryHostType, Config3), - Config5 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config4), - Config6 = ejabberd_node_utils:init(mim(), Config5), + Config5 = ejabberd_node_utils:init(mim(), Config4), dynamic_modules:restart(HostType, mod_disco, config_parser_helper:default_mod_config(mod_disco)), - Config6. + Config5. end_per_suite(Config) -> escalus_fresh:clean(), @@ -221,29 +235,57 @@ init_per_group(admin_http, Config) -> init_per_group(admin_cli, Config) -> graphql_helper:init_admin_cli(Config); init_per_group(domain_admin_muc, Config) -> - Config1 = ensure_muc_started(Config), - graphql_helper:init_domain_admin_handler(Config1); + maybe_enable_mam(), + ensure_muc_started(), + graphql_helper:init_domain_admin_handler(Config); +init_per_group(user, Config) -> + graphql_helper:init_user(Config); init_per_group(Group, Config) when Group =:= admin_muc_configured; Group =:= user_muc_configured -> - ensure_muc_started(Config); + disable_mam(), + ensure_muc_started(), + Config; +init_per_group(Group, Config) when Group =:= admin_muc_and_mam_configured; + Group =:= user_muc_and_mam_configured -> + case maybe_enable_mam() of + true -> + ensure_muc_started(), + Config; + false -> + {skip, "No MAM backend available"} + end; init_per_group(Group, Config) when Group =:= admin_muc_not_configured; Group =:= user_muc_not_configured -> - ensure_muc_stopped(Config); -init_per_group(user, Config) -> - graphql_helper:init_user(Config). + maybe_enable_mam(), + ensure_muc_stopped(), + Config. -ensure_muc_started(Config) -> +disable_mam() -> + dynamic_modules:ensure_modules(domain_helper:host_type(), [{mod_mam, stopped}]). + +maybe_enable_mam() -> + case mam_helper:backend() of + disabled -> + false; + Backend -> + MAMOpts = mam_helper:config_opts( + #{backend => Backend, + muc => #{host => subhost_pattern(muc_light_helper:muc_host_pattern())}, + async_writer => #{enabled => false}}), + dynamic_modules:ensure_modules(domain_helper:host_type(), [{mod_mam, MAMOpts}]), + true + end. + +ensure_muc_started() -> SecondaryHostType = domain_helper:secondary_host_type(), muc_helper:load_muc(), muc_helper:load_muc(SecondaryHostType), - mongoose_helper:ensure_muc_clean(), - Config. + mongoose_helper:ensure_muc_clean(). -ensure_muc_stopped(Config) -> +ensure_muc_stopped() -> SecondaryHostType = domain_helper:secondary_host_type(), muc_helper:unload_muc(), - muc_helper:unload_muc(SecondaryHostType), - Config. + muc_helper:unload_muc(SecondaryHostType). end_per_group(Group, _Config) when Group =:= user; Group =:= admin_http; @@ -254,8 +296,7 @@ end_per_group(_Group, _Config) -> escalus_fresh:clean(). init_per_testcase(TC, Config) -> - rest_helper:maybe_skip_mam_test_cases(TC, [user_get_room_messages, - admin_get_room_messages], Config). + escalus:init_per_testcase(TC, Config). end_per_testcase(TC, Config) -> escalus:end_per_testcase(TC, Config). @@ -286,6 +327,8 @@ admin_list_rooms(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_list_rooms_story/3). admin_list_rooms_story(Config, Alice, Bob) -> + Res0 = list_rooms(muc_helper:muc_host(), Alice, null, null, Config), + ?assertEqual([], extract_rooms(get_ok_value(?LIST_ROOMS_PATH, Res0))), AliceJID = jid:from_binary(escalus_client:short_jid(Alice)), BobJID = jid:from_binary(escalus_client:short_jid(Bob)), AliceRoom = rand_name(), @@ -293,11 +336,32 @@ admin_list_rooms_story(Config, Alice, Bob) -> muc_helper:create_instant_room(AliceRoom, AliceJID, <<"Ali">>, []), muc_helper:create_instant_room(BobRoom, BobJID, <<"Bob">>, [{public_list, false}]), Res1 = list_rooms(muc_helper:muc_host(), Alice, null, null, Config), - #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res1), + Rooms1 = [_, RoomB] = extract_rooms(get_ok_value(?LIST_ROOMS_PATH, Res1)), + ?assertEqual(lists:sort([AliceRoom, BobRoom]), lists:sort(Rooms1)), Res2 = list_rooms(unprep(muc_helper:muc_host()), Alice, null, null, Config), - #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res2), - ?assert(contain_room(AliceRoom, Rooms)), - ?assert(contain_room(BobRoom, Rooms)). + ?assertEqual(Rooms1, extract_rooms(get_ok_value(?LIST_ROOMS_PATH, Res2))), + Res3 = list_rooms(muc_helper:muc_host(), Alice, 1, 1, Config), + ?assertEqual([RoomB], extract_rooms(get_ok_value(?LIST_ROOMS_PATH, Res3))). + +admin_list_rooms_with_invalid_args(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceJid = escalus_users:get_jid(Config1, alice), + AliceDomain = escalus_users:get_host(Config1, alice), + Res1 = list_rooms(muc_helper:muc_host(), AliceDomain, null, null, Config1), + assert_coercion_err(Res1, <<"jid_without_local_part">>), + Res2 = list_rooms(muc_helper:muc_host(), AliceJid, 0, null, Config1), + assert_coercion_err(Res2, <<"Value is not a positive integer">>), + Res3 = list_rooms(muc_helper:muc_host(), AliceJid, null, -1, Config1), + assert_coercion_err(Res3, <<"Value is not a non-negative integer">>). + +admin_try_list_rooms_for_nonexistent_domain(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceJID = escalus_users:get_jid(Config1, alice), + Res1 = list_rooms(<<"baddomain">>, AliceJID, null, null, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res1), <<"not found">>)), + %% Domain instead of the MUC subdomain + Res2 = list_rooms(domain_helper:domain(), AliceJID, null, null, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res2), <<"not found">>)). admin_create_and_delete_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_and_delete_room_story/2). @@ -307,7 +371,7 @@ admin_create_and_delete_room_story(Config, Alice) -> MUCServer = muc_helper:muc_host(), RoomJID = jid:make_bare(Name, MUCServer), % Create instant room - Res = create_instant_room(MUCServer, Name, Alice, <<"Ali">>, Config), + Res = create_instant_room(RoomJID, Alice, <<"Ali">>, Config), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)), Res2 = list_rooms(MUCServer, Alice, null, null, Config), @@ -324,7 +388,8 @@ admin_create_room_with_unprepped_name(Config) -> AliceJid = escalus_users:get_jid(FreshConfig, alice), Name = <<$a, (rand_name())/binary>>, % make it start with a letter MUCServer = muc_helper:muc_host(), - Res = create_instant_room(unprep(MUCServer), unprep(Name), AliceJid, <<"Ali">>, FreshConfig), + RoomJID = jid:make_noprep(unprep(Name), unprep(MUCServer), <<>>), + Res = create_instant_room(RoomJID, AliceJid, <<"Ali">>, FreshConfig), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)), Res2 = list_rooms(MUCServer, AliceJid, null, null, Config), @@ -335,16 +400,31 @@ admin_try_create_instant_room_with_nonexistent_domain(Config) -> fun admin_try_create_instant_room_with_nonexistent_domain_story/2). admin_try_create_instant_room_with_nonexistent_domain_story(Config, Alice) -> - Res = create_instant_room(<<"unknown">>, rand_name(), Alice, <<"Ali">>, Config), + Res = create_instant_room(jid:make_bare(rand_name(), <<"unknown">>), Alice, <<"Ali">>, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)). admin_try_create_instant_room_with_nonexistent_user(Config) -> - Name = rand_name(), - MUCServer = muc_helper:muc_host(), + RoomJID = jid:make_bare(rand_name(), muc_helper:muc_host()), JID = <<(rand_name())/binary, "@localhost">>, - Res = create_instant_room(MUCServer, Name, JID, <<"Ali">>, Config), + Res = create_instant_room(RoomJID, JID, <<"Ali">>, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)). +admin_try_create_instant_room_with_invalid_args(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceJid = escalus_users:get_jid(Config1, alice), + AliceDomain = escalus_users:get_host(Config1, alice), + Domain = muc_helper:muc_host(), + Res1 = create_instant_room(<<"test room@", Domain/binary>>, AliceJid, <<"Ali">>, Config1), + assert_coercion_err(Res1, <<"failed_to_parse_jid">>), + Res2 = create_instant_room(<<"testroom@", Domain/binary, "/res1">>, AliceJid, <<"Ali">>, Config1), + assert_coercion_err(Res2, <<"jid_with_resource">>), + Res3 = create_instant_room(Domain, AliceJid, <<"Ali">>, Config1), + assert_coercion_err(Res3, <<"jid_without_local_part">>), + Res4 = create_instant_room(<<"testroom@", Domain/binary>>, AliceDomain, <<"Ali">>, Config1), + assert_coercion_err(Res4, <<"jid_without_local_part">>), + Res5 = create_instant_room(<<"testroom@", Domain/binary>>, AliceJid, <<>>, Config1), + assert_coercion_err(Res5, <<"empty_resource_name">>). + admin_try_delete_nonexistent_room(Config) -> RoomJID = jid:make_bare(<<"unknown">>, muc_helper:muc_host()), Res = delete_room(RoomJID, null, Config), @@ -465,9 +545,12 @@ admin_send_private_message(Config, Alice, Bob) -> BareAlice = escalus_client:short_jid(Alice), Res = send_private_message(RoomJID, BareAlice, BobNick, Message, Config), assert_no_full_jid(Res), + % Try send private message to empty nick + Res1 = send_private_message(RoomJID, Alice, <<>>, Message, Config), + assert_coercion_err(Res1, <<"empty_resource_name">>), % Send message - Res1 = send_private_message(RoomJID, Alice, BobNick, Message, Config), - assert_success(?SEND_PRIV_MESG_PATH, Res1), + Res2 = send_private_message(RoomJID, Alice, BobNick, Message, Config), + assert_success(?SEND_PRIV_MESG_PATH, Res2), assert_is_message_correct(RoomJID, AliceNick, <<"chat">>, Message, escalus:wait_for_stanza(Bob)). @@ -526,13 +609,14 @@ admin_get_room_messages(Config) -> fun admin_get_room_messages_story/3). admin_get_room_messages_story(Config, Alice, Bob) -> - RoomJID = jid:from_binary(?config(room_jid, Config)), + RoomJID = #jid{luser = RoomName, lserver = MUCDomain} = + jid:from_binary(?config(room_jid, Config)), enter_room(RoomJID, Bob, <<"Bobek">>), enter_room(RoomJID, Alice, <<"Ali">>), escalus:wait_for_stanzas(Bob, 2), send_message_to_room(RoomJID, Bob, <<"Hi!">>, Config), escalus:wait_for_stanzas(Bob, 1), - mam_helper:maybe_wait_for_archive(Config), + mam_helper:wait_for_room_archive_size(MUCDomain, RoomName, 1), Res = get_room_messages(RoomJID, 50, null, Config), #{<<"stanzas">> := [#{<<"stanza">> := StanzaXML}], <<"limit">> := 50} = get_ok_value(?GET_MESSAGES_PATH, Res), @@ -759,15 +843,15 @@ admin_send_message_to_room_muc_not_configured_story(Config, Alice) -> get_not_loaded(Res). admin_send_private_message_muc_not_configured(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], - fun admin_send_private_message_muc_not_configured_story/3). + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun admin_send_private_message_muc_not_configured_story/2). -admin_send_private_message_muc_not_configured_story(Config, Alice, Bob) -> +admin_send_private_message_muc_not_configured_story(Config, Alice) -> Nick = <<"ali">>, Res = send_private_message(get_room_name(), Alice, Nick, <<"body">>, Config), get_not_loaded(Res). -admin_get_room_messages_muc_not_configured(Config) -> +admin_get_room_messages_muc_or_mam_not_configured(Config) -> Res = get_room_messages(get_room_name(), 4, null, Config), get_not_loaded(Res). @@ -820,17 +904,16 @@ domain_admin_create_and_delete_room_no_permission(Config) -> fun domain_admin_create_and_delete_room_no_permission_story/2). domain_admin_create_and_delete_room_no_permission_story(Config, AliceBis) -> - Name = rand_name(), ExternalDomain = domain_helper:secondary_domain(), UnknownDomain = <<"unknown">>, - MUCServer = muc_helper:muc_host(), + RoomJid = jid:make_bare(rand_name(), muc_helper:muc_host()), ExternalServer = <<"muc.", ExternalDomain/binary>>, % Create instant room with a non-existent domain UnknownJID = <<(rand_name())/binary, "@", UnknownDomain/binary>>, - Res = create_instant_room(MUCServer, Name, UnknownJID, <<"Ali">>, Config), + Res = create_instant_room(RoomJid, UnknownJID, <<"Ali">>, Config), get_unauthorized(Res), % Create instant room with an external domain - Res2 = create_instant_room(MUCServer, Name, AliceBis, <<"Ali">>, Config), + Res2 = create_instant_room(RoomJid, AliceBis, <<"Ali">>, Config), get_unauthorized(Res2), % Delete instant room with a non-existent domain UnknownRoomJID = jid:make_bare(<<"unknown_room">>, UnknownDomain), @@ -973,6 +1056,17 @@ user_list_rooms_story(Config, Alice, Bob) -> ?assert(contain_room(AliceRoom, Rooms)), ?assert(contain_room(BobRoom, Rooms)). +user_try_list_rooms_for_nonexistent_domain(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_try_list_rooms_for_nonexistent_domain_story/2). + +user_try_list_rooms_for_nonexistent_domain_story(Config, Alice) -> + Res1 = user_list_rooms(Alice, <<"baddomain">>, null, null, Config), + ?assertMatch({_, _}, binary:match(get_err_msg(Res1), <<"not found">>)), + %% Domain instead of the MUC subdomain + Res2 = user_list_rooms(Alice, domain_helper:domain(), null, null, Config), + ?assertMatch({_, _}, binary:match(get_err_msg(Res2), <<"not found">>)). + user_create_and_delete_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_create_and_delete_room_story/2). @@ -981,7 +1075,7 @@ user_create_and_delete_room_story(Config, Alice) -> MUCServer = muc_helper:muc_host(), RoomJID = jid:make_bare(Name, MUCServer), % Create instant room - Res = user_create_instant_room(Alice, MUCServer, Name, <<"Ali">>, Config), + Res = user_create_instant_room(Alice, RoomJID, <<"Ali">>, Config), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)), Res2 = user_list_rooms(Alice, MUCServer, null, null, Config), @@ -1000,7 +1094,8 @@ user_create_room_with_unprepped_name(Config) -> user_create_room_with_unprepped_name_story(Config, Alice) -> Name = <<$a, (rand_name())/binary>>, % make it start with a letter MUCServer = muc_helper:muc_host(), - Res = user_create_instant_room(Alice, unprep(MUCServer), unprep(Name), <<"Ali">>, Config), + RoomJid = jid:make_noprep(unprep(Name), unprep(MUCServer), <<>>), + Res = user_create_instant_room(Alice, RoomJid, <<"Ali">>, Config), ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0}, get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)). @@ -1009,16 +1104,22 @@ user_try_create_instant_room_with_nonexistent_domain(Config) -> fun user_try_create_instant_room_with_nonexistent_domain_story/2). user_try_create_instant_room_with_nonexistent_domain_story(Config, Alice) -> - Res = user_create_instant_room(Alice, <<"unknown">>, rand_name(), <<"Ali">>, Config), + RoomJID = jid:make_bare(rand_name(), <<"unknown">>), + Res = user_create_instant_room(Alice, RoomJID, <<"Ali">>, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)). -user_try_create_instant_room_with_invalid_name(Config) -> +user_try_create_instant_room_with_invalid_args(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], - fun user_try_create_instant_room_with_invalid_name_story/2). + fun user_try_create_instant_room_with_invalid_args_story/2). -user_try_create_instant_room_with_invalid_name_story(Config, Alice) -> - Res = user_create_instant_room(Alice, muc_helper:muc_host(), <<"test room">>, <<"Ali">>, Config), - assert_coercion_err(Res, <<"failed_to_parse_room_name">>). +user_try_create_instant_room_with_invalid_args_story(Config, Alice) -> + Domain = muc_helper:muc_host(), + Res1 = user_create_instant_room(Alice, <<"test room@", Domain/binary>>, <<"Ali">>, Config), + assert_coercion_err(Res1, <<"failed_to_parse_jid">>), + Res2 = user_create_instant_room(Alice, <<"testroom@", Domain/binary, "/res1">>, <<"Ali">>, Config), + assert_coercion_err(Res2, <<"jid_with_resource">>), + Res3 = user_create_instant_room(Alice, <<"testroom@", Domain/binary>>, <<>>, Config), + assert_coercion_err(Res3, <<"empty_resource_name">>). user_try_delete_nonexistent_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -1273,13 +1374,14 @@ user_get_room_messages(Config) -> fun user_get_room_messages_story/3). user_get_room_messages_story(Config, Alice, Bob) -> - RoomJID = jid:from_binary(?config(room_jid, Config)), + RoomJID = #jid{luser = RoomName, lserver = MUCDomain} = + jid:from_binary(?config(room_jid, Config)), enter_room(RoomJID, Bob, <<"Bobek">>), enter_room(RoomJID, Alice, <<"Ali">>), escalus:wait_for_stanzas(Bob, 2), user_send_message_to_room(Bob, RoomJID, <<"Hi!">>, null, Config), escalus:wait_for_stanzas(Bob, 1), - mam_helper:maybe_wait_for_archive(Config), + mam_helper:wait_for_room_archive_size(MUCDomain, RoomName, 1), Res = user_get_room_messages(Alice, RoomJID, 50, null, Config), #{<<"stanzas">> := [#{<<"stanza">> := StanzaXML}], <<"limit">> := 50} = get_ok_value(?GET_MESSAGES_PATH, Res), @@ -1617,11 +1719,11 @@ user_send_private_message_muc_not_configured_story(Config, Alice) -> Res = user_send_private_message(Alice, get_room_name(), Message, BobNick, null, Config), get_not_loaded(Res). -user_get_room_messages_muc_not_configured(Config) -> +user_get_room_messages_muc_or_mam_not_configured(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], - fun user_get_room_messages_muc_not_configured_story/2). + fun user_get_room_messages_muc_or_mam_not_configured_story/2). -user_get_room_messages_muc_not_configured_story(Config, Alice) -> +user_get_room_messages_muc_or_mam_not_configured_story(Config, Alice) -> Res = user_get_room_messages(Alice, get_room_name(), 10, null, Config), get_not_loaded(Res). @@ -1630,7 +1732,7 @@ user_owner_set_user_affiliation_muc_not_configured(Config) -> fun user_owner_set_user_affiliation_muc_not_configured_story/2). user_owner_set_user_affiliation_muc_not_configured_story(Config, Alice) -> - Res = user_set_user_affiliation(Alice, get_room_name(), <<"eddie@localhost">>, member, Config), + Res = user_set_user_affiliation(Alice, get_room_name(), Alice, member, Config), get_not_loaded(Res). user_moderator_set_user_role_muc_not_configured(Config) -> @@ -1732,6 +1834,21 @@ contain_room(Name, #{<<"rooms">> := Rooms}) -> contain_room(Name, Rooms) when is_list(Rooms) -> lists:any(fun(#{<<"title">> := T}) -> T =:= Name end, Rooms). +extract_rooms(#{<<"rooms">> := [], <<"index">> := null, <<"count">> := 0, + <<"first">> := null, <<"last">> := null}) -> + []; +extract_rooms(#{<<"rooms">> := Rooms, <<"index">> := Index, <<"count">> := Count, + <<"first">> := First, <<"last">> := Last}) -> + Titles = lists:map(fun(#{<<"title">> := Title}) -> Title end, Rooms), + ?assertEqual(hd(Titles), First), + ?assertEqual(lists:last(Titles), Last), + ?assert(is_integer(Count) andalso Count >= length(Titles)), + ?assert(is_integer(Index) andalso Index >= 0), + Titles. + +returned_rooms(#{<<"rooms">> := Rooms}) -> + [Title || #{<<"title">> := Title} <- Rooms]. + assert_default_room_config(Response) -> ?assertMatch(#{<<"title">> := <<>>, <<"description">> := <<>>, @@ -1764,8 +1881,8 @@ get_room_name() -> %% Commands -create_instant_room(MUCDomain, Name, Owner, Nick, Config) -> - Vars = #{mucDomain => MUCDomain, name => Name, owner => user_to_bin(Owner), nick => Nick}, +create_instant_room(Room, Owner, Nick, Config) -> + Vars = #{room => room_to_bin(Room), owner => user_to_bin(Owner), nick => Nick}, execute_command(<<"muc">>, <<"createInstantRoom">>, Vars, Config). invite_user(Room, Sender, Recipient, Reason, Config) -> @@ -1875,8 +1992,8 @@ user_send_private_message(User, Room, Body, ToNick, Resource, Config) -> Vars = #{room => jid:to_binary(Room), body => Body, toNick => ToNick, resource => Resource}, execute_user_command(<<"muc">>, <<"sendPrivateMessage">>, User, Vars, Config). -user_create_instant_room(User, MUCDomain, Name, Nick, Config) -> - Vars = #{mucDomain => MUCDomain, name => Name, nick => Nick}, +user_create_instant_room(User, Room, Nick, Config) -> + Vars = #{room => room_to_bin(Room), nick => Nick}, execute_user_command(<<"muc">>, <<"createInstantRoom">>, User, Vars, Config). user_invite_user(User, Room, Recipient, Reason, Config) -> @@ -1900,3 +2017,6 @@ user_exit_room(User, Room, Nick, Resource, Config) -> user_list_room_affiliations(User, Room, Aff, Config) -> Vars = #{room => jid:to_binary(Room), affiliation => atom_to_enum_item(Aff)}, execute_user_command(<<"muc">>, <<"listRoomAffiliations">>, User, Vars, Config). + +room_to_bin(RoomJIDBin) when is_binary(RoomJIDBin) ->RoomJIDBin; +room_to_bin(RoomJID) -> jid:to_binary(RoomJID). diff --git a/big_tests/tests/mam_helper.erl b/big_tests/tests/mam_helper.erl index 26737b5019..f02ab59c59 100644 --- a/big_tests/tests/mam_helper.erl +++ b/big_tests/tests/mam_helper.erl @@ -16,7 +16,6 @@ -module(mam_helper). -include("mam_helper.hrl"). --include_lib("escalus/include/escalus.hrl"). -include_lib("escalus/include/escalus_xmlns.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("exml/include/exml_stream.hrl"). From af0872bb890e3f5dbd43e1da16bfef7d62ec9cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Fri, 25 Nov 2022 09:48:22 +0100 Subject: [PATCH 35/44] Refactored hook handlers in mod_roster --- src/mod_roster.erl | 191 ++++++++++++++++++++++------------------- src/mongoose_hooks.erl | 23 +++-- test/roster_SUITE.erl | 11 ++- 3 files changed, 127 insertions(+), 98 deletions(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 6e35d8bc7f..69af31c0cd 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -55,14 +55,14 @@ % Hook handlers -export([ - get_user_roster/2, - in_subscription/5, - out_subscription/4, - get_subscription_lists/2, - get_jid_info/4, + get_user_roster/3, + in_subscription/3, + out_subscription/3, + get_subscription_lists/3, + get_jid_info/3, remove_user/3, remove_domain/3, - get_versioning_feature/2, + get_versioning_feature/3, get_personal_data/3 ]). @@ -72,12 +72,10 @@ -export([config_metrics/1]). --ignore_xref([ - get_jid_info/4, get_personal_data/3, get_subscription_lists/2, - get_user_roster/2, get_user_rosters_length/2, get_versioning_feature/2, - in_subscription/5, item_to_xml/1, out_subscription/4, process_subscription_t/6, - remove_user/3, remove_domain/3, transaction/2 -]). +-ignore_xref([get_user_rosters_length/2, + item_to_xml/1, + process_subscription_t/6, + transaction/2]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -107,13 +105,15 @@ %% gdpr callback %%-------------------------------------------------------------------- --spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> - gdpr:personal_data(). -get_personal_data(Acc, HostType, #jid{ luser = LUser, lserver = LServer }) -> +-spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when + Acc :: gdpr:personal_data(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> Schema = ["jid", "name", "subscription", "ask", "groups", "askmessage", "xs"], Records = get_roster(HostType, LUser, LServer), SerializedRecords = lists:map(fun roster_record_to_gdpr_entry/1, Records), - [{roster, Schema, SerializedRecords} | Acc]. + {ok, [{roster, Schema, SerializedRecords} | Acc]}. -spec roster_record_to_gdpr_entry(roster()) -> gdpr:entry(). roster_record_to_gdpr_entry(#roster{ jid = JID, name = Name, @@ -136,13 +136,13 @@ roster_record_to_gdpr_entry(#roster{ jid = JID, name = Name, -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> any(). start(HostType, Opts = #{iqdisc := IQDisc}) -> mod_roster_backend:init(HostType, Opts), - ejabberd_hooks:add(hooks(HostType)), + gen_hook:add_handlers(hooks(HostType)), gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm, fun ?MODULE:process_iq/5, #{}, IQDisc). -spec stop(mongooseim:host_type()) -> any(). stop(HostType) -> - ejabberd_hooks:delete(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm). -spec config_spec() -> mongoose_config_spec:config_section(). @@ -178,16 +178,16 @@ remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts). supported_features() -> [dynamic_domains]. hooks(HostType) -> - [{roster_get, HostType, ?MODULE, get_user_roster, 50}, - {roster_in_subscription, HostType, ?MODULE, in_subscription, 50}, - {roster_out_subscription, HostType, ?MODULE, out_subscription, 50}, - {roster_get_subscription_lists, HostType, ?MODULE, get_subscription_lists, 50}, - {roster_get_jid_info, HostType, ?MODULE, get_jid_info, 50}, - {remove_user, HostType, ?MODULE, remove_user, 50}, - {remove_domain, HostType, ?MODULE, remove_domain, 50}, - {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50}, - {roster_get_versioning_feature, HostType, ?MODULE, get_versioning_feature, 50}, - {get_personal_data, HostType, ?MODULE, get_personal_data, 50}]. + [{roster_get, HostType, fun ?MODULE:get_user_roster/3, #{}, 50}, + {roster_in_subscription, HostType, fun ?MODULE:in_subscription/3, #{}, 50}, + {roster_out_subscription, HostType, fun ?MODULE:out_subscription/3, #{}, 50}, + {roster_get_subscription_lists, HostType, fun ?MODULE:get_subscription_lists/3, #{}, 50}, + {roster_get_jid_info, HostType, fun ?MODULE:get_jid_info/3, #{}, 50}, + {remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}, + {anonymous_purge_hook, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {roster_get_versioning_feature, HostType, fun ?MODULE:get_versioning_feature/3, #{}, 50}, + {get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50}]. -spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> {mongoose_acc:t(), jlib:iq()}. @@ -225,14 +225,19 @@ roster_version_on_db(HostType) -> gen_mod:get_module_opt(HostType, ?MODULE, store_current_id, false). %% Returns a list that may contain an xmlel with the XEP-237 feature if it's enabled. -get_versioning_feature(Acc, HostType) -> - case roster_versioning_enabled(HostType) of +-spec get_versioning_feature(Acc, Params, Extra) -> {ok, Acc} when + Acc :: [jlib:xmlel()], + Params :: map(), + Extra :: gen_hook:extra(). +get_versioning_feature(Acc, _, #{host_type := HostType}) -> + NewAcc = case roster_versioning_enabled(HostType) of true -> Feature = #xmlel{name = <<"ver">>, attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}]}, [Feature | Acc]; false -> [] - end. + end, + {ok, NewAcc}. roster_version(HostType, #jid{luser = LUser, lserver = LServer} = JID) -> case roster_version_on_db(HostType) of @@ -324,11 +329,12 @@ create_sub_el(Items, Version) -> {<<"ver">>, Version}], children = Items}]. --spec get_user_roster(mongoose_acc:t(), jid:jid() | {jid:luser(), jid:lserver()}) -> - mongoose_acc:t(). -get_user_roster(Acc, #jid{luser = LUser, lserver = LServer}) -> - HostType = mongoose_acc:host_type(Acc), - case mongoose_acc:get(roster, show_full_roster, false, Acc) of +-spec get_user_roster(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_user_roster(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> + NewAcc = case mongoose_acc:get(roster, show_full_roster, false, Acc) of true -> Roster = get_roster(HostType, LUser, LServer), mongoose_acc:append(roster, items, Roster, Acc); @@ -339,7 +345,8 @@ get_user_roster(Acc, #jid{luser = LUser, lserver = LServer}) -> true end, get_roster(HostType, LUser, LServer)), mongoose_acc:append(roster, items, Roster, Acc) - end. + end, + {ok, NewAcc}. item_to_xml(Item) -> Attrs1 = [{<<"jid">>, @@ -530,12 +537,14 @@ push_item_final(JID, From, Item, RosterVersion) -> children = [item_to_xml(Item)]}]}, ejabberd_router:route(From, JID, jlib:iq_to_xml(ResIQ)). --spec get_subscription_lists(Acc :: mongoose_acc:t(), JID :: jid:jid()) -> - mongoose_acc:t(). -get_subscription_lists(Acc, #jid{luser = LUser, lserver = LServer} = JID) -> +-spec get_subscription_lists(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_subscription_lists(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, _) -> Items = mod_roster_backend:get_subscription_lists(Acc, LUser, LServer), SubLists = fill_subscription_lists(JID, LServer, Items, [], [], []), - mongoose_acc:set(roster, subscription_lists, SubLists, Acc). + {ok, mongoose_acc:set(roster, subscription_lists, SubLists, Acc)}. fill_subscription_lists(JID, LServer, [#roster{} = I | Is], F, T, P) -> J = element(3, I#roster.usj), @@ -573,26 +582,30 @@ ask_to_pending(subscribe) -> out; ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. --spec in_subscription(Acc :: mongoose_acc:t(), - ToJID :: jid:jid(), - FromJID :: jid:jid(), - Type :: sub_presence(), - Reason :: iodata()) -> - mongoose_acc:t(). -in_subscription(Acc, ToJID, FromJID, Type, Reason) -> - HostType = mongoose_acc:host_type(Acc), +-spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to_jid := jid:jid(), + from_jid := jid:jid(), + type := mod_roster:sub_presence(), + reason := iodata()}, + Extra :: gen_hook:extra(). +in_subscription(Acc, + #{to_jid := ToJID, from_jid := FromJID, type := Type, reason := Reason}, + #{host_type := HostType}) -> Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason), - mongoose_acc:set(hook, result, Res, Acc). - --spec out_subscription(Acc :: mongoose_acc:t(), - FromJID :: jid:jid(), - ToJID :: jid:jid(), - Type :: sub_presence()) -> - mongoose_acc:t(). -out_subscription(Acc, FromJID, ToJID, Type) -> - HostType = mongoose_acc:host_type(Acc), + {ok, mongoose_acc:set(hook, result, Res, Acc)}. + +-spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to_jid := jid:jid(), + from_jid := jid:jid(), + type := mod_roster:sub_presence()}, + Extra :: gen_hook:extra(). +out_subscription(Acc, + #{to_jid := ToJID, from_jid := FromJID, type := Type}, + #{host_type := HostType}) -> Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>), - mongoose_acc:set(hook, result, Res, Acc). + {ok, mongoose_acc:set(hook, result, Res, Acc)}. -spec process_subscription(mongooseim:host_type(), in | out, jid:jid(), jid:jid(), sub_presence(), iodata()) -> @@ -782,10 +795,11 @@ in_auto_reply(_, _, _) -> none. get_user_rosters_length(HostType, JID) -> length(get_roster_old(HostType, JID)). --spec remove_user(mongoose_acc:t(), jid:luser(), jid:lserver()) -> mongoose_acc:t(). -remove_user(Acc, LUser, LServer) -> - HostType = mongoose_acc:host_type(Acc), - JID = jid:make_noprep(LUser, LServer, <<>>), +-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, #{host_type := HostType}) -> Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID), F = fun() -> mod_roster_backend:remove_user_t(HostType, LUser, LServer) end, case transaction(HostType, F) of @@ -794,8 +808,9 @@ remove_user(Acc, LUser, LServer) -> Result -> ?LOG_ERROR(#{what => remove_user_transaction_failed, reason => Result}) end, - Acc1. + {ok, Acc1}. +-spec try_send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t(). try_send_unsubscription_to_rosteritems(Acc, JID) -> try send_unsubscription_to_rosteritems(Acc, JID) @@ -809,8 +824,9 @@ try_send_unsubscription_to_rosteritems(Acc, JID) -> %% For each contact with Subscription: %% Both or From, send a "unsubscribed" presence stanza; %% Both or To, send a "unsubscribe" presence stanza. +-spec send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t(). send_unsubscription_to_rosteritems(Acc, JID) -> - Acc1 = get_user_roster(Acc, JID), + {ok, Acc1} = get_user_roster(Acc, #{jid => JID}, #{host_type => mongoose_acc:host_type(Acc)}), RosterItems = mongoose_acc:get(roster, items, [], Acc1), lists:foreach(fun(RosterItem) -> send_unsubscribing_presence(JID, RosterItem) @@ -839,10 +855,11 @@ send_presence_type(From, To, Type) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec remove_domain(mongoose_hooks:simple_acc(), - mongooseim:host_type(), jid:lserver()) -> - mongoose_hooks:simple_acc(). -remove_domain(Acc, HostType, Domain) -> +-spec remove_domain(Acc, Params, Extra) -> {ok , Acc} when + Acc :: mongoose_domain_api:remove_domain_acc(), + Params :: #{domain := jid:lserver()}, + Extra :: gen_hook:extra(). +remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> case backend_module:is_exported(mod_roster_backend, remove_domain_t, 2) of true -> F = fun() -> mod_roster_backend:remove_domain_t(HostType, Domain) end, @@ -856,7 +873,7 @@ remove_domain(Acc, HostType, Domain) -> false -> ok end, - Acc. + {ok, Acc}. -spec set_items(mongooseim:host_type(), jid:jid(), exml:element()) -> ok | {error, any()}. set_items(HostType, #jid{luser = LUser, lserver = LServer}, SubEl) -> @@ -932,22 +949,24 @@ process_item_attrs_ws(Item, []) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec get_jid_info(HookAcc, mongooseim:host_type(), - ToJID :: jid:jid(), - JID :: jid:jid() | jid:ljid()) -> HookAcc - when HookAcc :: {subscription_state(), [binary()]}. -get_jid_info(_, HostType, ToJID, JID) -> - case get_roster_entry(HostType, ToJID, JID, full) of - error -> {none, []}; - does_not_exist -> - LRJID = jid:to_bare(jid:to_lower(JID)), - case get_roster_entry(HostType, ToJID, LRJID, full) of - error -> {none, []}; - does_not_exist -> {none, []}; - R -> {R#roster.subscription, R#roster.groups} - end; - Re -> {Re#roster.subscription, Re#roster.groups} - end. +-spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when + Acc :: {subscription_state(), [binary()]}, + Params :: #{to_jid := jid:jid(), remote_jid := jid:jid() | jid:simple_jid()}, + Extra :: gen_hook:extra(). +get_jid_info(_, #{to_jid := ToJID, remote_jid := JID}, #{host_type := HostType}) -> + ToRosterEntry = get_roster_entry(HostType, ToJID, JID, full), + RemoteRosterEntryGetter = fun() -> get_roster_entry(HostType, ToJID, jid:to_bare(jid:to_lower(JID)), full) end, + NewAcc = determine_subscription_state(ToRosterEntry, RemoteRosterEntryGetter), + {ok, NewAcc}. + +-spec determine_subscription_state(RosterEntry, RosterEntryGetter) -> SubscriptionState when + RosterEntry :: roster() | does_not_exist | error, + RosterEntryGetter :: fun(() -> RosterEntry) | undefined, + SubscriptionState :: {subscription_state(), [binary()]}. +determine_subscription_state(error, _) -> {none, []}; +determine_subscription_state(does_not_exist, undefined) -> {none, []}; +determine_subscription_state(does_not_exist, Getter) -> determine_subscription_state(Getter(), undefined); +determine_subscription_state(R, _) -> {R#roster.subscription, R#roster.groups}. get_roster_old(HostType, #jid{lserver = LServer} = JID) -> get_roster_old(HostType, LServer, JID). diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index fa8259b9a5..a3317d3688 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -893,8 +893,10 @@ roster_groups(LServer) -> RemoteJID :: jid:jid() | jid:simple_jid(), Result :: {mod_roster:subscription_state(), [binary()]}. roster_get_jid_info(HostType, ToJID, RemBareJID) -> - run_hook_for_host_type(roster_get_jid_info, HostType, {none, []}, - [HostType, ToJID, RemBareJID]). + Params = #{to_jid => ToJID, remote_jid => RemBareJID}, + Args = [HostType, ToJID, RemBareJID], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(roster_get_jid_info, HostType, {none, []}, ParamsWithLegacyArgs). %%% @doc The `roster_get_subscription_lists' hook is called to extract %%% user's subscription list. @@ -908,9 +910,7 @@ roster_get_subscription_lists(HostType, Acc, JID) -> Params = #{jid => BareJID}, Args = [BareJID], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(roster_get_subscription_lists, HostType, Acc, - ParamsWithLegacyArgs). + run_hook_for_host_type(roster_get_subscription_lists, HostType, Acc, ParamsWithLegacyArgs). %%% @doc The `roster_get_versioning_feature' hook is %%% called to determine if roster versioning is enabled. @@ -918,7 +918,9 @@ roster_get_subscription_lists(HostType, Acc, JID) -> HostType :: mongooseim:host_type(), Result :: [exml:element()]. roster_get_versioning_feature(HostType) -> - run_hook_for_host_type(roster_get_versioning_feature, HostType, [], [HostType]). + Args = [HostType], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), + run_hook_for_host_type(roster_get_versioning_feature, HostType, [], ParamsWithLegacyArgs). %%% @doc The `roster_in_subscription' hook is called to determine %%% if a subscription presence is routed to a user. @@ -931,7 +933,7 @@ roster_get_versioning_feature(HostType) -> Result :: mongoose_acc:t(). roster_in_subscription(Acc, To, From, Type, Reason) -> ToJID = jid:to_bare(To), - Params = #{to_jid => ToJID, from => From, type => Type, reason => Reason}, + Params = #{to_jid => ToJID, from_jid => From, type => Type, reason => Reason}, Args = [ToJID, From, Type, Reason], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), @@ -946,9 +948,12 @@ roster_in_subscription(Acc, To, From, Type, Reason) -> Type :: mod_roster:sub_presence(), Result :: mongoose_acc:t(). roster_out_subscription(Acc, From, To, Type) -> + FromJID = jid:to_bare(From), + Params = #{to_jid => To, from_jid => FromJID, type => Type}, + Args = [FromJID, To, Type], HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(roster_out_subscription, HostType, Acc, - [jid:to_bare(From), To, Type]). + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(roster_out_subscription, HostType, Acc, ParamsWithLegacyArgs). %%% @doc The `roster_process_item' hook is called when a user's roster is set. -spec roster_process_item(HostType, LServer, Item) -> Result when diff --git a/test/roster_SUITE.erl b/test/roster_SUITE.erl index ad900efe7f..92665aa61e 100644 --- a/test/roster_SUITE.erl +++ b/test/roster_SUITE.erl @@ -55,7 +55,8 @@ init_per_testcase(_TC, C) -> end_per_testcase(_TC, C) -> Acc = mongoose_acc:new(?ACC_PARAMS), - mod_roster:remove_user(Acc, a(), domain()), + JID = jid:make_bare(a(), domain()), + mod_roster:remove_user(Acc, #{jid => JID}, #{host_type => mongoose_acc:host_type(Acc)}), mongoose_modules:stop(), delete_ets(), C. @@ -141,13 +142,17 @@ get_roster_old() -> get_roster_old(User) -> Acc = mongoose_acc:new(?ACC_PARAMS), - Acc1 = mod_roster:get_user_roster(Acc, jid:make(User, domain(), <<>>)), + Params = #{jid => jid:make_bare(User, domain())}, + Extra = #{host_type => mongoose_acc:host_type(Acc)}, + {ok, Acc1} = mod_roster:get_user_roster(Acc, Params, Extra), mongoose_acc:get(roster, items, Acc1). get_full_roster() -> Acc0 = mongoose_acc:new(?ACC_PARAMS), Acc1 = mongoose_acc:set(roster, show_full_roster, true, Acc0), - Acc2 = mod_roster:get_user_roster(Acc1, alice_jid()), + Params = #{jid => alice_jid()}, + Extra = #{host_type => mongoose_acc:host_type(Acc1)}, + {ok, Acc2} = mod_roster:get_user_roster(Acc1, Params, Extra), mongoose_acc:get(roster, items, Acc2). assert_state_old(Subscription, Ask) -> From 773749e4bab694d0e73fa99476f98733db9a3a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Mon, 28 Nov 2022 10:20:17 +0100 Subject: [PATCH 36/44] Refactored hook handlers in mod_privacy --- src/mongoose_hooks.erl | 10 ++- src/privacy/mod_privacy.erl | 149 +++++++++++++++++++++--------------- 2 files changed, 95 insertions(+), 64 deletions(-) diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index fa8259b9a5..babeea58c3 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -706,7 +706,10 @@ privacy_check_packet(Acc, JID, PrivacyList, FromToNameType, Dir) -> JID :: jid:jid(), Result :: mongoose_privacy:userlist(). privacy_get_user_list(HostType, JID) -> - run_hook_for_host_type(privacy_get_user_list, HostType, #userlist{}, [HostType, JID]). + Params = #{jid => JID}, + Args = [HostType, JID], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(privacy_get_user_list, HostType, #userlist{}, ParamsWithLegacyArgs). -spec privacy_iq_get(HostType, Acc, From, To, IQ, PrivList) -> Result when HostType :: mongooseim:host_type(), @@ -741,7 +744,10 @@ privacy_iq_set(HostType, Acc, From, To, IQ) -> NewList :: mongoose_privacy:userlist(), Result :: false | mongoose_privacy:userlist(). privacy_updated_list(HostType, OldList, NewList) -> - run_hook_for_host_type(privacy_updated_list, HostType, false, [OldList, NewList]). + Params = #{old_list => OldList, new_list => NewList}, + Args = [OldList, NewList], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(privacy_updated_list, HostType, false, ParamsWithLegacyArgs). %% Session management related hooks diff --git a/src/privacy/mod_privacy.erl b/src/privacy/mod_privacy.erl index c1691ba373..d20ee4b5ae 100644 --- a/src/privacy/mod_privacy.erl +++ b/src/privacy/mod_privacy.erl @@ -36,23 +36,19 @@ -export([supported_features/0]). -export([config_spec/0]). --export([process_iq_set/4, - process_iq_get/5, +%% Hook handlers +-export([process_iq_set/3, + process_iq_get/3, get_user_list/3, - check_packet/5, + check_packet/3, remove_user/3, remove_domain/3, updated_list/3, - disco_local_features/3, - remove_unused_backend_opts/1 - ]). + disco_local_features/3]). --export([config_metrics/1]). +-export([remove_unused_backend_opts/1]). --ignore_xref([ - behaviour_info/1, check_packet/5, get_user_list/3, process_iq_get/5, - process_iq_set/4, remove_user/3, updated_list/3, - remove_user/3, remove_domain/3]). +-export([config_metrics/1]). -include("jlib.hrl"). -include("mod_privacy.hrl"). @@ -73,12 +69,10 @@ -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok. start(HostType, Opts) when is_map(Opts) -> mod_privacy_backend:init(HostType, Opts), - ejabberd_hooks:add(legacy_hooks(HostType)), gen_hook:add_handlers(hooks(HostType)). -spec stop(mongooseim:host_type()) -> ok. stop(HostType) -> - ejabberd_hooks:delete(legacy_hooks(HostType)), gen_hook:delete_handlers(hooks(HostType)). config_spec() -> @@ -112,20 +106,16 @@ remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts). supported_features() -> [dynamic_domains]. -legacy_hooks(HostType) -> - [ - {privacy_iq_get, HostType, ?MODULE, process_iq_get, 50}, - {privacy_iq_set, HostType, ?MODULE, process_iq_set, 50}, - {privacy_get_user_list, HostType, ?MODULE, get_user_list, 50}, - {privacy_check_packet, HostType, ?MODULE, check_packet, 50}, - {privacy_updated_list, HostType, ?MODULE, updated_list, 50}, - {remove_user, HostType, ?MODULE, remove_user, 50}, - {remove_domain, HostType, ?MODULE, remove_domain, 50}, - {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50} - ]. - hooks(HostType) -> - [{disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 98}]. + [{disco_local_features, HostType, fun ?MODULE:disco_local_features/3, #{}, 98}, + {privacy_iq_get, HostType, fun ?MODULE:process_iq_get/3, #{}, 50}, + {privacy_iq_set, HostType, fun ?MODULE:process_iq_set/3, #{}, 50}, + {privacy_get_user_list, HostType, fun ?MODULE:get_user_list/3, #{}, 50}, + {privacy_check_packet, HostType, fun ?MODULE:check_packet/3, #{}, 50}, + {privacy_updated_list, HostType, fun ?MODULE:updated_list/3, #{}, 50}, + {remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50}, + {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50}, + {anonymous_purge_hook, HostType, fun ?MODULE:remove_user/3, #{}, 50}]. %% ------------------------------------------------------------------ %% Handlers @@ -139,12 +129,17 @@ disco_local_features(Acc = #{node := <<>>}, _, _) -> disco_local_features(Acc, _, _) -> {ok, Acc}. +-spec process_iq_get(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{from := jid:jid(), + iq := jlib:iq(), + priv_list := mongoose_privacy:userlist()}, + Extra :: gen_hook:extra(). process_iq_get(Acc, - _From = #jid{luser = LUser, lserver = LServer}, - _To, - #iq{xmlns = ?NS_PRIVACY, sub_el = #xmlel{children = Els}}, - #userlist{name = Active}) -> - HostType = mongoose_acc:host_type(Acc), + #{from := #jid{luser = LUser, lserver = LServer}, + iq := #iq{xmlns = ?NS_PRIVACY, sub_el = #xmlel{children = Els}}, + priv_list := #userlist{name = Active}}, + #{host_type := HostType}) -> Res = case xml:remove_cdata(Els) of [] -> process_lists_get(HostType, LUser, LServer, Active); @@ -159,9 +154,9 @@ process_iq_get(Acc, _ -> {error, mongoose_xmpp_errors:bad_request()} end, - mongoose_acc:set(hook, result, Res, Acc); -process_iq_get(Val, _, _, _, _) -> - Val. + {ok, mongoose_acc:set(hook, result, Res, Acc)}; +process_iq_get(Acc, _, _) -> + {ok, Acc}. process_lists_get(HostType, LUser, LServer, Active) -> case mod_privacy_backend:get_list_names(HostType, LUser, LServer) of @@ -186,9 +181,14 @@ process_list_get(HostType, LUser, LServer, {value, Name}) -> process_list_get(_HostType, _LUser, _LServer, false) -> {error, mongoose_xmpp_errors:bad_request()}. -process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}) -> +-spec process_iq_set(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{from := jid:jid(), iq := jlib:iq()}, + Extra :: gen_hook:extra(). +process_iq_set(Acc, + #{from := From, iq := #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}}, + #{host_type := HostType}) -> #xmlel{children = Els} = SubEl, - HostType = mongoose_acc:host_type(Acc), Res = case xml:remove_cdata(Els) of [#xmlel{name = Name, attrs = Attrs, children = SubEls}] -> ListName = xml:get_attr(<<"name">>, Attrs), @@ -206,9 +206,9 @@ process_iq_set(Acc, From, _To, #iq{xmlns = ?NS_PRIVACY, sub_el = SubEl}) -> _ -> {error, mongoose_xmpp_errors:bad_request()} end, - mongoose_acc:set(hook, result, Res, Acc); -process_iq_set(Val, _, _, _) -> - Val. + {ok, mongoose_acc:set(hook, result, Res, Acc)}; +process_iq_set(Acc, _, _) -> + {ok, Acc}. process_default_set(HostType, #jid{luser = LUser, lserver = LServer}, {value, Name}) -> case mod_privacy_backend:set_default_list(HostType, LUser, LServer, Name) of @@ -283,24 +283,40 @@ is_item_needdb(#listitem{type = subscription}) -> true; is_item_needdb(#listitem{type = group}) -> true; is_item_needdb(_) -> false. -get_user_list(_, HostType, #jid{luser = LUser, lserver = LServer}) -> - case mod_privacy_backend:get_default_list(HostType, LUser, LServer) of +-spec get_user_list(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_privacy:userlist(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_user_list(_, + #{jid := #jid{luser = LUser, lserver = LServer}}, + #{host_type := HostType}) -> + UserList = case mod_privacy_backend:get_default_list(HostType, LUser, LServer) of {ok, {Default, List}} -> NeedDb = is_list_needdb(List), #userlist{name = Default, list = List, needdb = NeedDb}; {error, _} -> #userlist{} - end. + end, + {ok, UserList}. %% From is the sender, To is the destination. %% If Dir = out, User@Server is the sender account (From). %% If Dir = in, User@Server is the destination account (To). -check_packet(Acc, _JID, #userlist{list = []}, _, _Dir) -> - mongoose_acc:set(hook, result, allow, Acc); -check_packet(Acc, JID, - #userlist{list = List, needdb = NeedDb}, - {From, To, Name, Type}, Dir) -> - HostType = mongoose_acc:host_type(Acc), +-spec check_packet(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid(), + privacy_list := mongoose_privacy:userlist(), + from_to_name_type := {jid:jid(), jid:jid(), binary(), binary()}, + dir := in | out}, + Extra :: gen_hook:extra(). +check_packet(Acc, #{privacy_list := #userlist{list = []}}, _) -> + {ok, mongoose_acc:set(hook, result, allow, Acc)}; +check_packet(Acc, + #{jid := JID, + privacy_list := #userlist{list = List, needdb = NeedDb}, + from_to_name_type := {From, To, Name, Type}, + dir := Dir}, + #{host_type := HostType}) -> PType = packet_directed_type(Dir, packet_type(Name, Type)), LJID = case Dir of in -> jid:to_lower(From); @@ -314,7 +330,7 @@ check_packet(Acc, JID, {[], []} end, CheckResult = check_packet_aux(List, PType, Type, LJID, Subscription, Groups), - mongoose_acc:set(hook, result, CheckResult, Acc). + {ok, mongoose_acc:set(hook, result, CheckResult, Acc)}. %% allow error messages check_packet_aux(_, message, <<"error">>, _JID, _Subscription, _Groups) -> @@ -390,23 +406,32 @@ is_type_match(subscription, Value, _JID, Subscription, _Groups) -> is_type_match(group, Value, _JID, _Subscription, Groups) -> lists:member(Value, Groups). -remove_user(Acc, User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - HostType = mongoose_acc:host_type(Acc), +-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: #{host_type := mongooseim:host_type()}. +remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> R = mod_privacy_backend:remove_user(HostType, LUser, LServer), - mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}), - Acc. + mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, LUser, LServer}), + {ok, Acc}. --spec remove_domain(mongoose_hooks:simple_acc(), - mongooseim:host_type(), jid:lserver()) -> - mongoose_hooks:simple_acc(). -remove_domain(Acc, HostType, Domain) -> +-spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_hooks:simple_acc(), + Params :: #{domain := jid:lserver()}, + Extra :: gen_hook:extra(). +remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> mod_privacy_backend:remove_domain(HostType, Domain), - Acc. + {ok, Acc}. -updated_list(_, #userlist{name = SameName}, #userlist{name = SameName} = New) -> New; -updated_list(_, Old, _) -> Old. +-spec updated_list(Acc, Params, Extra) -> {ok, Acc} when + Acc :: false | mongoose_privacy:userlist(), + Params :: #{old_list := mongoose_privacy:userlist(), + new_list := mongoose_privacy:userlist()}, + Extra :: gen_hook:extra(). +updated_list(_, #{old_list := #userlist{name = SameName}, + new_list := #userlist{name = SameName} = NewList}, + _) -> {ok, NewList}; +updated_list(_, #{old_list := OldList}, _) -> {ok, OldList}. %% ------------------------------------------------------------------ %% Deserialization From 63579d2a75bdfacdbbab0f00b7097d1ee6e5e106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Fri, 25 Nov 2022 09:45:42 +0100 Subject: [PATCH 37/44] Code review follow-up --- src/ejabberd_sm.erl | 4 ++-- src/mod_roster.erl | 20 ++++++++++---------- src/mod_shared_roster_ldap.erl | 16 ++++++++-------- src/mongoose_hooks.erl | 6 +++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index c8696205fd..380a27fde7 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -449,9 +449,9 @@ node_cleanup(Acc, #{node := Node}, _) -> -spec check_in_subscription(Acc, Args, Extra)-> {ok, Acc} | {stop, false} when Acc :: any(), - Args :: #{to_jid := jid:jid()}, + Args :: #{to := jid:jid()}, Extra :: map(). -check_in_subscription(Acc, #{to_jid := ToJID}, _) -> +check_in_subscription(Acc, #{to := ToJID}, _) -> case ejabberd_auth:does_user_exist(ToJID) of true -> {ok, Acc}; diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 69af31c0cd..ac68cacb63 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -584,25 +584,25 @@ ask_to_pending(Ask) -> Ask. -spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when Acc :: mongoose_acc:t(), - Params :: #{to_jid := jid:jid(), - from_jid := jid:jid(), - type := mod_roster:sub_presence(), + Params :: #{to := jid:jid(), + from := jid:jid(), + type := sub_presence(), reason := iodata()}, Extra :: gen_hook:extra(). in_subscription(Acc, - #{to_jid := ToJID, from_jid := FromJID, type := Type, reason := Reason}, + #{to := ToJID, from := FromJID, type := Type, reason := Reason}, #{host_type := HostType}) -> Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason), {ok, mongoose_acc:set(hook, result, Res, Acc)}. -spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when Acc :: mongoose_acc:t(), - Params :: #{to_jid := jid:jid(), - from_jid := jid:jid(), - type := mod_roster:sub_presence()}, + Params :: #{to := jid:jid(), + from := jid:jid(), + type := sub_presence()}, Extra :: gen_hook:extra(). out_subscription(Acc, - #{to_jid := ToJID, from_jid := FromJID, type := Type}, + #{to := ToJID, from := FromJID, type := Type}, #{host_type := HostType}) -> Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>), {ok, mongoose_acc:set(hook, result, Res, Acc)}. @@ -951,9 +951,9 @@ process_item_attrs_ws(Item, []) -> -spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when Acc :: {subscription_state(), [binary()]}, - Params :: #{to_jid := jid:jid(), remote_jid := jid:jid() | jid:simple_jid()}, + Params :: #{to := jid:jid(), remote := jid:jid() | jid:simple_jid()}, Extra :: gen_hook:extra(). -get_jid_info(_, #{to_jid := ToJID, remote_jid := JID}, #{host_type := HostType}) -> +get_jid_info(_, #{to := ToJID, remote := JID}, #{host_type := HostType}) -> ToRosterEntry = get_roster_entry(HostType, ToJID, JID, full), RemoteRosterEntryGetter = fun() -> get_roster_entry(HostType, ToJID, jid:to_bare(jid:to_lower(JID)), full) end, NewAcc = determine_subscription_state(ToRosterEntry, RemoteRosterEntryGetter), diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 8667f4f887..8922bcb3e7 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -218,9 +218,9 @@ get_subscription_lists(Acc, #{jid := #jid{lserver = LServer} = JID}, _) -> -spec get_jid_info(Acc, Params, Extra) -> {ok, Acc} when Acc :: {mod_roster:subscription_state(), [binary()]}, - Params :: #{to_jid := jid:jid(), remote_jid := jid:jid() | jid:simple_jid()}, + Params :: #{to := jid:jid(), remote := jid:jid() | jid:simple_jid()}, Extra :: gen_hook:extra(). -get_jid_info({Subscription, Groups}, #{to_jid := ToJID, remote_jid := JID}, _) -> +get_jid_info({Subscription, Groups}, #{to := ToJID, remote := JID}, _) -> ToUS = jid:to_lus(ToJID), US1 = jid:to_lus(JID), SRUsers = get_user_to_groups_map(ToUS, false), @@ -237,11 +237,11 @@ get_jid_info({Subscription, Groups}, #{to_jid := ToJID, remote_jid := JID}, _) - -spec in_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when Acc :: mongoose_acc:t(), - Params :: #{to_jid := jid:jid(), - from_jid := jid:jid(), + Params :: #{to := jid:jid(), + from := jid:jid(), type := mod_roster:sub_presence()}, Extra :: gen_hook:extra(). -in_subscription(Acc, #{to_jid := ToJID, from_jid := FromJID, type := Type}, _) -> +in_subscription(Acc, #{to := ToJID, from := FromJID, type := Type}, _) -> case process_subscription(in, ToJID, FromJID, Type) of stop -> {stop, Acc}; @@ -252,11 +252,11 @@ in_subscription(Acc, #{to_jid := ToJID, from_jid := FromJID, type := Type}, _) - -spec out_subscription(Acc, Params, Extra) -> {ok | stop, Acc} when Acc :: mongoose_acc:t(), - Params :: #{to_jid := jid:jid(), - from_jid := jid:jid(), + Params :: #{to := jid:jid(), + from := jid:jid(), type := mod_roster:sub_presence()}, Extra :: gen_hook:extra(). - out_subscription(Acc, #{to_jid := ToJID, from_jid := FromJID, type := Type}, _) -> + out_subscription(Acc, #{to := ToJID, from := FromJID, type := Type}, _) -> case process_subscription(out, FromJID, ToJID, Type) of stop -> {stop, Acc}; diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index a3317d3688..f394071e5d 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -893,7 +893,7 @@ roster_groups(LServer) -> RemoteJID :: jid:jid() | jid:simple_jid(), Result :: {mod_roster:subscription_state(), [binary()]}. roster_get_jid_info(HostType, ToJID, RemBareJID) -> - Params = #{to_jid => ToJID, remote_jid => RemBareJID}, + Params = #{to => ToJID, remote => RemBareJID}, Args = [HostType, ToJID, RemBareJID], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), run_hook_for_host_type(roster_get_jid_info, HostType, {none, []}, ParamsWithLegacyArgs). @@ -933,7 +933,7 @@ roster_get_versioning_feature(HostType) -> Result :: mongoose_acc:t(). roster_in_subscription(Acc, To, From, Type, Reason) -> ToJID = jid:to_bare(To), - Params = #{to_jid => ToJID, from_jid => From, type => Type, reason => Reason}, + Params = #{to => ToJID, from => From, type => Type, reason => Reason}, Args = [ToJID, From, Type, Reason], ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), @@ -949,7 +949,7 @@ roster_in_subscription(Acc, To, From, Type, Reason) -> Result :: mongoose_acc:t(). roster_out_subscription(Acc, From, To, Type) -> FromJID = jid:to_bare(From), - Params = #{to_jid => To, from_jid => FromJID, type => Type}, + Params = #{to => To, from => FromJID, type => Type}, Args = [FromJID, To, Type], HostType = mongoose_acc:host_type(Acc), ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), From d7a6236abc2970cb2c901abb99537f5a0d3b3449 Mon Sep 17 00:00:00 2001 From: Kamil Waz Date: Mon, 28 Nov 2022 13:29:21 +0100 Subject: [PATCH 38/44] Improve error handling in server --- big_tests/tests/graphql_server_SUITE.erl | 2 +- src/admin_extra/service_admin_extra_node.erl | 2 +- src/ejabberd_admin.erl | 2 +- ...mongoose_graphql_server_admin_mutation.erl | 32 ++++---- .../mongoose_graphql_server_admin_query.erl | 15 ++-- src/mongoose_server_api.erl | 79 ++++++++++--------- 6 files changed, 66 insertions(+), 66 deletions(-) diff --git a/big_tests/tests/graphql_server_SUITE.erl b/big_tests/tests/graphql_server_SUITE.erl index 94d90d391d..bc134fd06c 100644 --- a/big_tests/tests/graphql_server_SUITE.erl +++ b/big_tests/tests/graphql_server_SUITE.erl @@ -163,7 +163,7 @@ leave_but_no_cluster(Config) -> join_twice(Config) -> #{node := Node2} = RPCSpec2 = mim2(), get_ok_value([], join_cluster(atom_to_binary(Node2), Config)), - get_ok_value([], join_cluster(atom_to_binary(Node2), Config)), + ?assertEqual(<<"already_joined">>, get_err_code(join_cluster(atom_to_binary(Node2), Config))), distributed_helper:verify_result(RPCSpec2, add). remove_dead_from_cluster(Config) -> diff --git a/src/admin_extra/service_admin_extra_node.erl b/src/admin_extra/service_admin_extra_node.erl index 53162c65d0..3b8fdbaacb 100644 --- a/src/admin_extra/service_admin_extra_node.erl +++ b/src/admin_extra/service_admin_extra_node.erl @@ -43,7 +43,7 @@ commands() -> desc = "Get the Erlang cookie of this node", module = mongoose_server_api, function = get_cookie, args = [], - result = {cookie, string}}, + result = {res, restuple}}, #ejabberd_commands{name = remove_node, tags = [erlang], desc = "Remove a MongooseIM node from Mnesia clustering config", module = mongoose_server_api, function = remove_node, diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 346f5b49ed..752650d49b 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -77,7 +77,7 @@ commands() -> args = [], result = {res, rescode}}, #ejabberd_commands{name = get_loglevel, tags = [logs, server], desc = "Get the current loglevel", - module = mongoose_server_api, function = get_loglevel, + module = mongoose_server_api, function = get_loglevel_mongooseimctl, args = [], result = {res, restuple}}, #ejabberd_commands{name = register, tags = [accounts], diff --git a/src/graphql/admin/mongoose_graphql_server_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_server_admin_mutation.erl index 82c996df11..e883d5cd75 100644 --- a/src/graphql/admin/mongoose_graphql_server_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_server_admin_mutation.erl @@ -11,15 +11,10 @@ execute(#{method := cli}, server, <<"joinCluster">>, #{<<"node">> := Node}) -> case mongoose_server_api:join_cluster(binary_to_list(Node)) of - {mnesia_error, _} = Error -> - make_error(Error, #{cluster => Node}); - {error, Message} -> - make_error({internal_server_error, io_lib:format("~p", [Message])}, - #{cluster => Node}); - {pang, String} -> - make_error({timeout_error, String}, #{cluster => Node}); - {_, String} -> - {ok, String} + {ok, _} = Result -> + Result; + Error -> + make_error(Error, #{node => Node}) end; execute(#{method := http}, server, <<"joinCluster">>, #{<<"node">> := Node}) -> spawn(?MODULE, await_execution, @@ -40,22 +35,23 @@ execute(#{method := http}, server, <<"removeFromCluster">>, #{<<"node">> := Node execute(#{method := cli}, server, <<"leaveCluster">>, #{}) -> case mongoose_server_api:leave_cluster() of - {error, Message} -> - make_error({internal_server_error, io_lib:format("~p", [Message])}, #{}); - {not_in_cluster, String} -> - make_error({not_in_cluster_error, String}, #{}); - {_, String} -> - {ok, String} + {ok, _} = Result -> + Result; + Error -> + make_error(Error, #{}) end; execute(#{method := http}, server, <<"leaveCluster">>, #{}) -> spawn(?MODULE, await_execution, [1000, mongoose_server_api, leave_cluster, []]), {ok, "LeaveCluster scheduled"}; - execute(_Ctx, server, <<"removeNode">>, #{<<"node">> := Node}) -> mongoose_server_api:remove_node(binary_to_list(Node)); - execute(_Ctx, server, <<"setLoglevel">>, #{<<"level">> := LogLevel}) -> - mongoose_server_api:set_loglevel(LogLevel); + case mongoose_server_api:set_loglevel(LogLevel) of + {ok, _} = Result -> + Result; + Error -> + make_error(Error, #{level => LogLevel}) + end; execute(_Ctx, server, <<"stop">>, #{}) -> spawn(mongoose_server_api, stop, []), {ok, "Stop scheduled"}; diff --git a/src/graphql/admin/mongoose_graphql_server_admin_query.erl b/src/graphql/admin/mongoose_graphql_server_admin_query.erl index dc442b7e5a..50923131aa 100644 --- a/src/graphql/admin/mongoose_graphql_server_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_server_admin_query.erl @@ -8,13 +8,12 @@ -include("../mongoose_graphql_types.hrl"). execute(_Ctx, server, <<"status">>, _) -> - case mongoose_server_api:status() of - {ok, String} -> - {ok, #{<<"statusCode">> => <<"RUNNING">>, <<"message">> => String}}; - {_, String} -> - {ok, #{<<"statusCode">> => <<"NOT_RUNNING">>, <<"message">> => String}} - end; + {ok, {Status, String}} = mongoose_server_api:status(), + {ok, #{<<"statusCode">> => status_code(Status), <<"message">> => String}}; execute(_Ctx, server, <<"getLoglevel">>, _) -> - mongoose_server_api:graphql_get_loglevel(); + mongoose_server_api:get_loglevel(); execute(_Ctx, server, <<"getCookie">>, _) -> - {ok, mongoose_server_api:get_cookie()}. + mongoose_server_api:get_cookie(). + +status_code(true) -> <<"RUNNING">>; +status_code(false) -> <<"NOT_RUNNING">>. diff --git a/src/mongoose_server_api.erl b/src/mongoose_server_api.erl index 6a5637102d..1d0910376b 100644 --- a/src/mongoose_server_api.erl +++ b/src/mongoose_server_api.erl @@ -1,24 +1,25 @@ -module(mongoose_server_api). --export([get_loglevel/0, status/0, get_cookie/0, join_cluster/1, leave_cluster/0, +-export([get_loglevel_mongooseimctl/0]). + +-export([status/0, get_cookie/0, join_cluster/1, leave_cluster/0, remove_from_cluster/1, stop/0, restart/0, remove_node/1, set_loglevel/1, - graphql_get_loglevel/0]). + get_loglevel/0]). --ignore_xref([get_loglevel/0]). +-ignore_xref([get_loglevel_mongooseimctl/0]). --spec get_loglevel() -> {ok, string()}. -get_loglevel() -> +-spec get_loglevel_mongooseimctl() -> {ok, iolist()}. +get_loglevel_mongooseimctl() -> Level = mongoose_logs:get_global_loglevel(), Number = mongoose_logs:loglevel_keyword_to_number(Level), String = io_lib:format("global loglevel is ~p, which means '~p'", [Number, Level]), {ok, String}. --spec graphql_get_loglevel() -> {ok, mongoose_logs:atom_log_level()}. -graphql_get_loglevel() -> +-spec get_loglevel() -> {ok, mongoose_logs:atom_log_level()}. +get_loglevel() -> {ok, mongoose_logs:get_global_loglevel()}. --spec set_loglevel(mongoose_logs:atom_log_level()) -> - {ok, string()} | {invalid_level, string()}. +-spec set_loglevel(mongoose_logs:atom_log_level()) -> {ok, iolist()} | {invalid_level, iolist()}. set_loglevel(Level) -> case mongoose_logs:set_global_loglevel(Level) of ok -> @@ -27,31 +28,34 @@ set_loglevel(Level) -> {invalid_level, io_lib:format("Log level ~p does not exist.", [Level])} end. --spec status() -> {'mongooseim_not_running', string()} | {'ok', string()}. +-spec status() -> {ok, {boolean(), iolist()}} | {mongooseim_not_running, iolist()}. status() -> {InternalStatus, ProvidedStatus} = init:get_status(), String1 = io_lib:format("The node ~p is ~p. Status: ~p.", [node(), InternalStatus, ProvidedStatus]), - case lists:keysearch(mongooseim, 1, application:which_applications()) of - false -> - {mongooseim_not_running, String1 ++ " MongooseIM is not running in that node."}; - {value, {_, _, Version}} -> - {ok, String1 ++ io_lib:format(" MongooseIM ~s is running in that node.", [Version])} - end. - --spec get_cookie() -> string(). + Result = + case lists:keysearch(mongooseim, 1, application:which_applications()) of + false -> + {false, String1 ++ " MongooseIM is not running in that node."}; + {value, {_, _, Version}} -> + {true, + String1 ++ io_lib:format(" MongooseIM ~s is running in that node.", [Version])} + end, + {ok, Result}. + +-spec get_cookie() -> {ok, iolist()}. get_cookie() -> - atom_to_list(erlang:get_cookie()). + {ok, atom_to_list(erlang:get_cookie())}. --spec join_cluster(string()) -> {ok, string()} | {pang, string()} | {already_joined, string()} | - {mnesia_error, string()} | {error, any()}. +-spec join_cluster(string()) -> {ok, iolist()} + | {pang | already_joined | mnesia_error | error, iolist()}. join_cluster(NodeString) -> NodeAtom = list_to_atom(NodeString), NodeList = mnesia:system_info(db_nodes), case lists:member(NodeAtom, NodeList) of true -> String = io_lib:format( - "The MongooseIM node ~s has already joined the cluster~n.", [NodeString]), + "The MongooseIM node ~s has already joined the cluster.", [NodeString]), {already_joined, String}; _ -> do_join_cluster(NodeAtom) @@ -61,7 +65,7 @@ do_join_cluster(Node) -> try mongoose_cluster:join(Node) of ok -> String = io_lib:format("You have successfully added the MongooseIM node" - ++ " ~p to the cluster with node member ~p~n.", [node(), Node]), + " ~p to the cluster with node member ~p.", [node(), Node]), {ok, String} catch error:pang -> @@ -70,13 +74,15 @@ do_join_cluster(Node) -> {pang, String}; error:{cant_get_storage_type, {T, E, R}} -> String = - io_lib:format("Cannot get storage type for table ~p~n. Reason: ~p:~p", [T, E, R]), + io_lib:format("Cannot get storage type for table ~p. Reason: ~p:~p", [T, E, R]), {mnesia_error, String}; - E:R:S -> - {error, {E, R, S}} + E:R -> + String = + io_lib:format("Failed to join the cluster. Reason: ~p:~p", [E, R]), + {error, String} end. --spec leave_cluster() -> {ok, string()} | {error, term()} | {not_in_cluster, string()}. +-spec leave_cluster() -> {ok, string()} | {error | not_in_cluster, iolist()}. leave_cluster() -> NodeList = mnesia:system_info(running_db_nodes), ThisNode = node(), @@ -96,13 +102,12 @@ do_leave_cluster() -> {ok, String} catch E:R -> - {error, {E, R}} + String = io_lib:format("Failed to leave the cluster. Reason: ~p:~p", [E, R]), + {error, String} end. --spec remove_from_cluster(string()) -> {ok, string()} | - {node_is_alive, string()} | - {mnesia_error, string()} | - {rpc_error, string()}. +-spec remove_from_cluster(string()) -> {ok, iolist()} | + {node_is_alive | mnesia_error | rpc_error, iolist()}. remove_from_cluster(NodeString) -> Node = list_to_atom(NodeString), IsNodeAlive = mongoose_cluster:is_node_alive(Node), @@ -123,10 +128,10 @@ remove_dead_node(DeadNode) -> catch error:{node_is_alive, DeadNode} -> String = io_lib:format( - "The MongooseIM node ~p is alive but shoud not be.~n", [DeadNode]), + "The MongooseIM node ~p is alive but shoud not be.", [DeadNode]), {node_is_alive, String}; error:{del_table_copy_schema, R} -> - String = io_lib:format("Cannot delete table schema~n. Reason: ~p", [R]), + String = io_lib:format("Cannot delete table schema. Reason: ~p", [R]), {mnesia_error, String} end. @@ -139,10 +144,10 @@ remove_rpc_alive_node(AliveNode) -> {rpc_error, String}; ok -> String = io_lib:format( - "The MongooseIM node ~p has been removed from the cluster~n", [AliveNode]), + "The MongooseIM node ~p has been removed from the cluster", [AliveNode]), {ok, String}; Unknown -> - String = io_lib:format("Unknown error: ~p~n", [Unknown]), + String = io_lib:format("Unknown error: ~p", [Unknown]), {rpc_error, String} end. @@ -156,7 +161,7 @@ restart() -> timer:sleep(500), init:restart(). --spec remove_node(string()) -> {ok, string()}. +-spec remove_node(string()) -> {ok, iolist()}. remove_node(Node) -> mnesia:del_table_copy(schema, list_to_atom(Node)), {ok, "MongooseIM node removed from the Mnesia schema"}. From efa2c8e9dcddb2824816d71e170f99c52afa1fe7 Mon Sep 17 00:00:00 2001 From: Mikhail Uvarov Date: Wed, 23 Nov 2022 14:03:27 +0100 Subject: [PATCH 39/44] Fix cassandra auth parameters parsing --- src/wpool/mongoose_wpool_cassandra.erl | 6 +++++- test/mongoose_wpool_SUITE.erl | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/wpool/mongoose_wpool_cassandra.erl b/src/wpool/mongoose_wpool_cassandra.erl index 1832f6de6b..68b8ff4a44 100644 --- a/src/wpool/mongoose_wpool_cassandra.erl +++ b/src/wpool/mongoose_wpool_cassandra.erl @@ -5,6 +5,10 @@ -export([start/4]). -export([stop/2]). +-ifdef(TEST). +-export([prepare_cqerl_opts/1]). +-endif. + %% -------------------------------------------------------------- %% mongoose_wpool callbacks -spec init() -> ok. @@ -50,7 +54,7 @@ prepare_cqerl_opts(ConnOpts) -> cqerl_opts(keyspace, #{keyspace := Keyspace}) -> [{keyspace, Keyspace}]; cqerl_opts(auth, #{auth := #{plain := #{username := UserName, password := Password}}}) -> - [{cqerl_auth_plain_handler, [{UserName, Password}]}]; + [{auth, {cqerl_auth_plain_handler, [{UserName, Password}]}}]; cqerl_opts(tcp, #{}) -> [{tcp_opts, [{keepalive, true}]}]; % always set cqerl_opts(tls, #{tls := TLSOpts}) -> diff --git a/test/mongoose_wpool_SUITE.erl b/test/mongoose_wpool_SUITE.erl index f85a4ceae8..559945c363 100644 --- a/test/mongoose_wpool_SUITE.erl +++ b/test/mongoose_wpool_SUITE.erl @@ -39,7 +39,8 @@ all() -> dead_pool_is_restarted, dead_pool_is_stopped_before_restarted, riak_pool_cant_be_started_with_available_worker_strategy, - redis_pool_cant_be_started_with_available_worker_strategy + redis_pool_cant_be_started_with_available_worker_strategy, + cassandra_prepare_opts ]. %%-------------------------------------------------------------------- @@ -269,6 +270,13 @@ pool_cant_be_started_with_available_worker_strategy(Type) -> ?assertError({strategy_not_supported, Type, Host, Tag, available_worker}, mongoose_wpool:start_configured_pools(PoolDef)). +cassandra_prepare_opts(_Config) -> + %% Check that we pass auth options in the correct format to the Cassandra driver + AuthCfg = #{auth => #{plain => #{username => <<"user">>, password => <<"password">>}}}, + ?assertEqual([{auth, {cqerl_auth_plain_handler, [{<<"user">>, <<"password">>}]}}, + {tcp_opts, [{keepalive, true}]}], + mongoose_wpool_cassandra:prepare_cqerl_opts(AuthCfg)). + %%-------------------------------------------------------------------- %% Helpers %%-------------------------------------------------------------------- From 6555ab703a128e56e8fcfdfc25e320bafebff54d Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Tue, 29 Nov 2022 13:22:16 +0100 Subject: [PATCH 40/44] Fixing CR issues --- big_tests/tests/graphql_roster_SUITE.erl | 16 ++++++----- ...mongoose_graphql_roster_admin_mutation.erl | 2 ++ src/mod_roster_api.erl | 27 ++++++++++++------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/big_tests/tests/graphql_roster_SUITE.erl b/big_tests/tests/graphql_roster_SUITE.erl index 5cf2da2042..89e89badda 100644 --- a/big_tests/tests/graphql_roster_SUITE.erl +++ b/big_tests/tests/graphql_roster_SUITE.erl @@ -6,7 +6,7 @@ -import(graphql_helper, [execute_user_command/5, execute_command/4, get_listener_port/1, get_listener_config/1, get_ok_value/2, get_err_value/2, get_err_msg/1, get_err_msg/2, get_bad_request/1, user_to_jid/1, user_to_bin/1, - get_unauthorized/1]). + get_unauthorized/1, get_err_code/1]). -include_lib("eunit/include/eunit.hrl"). -include_lib("../../include/mod_roster.hrl"). @@ -65,7 +65,8 @@ admin_roster_tests() -> admin_list_contacts, admin_list_contacts_wrong_user, admin_get_contact, - admin_get_contact_wrong_user + admin_get_contact_wrong_user, + admin_subscribe_all_to_all_empty_list ]. domain_admin_tests() -> @@ -174,8 +175,7 @@ admin_try_add_contact_to_nonexistent_user(Config) -> User = ?NONEXISTENT_USER, Contact = ?NONEXISTENT_USER2, Res = admin_add_contact(User, Contact, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), User)), - check_contacts([], User). + ?assertEqual(<<"user_not_exist">>, get_err_code(Res)). admin_try_add_contact_with_unknown_domain(Config) -> User = ?NONEXISTENT_DOMAIN_USER, @@ -402,7 +402,7 @@ admin_list_contacts_wrong_user(Config) -> ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)), % Non-existent user with existent domain Res2 = admin_list_contacts(?NONEXISTENT_USER, Config), - ?assertEqual([], get_ok_value(?LIST_CONTACTS_PATH, Res2)). + ?assertEqual(<<"user_not_exist">>, get_err_code(Res2)). admin_get_contact(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], @@ -425,6 +425,10 @@ admin_get_contact_wrong_user(Config) -> Res2 = admin_get_contact(?NONEXISTENT_USER, ?NONEXISTENT_USER, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)). +admin_subscribe_all_to_all_empty_list(Config) -> + Res = admin_subscribe_all_to_all([], Config), + ?assertEqual([], get_ok_value(?SUBSCRIBE_ALL_TO_ALL_PATH, Res)). + %% User test cases user_add_and_delete_contact(Config) -> @@ -661,7 +665,7 @@ domain_admin_list_contacts_wrong_user(Config) -> get_unauthorized(Res), % Non-existent user with existent domain Res2 = admin_list_contacts(?NONEXISTENT_USER, Config), - ?assertEqual([], get_ok_value(?LIST_CONTACTS_PATH, Res2)). + ?assertEqual(<<"user_not_exist">>, get_err_code(Res2)). domain_admin_list_contacts_no_permission(Config) -> escalus:fresh_story_with_config(Config, [{alice_bis, 1}], diff --git a/src/graphql/admin/mongoose_graphql_roster_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_roster_admin_mutation.erl index c2728ab007..04d462992b 100644 --- a/src/graphql/admin/mongoose_graphql_roster_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_roster_admin_mutation.erl @@ -81,6 +81,8 @@ do_subscribe_to_all(User, Contacts) -> -spec do_subscribe_all_to_all([mongoose_graphql_roster:contact_input()]) -> [mongoose_graphql_roster:binary_result()]. +do_subscribe_all_to_all([]) -> + []; do_subscribe_all_to_all([_]) -> []; do_subscribe_all_to_all([User | Contacts]) -> diff --git a/src/mod_roster_api.erl b/src/mod_roster_api.erl index c29b5b4ee8..6ad6d35a7e 100644 --- a/src/mod_roster_api.erl +++ b/src/mod_roster_api.erl @@ -44,19 +44,26 @@ add_contact(#jid{lserver = LServer} = CallerJID, ContactJID, Name, Groups) -> list_contacts(#jid{lserver = LServer} = CallerJID) -> case mongoose_domain_api:get_domain_host_type(LServer) of {ok, HostType} -> - Acc0 = mongoose_acc:new(#{ location => ?LOCATION, - host_type => HostType, - lserver => LServer, - element => undefined }), - Acc1 = mongoose_acc:set(roster, show_full_roster, true, Acc0), - Acc2 = mongoose_hooks:roster_get(Acc1, CallerJID), - {ok, mongoose_acc:get(roster, items, Acc2)}; + case ejabberd_auth:does_user_exist(CallerJID) of + true -> + Acc0 = mongoose_acc:new(#{ location => ?LOCATION, + host_type => HostType, + lserver => LServer, + element => undefined }), + Acc1 = mongoose_acc:set(roster, show_full_roster, true, Acc0), + Acc2 = mongoose_hooks:roster_get(Acc1, CallerJID), + {ok, mongoose_acc:get(roster, items, Acc2)}; + false -> + {user_not_exist, io_lib:format("The user ~s does not exist", + [jid:to_binary(CallerJID)])} + end; {error, not_found} -> ?UNKNOWN_DOMAIN_RESULT end. -spec get_contact(jid:jid(), jid:jid()) -> - {ok, mod_roster:roster()} | {contact_not_found | internal | unknown_domain, iolist()}. + {ok, mod_roster:roster()} | + {contact_not_found | internal | unknown_domain | user_not_exist, iolist()}. get_contact(#jid{lserver = LServer} = UserJID, ContactJID) -> case mongoose_domain_api:get_domain_host_type(LServer) of {ok, HostType} -> @@ -122,7 +129,7 @@ set_mutual_subscription(UserA, UserB, disconnect) -> fun() -> delete_contact(UserB, UserA) end], case run_seq(Seq, ok) of ok -> - {ok, "Mututal subscription removed successfully"}; + {ok, "Mutual subscription removed successfully"}; Error -> Error end. @@ -138,7 +145,7 @@ subscribe_both({UserA, NameA, GroupsA}, {UserB, NameB, GroupsB}) -> fun() -> subscription(UserB, UserA, subscribed) end], case run_seq(Seq, ok) of ok -> - {ok, io_lib:format("Subscription between users ~p and ~p created successfully", + {ok, io_lib:format("Subscription between users ~s and ~s created successfully", [jid:to_binary(UserA), jid:to_binary(UserB)])}; Error -> Error From 929be7574852929e4206fd59119c6562e8cf340e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Mon, 28 Nov 2022 13:20:08 +0100 Subject: [PATCH 41/44] Refactored hook handlers in mod_pubsub --- src/mod_ping.erl | 2 +- src/mongoose_hooks.erl | 10 +- src/pubsub/mod_pubsub.erl | 265 ++++++++++++++++++++++---------------- 3 files changed, 163 insertions(+), 114 deletions(-) diff --git a/src/mod_ping.erl b/src/mod_ping.erl index 66d21575bd..ee8b4dc2fe 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -151,7 +151,7 @@ iq_ping(Acc, _From, _To, #iq{sub_el = SubEl} = IQ, _) -> -spec handle_remote_hook(Acc, Params, Extra) -> {ok, Acc} when Acc :: term(), - Params :: #{tag := atom(), args := term(), c2s_state := ejabberd_c2s:state()}, + Params :: #{tag := atom(), hook_args := term(), c2s_state := ejabberd_c2s:state()}, Extra :: gen_hook:extra(). handle_remote_hook(HandlerState, #{tag := mod_ping, hook_args := Args, c2s_state := C2SState}, _) -> {ok, handle_remote_call(Args, diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index da2557a9a9..bbd53b1448 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -343,7 +343,10 @@ packet_to_component(Acc, From, To) -> Pid :: pid(), Result :: mongoose_acc:t(). presence_probe_hook(HostType, Acc, From, To, Pid) -> - run_hook_for_host_type(presence_probe_hook, HostType, Acc, [From, To, Pid]). + Params = #{from => From, to => To, pid => Pid}, + Args = [From, To, Pid], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), + run_hook_for_host_type(presence_probe_hook, HostType, Acc, ParamsWithLegacyArgs). %%% @doc The `push_notifications' hook is called to push notifications. -spec push_notifications(HostType, Acc, NotificationForms, Options) -> Result when @@ -1672,8 +1675,11 @@ update_inbox_for_muc(HostType, Info) -> Features :: unknown | list(), Result :: mongoose_acc:t(). caps_recognised(Acc, From, Pid, Features) -> + Params = #{from => From, pid => Pid, features => Features}, + Args = [From, Pid, Features], + ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(caps_recognised, HostType, Acc, [From, Pid, Features]). + run_hook_for_host_type(caps_recognised, HostType, Acc, ParamsWithLegacyArgs). %% PubSub related hooks diff --git a/src/pubsub/mod_pubsub.erl b/src/pubsub/mod_pubsub.erl index c7a9a55268..c632084c3b 100644 --- a/src/pubsub/mod_pubsub.erl +++ b/src/pubsub/mod_pubsub.erl @@ -66,13 +66,18 @@ -define(PUSHNODE, <<"push">>). %% exports for hooks --export([presence_probe/4, caps_recognised/4, - in_subscription/5, out_subscription/4, - on_user_offline/5, remove_user/3, +-export([presence_probe/3, + caps_recognised/3, + in_subscription/3, + out_subscription/3, + on_user_offline/3, + remove_user/3, disco_local_features/3, - disco_sm_identity/1, - disco_sm_features/1, disco_sm_items/1, handle_pep_authorization_response/1, - handle_remote_hook/4]). + disco_sm_identity/3, + disco_sm_features/3, + disco_sm_items/3, + handle_pep_authorization_response/3, + handle_remote_hook/3]). %% exported iq handlers -export([iq_sm/4]). @@ -119,15 +124,31 @@ {?MOD_PUBSUB_DB_BACKEND, start, 0}, {?MOD_PUBSUB_DB_BACKEND, set_subscription_opts, 4}, {?MOD_PUBSUB_DB_BACKEND, stop, 0}, - affiliation_to_string/1, caps_recognised/4, create_node/7, default_host/0, - delete_item/4, delete_node/3, disco_sm_features/1, - disco_sm_identity/1, disco_sm_items/1, extended_error/3, get_cached_item/2, - get_item/3, get_items/2, get_personal_data/3, handle_pep_authorization_response/1, - handle_remote_hook/4, host/2, in_subscription/5, iq_sm/4, node_action/4, node_call/4, - on_user_offline/5, out_subscription/4, plugin/2, plugin/1, presence_probe/4, - publish_item/6, remove_user/3, send_items/7, serverhost/1, start_link/2, - string_to_affiliation/1, string_to_subscription/1, subscribe_node/5, - subscription_to_string/1, tree_action/3, unsubscribe_node/5, + affiliation_to_string/1, + create_node/7, + default_host/0, + delete_item/4, + delete_node/3, + extended_error/3, + get_cached_item/2, + get_item/3, + get_items/2, + host/2, + iq_sm/4, + node_action/4, + node_call/4, + plugin/2, + plugin/1, + publish_item/6, + send_items/7, + serverhost/1, + start_link/2, + string_to_affiliation/1, + string_to_subscription/1, + subscribe_node/5, + subscription_to_string/1, + tree_action/3, + unsubscribe_node/5, handle_msg/1 ]). @@ -376,15 +397,19 @@ process_packet(Acc, From, To, El, #{state := State}) -> %% GDPR callback %%==================================================================== --spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data(). -get_personal_data(Acc, _HostType, #jid{ luser = LUser, lserver = LServer }) -> - Payloads = mod_pubsub_db_backend:get_user_payloads(LUser, LServer), - Nodes = mod_pubsub_db_backend:get_user_nodes(LUser, LServer), - Subscriptions = mod_pubsub_db_backend:get_user_subscriptions(LUser, LServer), +-spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when + Acc :: gdpr:personal_data(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> + Payloads = mod_pubsub_db_backend:get_user_payloads(LUser, LServer), + Nodes = mod_pubsub_db_backend:get_user_nodes(LUser, LServer), + Subscriptions = mod_pubsub_db_backend:get_user_subscriptions(LUser, LServer), - [{pubsub_payloads, ["node_name", "item_id", "payload"], Payloads}, - {pubsub_nodes, ["node_name", "type"], Nodes}, - {pubsub_subscriptions, ["node_name"], Subscriptions} | Acc]. + NewAcc = [{pubsub_payloads, ["node_name", "item_id", "payload"], Payloads}, + {pubsub_nodes, ["node_name", "type"], Nodes}, + {pubsub_subscriptions, ["node_name"], Subscriptions} | Acc], + {ok, NewAcc}. %%==================================================================== %% gen_server callbacks @@ -398,11 +423,10 @@ init([ServerHost, Opts = #{host := SubdomainPattern}]) -> init_backend(ServerHost, Opts), Plugins = init_plugins(Host, ServerHost, Opts), - add_hooks(ServerHost, legacy_hooks()), gen_hook:add_handlers(hooks(ServerHost)), case lists:member(?PEPNODE, Plugins) of true -> - add_hooks(ServerHost, pep_hooks()), + gen_hook:add_handlers(pep_hooks(ServerHost)), add_pep_iq_handlers(ServerHost, Opts); false -> ok @@ -430,34 +454,24 @@ init_backend(ServerHost, Opts = #{backend := Backend}) -> mod_pubsub_db_backend:start(), maybe_start_cache_module(ServerHost, Opts). -add_hooks(ServerHost, Hooks) -> - [ ejabberd_hooks:add(Hook, ServerHost, ?MODULE, F, Seq) || {Hook, F, Seq} <- Hooks ]. - -delete_hooks(ServerHost, Hooks) -> - [ ejabberd_hooks:delete(Hook, ServerHost, ?MODULE, F, Seq) || {Hook, F, Seq} <- Hooks ]. - -legacy_hooks() -> - [ - {sm_remove_connection_hook, on_user_offline, 75}, - {presence_probe_hook, presence_probe, 80}, - {roster_in_subscription, in_subscription, 50}, - {roster_out_subscription, out_subscription, 50}, - {remove_user, remove_user, 50}, - {anonymous_purge_hook, remove_user, 50}, - {get_personal_data, get_personal_data, 50} - ]. - hooks(ServerHost) -> - [{disco_local_features, ServerHost, fun ?MODULE:disco_local_features/3, #{}, 75}]. - -pep_hooks() -> + [{disco_local_features, ServerHost, fun ?MODULE:disco_local_features/3, #{}, 75}, + {sm_remove_connection_hook, ServerHost, fun ?MODULE:on_user_offline/3, #{}, 75}, + {presence_probe_hook, ServerHost, fun ?MODULE:presence_probe/3, #{}, 80}, + {roster_in_subscription, ServerHost, fun ?MODULE:in_subscription/3, #{}, 50}, + {roster_out_subscription, ServerHost, fun ?MODULE:out_subscription/3, #{}, 50}, + {remove_user, ServerHost, fun ?MODULE:remove_user/3, #{}, 50}, + {anonymous_purge_hook, ServerHost, fun ?MODULE:remove_user/3, #{}, 50}, + {get_personal_data, ServerHost, fun ?MODULE:get_personal_data/3, #{}, 50}]. + +pep_hooks(ServerHost) -> [ - {caps_recognised, caps_recognised, 80}, - {disco_sm_identity, disco_sm_identity, 75}, - {disco_sm_features, disco_sm_features, 75}, - {disco_sm_items, disco_sm_items, 75}, - {filter_local_packet, handle_pep_authorization_response, 1}, - {c2s_remote_hook, handle_remote_hook, 100} + {caps_recognised, ServerHost, fun ?MODULE:caps_recognised/3, #{}, 80}, + {disco_sm_identity, ServerHost, fun ?MODULE:disco_sm_identity/3, #{}, 75}, + {disco_sm_features, ServerHost, fun ?MODULE:disco_sm_features/3, #{}, 75}, + {disco_sm_items, ServerHost, fun ?MODULE:disco_sm_items/3, #{}, 75}, + {filter_local_packet, ServerHost, fun ?MODULE:handle_pep_authorization_response/3, #{}, 1}, + {c2s_remote_hook, ServerHost, fun ?MODULE:handle_remote_hook/3, #{}, 100} ]. add_pep_iq_handlers(ServerHost, #{iqdisc := IQDisc}) -> @@ -629,10 +643,13 @@ disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}, _ disco_local_features(Acc, _, _) -> {ok, Acc}. --spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). -disco_sm_identity(Acc = #{from_jid := From, to_jid := To, node := Node}) -> +-spec disco_sm_identity(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_disco:identity_acc(), + Params :: map(), + Extra :: gen_hook:extra(). +disco_sm_identity(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> Identities = disco_identity(jid:to_lower(jid:to_bare(To)), Node, From), - mongoose_disco:add_identities(Identities, Acc). + {ok, mongoose_disco:add_identities(Identities, Acc)}. disco_identity(error, _Node, _From) -> []; @@ -662,10 +679,13 @@ pep_identity(Options) -> pep_identity() -> #{category => <<"pubsub">>, type => <<"pep">>}. --spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). -disco_sm_features(Acc = #{from_jid := From, to_jid := To, node := Node}) -> +-spec disco_sm_features(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_disco:feature_acc(), + Params :: map(), + Extra :: gen_hook:extra(). +disco_sm_features(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> Features = disco_features(jid:to_lower(jid:to_bare(To)), Node, From), - mongoose_disco:add_features(Features, Acc). + {ok, mongoose_disco:add_features(Features, Acc)}. -spec disco_features(error | jid:simple_jid(), binary(), jid:jid()) -> [mongoose_disco:feature()]. disco_features(error, _Node, _From) -> @@ -686,10 +706,13 @@ disco_features(Host, Node, From) -> _ -> [] end. --spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). -disco_sm_items(Acc = #{from_jid := From, to_jid := To, node := Node}) -> +-spec disco_sm_items(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_disco:item_acc(), + Params :: map(), + Extra :: gen_hook:extra(). +disco_sm_items(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> Items = disco_items(jid:to_lower(jid:to_bare(To)), Node, From), - mongoose_disco:add_items(Items, Acc). + {ok, mongoose_disco:add_items(Items, Acc)}. -spec disco_items(mod_pubsub:host(), mod_pubsub:nodeId(), jid:jid()) -> [mongoose_disco:item()]. disco_items(Host, <<>>, From) -> @@ -747,9 +770,13 @@ disco_item(Host, ItemId) -> %% callback that prevents routing subscribe authorizations back to the sender %% -handle_pep_authorization_response({From, To, Acc, #xmlel{ name = Name } = Packet}) -> +-spec handle_pep_authorization_response(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_hooks:filter_packet_acc(), + Params :: map(), + Extra :: gen_hook:extra(). +handle_pep_authorization_response({From, To, Acc, #xmlel{ name = Name } = Packet}, _, _) -> Type = mongoose_acc:stanza_type(Acc), - handle_pep_authorization_response(Name, Type, From, To, Acc, Packet). + {ok, handle_pep_authorization_response(Name, Type, From, To, Acc, Packet)}. handle_pep_authorization_response(_, <<"error">>, From, To, Acc, Packet) -> {From, To, Acc, Packet}; @@ -768,68 +795,85 @@ handle_pep_authorization_response(_, _, From, To, Acc, Packet) -> %% ------- %% callback for remote hook calls, to distribute pep messages from the node owner c2s process %% - -handle_remote_hook(HandlerState, pep_message, {Feature, From, Packet}, C2SState) -> +-spec handle_remote_hook(Acc, Params, Extra) -> {ok, Acc} when + Acc :: term(), + Params :: #{tag := atom(), hook_args := term(), c2s_state := ejabberd_c2s:state()}, + Extra :: gen_hook:extra(). +handle_remote_hook(HandlerState, + #{tag := pep_message, hook_args := {Feature, From, Packet}, c2s_state := C2SState}, + _) -> Recipients = mongoose_hooks:c2s_broadcast_recipients(C2SState, {pep_message, Feature}, From, Packet), lists:foreach(fun(USR) -> ejabberd_router:route(From, jid:make(USR), Packet) end, lists:usort(Recipients)), - HandlerState; -handle_remote_hook(HandlerState, _, _, _) -> - HandlerState. + {ok, HandlerState}; +handle_remote_hook(HandlerState, _, _) -> + {ok, HandlerState}. %% ------- %% presence hooks handling functions %% -caps_recognised(Acc, #jid{ luser = U, lserver = S } = JID, Pid, _Features) -> +-spec caps_recognised(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{from := jid:jid(), pid := pid()}, + Extra :: gen_hook:extra(). +caps_recognised(Acc, #{from := #jid{ luser = U, lserver = S } = JID, pid := Pid}, _) -> Host = host(S, S), IgnorePepFromOffline = gen_mod:get_module_opt(S, ?MODULE, ignore_pep_from_offline), notify_worker(S, U, {send_last_pep_items, Host, IgnorePepFromOffline, JID, Pid}), - Acc. + {ok, Acc}. -presence_probe(Acc, #jid{luser = U, lserver = S, lresource = _R} = JID, JID, _Pid) -> +-spec presence_probe(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{from := jid:jid(), to := jid:jid()}, + Extra :: gen_hook:extra(). +presence_probe(Acc, #{from := #jid{luser = U, lserver = S, lresource = _R} = JID, to := JID}, _) -> %% Get subdomain Host = host(S, S), Plugins = plugins(S), notify_worker(S, U, {send_last_pubsub_items, Host, _Recipient = JID, Plugins}), - Acc; -presence_probe(Acc, _Host, _JID, _Pid) -> - Acc. + {ok, Acc}; +presence_probe(Acc, _, _) -> + {ok, Acc}. %% ------- %% subscription hooks handling functions %% --spec out_subscription(Acc :: mongoose_acc:t(), - FromJID :: jid:jid(), - ToJID :: jid:jid(), - Type :: mod_roster:sub_presence()) -> - mongoose_acc:t(). -out_subscription(Acc, #jid{lserver = LServer, luser = LUser} = FromJID, ToJID, subscribed) -> - {PUser, PServer, PResource} = jid:to_lower(ToJID), +-spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to := jid:jid(), + from := jid:jid(), + type := mod_roster:sub_presence()}, + Extra :: gen_hook:extra(). +out_subscription(Acc, + #{to := #jid{luser = PUser, lserver = PServer, lresource = PResource}, + from := #jid{luser = LUser, lserver = LServer} = FromJID, + type := subscribed}, + _) -> PResources = case PResource of <<>> -> user_resources(PUser, PServer); _ -> [PResource] end, Host = host(LServer, LServer), notify_worker(LServer, LUser, {send_last_items_from_owner, Host, FromJID, {PUser, PServer, PResources}}), - Acc; -out_subscription(Acc, _, _, _) -> - Acc. - --spec in_subscription(Acc:: mongoose_acc:t(), - ToJID :: jid:jid(), - OwnerJID ::jid:jid(), - Type :: mod_roster:sub_presence(), - _:: any()) -> - mongoose_acc:t(). -in_subscription(Acc, ToJID, OwnerJID, unsubscribed, _) -> + {ok, Acc}; +out_subscription(Acc, _, _) -> + {ok, Acc}. + +-spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{to := jid:jid(), + from := jid:jid(), + type := mod_roster:sub_presence()}, + Extra :: gen_hook:extra(). +in_subscription(Acc, #{to := ToJID, from := OwnerJID, type := unsubscribed}, _) -> unsubscribe_user(ToJID, OwnerJID), - Acc; -in_subscription(Acc, _, _, _, _) -> - Acc. + {ok, Acc}; +in_subscription(Acc, _, _) -> + {ok, Acc}. unsubscribe_user(Entity, Owner) -> ServerHosts = lists:usort(lists:foldl( @@ -867,14 +911,15 @@ unsubscribe_user_per_plugin(Host, Entity, BJID, PType) -> %% ------- %% user remove hook handling function %% - -remove_user(Acc, User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), +-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: #{host_type := mongooseim:host_type()}. +remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> lists:foreach(fun(PType) -> remove_user_per_plugin_safe(LUser, LServer, plugin(PType)) end, plugins(LServer)), - Acc. + {ok, Acc}. remove_user_per_plugin_safe(LUser, LServer, Plugin) -> try @@ -930,11 +975,10 @@ terminate(_Reason, #state{host = Host, server_host = ServerHost, mongoose_domain_api:unregister_subdomain(ServerHost, SubdomainPattern), case lists:member(?PEPNODE, Plugins) of true -> - delete_hooks(ServerHost, pep_hooks()), + gen_hook:delete_handlers(pep_hooks(ServerHost)), delete_pep_iq_handlers(ServerHost); false -> ok end, - delete_hooks(ServerHost, legacy_hooks()), gen_hook:delete_handlers(hooks(ServerHost)), case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of undefined -> @@ -4400,19 +4444,18 @@ extended_headers(Jids) -> attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}]} || Jid <- Jids]. --spec on_user_offline(Acc :: mongoose_acc:t(), - SID :: 'undefined' | ejabberd_sm:sid(), - JID :: jid:jid(), - Info :: ejabberd_sm:info(), - Reason :: ejabberd_sm:close_reason()) -> Result :: mongoose_acc:t(). -on_user_offline(Acc, _, JID, _, _) -> - HT = mongoose_acc:host_type(Acc), - {User, Server, Resource} = jid:to_lower(JID), +-spec on_user_offline(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{jid := jid:jid()}, + Extra :: gen_hook:extra(). +on_user_offline(Acc, + #{jid := #jid{luser = User, lserver = Server, lresource = Resource}}, + #{host_type := HostType}) -> case user_resources(User, Server) of - [] -> purge_offline(HT, {User, Server, Resource}); + [] -> purge_offline(HostType, {User, Server, Resource}); _ -> true end, - Acc. + {ok, Acc}. purge_offline(HT, {_, LServer, _} = LJID) -> Host = host(HT, LServer), From c3404c318d795077299881350f8a3607405dda69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Thu, 1 Dec 2022 10:17:08 +0100 Subject: [PATCH 42/44] Refactored hook handlers in mod_stream_management --- src/ejabberd_hooks.erl | 4 +- .../mod_stream_management.erl | 59 ++++++++++--------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index 58d53353cc..2260dd1fbf 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -36,7 +36,9 @@ -export([gen_hook_fn_wrapper/3]). --ignore_xref([add/4, delete/4, error_running_hook/3, start_link/0]). +-ignore_xref([add/4, delete/4, error_running_hook/3, start_link/0, + % temporary until the module is deleted + add/1, delete/1]). -include("mongoose.hrl"). diff --git a/src/stream_management/mod_stream_management.erl b/src/stream_management/mod_stream_management.erl index da5e31a5e1..8295665e26 100644 --- a/src/stream_management/mod_stream_management.erl +++ b/src/stream_management/mod_stream_management.erl @@ -12,8 +12,8 @@ %% hooks handlers -export([c2s_stream_features/3, - remove_smid/5, - session_cleanup/5]). + remove_smid/3, + session_cleanup/3]). %% API for `ejabberd_c2s' -export([make_smid/0, @@ -29,8 +29,10 @@ register_stale_smid_h/3, remove_stale_smid_h/2]). --ignore_xref([c2s_stream_features/3, get_sid/2, get_stale_h/2, remove_smid/5, - register_stale_smid_h/3, remove_stale_smid_h/2, session_cleanup/5]). +-ignore_xref([get_sid/2, + get_stale_h/2, + register_stale_smid_h/3, + remove_stale_smid_h/2]). -type smid() :: base64:ascii_binary(). @@ -49,18 +51,19 @@ start(HostType, Opts) -> mod_stream_management_backend:init(HostType, Opts), ?LOG_INFO(#{what => stream_management_starting}), - ejabberd_hooks:add(hooks(HostType)), + gen_hook:add_handlers(hooks(HostType)), ok. stop(HostType) -> ?LOG_INFO(#{what => stream_management_stopping}), - ejabberd_hooks:delete(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), ok. +-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). hooks(HostType) -> - [{sm_remove_connection_hook, HostType, ?MODULE, remove_smid, 50}, - {c2s_stream_features, HostType, ?MODULE, c2s_stream_features, 50}, - {session_cleanup, HostType, ?MODULE, session_cleanup, 50}]. + [{sm_remove_connection_hook, HostType, fun ?MODULE:remove_smid/3, #{}, 50}, + {c2s_stream_features, HostType, fun ?MODULE:c2s_stream_features/3, #{}, 50}, + {session_cleanup, HostType, fun ?MODULE:session_cleanup/3, #{}, 50}]. -spec config_spec() -> mongoose_config_spec:config_section(). config_spec() -> @@ -120,31 +123,31 @@ stale_h_config_spec() -> %% hooks handlers %% --spec c2s_stream_features([exml:element()], mongooseim:host_type(), jid:lserver()) -> - [exml:element()]. -c2s_stream_features(Acc, _HostType, _Lserver) -> - lists:keystore(<<"sm">>, #xmlel.name, Acc, sm()). +-spec c2s_stream_features(Acc, Params, Extra) -> {ok, Acc} when + Acc :: [exml:element()], + Params :: map(), + Extra :: gen_hook:extra(). +c2s_stream_features(Acc, _, _) -> + {ok, lists:keystore(<<"sm">>, #xmlel.name, Acc, sm())}. sm() -> #xmlel{name = <<"sm">>, attrs = [{<<"xmlns">>, ?NS_STREAM_MGNT_3}]}. --spec remove_smid(Acc, SID, JID, Info, Reason) -> Acc1 when - Acc :: mongoose_acc:t(), - SID :: ejabberd_sm:sid(), - JID :: undefined | jid:jid(), - Info :: undefined | [any()], - Reason :: undefined | ejabberd_sm:close_reason(), - Acc1 :: mongoose_acc:t(). -remove_smid(Acc, SID, _JID, _Info, _Reason) -> +-spec remove_smid(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{sid := ejabberd_sm:sid()}, + Extra :: gen_hook:extra(). +remove_smid(Acc, #{sid := SID}, #{host_type := HostType}) -> + {ok, do_remove_smid(Acc, HostType, SID)}. + +-spec session_cleanup(Acc, Params, Extra) -> {ok, Acc} when + Acc :: mongoose_acc:t(), + Params :: #{sid := ejabberd_sm:sid()}, + Extra :: gen_hook:extra(). +session_cleanup(Acc, #{sid := SID}, #{host_type := HostType}) -> HostType = mongoose_acc:host_type(Acc), - do_remove_smid(Acc, HostType, SID). - --spec session_cleanup(Acc :: map(), LUser :: jid:luser(), LServer :: jid:lserver(), - LResource :: jid:lresource(), SID :: ejabberd_sm:sid()) -> any(). -session_cleanup(Acc, _LUser, _LServer, _LResource, SID) -> - HostType = mongoose_acc:host_type(Acc), - do_remove_smid(Acc, HostType, SID). + {ok, do_remove_smid(Acc, HostType, SID)}. -spec do_remove_smid(mongoose_acc:t(), mongooseim:host_type(), ejabberd_sm:sid()) -> mongoose_acc:t(). From 8d04e955e782ad5ef8f1bcbd708406c167eea841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20D=C5=82ugosz?= Date: Thu, 1 Dec 2022 12:11:02 +0100 Subject: [PATCH 43/44] Refactored hook handlers in auth_tokend_SUITE --- test/auth_tokens_SUITE.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/auth_tokens_SUITE.erl b/test/auth_tokens_SUITE.erl index 24bab70a1b..4324b0c401 100644 --- a/test/auth_tokens_SUITE.erl +++ b/test/auth_tokens_SUITE.erl @@ -233,17 +233,18 @@ mock_rdbms_backend() -> ok. mock_keystore() -> - ejabberd_hooks:add(get_key, host_type(), ?MODULE, mod_keystore_get_key, 50). + gen_hook:add_handler(get_key, host_type(), fun ?MODULE:mod_keystore_get_key/3, #{}, 50). mock_gen_iq_handler() -> meck:new(gen_iq_handler, []), meck:expect(gen_iq_handler, add_iq_handler_for_domain, fun (_, _, _, _, _, _) -> ok end). -mod_keystore_get_key(_, {KeyName, _} = KeyID) -> - case KeyName of +mod_keystore_get_key(_, #{key_id := {KeyName, _} = KeyID}, _) -> + Acc = case KeyName of token_secret -> [{KeyID, <<"access_or_refresh">>}]; provision_pre_shared -> [{KeyID, <<"provision">>}] - end. + end, + {ok, Acc}. mock_tested_backend() -> meck:new(mod_auth_token_backend, []), From b708a60a0f206b4c55f5ef54cf552cd1894f4c93 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Thu, 1 Dec 2022 10:58:04 +0100 Subject: [PATCH 44/44] Improve logging so that stacktrace does not mess into reason --- big_tests/tests/service_domain_db_SUITE.erl | 2 +- src/gen_hook.erl | 27 +++++++---------- src/safely.erl | 33 ++++++++++++--------- test/gen_hook_SUITE.erl | 2 +- test/safely_SUITE.erl | 6 ++-- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/big_tests/tests/service_domain_db_SUITE.erl b/big_tests/tests/service_domain_db_SUITE.erl index 334123bd12..b6ff25c2b8 100644 --- a/big_tests/tests/service_domain_db_SUITE.erl +++ b/big_tests/tests/service_domain_db_SUITE.erl @@ -1209,7 +1209,7 @@ stop_domain_removal_hook(HostType, Server) -> domain_removal_hook_fn(Acc, _Params, #{server := Server}) -> Server ! {wait, self()}, receive continue -> ok end, - Acc. + {ok, Acc}. stopper() -> receive diff --git a/src/gen_hook.erl b/src/gen_hook.erl index fedb7ba1d4..3c259083f2 100644 --- a/src/gen_hook.erl +++ b/src/gen_hook.erl @@ -210,31 +210,24 @@ run_hook([Handler | Ls], Acc, Params, Key) -> run_hook(Ls, NewAcc, Params, Key); {stop, NewAcc} -> {stop, NewAcc}; - Other -> - ?MODULE:error_running_hook(Other, Handler, Acc, Params, Key), + {exception, Info} -> + ?MODULE:error_running_hook(Info, Handler, Acc, Params, Key), run_hook(Ls, Acc, Params, Key) end. -spec apply_hook_function(#hook_handler{}, hook_acc(), hook_params()) -> - hook_fn_ret() | {'EXIT', Reason :: any()}. + hook_fn_ret() | safely:exception(). apply_hook_function(#hook_handler{hook_fn = HookFn, extra = Extra}, Acc, Params) -> safely:apply(HookFn, [Acc, Params, Extra]). -error_running_hook({Class, Reason}, Handler, Acc, Params, Key) -> - Extra = #{class => Class, reason => Reason}, - log_error_running_hook(Extra, Handler, Acc, Params, Key); -error_running_hook(Other, Handler, Acc, Params, Key) -> - Extra = #{error => Other}, - log_error_running_hook(Extra, Handler, Acc, Params, Key). - -log_error_running_hook(Extra, Handler, Acc, Params, Key) -> - ?LOG_ERROR(Extra#{what => hook_failed, - text => <<"Error running hook">>, - key => Key, - handler => Handler, - acc => Acc, - params => Params}). +error_running_hook(Info, Handler, Acc, Params, Key) -> + ?LOG_ERROR(Info#{what => hook_failed, + text => <<"Error running hook">>, + key => Key, + handler => Handler, + acc => Acc, + params => Params}). -spec make_hook_handler(hook_tuple()) -> #hook_handler{}. make_hook_handler({HookName, Tag, Function, Extra, Priority} = HookTuple) diff --git a/src/safely.erl b/src/safely.erl index 97764f4d54..f390012148 100644 --- a/src/safely.erl +++ b/src/safely.erl @@ -15,42 +15,47 @@ -ignore_xref([apply/3, apply_and_log/4]). -type error_class() :: error | exit | throw. --type catch_result(A) :: A | {error_class(), term()}. +-type error_info() :: #{class => error_class(), reason => term(), stacktrace => [term()]}. +-type exception() :: {exception, error_info()}. +-export_type([exception/0]). -define(MATCH_EXCEPTIONS_DO_LOG(F, Context), try F catch error:R:S -> - ?LOG_ERROR(Context#{class => error, reason => R, stacktrace => S}), - {error, {R, S}}; + Info = #{class => error, reason => R, stacktrace => S}, + ?LOG_ERROR(maps:merge(Context, Info)), + {exception, Info}; throw:R -> - ?LOG_ERROR(Context#{class => throw, reason => R}), - {throw, R}; + Info = #{class => throw, reason => R}, + ?LOG_ERROR(maps:merge(Context, Info)), + {exception, Info}; exit:R:S -> - ?LOG_ERROR(Context#{class => exit, reason => R, stacktrace => S}), - {exit, {R, S}} + Info = #{class => exit, reason => R, stacktrace => S}, + ?LOG_ERROR(maps:merge(Context, Info)), + {exception, Info} end). -define(MATCH_EXCEPTIONS(F), try F catch - error:R:S -> {error, {R, S}}; - throw:R -> {throw, R}; - exit:R:S -> {exit, {R, S}} + error:R:S -> {exception, #{class => error, reason => R, stacktrace => S}}; + throw:R -> {exception, #{class => throw, reason => R}}; + exit:R:S -> {exception, #{class => exit, reason => R, stacktrace => S}} end). --spec apply(fun((...) -> A), [term()]) -> catch_result(A). +-spec apply(fun((...) -> A), [term()]) -> A | exception(). apply(Function, Args) when is_function(Function), is_list(Args) -> ?MATCH_EXCEPTIONS(erlang:apply(Function, Args)). --spec apply(atom(), atom(), [term()]) -> catch_result(any()). +-spec apply(atom(), atom(), [term()]) -> term() | exception(). apply(Module, Function, Args) when is_atom(Function), is_list(Args) -> ?MATCH_EXCEPTIONS(erlang:apply(Module, Function, Args)). --spec apply_and_log(fun((...) -> A), [term()], map()) -> catch_result(A). +-spec apply_and_log(fun((...) -> A), [term()], map()) -> A | exception(). apply_and_log(Function, Args, Context) when is_function(Function), is_list(Args), is_map(Context) -> ?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Function, Args), Context). --spec apply_and_log(atom(), atom(), [term()], map()) -> catch_result(any()). +-spec apply_and_log(atom(), atom(), [term()], map()) -> term() | exception(). apply_and_log(Module, Function, Args, Context) when is_atom(Function), is_list(Args), is_map(Context) -> ?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Module, Function, Args), Context). diff --git a/test/gen_hook_SUITE.erl b/test/gen_hook_SUITE.erl index 54b210ddea..7b8c10d10a 100644 --- a/test/gen_hook_SUITE.erl +++ b/test/gen_hook_SUITE.erl @@ -286,7 +286,7 @@ errors_in_handlers_are_reported_but_ignored(_) -> ?assertEqual({ok, N}, gen_hook:run_fold(calculate, ?HOOK_TAG1, 0, #{n => 2})), %% check that error is reported ?assertEqual(true, meck:called(gen_hook, error_running_hook, - [{error, {some_error, '_'}}, + [#{class => error, reason => some_error, stacktrace => '_'}, {hook_handler, 3, ErrorHandlerFn, #{hook_name => calculate, hook_tag => ?HOOK_TAG1}}, 6, #{n => 2}, {calculate, ?HOOK_TAG1}])), diff --git a/test/safely_SUITE.erl b/test/safely_SUITE.erl index d377c26542..bb81d31361 100644 --- a/test/safely_SUITE.erl +++ b/test/safely_SUITE.erl @@ -14,7 +14,7 @@ handles_errors_similar_to_catch(_) -> %% These two must be on the same line for the stacktraces to be equal. {safely:apply(lists, min, [[]]),(catch apply(lists, min, [[]]))}, - {error, {function_clause, SafeST}} = SafeRes, + {exception, #{class := error, reason := function_clause, stacktrace := SafeST}} = SafeRes, {'EXIT', {function_clause, CatchST}} = CatchRes, true = (hd(CatchST) == hd(SafeST)), @@ -25,13 +25,13 @@ handles_errors_similar_to_catch(_) -> handles_exits_similar_to_errors(_) -> ExitF = fun() -> exit(i_quit) end, - {exit, {i_quit, _S}} = safely:apply(ExitF,[]), + {exception, #{class := exit, reason := i_quit, stacktrace := _S}} = safely:apply(ExitF,[]), {'EXIT', i_quit} = (catch apply(ExitF,[])), ok. handles_throws_unlike_catch(_) -> ThrowF = fun() -> throw(up) end, - {throw, up} = safely:apply(ThrowF,[]), + {exception, #{class := throw, reason := up}} = safely:apply(ThrowF,[]), up = (catch apply(ThrowF,[])), ok.