diff --git a/.circleci/template.yml b/.circleci/template.yml index 31bbf32e3c2..6a91c59d361 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -435,7 +435,7 @@ jobs: docs_build_deploy: parallelism: 1 docker: - - image: cimg/python:3.9.0-node + - image: cimg/python:3.11.0-node steps: - checkout - run: diff --git a/README.md b/README.md index 55be92f255b..3d27c42e49c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ * [Developer's guide](https://esl.github.io/MongooseDocs/latest/developers-guide/Testing-MongooseIM/) * [Packages](https://www.erlang-solutions.com/resources/download.html) * Product page: [https://www.erlang-solutions.com/products/mongooseim.html](https://www.erlang-solutions.com/products/mongooseim.html) -* Documentation: [https://esl.github.io/MongooseDocs/](https://esl.github.io/MongooseDocs/) +* Documentation: [https://esl.github.io/MongooseDocs/](https://esl.github.io/MongooseDocs/latest/) ## Get to know MongooseIM MongooseIM is a robust, scalable and efficient XMPP server at the core of an Instant Messaging platform aimed at large installations. diff --git a/big_tests/tests/domain_removal_SUITE.erl b/big_tests/tests/domain_removal_SUITE.erl index 594f144aa0f..91ba01d7339 100644 --- a/big_tests/tests/domain_removal_SUITE.erl +++ b/big_tests/tests/domain_removal_SUITE.erl @@ -19,6 +19,7 @@ all() -> {group, mam_removal}, {group, mam_removal_incremental}, {group, inbox_removal}, + {group, inbox_removal_incremental}, {group, muc_light_removal}, {group, muc_removal}, {group, private_removal}, @@ -39,6 +40,7 @@ groups() -> {mam_removal_incremental, [], [mam_pm_removal, mam_muc_removal]}, {inbox_removal, [], [inbox_removal]}, + {inbox_removal_incremental, [], [inbox_removal]}, {muc_light_removal, [], [muc_light_removal, muc_light_blocking_removal]}, {muc_removal, [], [muc_removal]}, @@ -108,6 +110,8 @@ group_to_modules(muc_removal) -> [{mod_muc, muc_helper:make_opts(Opts)}]; group_to_modules(inbox_removal) -> [{mod_inbox, inbox_helper:inbox_opts()}]; +group_to_modules(inbox_removal_incremental) -> + [{mod_inbox, (inbox_helper:inbox_opts())#{delete_domain_limit => 1}}]; group_to_modules(private_removal) -> [{mod_private, #{iqdisc => one_queue, backend => rdbms}}]; group_to_modules(roster_removal) -> diff --git a/big_tests/tests/graphql_account_SUITE.erl b/big_tests/tests/graphql_account_SUITE.erl index b0f8c1dd99c..2089087ac95 100644 --- a/big_tests/tests/graphql_account_SUITE.erl +++ b/big_tests/tests/graphql_account_SUITE.erl @@ -1,6 +1,7 @@ -module(graphql_account_SUITE). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -compile([export_all, nowarn_export_all]). @@ -26,8 +27,8 @@ all() -> groups() -> [{user_account, [parallel], user_account_tests()}, - {admin_account_http, [], admin_account_tests()}, - {admin_account_cli, [], admin_account_tests()}, + {admin_account_http, [], admin_account_tests() ++ admin_account_http_tests()}, + {admin_account_cli, [], admin_account_tests() ++ admin_account_cli_tests()}, {domain_admin_account, [], domain_admin_tests()}]. user_account_tests() -> @@ -55,6 +56,12 @@ admin_account_tests() -> admin_change_user_password, admin_change_non_existing_user_password]. +admin_account_http_tests() -> + [admin_import_users_http]. + +admin_account_cli_tests() -> + [admin_import_users_cli]. + domain_admin_tests() -> [admin_list_users, domain_admin_list_users_no_permission, @@ -168,6 +175,11 @@ end_per_testcase(domain_admin_register_user = C, Config) -> {Username, Domain} = proplists:get_value(user, Config), rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]), escalus:end_per_testcase(C, Config); +end_per_testcase(CaseName, Config) + when CaseName == admin_import_users_http; CaseName == admin_import_users_cli -> + Domain = domain_helper:domain(), + rpc(mim(), mongoose_account_api, unregister_user, [<<"john">>, Domain]), + escalus:end_per_testcase(CaseName, Config); end_per_testcase(CaseName, Config) -> escalus:end_per_testcase(CaseName, Config). @@ -401,6 +413,57 @@ admin_change_non_existing_user_password(Config) -> Resp3 = ban_user(?EMPTY_NAME_JID, NewPassword, Config), get_coercion_err_msg(Resp3). +admin_import_users_cli(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(_Alice) -> + % Non-existing file + Resp = import_users(<<"nonexisting.csv">>, Config), + ?assertEqual(<<"File not found">>, get_err_msg(Resp)), + % Summary + Path = filename:join(?config(mim_data_dir, Config), "users.csv"), + Path2 = replace_hosts_in_file(Path), + Resp2 = import_users(list_to_binary(Path2), Config), + Domain = domain_helper:domain(), + ?assertEqual(#{<<"status">> => <<"Completed">>, + <<"created">> => [<<"john@", Domain/binary>>], + <<"emptyPassword">> => [<<"elise@", Domain/binary>>], + <<"existing">> => [<<"alice@", Domain/binary>>], + <<"invalidJID">> => [<<",", Domain/binary, ",password">>], + <<"invalidRecord">> => [<<"elise,elise,", Domain/binary, ",esile">>], + <<"notAllowed">> => null}, + get_ok_value([data, account, importUsers], Resp2)) + end). + +admin_import_users_http(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(_Alice) -> + % Summary + Path = filename:join(?config(mim_data_dir, Config), "users.csv"), + Path2 = replace_hosts_in_file(Path), + Resp2 = import_users(list_to_binary(Path2), Config), + ?assertEqual(#{<<"status">> => <<"ImportUsers scheduled">>, + <<"created">> => null, + <<"emptyPassword">> => null, + <<"existing">> => null, + <<"invalidJID">> => null, + <<"invalidRecord">> => null, + <<"notAllowed">> => null}, + get_ok_value([data, account, importUsers], Resp2)), + Domain = domain_helper:domain(), + mongoose_helper:wait_until(fun() -> + rpc(mim(), mongoose_account_api, check_account, [<<"john">>, Domain]) + end, + {ok, io_lib:format("User ~s exists", [<<"john@", Domain/binary>>])}, + #{time_left => timer:seconds(20), + sleep_time => 1000, + name => verify_account_created}) + end). + +replace_hosts_in_file(Path) -> + {ok, Content} = file:read_file(Path), + Content2 = binary:replace(Content, <<"$host$">>, domain_helper:domain(), [global]), + Path2 = Path ++ ".tmp", + ok = file:write_file(Path2, Content2), + Path2. + domain_admin_list_users_no_permission(Config) -> % An unknown domain Resp1 = list_users(<<"unknown-domain">>, Config), @@ -551,3 +614,7 @@ ban_user(JID, Reason, Config) -> change_user_password(JID, NewPassword, Config) -> Vars = #{<<"user">> => JID, <<"newPassword">> => NewPassword}, execute_command(<<"account">>, <<"changeUserPassword">>, Vars, Config). + +import_users(Filename, Config) -> + Vars = #{<<"filename">> => Filename}, + execute_command(<<"account">>, <<"importUsers">>, Vars, Config). diff --git a/big_tests/tests/graphql_account_SUITE_data/users.csv b/big_tests/tests/graphql_account_SUITE_data/users.csv new file mode 100644 index 00000000000..70c43e22aa8 --- /dev/null +++ b/big_tests/tests/graphql_account_SUITE_data/users.csv @@ -0,0 +1,5 @@ +john,$host$,smith +alice,$host$,smith +elise,$host$, +,$host$,password +elise,elise,$host$,esile diff --git a/big_tests/tests/graphql_helper.erl b/big_tests/tests/graphql_helper.erl index 54567722bf1..59443be8dba 100644 --- a/big_tests/tests/graphql_helper.erl +++ b/big_tests/tests/graphql_helper.erl @@ -293,7 +293,7 @@ get_value([Field | Fields], Data) -> get_value(Fields, Data2). is_graphql_config(#{module := ejabberd_cowboy, handlers := Handlers}, ExpEpName) -> - lists:any(fun(#{module := mongoose_graphql_cowboy_handler, schema_endpoint := EpName}) -> + lists:any(fun(#{module := mongoose_graphql_handler, schema_endpoint := EpName}) -> ExpEpName =:= EpName; (_) -> false end, Handlers); diff --git a/big_tests/tests/graphql_metric_SUITE.erl b/big_tests/tests/graphql_metric_SUITE.erl index a144d158ba7..02de7370577 100644 --- a/big_tests/tests/graphql_metric_SUITE.erl +++ b/big_tests/tests/graphql_metric_SUITE.erl @@ -5,7 +5,8 @@ -compile([export_all, nowarn_export_all]). -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). --import(graphql_helper, [execute_command/4, get_ok_value/2, get_unauthorized/1]). +-import(graphql_helper, [execute_command/4, get_ok_value/2, get_unauthorized/1, + get_err_msg/1, get_err_code/1]). suite() -> MIM2NodeName = maps:get(node, distributed_helper:mim2()), @@ -27,15 +28,28 @@ metrics_tests() -> [get_all_metrics, get_all_metrics_check_by_type, get_by_name_global_erlang_metrics, + get_metrics_by_name_empty_args, + get_metrics_by_name_empty_string, + get_metrics_by_nonexistent_name, + get_metrics_for_specific_host_type, get_process_queue_length, get_inet_stats, get_vm_stats_memory, get_all_metrics_as_dicts, get_by_name_metrics_as_dicts, + get_metrics_as_dicts_by_nonexistent_name, get_metrics_as_dicts_with_key_one, + get_metrics_as_dicts_with_nonexistent_key, + get_metrics_as_dicts_empty_args, + get_metrics_as_dicts_empty_strings, get_cluster_metrics, get_by_name_cluster_metrics_as_dicts, - get_mim2_cluster_metrics]. + get_mim2_cluster_metrics, + get_cluster_metrics_for_nonexistent_nodes, + get_cluster_metrics_by_nonexistent_name, + get_cluster_metrics_with_nonexistent_key, + get_cluster_metrics_empty_args, + get_cluster_metrics_empty_strings]. domain_admin_metrics_tests() -> [domain_admin_get_metrics, @@ -126,6 +140,28 @@ get_by_name_global_erlang_metrics(Config) -> %% Other metrics are filtered out undef = maps:get(ReadsKey, Map, undef). +get_metrics_by_name_empty_args(Config) -> + Result = get_metrics([], Config), + ParsedResult = get_ok_value([data, metric, getMetrics], Result), + lists:foreach(fun check_metric_by_type/1, ParsedResult), + [_|_] = ParsedResult. + +get_metrics_by_name_empty_string(Config) -> + Result = get_metrics([<<>>], Config), + ParsedResult = get_ok_value([data, metric, getMetrics], Result), + [] = ParsedResult. + +get_metrics_by_nonexistent_name(Config) -> + Result = get_metrics([<<"not_existing">>], Config), + ParsedResult = get_ok_value([data, metric, getMetrics], Result), + [] = ParsedResult. + +get_metrics_for_specific_host_type(Config) -> + Result = get_metrics([<<"dummy auth">>], Config), + ParsedResult = get_ok_value([data, metric, getMetrics], Result), + lists:foreach(fun check_metric_by_type/1, ParsedResult), + [_|_] = ParsedResult. + get_process_queue_length(Config) -> Result = get_metrics([<<"global">>, <<"processQueueLengths">>], Config), ParsedResult = get_ok_value([data, metric, getMetrics], Result), @@ -167,13 +203,48 @@ get_by_name_metrics_as_dicts(Config) -> check_spiral_dict(Dict) end, ParsedResult). +get_metrics_as_dicts_by_nonexistent_name(Config) -> + Result = get_metrics_as_dicts_by_name([<<"not_existing">>], Config), + ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result), + [] = ParsedResult. + get_metrics_as_dicts_with_key_one(Config) -> Result = get_metrics_as_dicts_with_keys([<<"one">>], Config), ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result), Map = dict_objects_to_map(ParsedResult), SentName = [metric_host_type(), <<"xmppStanzaSent">>], [#{<<"key">> := <<"one">>, <<"value">> := One}] = maps:get(SentName, Map), - true = is_integer(One). + ?assert(is_integer(One)). + +get_metrics_as_dicts_with_nonexistent_key(Config) -> + Result = get_metrics_as_dicts_with_keys([<<"not_existing">>], Config), + ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result), + Map = dict_objects_to_map(ParsedResult), + SentName = [<<"global">>, <<"data">>, <<"xmpp">>, <<"received">>, <<"encrypted_size">>], + [] = maps:get(SentName, Map). + +get_metrics_as_dicts_empty_args(Config) -> + %% Empty name + Result = get_metrics_as_dicts([], [<<"median">>], Config), + ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result), + Map = dict_objects_to_map(ParsedResult), + SentName = [<<"global">>, <<"data">>, <<"xmpp">>, <<"received">>, <<"encrypted_size">>], + [#{<<"key">> := <<"median">>, <<"value">> := Median}] = maps:get(SentName, Map), + ?assert(is_integer(Median)), + %% Empty keys + Result2 = get_metrics_as_dicts([<<"global">>, <<"erlang">>], [], Config), + ParsedResult2 = get_ok_value([data, metric, getMetricsAsDicts], Result2), + ?assertEqual(length(ParsedResult2), 2). + +get_metrics_as_dicts_empty_strings(Config) -> + %% Name is an empty string + Result = get_metrics_as_dicts([<<>>], [<<"median">>], Config), + ParsedResult = get_ok_value([data, metric, getMetricsAsDicts], Result), + [] = ParsedResult, + %% Key is an empty string + Result2 = get_metrics_as_dicts([<<"global">>, <<"erlang">>], [<<>>], Config), + ParsedResult2 = get_ok_value([data, metric, getMetricsAsDicts], Result2), + [_|_] = ParsedResult2. get_cluster_metrics(Config) -> %% We will have at least these two nodes @@ -190,7 +261,7 @@ get_by_name_cluster_metrics_as_dicts(Config) -> NodeResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), Map = node_objects_to_map(NodeResult), %% Contains data for at least two nodes - true = maps:size(Map) > 1, + ?assert(maps:size(Map) > 1), %% Only xmppStanzaSent type maps:map(fun(_Node, [_|_] = NodeRes) -> lists:foreach(fun(#{<<"dict">> := Dict, @@ -205,6 +276,63 @@ get_mim2_cluster_metrics(Config) -> [#{<<"node">> := Node, <<"result">> := ResList}] = ParsedResult, check_node_result_is_valid(ResList, true). +get_cluster_metrics_for_nonexistent_nodes(Config) -> + Result = get_cluster_metrics_as_dicts_for_nodes([<<"nonexistent">>], Config), + ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), + [#{<<"node">> := _, <<"result">> := ResList}] = ParsedResult, + [#{<<"dict">> := [], <<"name">> := ErrorResult}] = ResList, + ?assert(ErrorResult == [<<"error">>, <<"nodedown">>]). + +get_cluster_metrics_by_nonexistent_name(Config) -> + Result = get_cluster_metrics_as_dicts_by_name([<<"nonexistent">>], Config), + ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), + [#{<<"node">> := _, <<"result">> := []}, + #{<<"node">> := _, <<"result">> := []}] = ParsedResult. + +get_cluster_metrics_with_nonexistent_key(Config) -> + Result = get_cluster_metrics_as_dicts_with_keys([<<"nonexistent">>], Config), + ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), + [#{<<"node">> := _, <<"result">> := [_|_]}, + #{<<"node">> := _, <<"result">> := [_|_]}] = ParsedResult. + +get_cluster_metrics_empty_args(Config) -> + Node = atom_to_binary(maps:get(node, distributed_helper:mim2())), + %% Empty name + Result = get_cluster_metrics_as_dicts([], [<<"one">>], [Node], Config), + ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), + [#{<<"node">> := Node, <<"result">> := ResList}] = ParsedResult, + Map = dict_objects_to_map(ResList), + SentName = [<<"global">>, <<"xmppStanzaSent">>], + [#{<<"key">> := <<"one">>, <<"value">> := One}] = maps:get(SentName, Map), + ?assert(is_integer(One)), + %% Empty keys + Result2 = get_cluster_metrics_as_dicts([<<"_">>], [], [Node], Config), + ParsedResult2 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result2), + [#{<<"node">> := Node, <<"result">> := ResList2}] = ParsedResult2, + check_node_result_is_valid(ResList2, true), + %% Empty nodes + Result3 = get_cluster_metrics_as_dicts([<<"_">>, <<"erlang">>], [<<"ets_limit">>], [], Config), + ParsedResult3 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result3), + NodeMap = node_objects_to_map(ParsedResult3), + ?assert(maps:size(NodeMap) > 1). + +get_cluster_metrics_empty_strings(Config) -> + Node = atom_to_binary(maps:get(node, distributed_helper:mim2())), + %% Name is an empty string + Result = get_cluster_metrics_as_dicts([<<>>], [<<"median">>], [Node], Config), + ParsedResult = get_ok_value([data, metric, getClusterMetricsAsDicts], Result), + [#{<<"node">> := Node, <<"result">> := []}] = ParsedResult, + %% Key is an empty string + Result2 = get_cluster_metrics_as_dicts([<<"_">>], [<<>>], [Node], Config), + ParsedResult2 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result2), + [#{<<"node">> := Node, <<"result">> := [_|_]}] = ParsedResult2, + %% Node is an empty string + Result3 = get_cluster_metrics_as_dicts([<<"_">>], [<<"median">>], [<<>>], Config), + ParsedResult3 = get_ok_value([data, metric, getClusterMetricsAsDicts], Result3), + [#{<<"node">> := _, <<"result">> := ResList}] = ParsedResult3, + [#{<<"dict">> := [], <<"name">> := ErrorResult}] = ResList, + ?assert(ErrorResult == [<<"error">>, <<"nodedown">>]). + check_node_result_is_valid(ResList, MetricsAreGlobal) -> %% Check that result contains something Map = dict_objects_to_map(ResList), @@ -215,7 +343,7 @@ check_node_result_is_valid(ResList, MetricsAreGlobal) -> check_spiral_dict(maps:get(SentName, Map)), [#{<<"key">> := <<"value">>,<<"value">> := V}] = maps:get([<<"global">>,<<"uniqueSessionCount">>], Map), - true = is_integer(V), + ?assert(is_integer(V)), HistObjects = maps:get([<<"global">>, <<"data">>, <<"xmpp">>, <<"sent">>, <<"compressed_size">>], Map), check_histogram(kv_objects_to_map(HistObjects)). @@ -277,6 +405,10 @@ get_metrics(Name, Config) -> get_metrics_as_dicts(Config) -> execute_command(<<"metric">>, <<"getMetricsAsDicts">>, #{}, Config). +get_metrics_as_dicts(Name, Keys, Config) -> + Vars = #{<<"name">> => Name, <<"keys">> => Keys}, + execute_command(<<"metric">>, <<"getMetricsAsDicts">>, Vars, Config). + get_metrics_as_dicts_by_name(Name, Config) -> Vars = #{<<"name">> => Name}, execute_command(<<"metric">>, <<"getMetricsAsDicts">>, Vars, Config). @@ -288,6 +420,10 @@ get_metrics_as_dicts_with_keys(Keys, Config) -> get_cluster_metrics_as_dicts(Config) -> execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, #{}, Config). +get_cluster_metrics_as_dicts(Name, Keys, Nodes, Config) -> + Vars = #{<<"name">> => Name, <<"nodes">> => Nodes, <<"keys">> => Keys}, + execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config). + get_cluster_metrics_as_dicts_by_name(Name, Config) -> Vars = #{<<"name">> => Name}, execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config). @@ -296,16 +432,20 @@ get_cluster_metrics_as_dicts_for_nodes(Nodes, Config) -> Vars = #{<<"nodes">> => Nodes}, execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config). +get_cluster_metrics_as_dicts_with_keys(Keys, Config) -> + Vars = #{<<"keys">> => Keys}, + execute_command(<<"metric">>, <<"getClusterMetricsAsDicts">>, Vars, Config). + %% Helpers check_spiral_dict(Dict) -> [#{<<"key">> := <<"count">>, <<"value">> := Count}, #{<<"key">> := <<"one">>, <<"value">> := One}] = Dict, - true = is_integer(Count), - true = is_integer(One). + ?assert(is_integer(Count)), + ?assert(is_integer(One)). values_are_integers(Map, Keys) -> - lists:foreach(fun(Key) -> true = is_integer(maps:get(Key, Map)) end, Keys). + lists:foreach(fun(Key) -> ?assert(is_integer(maps:get(Key, Map))) end, Keys). metric_host_type() -> binary:replace(domain_helper:host_type(), <<" ">>, <<"_">>, [global]). diff --git a/big_tests/tests/graphql_muc_SUITE.erl b/big_tests/tests/graphql_muc_SUITE.erl index 2ef504a9f95..65242620caf 100644 --- a/big_tests/tests/graphql_muc_SUITE.erl +++ b/big_tests/tests/graphql_muc_SUITE.erl @@ -270,7 +270,7 @@ maybe_enable_mam() -> Backend -> MAMOpts = mam_helper:config_opts( #{backend => Backend, - muc => #{host => subhost_pattern(muc_light_helper:muc_host_pattern())}, + muc => #{host => subhost_pattern(muc_helper:muc_host_pattern())}, async_writer => #{enabled => false}}), dynamic_modules:ensure_modules(domain_helper:host_type(), [{mod_mam, MAMOpts}]), true diff --git a/big_tests/tests/graphql_muc_light_SUITE.erl b/big_tests/tests/graphql_muc_light_SUITE.erl index 8093464ce81..cd0df8df8eb 100644 --- a/big_tests/tests/graphql_muc_light_SUITE.erl +++ b/big_tests/tests/graphql_muc_light_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_user_command/5, execute_command/4, get_listener_port/1, get_listener_config/1, get_ok_value/2, get_err_msg/1, get_coercion_err_msg/1, make_creds/1, get_unauthorized/1, @@ -41,29 +41,40 @@ all() -> [{group, user}, {group, admin_http}, {group, admin_cli}, - {group, domain_admin_muc_light}]. + {group, domain_admin}]. groups() -> [{user, [], user_groups()}, {admin_http, [], admin_groups()}, {admin_cli, [], admin_groups()}, + {domain_admin, domain_admin_groups()}, {user_muc_light, [parallel], user_muc_light_tests()}, + {user_muc_light_with_mam, [parallel], user_muc_light_with_mam_tests()}, {user_muc_light_not_configured, [], user_muc_light_not_configured_tests()}, {admin_muc_light, [], admin_muc_light_tests()}, + {admin_muc_light_with_mam, [], admin_muc_light_with_mam_tests()}, {domain_admin_muc_light, [], domain_admin_muc_light_tests()}, + {domain_admin_muc_light_with_mam, [], domain_admin_muc_light_with_mam_tests()}, {admin_muc_light_not_configured, [], admin_muc_light_not_configured_tests()}]. user_groups() -> [{group, user_muc_light}, + {group, user_muc_light_with_mam}, {group, user_muc_light_not_configured}]. admin_groups() -> [{group, admin_muc_light}, + {group, admin_muc_light_with_mam}, {group, admin_muc_light_not_configured}]. +domain_admin_groups() -> + [{group, domain_admin_muc_light}, + {group, domain_admin_muc_light_with_mam}]. + user_muc_light_tests() -> [user_create_room, user_create_room_with_unprepped_domain, + user_create_room_with_bad_config, user_create_room_with_custom_fields, user_create_identified_room, user_create_room_with_unprepped_id, @@ -75,13 +86,16 @@ user_muc_light_tests() -> user_kick_user, user_send_message_to_room, user_send_message_to_room_errors, - user_get_room_messages, + user_get_room_messages_muc_light_or_mam_not_configured, user_list_rooms, user_list_room_users, user_get_room_config, user_blocking_list ]. +user_muc_light_with_mam_tests() -> + [user_get_room_messages]. + user_muc_light_not_configured_tests() -> [user_create_room_muc_light_not_configured, user_change_room_config_muc_light_not_configured, @@ -89,7 +103,7 @@ user_muc_light_not_configured_tests() -> user_delete_room_muc_light_not_configured, user_kick_user_muc_light_not_configured, user_send_message_to_room_muc_light_not_configured, - user_get_room_messages_muc_light_not_configured, + user_get_room_messages_muc_light_or_mam_not_configured, user_list_rooms_muc_light_not_configured, user_list_room_users_muc_light_not_configured, user_get_room_config_muc_light_not_configured, @@ -97,9 +111,11 @@ user_muc_light_not_configured_tests() -> admin_muc_light_tests() -> [admin_create_room, + admin_create_room_non_existent_domain, admin_create_room_with_unprepped_domain, admin_create_room_with_custom_fields, admin_create_identified_room, + admin_create_identified_room_non_existent_domain, admin_create_room_with_unprepped_id, admin_change_room_config, admin_change_room_config_with_custom_fields, @@ -112,8 +128,7 @@ admin_muc_light_tests() -> admin_kick_user, admin_send_message_to_room, admin_send_message_to_room_errors, - admin_get_room_messages, - admin_get_room_messages_non_existent_domain, + admin_get_room_messages_muc_light_or_mam_not_configured, admin_list_user_rooms, admin_list_user_rooms_non_existent_domain, admin_list_room_users, @@ -125,6 +140,10 @@ admin_muc_light_tests() -> admin_blocking_list_errors ]. +admin_muc_light_with_mam_tests() -> + [admin_get_room_messages, + admin_get_room_messages_non_existent_domain]. + domain_admin_muc_light_tests() -> [admin_create_room, admin_create_room_with_unprepped_domain, @@ -147,8 +166,7 @@ domain_admin_muc_light_tests() -> admin_send_message_to_room, admin_send_message_to_room_errors, domain_admin_send_message_to_room_no_permission, - admin_get_room_messages, - domain_admin_get_room_messages_no_permission, + admin_get_room_messages_muc_light_or_mam_not_configured, admin_list_user_rooms, domain_admin_list_user_rooms_no_permission, admin_list_room_users, @@ -159,6 +177,10 @@ domain_admin_muc_light_tests() -> domain_admin_blocking_list_no_permission ]. +domain_admin_muc_light_with_mam_tests() -> + [admin_get_room_messages, + domain_admin_get_room_messages_no_permission]. + admin_muc_light_not_configured_tests() -> [admin_create_room_muc_light_not_configured, admin_change_room_config_muc_light_not_configured, @@ -166,7 +188,7 @@ admin_muc_light_not_configured_tests() -> admin_delete_room_muc_light_not_configured, admin_kick_user_muc_light_not_configured, admin_send_message_to_room_muc_light_not_configured, - admin_get_room_messages_muc_light_not_configured, + admin_get_room_messages_muc_light_or_mam_not_configured, admin_list_user_rooms_muc_light_not_configured, admin_list_room_users_muc_light_not_configured, admin_get_room_config_muc_light_not_configured, @@ -177,9 +199,8 @@ init_per_suite(Config) -> SecondaryHostType = domain_helper:secondary_host_type(), Config1 = dynamic_modules:save_modules(HostType, Config), Config2 = dynamic_modules:save_modules(SecondaryHostType, Config1), - Config3 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config2), - Config4 = ejabberd_node_utils:init(mim(), Config3), - escalus:init_per_suite(Config4). + Config3 = ejabberd_node_utils:init(mim(), Config2), + escalus:init_per_suite(Config3). end_per_suite(Config) -> escalus_fresh:clean(), @@ -197,24 +218,51 @@ custom_schema() -> {<<"music">>, <<>>, music, binary}, %% Default fields {<<"roomname">>, <<>>, roomname, binary}, - {<<"subject">>, <<>>, subject, binary}]. + {<<"subject">>, <<"Test">>, subject, binary}]. init_per_group(admin_http, Config) -> graphql_helper:init_admin_handler(Config); init_per_group(admin_cli, Config) -> graphql_helper:init_admin_cli(Config); -init_per_group(domain_admin_muc_light, Config) -> - Config1 = ensure_muc_light_started(Config), - graphql_helper:init_domain_admin_handler(Config1); +init_per_group(domain_admin, Config) -> + graphql_helper:init_domain_admin_handler(Config); init_per_group(Group, Config) when Group =:= user_muc_light; - Group =:= admin_muc_light -> + Group =:= admin_muc_light; + Group =:= domain_admin_muc_light -> + disable_mam(), ensure_muc_light_started(Config); +init_per_group(Group, Config) when Group =:= user_muc_light_with_mam; + Group =:= admin_muc_light_with_mam; + Group =:= domain_admin_muc_light_with_mam -> + case maybe_enable_mam() of + true -> + ensure_muc_light_started(Config); + false -> + {skip, "No MAM backend available"} + end; init_per_group(Group, Config) when Group =:= user_muc_light_not_configured; Group =:= admin_muc_light_not_configured -> + maybe_enable_mam(), ensure_muc_light_stopped(Config); init_per_group(user, Config) -> graphql_helper:init_user(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_light_started(Config) -> HostType = domain_helper:host_type(), SecondaryHostType = domain_helper:secondary_host_type(), @@ -233,8 +281,8 @@ ensure_muc_light_stopped(Config) -> end_per_group(Group, _Config) when Group =:= user; Group =:= admin_http; - Group =:= domain_admin_muc_light; - Group =:= admin_cli -> + Group =:= admin_cli; + Group =:= domain_admin -> graphql_helper:clean(); end_per_group(_Group, _Config) -> escalus_fresh:clean(). @@ -278,6 +326,18 @@ user_create_room_with_unprepped_domain_story(Config, Alice) -> get_ok_value(?CREATE_ROOM_PATH, Res), ?assertMatch(#jid{lserver = MucServer}, jid:from_binary_noprep(JID)). +user_create_room_with_bad_config(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], + fun user_create_room_with_bad_config_story/2). + +user_create_room_with_bad_config_story(Config, Alice) -> + MucServer = ?config(muc_light_host, Config), + Name = <<"room with custom fields">>, + Subject = <<"testing_custom">>, + Opts = #{<<"badkey">> => <<"badvalue">>}, + Res = user_create_room_with_options(Alice, MucServer, Name, Subject, null, Opts, Config), + ?assertMatch({_, _}, binary:match(get_err_msg(Res), <<"Validation failed for key: badkey">>)). + user_create_room_with_custom_fields(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_create_room_with_custom_fields_story/2). @@ -289,7 +349,8 @@ user_create_room_with_custom_fields_story(Config, Alice) -> Opts = [#{<<"key">> => <<"background">>, <<"value">> => <<"red">>}, #{<<"key">> => <<"roomname">>, <<"value">> => Name}, #{<<"key">> => <<"subject">>, <<"value">> => Subject}], - Res = user_create_room_with_options(Alice, MucServer, Name, Subject, null, #{<<"background">> => <<"red">>}, Config), + Res = user_create_room_with_options(Alice, MucServer, Name, Subject, null, + #{<<"background">> => <<"red">>}, Config), #{<<"jid">> := JID, <<"name">> := Name, <<"subject">> := Subject, <<"participants">> := Participants, <<"options">> := Opts} = get_ok_value(?CREATE_ROOM_PATH, Res), @@ -427,7 +488,7 @@ user_invite_user_errors_story(Config, Alice, Bob) -> create_room(MUCServer, <<"first room">>, <<"subject">>, AliceBin), % Try to invite a user to not existing room Res = user_invite_user(Alice, make_bare_jid(?UNKNOWN, MUCServer), BobBin, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not occupy this room">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)), % User without rooms tries to invite a user Res2 = user_invite_user(Bob, jid:to_binary(RoomJID), AliceBin, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not occupy this room">>)), @@ -460,7 +521,7 @@ user_delete_room_story(Config, Alice, Bob) -> ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"not found">>)), % Try with a non-existent room Res4 = user_delete_room(Alice, make_bare_jid(?UNKNOWN, MUCServer), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"not existing room">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"not found">>)). user_kick_user(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun user_kick_user_story/3). @@ -478,11 +539,10 @@ user_kick_user_story(Config, Alice, Bob) -> ?assertNotEqual(nomatch, binary:match(get_ok_value(?KICK_USER_PATH, Res), <<"successfully">>)), ?assertEqual(1, length(get_room_aff(RoomJID))), - % Member cannot kick the room owner. The kick stanza is sent successfully, - % but is ignored by server + % Member cannot kick the room owner. {ok, _} = invite_user(RoomJID, AliceBin, BobBin), Res2 = user_kick_user(Bob, jid:to_binary(RoomJID), AliceBin, Config), - ?assertNotEqual(nomatch, binary:match(get_ok_value(?KICK_USER_PATH, Res2), <<"successfully">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not have permission">>)), ?assertEqual(2, length(get_room_aff(RoomJID))), % Owner kicks the member from a room {ok, _} = invite_user(RoomJID, AliceBin, BobBin), @@ -540,7 +600,7 @@ user_get_room_messages_story(Config, Alice, Bob) -> create_room(MUCServer, <<"first room">>, <<"subject">>, AliceBin), Message = <<"Hello friends">>, send_message_to_room(RoomJID, jid:from_binary(AliceBin), Message), - mam_helper:maybe_wait_for_archive(Config), + mam_helper:wait_for_room_archive_size(MUCServer, RoomID, 1), % Get messages so far Limit = 40, Res = user_get_room_messages(Alice, jid:to_binary(RoomJID), Limit, null, Config), @@ -557,7 +617,7 @@ user_get_room_messages_story(Config, Alice, Bob) -> % Try with a non-existent domain Res4 = user_get_room_messages( Alice, make_bare_jid(RoomID, ?UNKNOWN_DOMAIN), Limit, null, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"not found">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"server not found">>)), % Try with a user that is not a room member Res5 = user_get_room_messages(Bob, jid:to_binary(RoomJID), Limit, null, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res5), <<"not occupy this room">>)). @@ -681,13 +741,15 @@ user_blocking_list_story(Config, Alice, Bob) -> %% Domain admin test cases domain_admin_create_room_no_permission(Config) -> - escalus:fresh_story_with_config(Config, [{alice_bis, 1}], - fun domain_admin_create_room_no_permission_story/2). + escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], + fun domain_admin_create_room_no_permission_story/3). -domain_admin_create_room_no_permission_story(Config, AliceBis) -> +domain_admin_create_room_no_permission_story(Config, Alice, AliceBis) -> + AliceBin = escalus_client:short_jid(Alice), AliceBisBin = escalus_client:short_jid(AliceBis), InvalidUser = make_bare_jid(?UNKNOWN, ?UNKNOWN_DOMAIN), MucServer = ?config(muc_light_host, Config), + ExternalMucServer = ?config(secondary_muc_light_host, Config), Name = <<"first room">>, Subject = <<"testing">>, % Try with a non-existent domain @@ -695,7 +757,10 @@ domain_admin_create_room_no_permission_story(Config, AliceBis) -> get_unauthorized(Res), % Try with an external domain Res2 = create_room(MucServer, Name, AliceBisBin, Subject, null, Config), - get_unauthorized(Res2). + get_unauthorized(Res2), + % Try to create a room at an external domain + Res3 = create_room(ExternalMucServer, Name, AliceBin, Subject, null, Config), + get_unauthorized(Res3). domain_admin_create_identified_room_no_permission(Config) -> escalus:fresh_story_with_config(Config, [{alice_bis, 1}], @@ -943,9 +1008,14 @@ admin_blocking_list_null_story(Config, Alice, Bob) -> admin_blocking_list_errors(Config) -> InvalidUser = make_bare_jid(?UNKNOWN, ?UNKNOWN_DOMAIN), Res = get_user_blocking(InvalidUser, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"domain does not exist">>)), Res2 = set_blocking(InvalidUser, [], Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"domain does not exist">>)), + BadUser = make_bare_jid(<<"baduser">>, domain_helper:domain()), + Res3 = get_user_blocking(BadUser, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"user does not exist">>)), + Res4 = set_blocking(BadUser, [], Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"user does not exist">>)). admin_create_room(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_room_story/2). @@ -961,9 +1031,20 @@ admin_create_room_story(Config, Alice) -> <<"participants">> := Participants} = get_ok_value(?CREATE_ROOM_PATH, Res), ?assertMatch(#jid{lserver = MucServer}, jid:from_binary(JID)), ?assertEqual([#{<<"jid">> => AliceBinLower, <<"affiliation">> => <<"OWNER">>}], Participants), - % Try with a non-existent domain - Res2 = create_room(?UNKNOWN_DOMAIN, Name, AliceBin, Subject, null, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). + % Try with a non-existent user + BadUser = make_bare_jid(<<"baduser">>, domain_helper:domain()), + Res1 = create_room(?config(muc_light_host, Config), null, BadUser, null, null, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res1), <<"user does not exist">>)). + +admin_create_room_non_existent_domain(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceBin = escalus_users:get_jid(Config1, alice), + MucServer = ?config(muc_light_host, Config), + Res1 = create_room(?UNKNOWN_DOMAIN, null, AliceBin, null, null, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res1), <<"MUC Light server not found">>)), + BadUser = make_bare_jid(<<"baduser">>, ?UNKNOWN_DOMAIN), + Res2 = create_room(MucServer, null, BadUser, null, null, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res2), <<"User's domain does not exist">>)). admin_create_room_with_unprepped_domain(Config) -> FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), @@ -1011,14 +1092,23 @@ admin_create_identified_room_story(Config, Alice) -> get_ok_value(?CREATE_ROOM_PATH, Res), ?assertMatch(#jid{luser = Id, lserver = MucServer}, jid:from_binary(JID)), % Create a room with an existing ID - Res2 = create_room(MucServer, <<"snd room">>, AliceBin, Subject, Id, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"already exists">>)), - % Try with a non-existent domain - Res3 = create_room(?UNKNOWN_DOMAIN, <<"name">>, AliceBin, Subject, Id, Config), - ?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), - ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Res4), <<"empty_room_name">>)). + Res1 = create_room(MucServer, <<"snd room">>, AliceBin, Subject, Id, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res1), <<"already exists">>)), + % Try with a non-existent user + BadUser = make_bare_jid(<<"baduser">>, domain_helper:domain()), + Res2 = create_room(?config(muc_light_host, Config), null, BadUser, null, Id, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"user does not exist">>)). + +admin_create_identified_room_non_existent_domain(Config) -> + Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), + AliceBin = escalus_users:get_jid(Config1, alice), + Id = <<"bad_room">>, + MucServer = ?config(muc_light_host, Config), + Res1 = create_room(?UNKNOWN_DOMAIN, null, AliceBin, null, Id, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res1), <<"MUC Light server not found">>)), + BadUser = make_bare_jid(<<"baduser">>, ?UNKNOWN_DOMAIN), + Res2 = create_room(MucServer, null, BadUser, null, Id, Config1), + ?assertMatch({_, _}, binary:match(get_err_msg(Res2), <<"User's domain does not exist">>)). admin_create_room_with_unprepped_id(Config) -> FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}]), @@ -1075,16 +1165,15 @@ admin_change_room_config_with_custom_fields_story(Config, Alice) -> get_ok_value(?CHANGE_CONFIG_PATH, Res2)). admin_change_room_config_errors(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], - fun admin_change_room_config_errors_story/3). + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}, {kate, 1}], + fun admin_change_room_config_errors_story/4). -admin_change_room_config_errors_story(Config, Alice, Bob) -> +admin_change_room_config_errors_story(Config, Alice, Bob, Kate) -> AliceBin = escalus_client:short_jid(Alice), BobBin = escalus_client:short_jid(Bob), MUCServer = ?config(muc_light_host, Config), RoomName = <<"first room">>, - {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = - create_room(MUCServer, RoomName, <<"subject">>, AliceBin), + {ok, #{jid := RoomJID}} = create_room(MUCServer, RoomName, <<"subject">>, AliceBin), {ok, _} = invite_user(RoomJID, AliceBin, BobBin), % Try to change the config of the non-existent room Res = change_room_configuration( @@ -1094,12 +1183,17 @@ admin_change_room_config_errors_story(Config, Alice, Bob) -> Res2 = change_room_configuration( jid:to_binary(RoomJID), <<"wrong-user@wrong-domain">>, RoomName, <<"subject2">>, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not occupy this room">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"User's domain does not exist">>)), % Try to change a config by the user without permission Res3 = change_room_configuration( jid:to_binary(RoomJID), BobBin, RoomName, <<"subject2">>, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), - <<"does not have permission to change">>)). + <<"does not have permission to change">>)), + % Try to change a config by a user not occupying the room + KateBin = escalus_client:short_jid(Kate), + Res4 = change_room_configuration( + jid:to_binary(RoomJID), KateBin, RoomName, <<"subject2">>, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"does not occupy">>)). admin_change_room_config_non_existent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], @@ -1149,7 +1243,7 @@ admin_invite_user_errors_story(Config, Alice, Bob) -> create_room(MUCServer, <<"first room">>, <<"subject">>, AliceBin), % Try to invite a user to not existing room Res = invite_user(make_bare_jid(?UNKNOWN, MUCServer), AliceBin, BobBin, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not occupy this room">>)), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)), % User without rooms tries to invite a user Res2 = invite_user(jid:to_binary(RoomJID), BobBin, AliceBin, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not occupy this room">>)), @@ -1164,15 +1258,14 @@ admin_delete_room_story(Config, Alice) -> AliceBin = escalus_client:short_jid(Alice), MUCServer = ?config(muc_light_host, Config), Name = <<"first room">>, - {ok, #{jid := #jid{luser = RoomID} = RoomJID}} = - create_room(MUCServer, Name, <<"subject">>, AliceBin), + {ok, #{jid := RoomJID}} = create_room(MUCServer, Name, <<"subject">>, AliceBin), Res = delete_room(jid:to_binary(RoomJID), Config), ?assertNotEqual(nomatch, binary:match(get_ok_value(?DELETE_ROOM_PATH, Res), <<"successfully">>)), ?assertEqual({error, not_exists}, get_room_info(jid:from_binary(RoomJID))), % Try with a non-existent room Res2 = delete_room(make_bare_jid(?UNKNOWN, MUCServer), Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"Cannot remove">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)). admin_delete_room_non_existent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -1257,7 +1350,7 @@ admin_get_room_messages_story(Config, Alice) -> {ok, _} = create_room(MUCServer, RoomName2, <<"subject">>, AliceBin), Message = <<"Hello friends">>, send_message_to_room(RoomJID, jid:from_binary(AliceBin), Message), - mam_helper:maybe_wait_for_archive(Config), + mam_helper:wait_for_room_archive_size(MUCServer, RoomID, 1), % Get messages so far Limit = 40, Res = get_room_messages(jid:to_binary(RoomJID), Limit, null, Config), @@ -1270,7 +1363,10 @@ admin_get_room_messages_story(Config, Alice) -> ?assertMatch(#{<<"stanzas">> := [], <<"limit">> := 50}, get_ok_value(?GET_MESSAGES_PATH, Res2)), % Try to pass too big page size value Res3 = get_room_messages(jid:to_binary(RoomJID), 51, Before, Config), - ?assertMatch(#{<<"limit">> := 50},get_ok_value(?GET_MESSAGES_PATH, Res3)). + ?assertMatch(#{<<"limit">> := 50}, get_ok_value(?GET_MESSAGES_PATH, Res3)), + % Try with a non-existent room + Res4 = get_room_messages(make_bare_jid(<<"badroom">>, MUCServer), null, null, Config), + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res4), <<"Room not found">>)). admin_get_room_messages_non_existent_domain(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], @@ -1302,11 +1398,11 @@ admin_list_user_rooms_story(Config, Alice) -> lists:sort(get_ok_value(?LIST_USER_ROOMS_PATH, Res))), % Try with a non-existent user Res2 = list_user_rooms(<<"not-exist@", Domain/binary>>, Config), - ?assertEqual([], lists:sort(get_ok_value(?LIST_USER_ROOMS_PATH, Res2))). + ?assertMatch({_, _}, binary:match(get_err_msg(Res2), <<"does not exist">>)). admin_list_user_rooms_non_existent_domain(Config) -> Res = list_user_rooms(<<"not-exist@not-exist">>, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)). admin_list_room_users(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_list_room_users_story/2). @@ -1430,11 +1526,11 @@ user_send_message_to_room_muc_light_not_configured_story(Config, Alice) -> Res = user_send_message_to_room(Alice, get_room_name(), MsgBody, Config), get_not_loaded(Res). -user_get_room_messages_muc_light_not_configured(Config) -> +user_get_room_messages_muc_light_or_mam_not_configured(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], - fun user_get_room_messages_muc_light_not_configured_story/2). + fun user_get_room_messages_muc_light_or_mam_not_configured_story/2). -user_get_room_messages_muc_light_not_configured_story(Config, Alice) -> +user_get_room_messages_muc_light_or_mam_not_configured_story(Config, Alice) -> Before = <<"2022-02-17T04:54:13+00:00">>, Res = user_get_room_messages(Alice, get_room_name(), 51, Before, Config), get_not_loaded(Res). @@ -1532,7 +1628,7 @@ admin_send_message_to_room_muc_light_not_configured_story(Config, Alice) -> Res = send_message_to_room(get_room_name(), AliceBin, MsgBody, Config), get_not_loaded(Res). -admin_get_room_messages_muc_light_not_configured(Config) -> +admin_get_room_messages_muc_light_or_mam_not_configured(Config) -> Limit = 40, Res = get_room_messages(get_room_name(), Limit, null, Config), get_not_loaded(Res). @@ -1585,7 +1681,8 @@ get_room_messages(ID, Domain) -> create_room(Domain, Name, Subject, CreatorBin) -> CreatorJID = jid:from_binary(CreatorBin), - rpc(mim(), mod_muc_light_api, create_room, [Domain, CreatorJID, Name, Subject]). + Config = #{<<"roomname">> => Name, <<"subject">> => Subject}, + rpc(mim(), mod_muc_light_api, create_room, [Domain, CreatorJID, Config]). invite_user(RoomJID, SenderBin, RecipientBin) -> SenderJID = jid:from_binary(SenderBin), diff --git a/big_tests/tests/graphql_private_SUITE.erl b/big_tests/tests/graphql_private_SUITE.erl index 787ed162407..daf314c70d2 100644 --- a/big_tests/tests/graphql_private_SUITE.erl +++ b/big_tests/tests/graphql_private_SUITE.erl @@ -4,7 +4,7 @@ -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(graphql_helper, [execute_user_command/5, execute_command/4, get_ok_value/2, get_err_code/1, - user_to_bin/1, get_unauthorized/1, get_not_loaded/1]). + user_to_bin/1, get_unauthorized/1, get_not_loaded/1, get_coercion_err_msg/1]). -include_lib("eunit/include/eunit.hrl"). -include_lib("exml/include/exml.hrl"). @@ -39,6 +39,7 @@ user_groups() -> user_private_tests() -> [user_set_private, user_get_private, + user_get_private_empty_namespace, parse_xml_error]. user_private_not_configured_tests() -> @@ -54,6 +55,7 @@ domain_admin_private_tests() -> admin_private_tests() -> [admin_set_private, admin_get_private, + admin_get_private_empty_namespace, no_user_error_set, no_user_error_get]. @@ -130,7 +132,7 @@ user_set_private(Config, Alice) -> ElemStr = exml:to_binary(private_input()), ResultSet = user_set_private(Alice, ElemStr, Config), ParsedResultSet = get_ok_value([data, private, setPrivate], ResultSet), - ?assertEqual(<<"[]">>, ParsedResultSet), + ?assertEqual(<<"DATA">>, ParsedResultSet), ResultGet = user_get_private(Alice, <<"my_element">>, <<"alice:private:ns">>, Config), ParsedResultGet = get_ok_value([data, private, getPrivate], ResultGet), ?assertEqual(ElemStr, ParsedResultGet). @@ -147,12 +149,23 @@ user_get_private(Config, Alice) -> ParsedResultGet = get_ok_value([data, private, getPrivate], ResultGet), ?assertEqual(ElemStr, ParsedResultGet). +user_get_private_empty_namespace(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_get_private_empty_namespace/2). + +user_get_private_empty_namespace(Config, Alice) -> + ResultGet = user_get_private(Alice, <<"">>, <<"">>, Config), + ?assertEqual(<<"Input coercion failed for type NonEmptyString with value <<>>." + " The reason it failed is: \"Given string is empty\"">>, + get_coercion_err_msg(ResultGet)). + parse_xml_error(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun parse_xml_error/2). parse_xml_error(Config, Alice) -> ResultSet = user_set_private(Alice, <<"AAAABBBB">>, Config), - ?assertEqual(<<"parse_error">>, get_err_code(ResultSet)). + ?assertEqual(<<"Input coercion failed for type XmlElement with value <<\"AAAABBBB\">>." + " The reason it failed is: \"expected <\"">>, + get_coercion_err_msg(ResultSet)). % User private not configured test cases @@ -181,7 +194,7 @@ admin_set_private(Config, Alice) -> ElemStr = exml:to_binary(private_input()), ResultSet = admin_set_private(AliceBin, ElemStr, Config), ParsedResultSet = get_ok_value([data, private, setPrivate], ResultSet), - ?assertEqual(<<"[]">>, ParsedResultSet), + ?assertEqual(<<"DATA">>, ParsedResultSet), ResultGet = admin_get_private(AliceBin, <<"my_element">>, <<"alice:private:ns">>, Config), ParsedResultGet = get_ok_value([data, private, getPrivate], ResultGet), ?assertEqual(ElemStr, ParsedResultGet). @@ -199,6 +212,16 @@ admin_get_private(Config, Alice) -> ParsedResultGet = get_ok_value([data, private, getPrivate], ResultGet), ?assertEqual(ElemStr, ParsedResultGet). +admin_get_private_empty_namespace(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_get_private_empty_namespace/2). + +admin_get_private_empty_namespace(Config, Alice) -> + AliceBin = user_to_bin(Alice), + ResultGet = admin_get_private(AliceBin, <<"">>, <<"">>, Config), + ?assertEqual(<<"Input coercion failed for type NonEmptyString with value <<>>." + " The reason it failed is: \"Given string is empty\"">>, + get_coercion_err_msg(ResultGet)). + no_user_error_set(Config) -> ElemStr = exml:to_binary(private_input()), Result = admin_set_private(<<"eddie@otherhost">>, ElemStr, Config), diff --git a/big_tests/tests/graphql_session_SUITE.erl b/big_tests/tests/graphql_session_SUITE.erl index d67d4af521e..e67eebd2566 100644 --- a/big_tests/tests/graphql_session_SUITE.erl +++ b/big_tests/tests/graphql_session_SUITE.erl @@ -8,7 +8,8 @@ -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]). + get_listener_config/1, get_ok_value/2, get_err_msg/1, get_unauthorized/1, + get_coercion_err_msg/1]). suite() -> require_rpc_nodes([mim]) ++ escalus:suite(). @@ -38,7 +39,8 @@ admin_session_tests() -> admin_get_user_resource, admin_list_users_with_status, admin_count_users_with_status, - admin_kick_session, + admin_kick_user_session, + admin_kick_user, admin_set_presence, admin_set_presence_away, admin_set_presence_unavailable]. @@ -54,7 +56,8 @@ domain_admin_session_tests() -> domain_admin_get_user_resource_no_permission, domain_admin_list_users_with_status, domain_admin_count_users_with_status, - admin_kick_session, + admin_kick_user_session, + domain_admin_kick_user_session_no_permission, domain_admin_kick_user_no_permission, admin_set_presence, admin_set_presence_away, @@ -197,12 +200,22 @@ domain_admin_get_user_resource_story_no_permission_story(Config, AliceBis) -> Res = get_user_resource(AliceBisJID, 2, Config), get_unauthorized(Res). +domain_admin_kick_user_session_no_permission(Config) -> + escalus:fresh_story_with_config(Config, [{alice_bis, 1}], + fun domain_admin_kick_user_session_no_permission_story/2). + +domain_admin_kick_user_session_no_permission_story(Config, AliceBis) -> + AliceBisJID = escalus_client:full_jid(AliceBis), + Reason = <<"Test kick">>, + Res = kick_user_session(AliceBisJID, Reason, Config), + get_unauthorized(Res). + domain_admin_kick_user_no_permission(Config) -> escalus:fresh_story_with_config(Config, [{alice_bis, 1}], fun domain_admin_kick_user_no_permission_story/2). domain_admin_kick_user_no_permission_story(Config, AliceBis) -> - AliceBisJID = escalus_client:full_jid(AliceBis), + AliceBisJID = escalus_client:short_jid(AliceBis), Reason = <<"Test kick">>, Res = kick_user(AliceBisJID, Reason, Config), get_unauthorized(Res). @@ -290,7 +303,10 @@ admin_list_sessions_story(Config, _Alice, AliceB, _Bob) -> ?assertEqual(1, length(Sessions2)), Res3 = list_sessions(unprep(BisDomain), Config), Sessions3 = get_ok_value(Path, Res3), - ?assertEqual(1, length(Sessions3)). + ?assertEqual(1, length(Sessions3)), + % List sessions for a non-existing domain + Res4 = list_sessions(<<"nonexisting">>, Config), + ?assertEqual(<<"Domain not found">>, get_err_msg(Res4)). admin_count_sessions(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {bob, 1}], @@ -307,7 +323,10 @@ admin_count_sessions_story(Config, _Alice, AliceB, _Bob) -> Res2 = count_sessions(BisDomain, Config), ?assertEqual(1, get_ok_value(Path, Res2)), Res3 = count_sessions(unprep(BisDomain), Config), - ?assertEqual(1, get_ok_value(Path, Res3)). + ?assertEqual(1, get_ok_value(Path, Res3)), + % Count sessions for a non-existing domain + Res4 = count_sessions(<<"nonexisting">>, Config), + ?assertEqual(<<"Domain not found">>, get_err_msg(Res4)). admin_list_user_sessions(Config) -> escalus:fresh_story_with_config(Config, [{alice, 2}, {bob, 1}], @@ -321,7 +340,11 @@ admin_list_user_sessions_story(Config, Alice, Alice2, _Bob) -> ExpectedRes = lists:map(fun escalus_utils:jid_to_lower/1, [S1JID, S2JID]), Sessions = get_ok_value(Path, Res), ?assertEqual(2, length(Sessions)), - check_users(ExpectedRes, Sessions). + check_users(ExpectedRes, Sessions), + % Check for a non-existing user + Domain = domain(), + Res2 = list_user_sessions(<<"alien@", Domain/binary>>, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res2)). admin_count_user_resources(Config) -> escalus:fresh_story_with_config(Config, [{alice, 3}], fun admin_count_user_resources_story/4). @@ -330,7 +353,11 @@ admin_count_user_resources_story(Config, Alice, _Alice2, _Alice3) -> Path = [data, session, countUserResources], JID = escalus_client:full_jid(Alice), Res = count_user_resources(JID, Config), - ?assertEqual(3, get_ok_value(Path, Res)). + ?assertEqual(3, get_ok_value(Path, Res)), + % Check for a non-existing user + Domain = domain(), + Res2 = count_user_resources(<<"alien@", Domain/binary>>, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res2)). admin_get_user_resource(Config) -> escalus:fresh_story_with_config(Config, [{alice, 3}], fun admin_get_user_resource_story/4). @@ -343,7 +370,11 @@ admin_get_user_resource_story(Config, Alice, Alice2, _Alice3) -> ?assertEqual(escalus_client:resource(Alice2), get_ok_value(Path, Res)), % Provide a wrong resource number Res2 = get_user_resource(JID, 4, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"Wrong resource number">>)). + ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"Wrong resource number">>)), + % Check for a non-existing user + Domain = domain(), + Res3 = get_user_resource(<<"alien@", Domain/binary>>, 1, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res3)). admin_count_users_with_status(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], @@ -363,7 +394,10 @@ admin_count_users_with_status_story(Config, Alice, AliceB) -> assert_count_users_with_status(1, unprep(domain_helper:domain()), AwayStatus, Config), % Count users with dnd status globally escalus_client:send(AliceB, DndPresence), - assert_count_users_with_status(1, null, DndStatus, Config). + assert_count_users_with_status(1, null, DndStatus, Config), + % Count users with dnd status for a non-existing domain + Res = count_users_with_status(<<"nonexisting">>, DndStatus, Config), + ?assertEqual(<<"Domain not found">>, get_err_msg(Res)). admin_list_users_with_status(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], @@ -385,29 +419,51 @@ admin_list_users_with_status_story(Config, Alice, AliceB) -> assert_list_users_with_status([AliceJID], unprep(domain()), AwayStatus, Config), % List users with dnd status globally escalus_client:send(AliceB, DndPresence), - assert_list_users_with_status([AliceBJID], null, DndStatus, Config). + assert_list_users_with_status([AliceBJID], null, DndStatus, Config), + % List users with dnd status for a non-existing domain + Res = count_users_with_status(<<"nonexisting">>, DndStatus, Config), + ?assertEqual(<<"Domain not found">>, get_err_msg(Res)). -admin_kick_session(Config) -> - escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_kick_session_story/3). +admin_kick_user_session(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_kick_user_session_story/3). -admin_kick_session_story(Config, Alice1, Alice2) -> +admin_kick_user_session_story(Config, Alice1, Alice2) -> JIDA1 = escalus_client:full_jid(Alice1), JIDA2 = escalus_client:full_jid(Alice2), Reason = <<"Test kick">>, - Path = [data, session, kickUser, message], - Res = kick_user(JIDA1, Reason, Config), + Path = [data, session, kickUserSession, message], + Res = kick_user_session(JIDA1, Reason, Config), % Kick an active session ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res), <<"kicked">>)), - ?assertEqual(JIDA1, get_ok_value([data, session, kickUser, jid], Res)), + ?assertEqual(JIDA1, get_ok_value([data, session, kickUserSession, jid], Res)), % Try to kick an offline session - Res2 = kick_user(JIDA1, Reason, Config), + Res2 = kick_user_session(JIDA1, Reason, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"No active session">>)), % Try to kick a session with JID without a resource - Res3 = kick_user(escalus_client:short_jid(Alice2), Reason, Config), - ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"No active session">>)), - % Kick another active session - Res4 = kick_user(JIDA2, Reason, Config), - ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res4), <<"kicked">>)). + Res3 = kick_user_session(escalus_client:short_jid(Alice2), Reason, Config), + ?assertNotEqual(nomatch, binary:match(get_coercion_err_msg(Res3), <<"jid_without_resource">>)), + % Kick active session without reason text + Res4 = kick_user_session(JIDA2, null, Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res4), <<"kicked">>)), + % Kick a non-existing user + Domain = domain(), + Res5 = kick_user_session(<<"alien@", Domain/binary, "/mobile">>, null, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res5)). + +admin_kick_user(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_kick_user_story/3). + +admin_kick_user_story(Config, Alice1, _Alice2) -> + AliceJID = escalus_client:short_jid(Alice1), + Reason = <<"Test kick">>, + Res1 = kick_user(AliceJID, Reason, Config), + Res2 = get_ok_value([data, session, kickUser], Res1), + ?assertEqual(2, length(Res2)), + ?assertEqual([true, true], [Kicked || #{<<"kicked">> := Kicked} <- Res2]), + % Kick a non-existing user + Domain = domain(), + Res5 = kick_user(<<"alien@", Domain/binary>>, null, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res5)). admin_set_presence(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_set_presence_story/2). @@ -422,10 +478,14 @@ admin_set_presence_story(Config, Alice) -> % Send short JID Res = set_presence(ShortJID, Type, Show, Status, Priority, Config), ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"resource is empty">>)), + % Non-existing user + Domain = domain(), + Res2 = set_presence(<<"alien@", Domain/binary, "/mobile">>, Type, Show, Status, Priority, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Res2)), % Send full JID Path = [data, session, setPresence, message], - Res2 = set_presence(JID, Type, Show, Status, Priority, Config), - ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res2), <<"set successfully">>)), + Res3 = set_presence(JID, Type, Show, Status, Priority, Config), + ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res3), <<"set successfully">>)), Presence = escalus:wait_for_stanza(Alice), ?assertNot(escalus_pred:is_presence_with_show(<<"online">>, Presence)), escalus:assert(is_presence_with_type, [<<"available">>], Presence), @@ -500,6 +560,10 @@ count_users_with_status(Domain, Status, Config) -> Vars = #{<<"domain">> => Domain, <<"status">> => Status}, execute_command(<<"session">>, <<"countUsersWithStatus">>, Vars, Config). +kick_user_session(JID, Reason, Config) -> + Vars = #{<<"user">> => JID, <<"reason">> => Reason}, + execute_command(<<"session">>, <<"kickUserSession">>, Vars, Config). + kick_user(JID, Reason, Config) -> Vars = #{<<"user">> => JID, <<"reason">> => Reason}, execute_command(<<"session">>, <<"kickUser">>, Vars, Config). diff --git a/big_tests/tests/graphql_stanza_SUITE.erl b/big_tests/tests/graphql_stanza_SUITE.erl index 203ee5ca1c6..5b66396b8ec 100644 --- a/big_tests/tests/graphql_stanza_SUITE.erl +++ b/big_tests/tests/graphql_stanza_SUITE.erl @@ -392,7 +392,7 @@ user_send_stanza_with_spoofed_from_story(Config, Alice, Bob) -> admin_send_unparsable_stanza(Config) -> Res = send_stanza(<<">, Config), - ?assertEqual(<<"Input coercion failed for type Stanza with value <<\">. " + ?assertEqual(<<"Input coercion failed for type XmlElement with value <<\">. " "The reason it failed is: \"expected >\"">>, get_coercion_err_msg(Res)). diff --git a/big_tests/tests/graphql_vcard_SUITE.erl b/big_tests/tests/graphql_vcard_SUITE.erl index a1afbdf33d6..be035249aed 100644 --- a/big_tests/tests/graphql_vcard_SUITE.erl +++ b/big_tests/tests/graphql_vcard_SUITE.erl @@ -6,6 +6,7 @@ -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, get_ok_value/2, skip_null_fields/1, get_err_msg/1, get_unauthorized/1, get_not_loaded/1, get_err_code/1]). +-import(domain_helper, [domain/0]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -200,7 +201,10 @@ user_get_others_vcard_no_user(Config) -> user_get_others_vcard_no_user(Config, Alice) -> Result = user_get_vcard(Alice, <<"eddie@otherhost">>, Config), - ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). + ?assertEqual(<<"User's domain does not exist">>, get_err_msg(Result)), + Domain = domain(), + Result2 = user_get_vcard(Alice, <<"eddie@", Domain/binary>>, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Result2)). % User VCard not configured test cases @@ -279,7 +283,10 @@ admin_set_vcard(Config, Alice, _Bob) -> admin_set_vcard_no_host(Config) -> Vcard = complete_vcard_input(), Result = set_vcard(Vcard, <<"eddie@otherhost">>, Config), - ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). + ?assertEqual(<<"User's domain does not exist">>, get_err_msg(Result)), + Domain = domain(), + Result2 = set_vcard(Vcard, <<"eddie@", Domain/binary>>, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Result2)). admin_get_vcard(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], @@ -310,7 +317,10 @@ admin_get_vcard_no_vcard(Config, Alice) -> admin_get_vcard_no_host(Config) -> Result = get_vcard(<<"eddie@otherhost">>, Config), - ?assertEqual(<<"Host does not exist">>, get_err_msg(Result)). + ?assertEqual(<<"User's domain does not exist">>, get_err_msg(Result)), + Domain = domain(), + Result2 = get_vcard(<<"eddie@", Domain/binary>>, Config), + ?assertEqual(<<"Given user does not exist">>, get_err_msg(Result2)). %% Admin VCard not configured test cases diff --git a/big_tests/tests/muc_light_http_api_SUITE.erl b/big_tests/tests/muc_light_http_api_SUITE.erl index 653af74e85e..55448bd1902 100644 --- a/big_tests/tests/muc_light_http_api_SUITE.erl +++ b/big_tests/tests/muc_light_http_api_SUITE.erl @@ -208,6 +208,8 @@ create_room_errors(Config) -> post(admin, Path, maps:remove(subject, Body)), {{<<"400">>, _}, <<"Invalid owner JID">>} = post(admin, Path, Body#{owner := <<"@invalid">>}), + {{<<"400">>, _}, <<"Given user does not exist">>} = + post(admin, Path, Body#{owner := <<"baduser@", (domain())/binary>>}), {{<<"404">>, _}, <<"MUC Light server not found">>} = post(admin, path([domain_helper:domain()]), Body). @@ -228,6 +230,8 @@ create_identifiable_room_errors(Config) -> putt(admin, Path, maps:remove(subject, Body)), {{<<"400">>, _}, <<"Invalid owner JID">>} = putt(admin, Path, Body#{owner := <<"@invalid">>}), + {{<<"400">>, _}, <<"Given user does not exist">>} = + post(admin, Path, Body#{owner := <<"baduser@", (domain())/binary>>}), {{<<"403">>, _}, <<"Room already exists">>} = putt(admin, Path, Body#{id := <<"ID1">>, name := <<"NameB">>}), {{<<"404">>, _}, <<"MUC Light server not found">>} = @@ -249,8 +253,12 @@ invite_to_room_errors(Config) -> rest_helper:post(admin, Path, Body#{recipient := <<"@invalid">>}), {{<<"400">>, _}, <<"Invalid sender JID">>} = rest_helper:post(admin, Path, Body#{sender := <<"@invalid">>}), + {{<<"400">>, _}, <<"Given user does not exist">>} = + rest_helper:post(admin, Path, Body#{sender := <<"baduser@", (domain())/binary>>}), {{<<"403">>, _}, <<"Given user does not occupy this room">>} = rest_helper:post(admin, Path, Body#{sender := BobJid, recipient := AliceJid}), + {{<<"404">>, _}, <<"Room not found">>} = + rest_helper:post(admin, path([muc_light_domain(), "badroom", "participants"]), Body), {{<<"404">>, _}, <<"MUC Light server not found">>} = rest_helper:post(admin, path([domain(), Name, "participants"]), Body). @@ -270,9 +278,11 @@ send_message_errors(Config) -> rest_helper:post(admin, Path, maps:remove(from, Body)), {{<<"400">>, _}, <<"Invalid sender JID">>} = rest_helper:post(admin, Path, Body#{from := <<"@invalid">>}), + {{<<"400">>, _}, <<"Given user does not exist">>} = + rest_helper:post(admin, Path, Body#{from := <<"baduser@", (domain())/binary>>}), {{<<"403">>, _}, <<"Given user does not occupy this room">>} = rest_helper:post(admin, Path, Body#{from := BobJid}), - {{<<"403">>, _}, <<"Given user does not occupy this room">>} = + {{<<"404">>, _}, <<"Room not found">>} = rest_helper:post(admin, path([muc_light_domain(), "badroom", "messages"]), Body), {{<<"404">>, _}, <<"MUC Light server not found">>} = rest_helper:post(admin, path([domain(), Name, "messages"]), Body). @@ -284,7 +294,7 @@ delete_room_errors(_Config) -> delete(admin, path([muc_light_domain()])), {{<<"404">>, _}, _} = delete(admin, path([muc_light_domain(), "badroom"])), - {{<<"404">>, _}, <<"Cannot remove not existing room">>} = + {{<<"404">>, _}, <<"Room not found">>} = delete(admin, path([muc_light_domain(), "badroom", "management"])), {{<<"404">>, _}, <<"MUC Light server not found">>} = delete(admin, path([domain(), "badroom", "management"])), diff --git a/big_tests/tests/rest_client_SUITE.erl b/big_tests/tests/rest_client_SUITE.erl index 6655ef838bb..c1bd5f06736 100644 --- a/big_tests/tests/rest_client_SUITE.erl +++ b/big_tests/tests/rest_client_SUITE.erl @@ -72,8 +72,7 @@ muc_test_cases() -> user_can_leave_a_room, invitation_to_room_is_forbidden_for_non_member, msg_is_sent_and_delivered_in_room, - sending_message_by_not_room_member_results_in_forbidden, - sending_invalid_message_to_room_results_in_bad_request, + room_message_sending_errors, messages_are_archived_in_room, chat_markers_are_archived_in_room, room_message_query_errors, @@ -490,12 +489,15 @@ user_removal_errors(Config) -> RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]), Path = <<"/rooms/", RoomID/binary, "/users/">>, BobJid = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), + AliceJid = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), Creds = credentials({alice, Alice}), {?BAD_REQUEST, <<"Invalid user JID: @invalid">>} = rest_helper:delete(client, <>, Creds), {?BAD_REQUEST, <<"Missing JID">>} = rest_helper:delete(client, Path, Creds), - {?NOT_FOUND, <<"Room does not exist">>} = + {?FORBIDDEN, <<"Given user does not have permission", _/binary>>} = + rest_helper:delete(client, <>, credentials({bob, Bob})), + {?NOT_FOUND, <<"Room not found">>} = rest_helper:delete(client, <<"/rooms/badroom/users/", BobJid/binary>>, Creds) end). @@ -527,17 +529,8 @@ msg_is_sent_and_delivered_in_room(Config) -> given_new_room_with_users_and_msgs({alice, Alice}, [{bob, Bob}]) end). -sending_message_by_not_room_member_results_in_forbidden(Config) -> +room_message_sending_errors(Config) -> escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> - Sender = {alice, Alice}, - RoomID = given_new_room_with_users(Sender, []), - Result = given_message_sent_to_room(RoomID, {bob, Bob}, #{body => <<"Hello, I'm not member">>}), - ?assertMatch({?FORBIDDEN, _}, Result) - - end). - -sending_invalid_message_to_room_results_in_bad_request(Config) -> - escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Sender = {alice, Alice}, RoomID = given_new_room_with_users(Sender, []), InvalidMarker = #{type => <<"bad">>, id => <<"some_id">>}, @@ -550,7 +543,11 @@ sending_invalid_message_to_room_results_in_bad_request(Config) -> {?BAD_REQUEST, <<"Invalid chat marker">>} = given_message_sent_to_room(RoomID, Sender, #{chat_marker => InvalidMarker}), {?BAD_REQUEST, <<"Invalid request body">>} = - given_message_sent_to_room(RoomID, Sender, <<"This is not JSON object">>) + given_message_sent_to_room(RoomID, Sender, <<"This is not JSON object">>), + {?FORBIDDEN, _} = + given_message_sent_to_room(RoomID, {bob, Bob}, #{body => <<"Hi">>}), + {?NOT_FOUND, <<"Room not found">>} = + given_message_sent_to_room(<<"badroom">>, Sender, #{body => <<"Hi">>}) end). messages_are_archived_in_room(Config) -> @@ -651,7 +648,9 @@ room_message_query_errors(Config) -> {?BAD_REQUEST, <<"Invalid value of 'before'">>} = rest_helper:gett(client, <>, Creds), {?BAD_REQUEST, <<"Invalid query string">>} = - rest_helper:gett(client, <>, Creds) + rest_helper:gett(client, <>, Creds), + {?NOT_FOUND, <<"Room not found">>} = + rest_helper:gett(client, <<"/rooms/badroom/messages">>, Creds) end). room_is_created_with_given_jid(Config) -> diff --git a/big_tests/tests/rest_helper.erl b/big_tests/tests/rest_helper.erl index 19bd18fd1fe..c98a09e87b6 100644 --- a/big_tests/tests/rest_helper.erl +++ b/big_tests/tests/rest_helper.erl @@ -267,7 +267,7 @@ inject_creds_to_opts(Handler, _Creds) -> % @doc Checks whether a config for a port is an admin, client or GraphQL one. % This is determined based on handler modules used. is_roles_config(#{module := ejabberd_cowboy, handlers := Handlers}, {graphql, SchemaEndpoint}) -> - lists:any(fun(#{module := mongoose_graphql_cowboy_handler, schema_endpoint := Ep}) -> + lists:any(fun(#{module := mongoose_graphql_handler, schema_endpoint := Ep}) -> SchemaEndpoint =:= Ep; (_) -> false diff --git a/doc/History.md b/doc/History.md index 30ab79c6b04..f9f069bf5c8 100644 --- a/doc/History.md +++ b/doc/History.md @@ -1,40 +1,64 @@ # MongooseIM history -## 2011: Fork of ejabberd +## 2022: GraphQL -MongooseIM's birthplace is a private Erlang Solutions' branch of ProcessOne's ejabberd - an XMPP/Jabber server written in Erlang. -What would later become a leading, highly customisable and scalable XMPP platform, originated in a strong idea - storing all internal strings in binaries instead of lists, among other significant improvements. +New GraphQL API allows to access MongooseIM using HTTP protocol to extract data and make changes in a flexible way. +The command-line interface (CLI) has been reworked to match the GraphQL functionality. +The configuration for the admin and the client API has been simplified. -The change was introduced in 0.1.0 proto-MongooseIM release and 3.0.0-alpha-X series of ejabberd. -This opened the door for achieving higher performance, lower latency and introducing other subsequent improvements building up to a platform we are truly proud of. +## 2020-2021: Friendly, cloud-native and dynamic -### Initial differences from the parent project +With the new configuration format, improved logging, and many more changes, MongooseIM has become more friendly for DevOps than ever before. +This goes hand in hand with the prioritisation of solutions that enable MongooseIM to be easily deployed to the cloud. -This project began its life as a fork of ejabberd v.2.1.8, and later underwent major cleanup, refactoring and optimization: +Whether in the cloud or on-premise, it is now possible to have a multi-tenant setup, powered by the new dynamic XMPP domains feature. +It means thousands of domains can be simply set up, managed, and removed dynamically, without a noticeable performance overhead. -* Bringing the project source tree to compliance with OTP project structure recommendations -* Swapping `autotools` for the Erlang community-standard build tool `rebar` -* Removal of obsolete and/or rarely used modules to reduce maintenance burden -* Reduction of runtime memory consumption by refactoring the code - to use Erlang's binary data type for string manipulation and storage - instead of operating on linked lists of characters -* Functional test coverage of the system according to corresponding - RFCs and XEPs +Releases: -## 2012-2015: Fully independent project growing fast +* [MongooseIM 5.1.0](https://github.com/esl/MongooseIM/releases/tag/5.1.0) in June 2022. +* [MongooseIM 5.0.0](https://github.com/esl/MongooseIM/releases/tag/5.0.0) in October 2021. +* [MongooseIM 4.2.0](https://github.com/esl/MongooseIM/releases/tag/4.2.0) in April 2021. +* [MongooseIM 4.1.0](https://github.com/esl/MongooseIM/releases/tag/4.1.0) in February 2021. +* [MongooseIM 4.0.0](https://github.com/esl/MongooseIM/releases/tag/4.0.0) in September 2020. +* [MongooseIM 3.7.0](https://github.com/esl/MongooseIM/releases/tag/3.7.0) in May 2020. +* [MongooseIM 3.6.0](https://github.com/esl/MongooseIM/releases/tag/3.6.0) in January 2020. -The next steps were achieving full OTP and `rebar` compliance, removal of obsolete and/or rarely used modules, reduction of the runtime memory consumption and functional test coverage. -[MongooseIM 1.0.0](https://github.com/esl/MongooseIM/releases/tag/1.0.0) was released on July 10th of 2012. +## 2018-2019: Global distribution ready -MongooseIM XMPP server fully independently went through multiple versions, following its own path with its own resources: [1.1.x](https://github.com/esl/MongooseIM/releases/tag/1.1.0) in 2012, [1.2.x](https://github.com/esl/MongooseIM/releases/tag/1.2.0) in 2013, [1.3.x](https://github.com/esl/MongooseIM/releases/tag/1.3.0), [1.4.x](https://github.com/esl/MongooseIM/releases/tag/1.4.0), [1.5.x](https://github.com/esl/MongooseIM/releases/tag/1.5.0) in 2014, and [1.6.x](https://github.com/esl/MongooseIM/releases/tag/1.6.0) in 2015. +* Focus on global scale architecture. +* Chat bot integrations. +* Optimizations for IoT clients. +* GDPR compliance. +* New XML parser [exml](https://github.com/esl/exml). -## 2016: Pivot to fullstack messaging platform +Releases: + +* [MongooseIM 3.5.0](https://github.com/esl/MongooseIM/releases/tag/3.5.0) in October 2019. +* [MongooseIM 3.4.0](https://github.com/esl/MongooseIM/releases/tag/3.4.0) in June 2019. +* [MongooseIM 3.3.0](https://github.com/esl/MongooseIM/releases/tag/3.3.0) in March 2019. +* [MongooseIM 3.2.0](https://github.com/esl/MongooseIM/releases/tag/3.2.0) in November 2018. +* [MongooseIM 3.1.1](https://github.com/esl/MongooseIM/releases/tag/3.1.1) in July 2018. +* [MongooseIM 3.0.1](https://github.com/esl/MongooseIM/releases/tag/3.0.1) in May 2018. +* [MongooseIM 2.2.2](https://github.com/esl/MongooseIM/releases/tag/2.2.2) in April 2018. +* [MongooseIM 2.1.1](https://github.com/esl/MongooseIM/releases/tag/2.1.1) in January 2018. + +## 2017: Platform expansion and strengthening + +[MongooseIM 2.1.0](https://github.com/esl/MongooseIM/releases/tag/2.1.0) in October 2017. -MongooseIM Platform appeared in 2016, with the release of [MongooseIM XMPP server 2.0.0](https://github.com/esl/MongooseIM/releases/tag/2.0.0). +New components were added to the MongooseIM platform: -The MongooseIM platform components were: +* [MongoosePush](https://github.com/esl/mongoosepush), push notifications server +* [MongooseICE](https://github.com/esl/MongooseICE), ICE server to help with voice calls functionality +* [Mangosta iOS](https://github.com/esl/mangosta-ios), demo XMPP client application for iOS +* [Mangosta Android](https://github.com/esl/mangosta-android), demo XMPP client application for Android -* MongooseIM XMPP server, featuring a unique REST API for client developers and MUC light +## 2016: Pivot to fullstack messaging platform + +MongooseIM Platform was created, that included a list of components: + +* [MongooseIM XMPP server 2.0.0](https://github.com/esl/MongooseIM/releases/tag/2.0.0), featuring a unique REST API for client developers and MUC light * [WombatOAM](https://www.erlang-solutions.com/capabilities/wombatoam/), for monitoring and operations * [escalus](https://github.com/esl/escalus), an Erlang XMPP client for test automation * [amoc](https://github.com/esl/amoc), for load generation @@ -43,27 +67,31 @@ The MongooseIM platform components were: * [Retrofit](https://square.github.io/retrofit/) by Square for Android in Java (third party) * [Jayme](https://github.com/inaka/Jayme) by Inaka for iOS in Swift -## 2017: Platform expansion and strengthening - -We also introduced some MongooseIM platform components that are independent of the XMPP server. -So far the list includes: +## 2012-2015: Fully independent project growing fast -* [Mangosta iOS](https://github.com/esl/mangosta-ios) -* [Mangosta Android](https://github.com/esl/mangosta-android) -* [MongoosePush](https://github.com/esl/mongoosepush) -* [MongooseICE](https://github.com/esl/MongooseICE) +* Full OTP and `rebar` compliance. +* Removal of obsolete and/or rarely used modules. +* Reduction of the runtime memory consumption and functional test coverage. +* Added Message Archive Management support (XEP-0313). -## 2018-2019: Global distribution ready +Releases: -The next step on our journey with the MongooseIM platform was to enable building global scale architectures. -This was necessary to welcome the massive influx of users that come with a full stack IoT and chatbot solution. +* [MongooseIM 1.6.x](https://github.com/esl/MongooseIM/releases/tag/1.6.0) in October 2015. +* [MongooseIM 1.5.x](https://github.com/esl/MongooseIM/releases/tag/1.5.0) in December 2014. +* [MongooseIM 1.4.x](https://github.com/esl/MongooseIM/releases/tag/1.4.0) in May 2014. +* [MongooseIM 1.3.x](https://github.com/esl/MongooseIM/releases/tag/1.3.0) in January 2014. +* [MongooseIM 1.2.x](https://github.com/esl/MongooseIM/releases/tag/1.2.0) in May 2013. +* [MongooseIM 1.1.x](https://github.com/esl/MongooseIM/releases/tag/1.1.0) in December 2012. +* [MongooseIM 1.0.0](https://github.com/esl/MongooseIM/releases/tag/1.0.0) in July 2012. -Erlang Solution's goal is to utilise XMPP features suited for chatbots, and build open standards for the completeness of our solution. +## 2011: Fork of ejabberd -## 2020-2021: Friendly, cloud-native and dynamic +This project began its life as a fork of ejabberd v.2.1.8. -With the new configuration format, improved logging, and many more changes, MongooseIM has become more friendly for DevOps than ever before. -This goes hand in hand with the prioritisation of solutions that enable MongooseIM to be easily deployed to the cloud. +Version 0.1.0 included: -Whether in the cloud or on-premise, it is now possible to have a multi-tenant setup, powered by the new dynamic XMPP domains feature. -It means thousands of domains can be simply set up, managed, and removed dynamically, without a noticeable performance overhead. +* Replaced strings with binaries to significantly reduce memory consumption. +* Refactored directory structure of the project to be OTP complient. +* Replaced `autotools` with the `rebar` build tool. +* Removed obsolete and/or rarely used modules to reduce maintenance burden. +* Added functional tests based on RFCs and XEPs. diff --git a/doc/configuration/listen.md b/doc/configuration/listen.md index 4b9552a8ad9..4477b9f8815 100644 --- a/doc/configuration/listen.md +++ b/doc/configuration/listen.md @@ -423,7 +423,7 @@ There are the following options for each of the HTTP listeners: * `mod_bosh` - for [BOSH](https://xmpp.org/extensions/xep-0124.html) connections, * `mod_websockets` - for [WebSocket](https://tools.ietf.org/html/rfc6455) connections, - * `mongoose_graphql_cowboy_handler` - for GraphQL API, + * `mongoose_graphql_handler` - for GraphQL API, * `mongoose_admin_api`, `mongoose_client_api` - for REST API. These types are described below in more detail. @@ -506,12 +506,12 @@ Maximum allowed incoming stanza size. This subsection enables external component connections over WebSockets. See the [service](#xmpp-components-listenservice) listener section for details. -### Handler types: GraphQL API - `mongoose_graphql_cowboy_handler` +### Handler types: GraphQL API - `mongoose_graphql_handler` For more information about the API, see the [Admin interface](../graphql-api/Admin-GraphQL.md) and [User interface](../graphql-api/User-GraphQL.md) documentation. The following options are supported for this handler: -#### `listen.http.handlers.mongoose_graphql_cowboy_handler.schema_endpoint` +#### `listen.http.handlers.mongoose_graphql_handler.schema_endpoint` * **Syntax:** string, one of `"admin"`, `"domain_admin"`, `"user"` * **Default:** no default, this option is mandatory * **Example:** `schema_endpoint = "admin"` @@ -522,19 +522,19 @@ Specifies the schema endpoint: * `domain_admin` - Endpoint with the admin commands. A domain admin has permission to execute only commands with the owned domain. See the recommended configuration - [Example 3](#example-3-domain-admin-graphql-api). * `user` - Endpoint with the user commands. Used to manage the authorized user. See the recommended configuration - [Example 4](#example-4-user-graphql-api). -#### `listen.http.handlers.mongoose_graphql_cowboy_handler.username` - only for `admin` +#### `listen.http.handlers.mongoose_graphql_handler.username` - only for `admin` * **Syntax:** string * **Default:** not set * **Example:** `username = "admin"` When set, enables authentication for the admin API, otherwise it is disabled. Requires setting `password`. -#### `listen.http.handlers.mongoose_graphql_cowboy_handler.password` - only for `admin` +#### `listen.http.handlers.mongoose_graphql_handler.password` - only for `admin` * **Syntax:** string * **Default:** not set * **Example:** `password = "secret"` -#### `listen.http.handlers.mongoose_graphql_cowboy_handler.allowed_categories` +#### `listen.http.handlers.mongoose_graphql_handler.allowed_categories` * **Syntax:** non-empty array of strings. Allowed values: `"checkAuth", "account", "domain", "last", "muc", "muc_light", "session", "stanza", "roster", "vcard", "private", "metric", "stat", "gdpr", "mnesia", "server", "inbox", "http_upload", "offline", "token"` * **Default:** all GraphQL categories enabled * **Example:** `allowed_categories = ["domain", "last"]` @@ -666,7 +666,7 @@ GraphQL API for administration, the listener is bound to 127.0.0.1 for increased transport.num_acceptors = 5 transport.max_connections = 10 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "localhost" path = "/api/graphql" schema_endpoint = "admin" @@ -686,7 +686,7 @@ GraphQL API for the domain admin. transport.num_acceptors = 10 transport.max_connections = 1024 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "_" path = "/api/graphql" schema_endpoint = "domain_admin" @@ -703,7 +703,7 @@ GraphQL API for the user. transport.num_acceptors = 10 transport.max_connections = 1024 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "_" path = "/api/graphql" schema_endpoint = "user" diff --git a/doc/graphql-api/Admin-GraphQL.md b/doc/graphql-api/Admin-GraphQL.md index 17d178fbc9a..5226e6625f2 100644 --- a/doc/graphql-api/Admin-GraphQL.md +++ b/doc/graphql-api/Admin-GraphQL.md @@ -2,7 +2,7 @@ The new GraphQL admin API contains all the commands available through the REST API, and the vast majority of the CLI (`mongooseimctl`) commands. Only commands that wouldn't have worked well with GraphQL style have been omitted. -We can distinguish two levels of the administration. A global admin (has access to all commands), and the admin per domain (has access only to the own domain). Each of them is handled by a different endpoint. Please see the configuration [Listen](../../configuration/listen/#handler-types-graphql-api-mongoose_graphql_cowboy_handler) section for more details. +We can distinguish two levels of the administration. A global admin (has access to all commands), and the admin per domain (has access only to the own domain). Each of them is handled by a different endpoint. Please see the configuration [Listen](../../configuration/listen/#handler-types-graphql-api-mongoose_graphql_handler) section for more details. There is only one schema for both admin types. Admin per domain simply has no permissions to execute global commands or commands with not owned domain. The API documentation clearly says which commands are global. @@ -27,7 +27,7 @@ with the word `Basic` followed by a space and a base64-encoded string. ### Global admin endpoint -The authentication for global admin is optional because this endpoint shouldn't be exposed outside. The credentials set in the handler section in the config enables the authentication. Please see the [GraphQL handler](../configuration/listen.md#handler-types-graphql-api-mongoose_graphql_cowboy_handler) section for more details. +The authentication for global admin is optional because this endpoint shouldn't be exposed outside. The credentials set in the handler section in the config enables the authentication. Please see the [GraphQL handler](../configuration/listen.md#handler-types-graphql-api-mongoose_graphql_handler) section for more details. The base64-encoded string should have the form `LOGIN:PASSWORD`, where: diff --git a/doc/modules/mod_auth_token.md b/doc/modules/mod_auth_token.md index 0715c66b051..d38983b9716 100644 --- a/doc/modules/mod_auth_token.md +++ b/doc/modules/mod_auth_token.md @@ -154,7 +154,7 @@ Refresh tokens issued by the server can be used to: An administrator may *revoke* a refresh token: ```sh -mongooseimctl revoke_token owner@xmpphost +mongooseimctl token revokeToken --user owner@xmpphost ``` A client can no longer use a revoked token either for authentication or requesting new access tokens. diff --git a/doc/modules/mod_inbox.md b/doc/modules/mod_inbox.md index 1ad70000e46..4644d35dc14 100644 --- a/doc/modules/mod_inbox.md +++ b/doc/modules/mod_inbox.md @@ -48,6 +48,14 @@ How old entries in the bin can be before the automatic bin cleaner collects them How often the automatic garbage collection runs over the bin. +#### `modules.mod_inbox.delete_domain_limit` + +* **Syntax:** non-negative integer or the string `"infinity"` +* **Default:** `"infinity"` +* **Example:** `modules.mod_inbox.delete_domain_limit = 10000` + +Domain deletion can be an expensive operation, as it requires to delete potentially many thousands of records from the DB. By default, the delete operation deletes everything in a transaction, but it might be desired, to handle timeouts and table locks more gracefully, to delete the records in batches. This limit establishes the size of the batch. + ### `modules.mod_inbox.reset_markers` * **Syntax:** array of strings, out of `"displayed"`, `"received"`, `"acknowledged"` * **Default:** `["displayed"]` diff --git a/priv/graphql/schemas/admin/account.gql b/priv/graphql/schemas/admin/account.gql index d749c7f7c22..53443aea24c 100644 --- a/priv/graphql/schemas/admin/account.gql +++ b/priv/graphql/schemas/admin/account.gql @@ -35,6 +35,8 @@ type AccountAdminMutation @protected{ "Change the password of a user" changeUserPassword(user: JID!, newPassword: String!): UserPayload @protected(type: DOMAIN, args: ["user"]) + "Import users from a CSV file" + importUsers(filename: NonEmptyString!): ImportPayload } "Modify user payload" @@ -45,6 +47,24 @@ type UserPayload{ message: String! } +"Import users payload" +type ImportPayload{ + "Status" + status: String! + "Users created" + created: [JID!] + "Users that were already existing" + existing: [JID!] + "Users there were not allowed to be created" + notAllowed: [JID!] + "Users with invalid JIDs" + invalidJID: [String!] + "Users with empty passwords" + emptyPassword: [JID!] + "Invalid records" + invalidRecord: [String!] +} + "Check password correctness payload" type CheckPasswordPayload{ "Status of the password correctness" diff --git a/priv/graphql/schemas/admin/muc_light.gql b/priv/graphql/schemas/admin/muc_light.gql index aae9ae33599..fa7f45c8349 100644 --- a/priv/graphql/schemas/admin/muc_light.gql +++ b/priv/graphql/schemas/admin/muc_light.gql @@ -3,23 +3,24 @@ 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: RoomName, options: [RoomConfigDictEntryInput!]): Room - @protected(type: DOMAIN, args: ["owner"]) + createRoom(mucDomain: DomainName!, name: String, owner: JID!, subject: String, id: RoomName, + options: [RoomConfigDictEntryInput!]): Room + @protected(type: DOMAIN, args: ["mucDomain", "owner"]) @use(arg: "mucDomain") "Change configuration of a MUC Light room" - changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room + changeRoomConfiguration(room: BareJID!, owner: JID!, name: String, subject: String, + options: [RoomConfigDictEntryInput!]): Room @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Invite a user to a MUC Light room" - inviteUser(room: JID!, sender: JID!, recipient: JID!): String + inviteUser(room: BareJID!, sender: JID!, recipient: JID!): String @protected(type: DOMAIN, args: ["sender"]) @use(arg: "room") "Remove a MUC Light room" - deleteRoom(room: JID!): String + deleteRoom(room: BareJID!): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Kick a user from a MUC Light room" - kickUser(room: JID!, user: JID!): String + kickUser(room: BareJID!, user: JID!): String @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Send a message to a MUC Light room" - sendMessageToRoom(room: JID!, from: JID!, body: String!): String + sendMessageToRoom(room: BareJID!, from: JID!, body: String!): String @protected(type: DOMAIN, args: ["from"]) @use(arg: "room") "Set the user's list of blocked entities" setBlockingList(user: JID!, items: [BlockingInput!]!): String @@ -31,13 +32,13 @@ Allow admin to get information about Multi-User Chat Light rooms. """ type MUCLightAdminQuery @protected @use(modules: ["mod_muc_light"]){ "Get the MUC Light 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"]) "Get configuration of the MUC Light room" - getRoomConfig(room: JID!): Room + getRoomConfig(room: BareJID!): Room @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Get users list of given MUC Light room" - listRoomUsers(room: JID!): [RoomUser!] + listRoomUsers(room: BareJID!): [RoomUser!] @protected(type: DOMAIN, args: ["room"]) @use(arg: "room") "Get the list of MUC Light rooms that the user participates in" listUserRooms(user: JID!): [JID!] diff --git a/priv/graphql/schemas/admin/private.gql b/priv/graphql/schemas/admin/private.gql index 8413237d2d8..f22204c9c67 100644 --- a/priv/graphql/schemas/admin/private.gql +++ b/priv/graphql/schemas/admin/private.gql @@ -3,7 +3,7 @@ Allow admin to set the user's private data """ type PrivateAdminMutation @protected @use(modules: ["mod_private"]){ "Set the user's private data" - setPrivate(user: JID!, elementString: String!): String + setPrivate(user: JID!, elementString: XmlElement!): XmlElement @protected(type: DOMAIN, args: ["user"]) @use(arg: "user") } @@ -12,6 +12,6 @@ Allow admin to get the user's private data """ type PrivateAdminQuery @protected @use(modules: ["mod_private"]){ "Get the user's private data" - getPrivate(user: JID!, element: String!, nameSpace: String!): String + getPrivate(user: JID!, element: String!, nameSpace: NonEmptyString!): XmlElement @protected(type: DOMAIN, args: ["user"]) @use(arg: "user") } diff --git a/priv/graphql/schemas/admin/session.gql b/priv/graphql/schemas/admin/session.gql index 5782e4bd07e..c353ea4ba4c 100644 --- a/priv/graphql/schemas/admin/session.gql +++ b/priv/graphql/schemas/admin/session.gql @@ -15,13 +15,13 @@ 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): ResourceName + getUserResource(user: JID!, number: PosInt!): 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!] + listUsersWithStatus(domain: DomainName, status: NonEmptyString!): [UserStatus!] @protected(type: DOMAIN, args: ["domain"]) "Get the number of logged users with this status for a specified domain or globally" - countUsersWithStatus(domain: DomainName, status: String!): Int + countUsersWithStatus(domain: DomainName, status: NonEmptyString!): Int @protected(type: DOMAIN, args: ["domain"]) } @@ -29,8 +29,11 @@ type SessionAdminQuery @protected{ Allow admin to manage sessions. """ type SessionAdminMutation @protected{ - "Kick a user session. User JID should contain resource" - kickUser(user: JID!, reason: String!): SessionPayload + "Kick a user session. User JID must contain resource" + kickUserSession(user: FullJID!, reason: String): KickUserResult + @protected(type: DOMAIN, args: ["user"]) + "Kick user sessions" + kickUser(user: BareJID!, reason: String): [KickUserResult!] @protected(type: DOMAIN, args: ["user"]) "Set presence of a session. User JID should contain resource" setPresence(user: JID!, type: PresenceType!, show: PresenceShow, status: String, priority: Int): SessionPayload @@ -71,6 +74,18 @@ enum PresenceShow{ XA } +"Kick user session result" +type KickUserResult { + "Full JID with username, server and resource" + jid: JID! + "Result status" + kicked: Boolean! + "Result code (if failed)" + code: String + "Result message" + message: String! +} + "Modify session payload" type SessionPayload{ "Full JID with username, server and resource" diff --git a/priv/graphql/schemas/admin/stanza.gql b/priv/graphql/schemas/admin/stanza.gql index 0a2885eb706..ef3eb4e1654 100644 --- a/priv/graphql/schemas/admin/stanza.gql +++ b/priv/graphql/schemas/admin/stanza.gql @@ -16,7 +16,7 @@ type StanzaAdminMutation @protected{ sendMessageHeadLine(from: JID!, to: JID!, subject: String, body: String): SendStanzaPayload @protected(type: DOMAIN, args: ["from"]) "Send an arbitrary stanza. Only for global admin" - sendStanza(stanza: Stanza): SendStanzaPayload + sendStanza(stanza: XmlElement): SendStanzaPayload @protected(type: GLOBAL) } diff --git a/priv/graphql/schemas/global/muc_light.gql b/priv/graphql/schemas/global/muc_light.gql index 1852da4da95..7eba4796b3b 100644 --- a/priv/graphql/schemas/global/muc_light.gql +++ b/priv/graphql/schemas/global/muc_light.gql @@ -61,11 +61,11 @@ type RoomConfigDictEntry{ "Room data" type Room{ "Room's JId" - jid: JID! + jid: BareJID! "Name of the room" - name: String! + name: String "Subject of the room" - subject: String! + subject: String "List of participants" participants: [RoomUser!]! "Configuration options" diff --git a/priv/graphql/schemas/global/scalar_types.gql b/priv/graphql/schemas/global/scalar_types.gql index e84ddbb7338..4c316d891e3 100644 --- a/priv/graphql/schemas/global/scalar_types.gql +++ b/priv/graphql/schemas/global/scalar_types.gql @@ -1,7 +1,7 @@ "Date and time represented using **YYYY-MM-DDTHH:mm:ssZ** format" scalar DateTime -"Body of a data structure exchanged by XMPP entities in XML streams" -scalar Stanza @spectaql(options: [{ key: "example", value: "Hi!" }]) +"String containing the XML document" +scalar XmlElement @spectaql(options: [{ key: "example", value: "Hi!" }]) "Unique XMPP identifier in the form of **node@domain** or **node@domain/resource" scalar JID @spectaql(options: [{ key: "example", value: "alice@localhost" }]) "JID without a resource" diff --git a/priv/graphql/schemas/global/stanza.gql b/priv/graphql/schemas/global/stanza.gql index 22eb60c9c08..1f2cb870095 100644 --- a/priv/graphql/schemas/global/stanza.gql +++ b/priv/graphql/schemas/global/stanza.gql @@ -15,7 +15,7 @@ type StanzaMap{ "ID of the stanza" stanza_id: String "Stanza's data" - stanza: Stanza + stanza: XmlElement } "Send stanza payload" diff --git a/priv/graphql/schemas/user/muc_light.gql b/priv/graphql/schemas/user/muc_light.gql index 4d0c246fd61..1b1b66076f3 100644 --- a/priv/graphql/schemas/user/muc_light.gql +++ b/priv/graphql/schemas/user/muc_light.gql @@ -3,20 +3,25 @@ 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: RoomName, options: [RoomConfigDictEntryInput!]): Room + createRoom(mucDomain: DomainName!, name: String, subject: String, id: RoomName, options: [RoomConfigDictEntryInput!]): Room + @use(arg: "mucDomain") "Change configuration of a MUC Light room" - changeRoomConfiguration(room: JID!, name: String!, subject: String!, options: [RoomConfigDictEntryInput!]): Room @use(arg: "room") + changeRoomConfiguration(room: BareJID!, name: String, subject: String, options: [RoomConfigDictEntryInput!]): Room @use(arg: "room") "Invite a user to a MUC Light room" - inviteUser(room: JID!, recipient: JID!): String @use(arg: "room") + inviteUser(room: BareJID!, recipient: JID!): String + @use(arg: "room") "Remove a MUC Light room" - deleteRoom(room: JID!): String @use(arg: "room") + deleteRoom(room: BareJID!): String + @use(arg: "room") "Kick a user from a MUC Light room" - kickUser(room: JID!, user: JID): String @use(arg: "room") + kickUser(room: BareJID!, user: JID): String + @use(arg: "room") "Send a message to a MUC Light room" - sendMessageToRoom(room: JID!, body: String!): String @use(arg: "room") + sendMessageToRoom(room: BareJID!, body: String!): String + @use(arg: "room") "Set the user blocking list" - setBlockingList(items: [BlockingInput!]!): String @use + setBlockingList(items: [BlockingInput!]!): String + @use } """ @@ -24,13 +29,18 @@ Allow user to get information about Multi-User Chat Light rooms. """ type MUCLightUserQuery @protected @use(modules: ["mod_muc_light"]){ "Get the MUC Light 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"]) "Get configuration of the MUC Light room" - getRoomConfig(room: JID!): Room @use(arg: "room") + getRoomConfig(room: BareJID!): Room + @use(arg: "room") "Get users list of given MUC Light room" - listRoomUsers(room: JID!): [RoomUser!] @use(arg: "room") + listRoomUsers(room: BareJID!): [RoomUser!] + @use(arg: "room") "Get the list of MUC Light rooms that the user participates in" - listRooms: [JID!] @use + listRooms: [JID!] + @use "Get the user blocking list" - getBlockingList: [BlockingItem!] @use + getBlockingList: [BlockingItem!] + @use } diff --git a/priv/graphql/schemas/user/private.gql b/priv/graphql/schemas/user/private.gql index f8f2d591657..1548774d6d5 100644 --- a/priv/graphql/schemas/user/private.gql +++ b/priv/graphql/schemas/user/private.gql @@ -3,7 +3,7 @@ Allow user to set own private """ type PrivateUserMutation @protected @use(modules: ["mod_private"]){ "Set user's own private" - setPrivate(elementString: String!): String @use +setPrivate(elementString: XmlElement!): XmlElement @use } """ @@ -11,5 +11,5 @@ Allow user to get own private """ type PrivateUserQuery @protected @use(modules: ["mod_private"]){ "Get user's own private" - getPrivate(element: String!, nameSpace: String!): String @use + getPrivate(element: String!, nameSpace: NonEmptyString!): XmlElement @use } diff --git a/priv/graphql/schemas/user/stanza.gql b/priv/graphql/schemas/user/stanza.gql index d2664e8cc60..9805b611540 100644 --- a/priv/graphql/schemas/user/stanza.gql +++ b/priv/graphql/schemas/user/stanza.gql @@ -16,7 +16,7 @@ type StanzaUserMutation @protected{ "Send a headline message to a local or remote bare or full JID" sendMessageHeadLine(from: JID, to: JID!, subject: String, body: String): SendStanzaPayload "Send an arbitrary stanza" - sendStanza(stanza: Stanza): SendStanzaPayload + sendStanza(stanza: XmlElement): SendStanzaPayload } type StanzaUserSubscription @protected{ diff --git a/rebar.lock b/rebar.lock index 202a2758be1..519b1b1f6f2 100644 --- a/rebar.lock +++ b/rebar.lock @@ -49,7 +49,7 @@ {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, {<<"graphql">>, {git,"https://github.com/esl/graphql-erlang.git", - {ref,"7bca478ed484b0859f986f3c74f1013c872265bb"}}, + {ref,"b9a68c8f2249eff11ce477121304a3649ab623d1"}}, 0}, {<<"gun">>,{pkg,<<"gun">>,<<"1.3.3">>},0}, {<<"hackney">>,{pkg,<<"hackney">>,<<"1.13.0">>},1}, diff --git a/rel/files/mongooseim b/rel/files/mongooseim index 2d81cfc20b7..a3da0ec141e 100755 --- a/rel/files/mongooseim +++ b/rel/files/mongooseim @@ -37,7 +37,7 @@ mkdir -p "$RUNNER_LOG_DIR" export ERL_CRASH_DUMP="${RUNNER_LOG_DIR}/erl_crash.dump" # Extract the target node name from node.args -NAME_ARG=`egrep -e '^-s?name' "$RUNNER_ETC_DIR"/vm.args` +NAME_ARG=`grep -E '^-s?name' "$RUNNER_ETC_DIR"/vm.args` if [ -z "$NAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 diff --git a/rel/files/mongooseim.toml b/rel/files/mongooseim.toml index 2bea73b1edd..26db996f7c0 100644 --- a/rel/files/mongooseim.toml +++ b/rel/files/mongooseim.toml @@ -86,7 +86,7 @@ transport.num_acceptors = 10 transport.max_connections = 1024 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "localhost" path = "/api/graphql" schema_endpoint = "admin" @@ -100,7 +100,7 @@ transport.num_acceptors = 10 transport.max_connections = 1024 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "_" path = "/api/graphql" schema_endpoint = "domain_admin" @@ -112,7 +112,7 @@ transport.num_acceptors = 10 transport.max_connections = 1024 - [[listen.http.handlers.mongoose_graphql_cowboy_handler]] + [[listen.http.handlers.mongoose_graphql_handler]] host = "_" path = "/api/graphql" schema_endpoint = "user" diff --git a/rel/files/mongooseimctl b/rel/files/mongooseimctl index 9c75819f782..fdd1eb94153 100755 --- a/rel/files/mongooseimctl +++ b/rel/files/mongooseimctl @@ -35,13 +35,13 @@ SCRIPTS_DIR="$MIM_DIR"/scripts EJABBERD_STATUS_PATH="{{mongooseim_status_dir}}/status" export EJABBERD_STATUS_PATH="$EJABBERD_STATUS_PATH" -COOKIE_ARG=`grep -e '^-setcookie' "$EJABBERD_VMARGS_PATH"` +COOKIE_ARG=`grep '^-setcookie' "$EJABBERD_VMARGS_PATH"` if [ -z "$COOKIE_ARG" ]; then echo "vm.args needs to have a -setcookie parameter." exit 1 fi -NODENAME_ARG=`egrep -e '^-s?name' "$EJABBERD_VMARGS_PATH"` +NODENAME_ARG=`grep -E '^-s?name' "$EJABBERD_VMARGS_PATH"` if [ -z "$NODENAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 diff --git a/src/admin_extra/service_admin_extra_accounts.erl b/src/admin_extra/service_admin_extra_accounts.erl index 108aa35d114..8e0d875b43e 100644 --- a/src/admin_extra/service_admin_extra_accounts.erl +++ b/src/admin_extra/service_admin_extra_accounts.erl @@ -146,7 +146,7 @@ delete_old_users_for_domain(Domain, Days) -> Error end. --spec ban_account(jid:user(), jid:server(), binary() | string()) -> +-spec ban_account(jid:user(), jid:server(), binary()) -> mongoose_account_api:change_password_result(). ban_account(User, Host, ReasonText) -> mongoose_account_api:ban_account(User, Host, ReasonText). diff --git a/src/admin_extra/service_admin_extra_private.erl b/src/admin_extra/service_admin_extra_private.erl index 72a25869d13..3eb22a510fb 100644 --- a/src/admin_extra/service_admin_extra_private.erl +++ b/src/admin_extra/service_admin_extra_private.erl @@ -74,13 +74,23 @@ commands() -> private_get(Username, Host, Element, Ns) -> JID = jid:make(Username, Host, <<>>), case mod_private_api:private_get(JID, Element, Ns) of - {ok, String} -> String; + {ok, Xml} -> exml:to_list(Xml); Error -> Error end. -spec private_set(jid:user(), jid:server(), ElementString :: binary()) -> {Res, string()} when - Res :: ok | not_found | not_loaded | parse_error. + Res :: ok | not_found | parse_error. private_set(Username, Host, ElementString) -> - JID = jid:make(Username, Host, <<>>), - mod_private_api:private_set(JID, ElementString). + case exml:parse(ElementString) of + {error, Error} -> + String = io_lib:format("Error found parsing the element: '~ts' Error: ~ts", + [ElementString, Error]), + {parse_error, String}; + {ok, Xml} -> + JID = jid:make(Username, Host, <<>>), + case mod_private_api:private_set(JID, Xml) of + {ok, _} -> {ok, ""}; + Error -> Error + end + end. diff --git a/src/admin_extra/service_admin_extra_sessions.erl b/src/admin_extra/service_admin_extra_sessions.erl index 959fec65e85..b36fd5bdb10 100644 --- a/src/admin_extra/service_admin_extra_sessions.erl +++ b/src/admin_extra/service_admin_extra_sessions.erl @@ -28,10 +28,8 @@ -export([ commands/0, - num_resources/2, resource_num/3, - kick_session/2, kick_session/4, status_num/2, status_num/1, status_list/2, status_list/1, @@ -42,12 +40,13 @@ ]). -ignore_xref([ - commands/0, num_resources/2, resource_num/3, kick_session/2, kick_session/4, + commands/0, num_resources/2, resource_num/3, kick_session/4, status_num/2, status_num/1, status_list/2, status_list/1, connected_users_info/0, connected_users_info/1, set_presence/7, user_sessions_info/2 ]). +-include_lib("jid/include/jid.hrl"). -include("ejabberd_commands.hrl"). -type status() :: mongoose_session_api:status(). @@ -67,8 +66,16 @@ commands() -> {priority, integer}, {node, string}, {uptime, integer} - ]}} - }, + ]}}}, + + UserStatusDisplay = {list, + {userstatus, {tuple, + [{user, string}, + {host, string}, + {resource, string}, + {priority, integer}, + {status, string} + ]}}}, [ #ejabberd_commands{name = num_resources, tags = [session], @@ -86,7 +93,7 @@ commands() -> module = ?MODULE, function = kick_session, args = [{user, binary}, {host, binary}, {resource, binary}, {reason, binary}], - result = {res, restuple}}, + result = {res, integer}}, #ejabberd_commands{name = status_num_host, tags = [session, stats], desc = "Number of logged users with this status in host", module = ?MODULE, function = status_num, @@ -101,28 +108,12 @@ commands() -> desc = "List of users logged in host with their statuses", module = ?MODULE, function = status_list, args = [{host, binary}, {status, binary}], - result = {users, {list, - {userstatus, {tuple, [ - {user, string}, - {host, string}, - {resource, string}, - {priority, integer}, - {status, string} - ]}} - }}}, + result = {users, UserStatusDisplay}}, #ejabberd_commands{name = status_list, tags = [session], desc = "List of logged users with this status", module = ?MODULE, function = status_list, args = [{status, binary}], - result = {users, {list, - {userstatus, {tuple, [ - {user, string}, - {host, string}, - {resource, string}, - {priority, integer}, - {status, string} - ]}} - }}}, + result = {users, UserStatusDisplay}}, #ejabberd_commands{name = connected_users_info, tags = [session], desc = "List all established sessions and their information", @@ -159,52 +150,80 @@ commands() -> -spec num_resources(jid:user(), jid:server()) -> non_neg_integer(). num_resources(User, Host) -> - mongoose_session_api:num_resources(User, Host). + JID = jid:make(User, Host, <<>>), + {ok, Value} = mongoose_session_api:num_resources(JID), + Value. -spec resource_num(jid:user(), jid:server(), integer()) -> mongoose_session_api:res_number_result(). resource_num(User, Host, Num) -> - mongoose_session_api:get_user_resource(User, Host, Num). - --spec kick_session(jid:jid(), binary()) -> mongoose_session_api:kick_session_result(). -kick_session(JID, ReasonText) -> - mongoose_session_api:kick_session(JID, ReasonText). + JID = jid:make(User, Host, <<>>), + mongoose_session_api:get_user_resource(JID, Num). -spec kick_session(jid:user(), jid:server(), jid:resource(), binary()) -> mongoose_session_api:kick_session_result(). kick_session(User, Server, Resource, ReasonText) -> - mongoose_session_api:kick_session(User, Server, Resource, ReasonText). + mongoose_session_api:kick_session(jid:make(User, Server, Resource), ReasonText). -spec status_num(jid:server(), status()) -> non_neg_integer(). status_num(Host, Status) -> - mongoose_session_api:num_status_users(Host, Status). + {ok, Value} = mongoose_session_api:num_status_users(Host, Status), + Value. -spec status_num(status()) -> non_neg_integer(). status_num(Status) -> - mongoose_session_api:num_status_users(Status). + {ok, Value} = mongoose_session_api:num_status_users(Status), + Value. --spec status_list(jid:server(), status()) -> [mongoose_session_api:status_user_info()]. +-spec status_list(jid:server(), status()) -> [tuple()]. status_list(Host, Status) -> - mongoose_session_api:list_status_users(Host, Status). + {ok, Sessions} = mongoose_session_api:list_status_users(Host, Status), + format_status_users(Sessions). --spec status_list(binary()) -> [mongoose_session_api:status_user_info()]. +-spec status_list(binary()) -> [tuple()]. status_list(Status) -> - mongoose_session_api:list_status_users(Status). + {ok, Sessions} = mongoose_session_api:list_status_users(Status), + format_status_users(Sessions). --spec connected_users_info() -> mongoose_session_api:list_sessions_result(). +-spec connected_users_info() -> [tuple()]. connected_users_info() -> - mongoose_session_api:list_sessions(). + {ok, Sessions} = mongoose_session_api:list_sessions(), + format_sessions(Sessions). --spec connected_users_info(jid:server()) -> mongoose_session_api:list_sessions_result(). +-spec connected_users_info(jid:server()) -> [tuple()]. connected_users_info(Host) -> - mongoose_session_api:list_sessions(Host). + {ok, Sessions} = mongoose_session_api:list_sessions(Host), + format_sessions(Sessions). -spec set_presence(jid:user(), jid:server(), jid:resource(), Type :: binary(), Show :: binary(), Status :: binary(), Prio :: binary()) -> ok. set_presence(User, Host, Resource, Type, Show, Status, Priority) -> - mongoose_session_api:set_presence(User, Host, Resource, Type, Show, Status, Priority), + JID = jid:make(User, Host, Resource), + mongoose_session_api:set_presence(JID, Type, Show, Status, Priority), ok. --spec user_sessions_info(jid:user(), jid:server()) -> [mongoose_session_api:session_info()]. +-spec user_sessions_info(jid:user(), jid:server()) -> [tuple()]. user_sessions_info(User, Host) -> - mongoose_session_api:list_user_sessions(User, Host). + JID = jid:make(User, Host, <<>>), + {ok, Sessions} = mongoose_session_api:list_user_sessions(JID), + format_sessions(Sessions). + +% Internal + +format_sessions(Sessions) -> + lists:map(fun(S) -> format_session(S) end, Sessions). + +format_session({USR, Conn, Address, Prio, Node, Uptime}) -> + {IP, Port} = from_address(Address), + {jid:to_binary(USR), atom_to_list(Conn), IP, Port, Prio, atom_to_list(Node), Uptime}. + +from_address(undefined) -> + {undefined, undefined}; +from_address({IP, Port}) -> + {inet:ntoa(IP), Port}. + +format_status_users(Sessions) -> + lists:map(fun(S) -> format_status_user(S) end, Sessions). + +format_status_user({#jid{luser = User, lserver = Server, lresource = Resource}, Prio, Status}) -> + {User, Server, Resource, Prio, Status}. diff --git a/src/c2s/mongoose_c2s_hooks.erl b/src/c2s/mongoose_c2s_hooks.erl index f6c46eb5622..49bfa85f692 100644 --- a/src/c2s/mongoose_c2s_hooks.erl +++ b/src/c2s/mongoose_c2s_hooks.erl @@ -41,10 +41,7 @@ Params :: params(), Result :: result(). user_send_packet(HostType, Acc, Params) -> - {From, To, El} = mongoose_acc:packet(Acc), - Args = [From, To, El], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - gen_hook:run_fold(user_send_packet, HostType, Acc, ParamsWithLegacyArgs). + gen_hook:run_fold(user_send_packet, HostType, Acc, Params). %% @doc Triggered when a user receives a packet through any routing mechanism. %% Examples of handlers can be metrics or carbons. @@ -53,12 +50,8 @@ user_send_packet(HostType, Acc, Params) -> Acc :: mongoose_acc:t(), Params :: params(), Result :: result(). -user_receive_packet(HostType, Acc, #{c2s_data := C2SData} = Params) -> - {From, To, El} = mongoose_acc:packet(Acc), - Jid = mongoose_c2s:get_jid(C2SData), - Args = [Jid, From, To, El], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - gen_hook:run_fold(user_receive_packet, HostType, Acc, ParamsWithLegacyArgs). +user_receive_packet(HostType, Acc, Params) -> + gen_hook:run_fold(user_receive_packet, HostType, Acc, Params). %% @doc Triggered when the user sends a stanza of type `message' -spec user_send_message(HostType, Acc, Params) -> Result when diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 752650d49b4..fcd5b16af55 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -26,8 +26,6 @@ -module(ejabberd_admin). -author('mickael.remond@process-one.net'). --define(REGISTER_WORKERS_NUM, 10). - -export([start/0, stop/0, %% Server %% Accounts @@ -38,8 +36,6 @@ delete_expired_messages/1, delete_old_messages/2, remove_from_cluster/1]). --export([registrator_proc/1]). - -ignore_xref([ backup_mnesia/1, delete_expired_messages/1, delete_old_messages/2, dump_mnesia/1, dump_table/2, @@ -104,9 +100,9 @@ commands() -> desc = "Import users from CSV file", module = ?MODULE, function = import_users, args = [{file, string}], - result = {users, {list, {res, {tuple, - [{result, atom}, - {user, binary}]}}}}}, + result = {summary, {list, {res, {tuple, + [{reason, binary}, + {users, {list, {user, binary}}}]}}}}}, #ejabberd_commands{name = delete_expired_messages, tags = [purge], desc = "Delete expired offline messages from database", module = ?MODULE, function = delete_expired_messages, @@ -245,90 +241,10 @@ unregister(User, Host) -> registered_users(Host) -> mongoose_account_api:list_users(Host). --spec import_users(Filename :: string()) -> [{ok, jid:user()} | - {exists, jid:user()} | - {not_allowed, jid:user()} | - {invalid_jid, jid:user()} | - {null_password, jid:user()} | - {bad_csv, binary()}]. -import_users(File) -> - {ok, CsvStream} = erl_csv:decode_new_s(File), - Workers = spawn_link_workers(), - WorkersQueue = queue:from_list(Workers), - do_import(CsvStream, WorkersQueue). - --spec do_import(erl_csv:csv_stream(), Workers :: queue:queue()) -> - [{ok, jid:user()} | - {exists, jid:user()} | - {not_allowed, jid:user()} | - {invalid_jid, jid:user()} | - {null_password, jid:user()} | - {bad_csv, binary()}]. -do_import(stream_end, WQueue) -> - Workers = queue:to_list(WQueue), - lists:flatmap(fun get_results_from_registrator/1, Workers); -do_import(Stream, WQueue) -> - {ok, Decoded, MoreStream} = erl_csv:decode_s(Stream), - WQueue1 = send_job_to_next_worker(Decoded, WQueue), - do_import(MoreStream, WQueue1). - --spec spawn_link_workers() -> [pid()]. -spawn_link_workers() -> - [ spawn_link(?MODULE, registrator_proc, [self()]) || - _ <- lists:seq(1, ?REGISTER_WORKERS_NUM)]. - --spec get_results_from_registrator(Worker :: pid()) -> - [{ok, jid:user()} | - {exists, jid:user()} | - {not_allowed, jid:user()} | - {invalid_jid, jid:user()} | - {null_password, jid:user()} | - {bad_csv, binary()}]. -get_results_from_registrator(Pid) -> - Pid ! get_result, - receive - {result, Result} -> Result - end. - -send_job_to_next_worker([], WQueue) -> - WQueue; -send_job_to_next_worker([Record], WQueue) -> - {{value, Worker}, Q1} = queue:out(WQueue), - Worker ! {proccess, Record}, - queue:in(Worker, Q1). - --spec registrator_proc(Manager :: pid()) -> ok. -registrator_proc(Manager) -> - registrator_proc(Manager, []). - --spec registrator_proc(Manager :: pid(), any()) -> ok. -registrator_proc(Manager, Result) -> - receive - {proccess, Data} -> - RegisterResult = do_register(Data), - registrator_proc(Manager, [RegisterResult | Result]); - get_result -> Manager ! {result, Result} - end, - ok. - --spec do_register([binary()]) -> {ok, jid:user()} | - {exists, jid:user()} | - {not_allowed, jid:user()} | - {invalid_jid, jid:user()} | - {null_password, jid:user()} | - {bad_csv, binary()}. -do_register([User, Host, Password]) -> - case ejabberd_auth:try_register(jid:make(User, Host, <<>>), Password) of - {error, Reason} -> {Reason, User}; - _ -> {ok, User} - end; - -do_register(List) -> - JoinBinary = fun(Elem, <<"">>) -> Elem; - (Elem, Acc) -> <> - end, - Info = lists:foldr(JoinBinary, <<"">>, List), - {bad_csv, Info}. +-spec import_users(file:filename()) -> [{binary(), jid:user() | binary()}]. +import_users(Filename) -> + {ok, Result} = mongoose_import_users:run(Filename), + maps:to_list(Result). %%% %%% Purge DB diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index cdb18b6f51d..a5d6b4cb17a 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -49,7 +49,6 @@ start(normal, _Args) -> mongoose_graphql:init(), translate:start(), ejabberd_node_id:start(), - ejabberd_ctl:init(), ejabberd_commands:init(), mongoose_graphql_commands:start(), mongoose_config:start(), diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index 20b1bce73f7..8f60294066b 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -47,16 +47,14 @@ -author('alexey@process-one.net'). -export([start/0, - init/0, process/1, - process2/2, - register_commands/3, - unregister_commands/3]). + process2/2]). --ignore_xref([process/1, process2/2, register_commands/3, start/0, unregister_commands/3]). +-ignore_xref([process/1, process2/2, start/0]). -include("ejabberd_ctl.hrl"). -include("ejabberd_commands.hrl"). +-include("mongoose_logger.hrl"). -type format() :: integer | string | binary | {list, format()}. -type format_type() :: binary() | string() | char(). @@ -105,37 +103,6 @@ start() -> halt(?STATUS_USAGE) end. - --spec init() -> atom() | ets:tid(). -init() -> - ets:new(ejabberd_ctl_cmds, [named_table, set, public]), - ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]). - - -%%----------------------------- -%% mongooseimctl Command managment -%%----------------------------- - --spec register_commands(CmdDescs :: [tuple()] | tuple(), - Module :: atom(), - Function :: atom()) -> 'ok'. -register_commands(CmdDescs, Module, Function) -> - ets:insert(ejabberd_ctl_cmds, CmdDescs), - ejabberd_hooks:add(ejabberd_ctl_process, global, Module, Function, 50), - ok. - - --spec unregister_commands(CmdDescs :: [any()], - Module :: atom(), - Function :: atom()) -> 'ok'. -unregister_commands(CmdDescs, Module, Function) -> - lists:foreach(fun(CmdDesc) -> - ets:delete_object(ejabberd_ctl_cmds, CmdDesc) - end, CmdDescs), - ejabberd_hooks:delete(ejabberd_ctl_process, global, Module, Function, 50), - ok. - - %%----------------------------- %% Process %%----------------------------- @@ -276,7 +243,7 @@ process2(Args, AccessCommands) -> %% @private process2(Args, Auth, AccessCommands) -> - case try_run_ctp(Args, Auth, AccessCommands) of + case try_call_command(Args, Auth, AccessCommands) of {String, wrong_command_arguments} when is_list(String) -> io:format(lists:flatten(["\n" | String]++["\n"])), [CommandString | _] = Args, @@ -299,36 +266,13 @@ get_accesscommands() -> %%----------------------------- %% Command calling %%----------------------------- - --spec try_run_ctp(Args :: [string()], - Auth :: ejabberd_commands:auth(), - AccessCommands :: ejabberd_commands:access_commands() - ) -> string() | integer() | {string(), integer()} | {string(), wrong_command_arguments}. -try_run_ctp(Args, Auth, AccessCommands) -> - try mongoose_hooks:ejabberd_ctl_process(false, Args) of - false when Args /= [] -> - try_call_command(Args, Auth, AccessCommands); - false -> - print_usage(), - {"", ?STATUS_USAGE}; - Status -> - {"", Status} - catch - exit:Why -> - print_usage(), - {io_lib:format("Error in mongooseimctl process: ~p", [Why]), ?STATUS_USAGE}; - Error:Why -> - %% In this case probably ejabberd is not started, so let's show Status - process(["status"]), - ?PRINT("~n", []), - {io_lib:format("Error in mongooseimctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE} - end. - - -spec try_call_command(Args :: [string()], Auth :: ejabberd_commands:auth(), AccessCommands :: ejabberd_commands:access_commands() ) -> string() | integer() | {string(), integer()} | {string(), wrong_command_arguments}. +try_call_command([], _, _) -> + print_usage(), + {"", ?STATUS_USAGE}; try_call_command(Args, Auth, AccessCommands) -> try call_command(Args, Auth, AccessCommands) of {error, command_unknown} -> @@ -426,7 +370,7 @@ format_error(Error) -> -spec format_result(In :: tuple() | atom() | integer() | string() | binary(), {_, 'atom'|'integer'|'string'|'binary'} ) -> string() | {string(), _}. -format_result({Atom, Error}, _) when Atom =/= ok -> +format_result({Atom, Error}, _) when is_atom(Atom), Atom =/= ok -> {io_lib:format("Error: ~ts", [format_error(Error)]), make_status(error)}; format_result(Atom, {_Name, atom}) -> io_lib:format("~p", [Atom]); diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl deleted file mode 100644 index 2260dd1fbfd..00000000000 --- a/src/ejabberd_hooks.erl +++ /dev/null @@ -1,115 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_hooks.erl -%%% Author : Alexey Shchepin -%%% Purpose : Manage hooks -%%% Created : 8 Aug 2004 by Alexey Shchepin -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2011 ProcessOne -%%% -%%% This program is free software; you can redistribute it and/or -%%% modify it under the terms of the GNU General Public License as -%%% published by the Free Software Foundation; either version 2 of the -%%% License, or (at your option) any later version. -%%% -%%% This program is distributed in the hope that it will be useful, -%%% but WITHOUT ANY WARRANTY; without even the implied warranty of -%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%%% General Public License for more details. -%%% -%%% You should have received a copy of the GNU General Public License -%%% along with this program; if not, write to the Free Software -%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_hooks). --author('alexey@process-one.net'). - -%% External exports --export([add/5, - delete/5, - add_args/2]). - --export([add/1, - delete/1]). - --export([gen_hook_fn_wrapper/3]). - --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"). - --type hook() :: {HookName :: atom(), - HostType :: mongooseim:host_type() | global, - Module :: module(), - Fn :: atom(), - Priority:: integer()}. - --export_type([hook/0]). -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- - -%% @doc Add a module and function to the given hook. -%% The integer Priority is used to sort the calls: -%% low numbers are executed before high numbers. --spec add(HookName :: atom(), - HostType :: mongooseim:host_type() | global, - Module :: module(), - Function :: atom(), - Priority :: integer()) -> ok. -add(HookName, HostType, Module, Function, Priority) -> - add_hook({HookName, HostType, Module, Function, Priority}). - --spec add([hook()]) -> ok. -add(Hooks) when is_list(Hooks) -> - [add_hook(Hook) || Hook <- Hooks], - ok. - -%% @doc Delete a module and function from this hook. -%% It is important to indicate exactly the same information as when the call was added. --spec delete(HookName :: atom(), - HostType :: mongooseim:host_type() | global, - Module :: module(), - Function :: atom(), - Priority :: integer()) -> ok. -delete(HookName, HostType, Module, Function, Priority) -> - delete_hook({HookName, HostType, Module, Function, Priority}). - --spec delete([hook()]) -> ok. -delete(Hooks) when is_list(Hooks) -> - [delete_hook(Hook) || Hook <- Hooks], - ok. - --spec add_args(HookParams :: map(), LegacyArgsList :: [term()]) -> - HookParamsWithArgs :: map(). -add_args(HookParams, LegacyArgsList) -> - HookParams#{args => LegacyArgsList}. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - --spec add_hook(hook()) -> ok. -add_hook({HookName, HostType, Module, Function, Priority}) when is_atom(Function) -> - gen_hook:add_handler(HookName, HostType, - fun ?MODULE:gen_hook_fn_wrapper/3, - #{module => Module, function => Function}, - Priority). - --spec delete_hook(hook()) -> ok. -delete_hook({HookName, HostType, Module, Function, Priority}) when is_atom(Function) -> - gen_hook:delete_handler(HookName, HostType, - fun ?MODULE:gen_hook_fn_wrapper/3, - #{module => Module, function => Function}, - Priority). - -gen_hook_fn_wrapper(Acc, #{args := Args}, #{module := Module, function := Function}) -> - case apply(Module, Function, [Acc | Args]) of - stop -> {stop, stopped}; - {stop, NewAcc} -> {stop, NewAcc}; - NewAcc -> {ok, NewAcc} - end. diff --git a/src/gen_hook.erl b/src/gen_hook.erl index 21ad1d58873..75880584e55 100644 --- a/src/gen_hook.erl +++ b/src/gen_hook.erl @@ -57,7 +57,11 @@ -type hook_list() :: hook_list(hook_fn()). -type hook_list(HookFn) :: [hook_tuple(HookFn)]. --export_type([hook_fn/0, hook_list/0, hook_list/1, hook_fn_ret/0, hook_fn_ret/1, extra/0]). +-export_type([hook_fn/0, + hook_list/0, + hook_fn_ret/0, + hook_fn_ret/1, + extra/0]). -record(hook_handler, {prio :: pos_integer(), hook_fn :: hook_fn(), diff --git a/src/global_distrib/mod_global_distrib_bounce.erl b/src/global_distrib/mod_global_distrib_bounce.erl index 7a65a4c89a1..35c668c69fd 100644 --- a/src/global_distrib/mod_global_distrib_bounce.erl +++ b/src/global_distrib/mod_global_distrib_bounce.erl @@ -53,7 +53,7 @@ start(HostType, _Opts) -> -spec stop(mongooseim:host_type()) -> any(). stop(HostType) -> ejabberd_sup:stop_child(?MODULE), - gen_hook:add_handlers(hooks(HostType)), + gen_hook:delete_handlers(hooks(HostType)), ets:delete(?MS_BY_TARGET), ets:delete(?MESSAGE_STORE). diff --git a/src/graphql/admin/mongoose_graphql_account_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_account_admin_mutation.erl index c658c035c21..2d4c06ed859 100644 --- a/src/graphql/admin/mongoose_graphql_account_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_account_admin_mutation.erl @@ -2,6 +2,7 @@ -behaviour(mongoose_graphql). -export([execute/4]). +-export([await_execution/4]). -ignore_xref([execute/4]). @@ -9,14 +10,19 @@ -import(mongoose_graphql_helper, [format_result/2, make_error/2]). -execute(_Ctx, _Obj, <<"registerUser">>, Args) -> +execute(_Ctx, account, <<"registerUser">>, Args) -> register_user(Args); -execute(_Ctx, _Obj, <<"removeUser">>, Args) -> +execute(_Ctx, account, <<"removeUser">>, Args) -> remove_user(Args); -execute(_Ctx, _Obj, <<"banUser">>, Args) -> +execute(_Ctx, account, <<"banUser">>, Args) -> ban_user(Args); -execute(_Ctx, _Obj, <<"changeUserPassword">>, Args) -> - change_user_password(Args). +execute(_Ctx, account, <<"changeUserPassword">>, Args) -> + change_user_password(Args); +execute(#{method := cli}, account, <<"importUsers">>, Args) -> + import_users(Args); +execute(#{method := http}, account, <<"importUsers">>, #{<<"filename">> := Filename}) -> + spawn(?MODULE, await_execution, [1000, mongoose_account_api, import_users, [Filename]]), + {ok, #{<<"status">> => <<"ImportUsers scheduled">>}}. % Internal @@ -49,6 +55,13 @@ change_user_password(#{<<"user">> := JID, <<"newPassword">> := Password}) -> Result = mongoose_account_api:change_password(JID, Password), format_user_payload(Result, JID). +-spec import_users(map()) -> {ok, map()} | {error, resolver_error()}. +import_users(#{<<"filename">> := Filename}) -> + case mongoose_account_api:import_users(Filename) of + {ok, _} = Result -> Result; + Error -> make_error(Error, #{filename => Filename}) + end. + -spec format_user_payload({atom(), string()}, jid:jid()) -> {ok, map()} | {error, resolver_error()}. format_user_payload(InResult, JID) -> case InResult of @@ -61,3 +74,7 @@ format_user_payload(InResult, JID) -> -spec make_user_payload(string(), jid:literal_jid()) -> map(). make_user_payload(Msg, JID) -> #{<<"message">> => iolist_to_binary(Msg), <<"jid">> => JID}. + +await_execution(Timeout, Module, Fun, Args) -> + timer:sleep(Timeout), + apply(Module, Fun, Args). diff --git a/src/graphql/admin/mongoose_graphql_metric_admin_query.erl b/src/graphql/admin/mongoose_graphql_metric_admin_query.erl index c2fdebaf76d..4ef97b7c63c 100644 --- a/src/graphql/admin/mongoose_graphql_metric_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_metric_admin_query.erl @@ -5,8 +5,6 @@ -ignore_xref([execute/4]). --include("mongoose_logger.hrl"). - execute(_Ctx, _Obj, <<"getMetrics">>, Args) -> Name = get_name(Args), mongoose_metrics_api:get_metrics(Name); diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl index 6a2cd7a36b4..87e68dfdb86 100644 --- a/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl @@ -8,9 +8,8 @@ -include("../mongoose_graphql_types.hrl"). -import(mongoose_graphql_helper, [make_error/2, format_result/2]). --import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1, - prepare_blocking_items/1, - null_to_default/2, options_to_map/1, get_not_loaded/1]). +-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1, prepare_blocking_items/1, + null_to_default/2, config_to_map/3, get_not_loaded/1]). execute(_Ctx, _Obj, <<"createRoom">>, Args) -> create_room(Args); @@ -31,19 +30,19 @@ execute(_Ctx, _Obj, <<"setBlockingList">>, Args) -> create_room(#{<<"id">> := RoomID, <<"mucDomain">> := MUCDomain, <<"name">> := RoomName, <<"owner">> := CreatorJID, <<"subject">> := Subject, <<"options">> := Options}) -> case mod_muc_light_api:create_room(MUCDomain, null_to_default(RoomID, <<>>), CreatorJID, - RoomName, Subject, options_to_map(Options)) of + config_to_map(RoomName, Subject, Options)) of {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{mucDomain => MUCDomain, id => RoomID, creator => CreatorJID}) + make_error(Err, #{mucDomain => MUCDomain, id => RoomID, + creator => jid:to_binary(CreatorJID)}) end. -spec change_room_config(map()) -> {ok, map()} | {error, resolver_error()}. change_room_config(#{<<"room">> := RoomJID, <<"name">> := RoomName, <<"owner">> := OwnerJID, <<"subject">> := Subject, <<"options">> := Options}) -> - OptMap = options_to_map(Options), - Config = OptMap#{<<"roomname">> => RoomName, <<"subject">> => Subject}, + Config = config_to_map(RoomName, Subject, Options), case mod_muc_light_api:change_room_config(RoomJID, OwnerJID, Config) of {ok, Room} -> {ok, make_room(Room)}; @@ -65,8 +64,8 @@ invite_user(#{<<"room">> := RoomJID, <<"sender">> := SenderJID, -spec kick_user(map()) -> {ok, binary()} | {error, resolver_error()}. kick_user(#{<<"room">> := RoomJID, <<"user">> := UserJID}) -> - Result = mod_muc_light_api:remove_user_from_room(RoomJID, UserJID, UserJID), - format_result(Result, #{user => UserJID}). + Result = mod_muc_light_api:change_affiliation(RoomJID, UserJID, UserJID, remove), + format_result(Result, #{user => jid:to_binary(UserJID)}). -spec send_msg_to_room(map()) -> {ok, binary()} | {error, resolver_error()}. send_msg_to_room(#{<<"room">> := RoomJID, <<"from">> := FromJID, <<"body">> := Message}) -> diff --git a/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl index 3d6c812c6c7..a4c35e7b0d2 100644 --- a/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl @@ -29,7 +29,7 @@ list_user_rooms(#{<<"user">> := UserJID}) -> {ok, Rooms} -> {ok, [{ok, R} || R <- Rooms]}; Err -> - make_error(Err, #{user => UserJID}) + make_error(Err, #{user => jid:to_binary(UserJID)}) end. -spec list_room_users(map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}. @@ -38,7 +38,7 @@ list_room_users(#{<<"room">> := RoomJID}) -> {ok, Affs} -> {ok, [make_ok_user(A) || A <- Affs]}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_room_config(map()) -> {ok, map()} | {error, resolver_error()}. @@ -47,7 +47,7 @@ get_room_config(#{<<"room">> := RoomJID}) -> {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_room_messages(map()) -> {ok, map()} | {error, resolver_error()}. @@ -60,7 +60,7 @@ get_room_messages(#{<<"room">> := RoomJID, <<"pageSize">> := PageSize, Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows), {ok, #{<<"stanzas">> => Maps, <<"limit">> => PageSize2}}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_blocking_list(map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}. @@ -70,5 +70,5 @@ get_blocking_list(#{<<"user">> := UserJID}) -> Items2 = lists:map(fun mongoose_graphql_muc_light_helper:blocking_item_to_map/1, Items), {ok, Items2}; Err -> - make_error(Err, #{user => UserJID}) + make_error(Err, #{user => jid:to_binary(UserJID)}) end. diff --git a/src/graphql/admin/mongoose_graphql_private_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_private_admin_mutation.erl index 1b85f7a4ed1..d9908197fba 100644 --- a/src/graphql/admin/mongoose_graphql_private_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_private_admin_mutation.erl @@ -13,5 +13,6 @@ execute(_Ctx, _Obj, <<"setPrivate">>, #{<<"user">> := CallerJID, <<"elementString">> := Element}) -> case mod_private_api:private_set(CallerJID, Element) of {ok, _} = Result -> Result; - Error -> make_error(Error, #{user => CallerJID, element => Element}) + Error -> make_error(Error, #{user => jid:to_binary(CallerJID), + element => exml:to_binary(Element)}) end. diff --git a/src/graphql/admin/mongoose_graphql_private_admin_query.erl b/src/graphql/admin/mongoose_graphql_private_admin_query.erl index e115138095d..02b181f9282 100644 --- a/src/graphql/admin/mongoose_graphql_private_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_private_admin_query.erl @@ -14,5 +14,6 @@ execute(_Ctx, _Obj, <<"getPrivate">>, #{<<"user">> := CallerJID, case mod_private_api:private_get(CallerJID, Element, SubElement) of {ok, _} = Result -> Result; Error -> - make_error(Error, #{user => CallerJID, element => Element, subElement => SubElement}) + make_error(Error, #{user => jid:to_binary(CallerJID), element => Element, + subElement => SubElement}) end. diff --git a/src/graphql/admin/mongoose_graphql_session_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_session_admin_mutation.erl index 64e13fa6e18..5c7610c7b3b 100644 --- a/src/graphql/admin/mongoose_graphql_session_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_session_admin_mutation.erl @@ -9,15 +9,26 @@ -import(mongoose_graphql_helper, [make_error/2]). +execute(_Ctx, _Obj, <<"kickUserSession">>, Args) -> + kick_user_session(Args); execute(_Ctx, _Obj, <<"kickUser">>, Args) -> kick_user(Args); execute(_Ctx, _Obj, <<"setPresence">>, Args) -> set_presence(Args). --spec kick_user(map()) -> {ok, map()} | {error, resolver_error()}. +-spec kick_user_session(map()) -> {ok, mongoose_session_api:kick_user_result()} | {error, resolver_error()}. +kick_user_session(#{<<"user">> := JID, <<"reason">> := KickReason}) -> + case mongoose_session_api:kick_session(JID, KickReason) of + {ok, Result} -> {ok, Result}; + Error -> make_error(Error, #{jid => jid:to_binary(JID), reason => KickReason}) + end. + +-spec kick_user(map()) -> {ok, [mongoose_session_api:kick_user_result()]} | {error, resolver_error()}. kick_user(#{<<"user">> := JID, <<"reason">> := KickReason}) -> - Result = mongoose_session_api:kick_session(JID, KickReason), - format_session_payload(Result, JID). + case mongoose_session_api:kick_sessions(JID, KickReason) of + {ok, Result} -> {ok, Result}; + Error -> make_error(Error, #{jid => jid:to_binary(JID), reason => KickReason}) + end. -spec set_presence(map()) -> {ok, map()} | {error, resolver_error()}. set_presence(#{<<"user">> := JID, <<"type">> := Type, @@ -43,7 +54,7 @@ format_session_payload(InResult, JID) -> {ok, Msg} -> {ok, make_session_payload(Msg, JID)}; Result -> - make_error(Result, #{jid => JID}) + make_error(Result, #{jid => jid:to_binary(JID)}) end. -spec make_session_payload(binary(), jid:jid()) -> map(). diff --git a/src/graphql/admin/mongoose_graphql_session_admin_query.erl b/src/graphql/admin/mongoose_graphql_session_admin_query.erl index 2dca3c5a18f..ba051cd7454 100644 --- a/src/graphql/admin/mongoose_graphql_session_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_session_admin_query.erl @@ -5,8 +5,10 @@ -ignore_xref([execute/4]). +-include("../mongoose_graphql_types.hrl"). + -import(mongoose_graphql_session_helper, [format_sessions/1, format_status_users/1]). --import(mongoose_graphql_helper, [format_result/2]). +-import(mongoose_graphql_helper, [format_result/2, make_error/2]). execute(_Ctx, _Obj, <<"listSessions">>, Args) -> list_sessions(Args); @@ -23,47 +25,57 @@ execute(_Ctx, _Obj, <<"listUsersWithStatus">>, Args) -> execute(_Ctx, _Obj, <<"countUsersWithStatus">>, Args) -> count_users_with_status(Args). --spec list_sessions(map()) -> {ok, mongoose_graphql_session_helper:session_list()}. +-spec list_sessions(map()) -> {ok, mongoose_graphql_session_helper:session_list()} | {error, resolver_error()}. list_sessions(#{<<"domain">> := null}) -> - Sessions = mongoose_session_api:list_sessions(), + {ok, Sessions} = mongoose_session_api:list_sessions(), {ok, format_sessions(Sessions)}; list_sessions(#{<<"domain">> := Domain}) -> - Sessions = mongoose_session_api:list_sessions(Domain), - {ok, format_sessions(Sessions)}. + case mongoose_session_api:list_sessions(Domain) of + {ok, Sessions} -> + {ok, format_sessions(Sessions)}; + Error -> + make_error(Error, #{domain => Domain}) + end. --spec count_sessions(map()) -> {ok, non_neg_integer()}. +-spec count_sessions(map()) -> {ok, non_neg_integer()} | {error, resolver_error()}. count_sessions(#{<<"domain">> := null}) -> - {ok, mongoose_session_api:count_sessions()}; + mongoose_session_api:count_sessions(); count_sessions(#{<<"domain">> := Domain}) -> - {ok, mongoose_session_api:count_sessions(Domain)}. + format_result(mongoose_session_api:count_sessions(Domain), #{domain => Domain}). -spec list_user_sessions(map()) -> {ok, mongoose_graphql_session_helper:session_list()}. list_user_sessions(#{<<"user">> := JID}) -> - Sessions = mongoose_session_api:list_user_sessions(JID), - {ok, format_sessions(Sessions)}. + case mongoose_session_api:list_user_sessions(JID) of + {ok, Sessions} -> + {ok, format_sessions(Sessions)}; + Error -> + make_error(Error, #{user => jid:to_binary(JID)}) + end. --spec count_user_resources(map()) -> {ok, non_neg_integer()}. +-spec count_user_resources(map()) -> {ok, non_neg_integer()} | {error, resolver_error()}. count_user_resources(#{<<"user">> := JID}) -> - Number = mongoose_session_api:num_resources(JID), - {ok, Number}. + format_result(mongoose_session_api:num_resources(JID), #{jid => jid:to_binary(JID)}). --spec get_user_resource(map()) -> {ok, jid:lresource()}. +-spec get_user_resource(map()) -> {ok, jid:lresource()} | {error, resolver_error()}. get_user_resource(#{<<"user">> := JID, <<"number">> := ResNumber}) -> Result = mongoose_session_api:get_user_resource(JID, ResNumber), - format_result(Result, #{number => ResNumber}). + format_result(Result, #{user => jid:to_binary(JID), number => ResNumber}). --spec list_users_with_status(map()) -> {ok, mongoose_graphql_session_helper:status_user_list()}. +-spec list_users_with_status(map()) -> {ok, mongoose_graphql_session_helper:status_user_list()} + | {error, resolver_error()}. list_users_with_status(#{<<"domain">> := null, <<"status">> := Status}) -> - StatusUsers = mongoose_session_api:list_status_users(Status), + {ok, StatusUsers} = mongoose_session_api:list_status_users(Status), {ok, format_status_users(StatusUsers)}; list_users_with_status(#{<<"domain">> := Domain, <<"status">> := Status}) -> - StatusUsers = mongoose_session_api:list_status_users(Domain, Status), - {ok, format_status_users(StatusUsers)}. + case mongoose_session_api:list_status_users(Domain, Status) of + {ok, StatusUsers} -> + {ok, format_status_users(StatusUsers)}; + Error -> + make_error(Error, #{domain => Domain, status => Status}) + end. --spec count_users_with_status(map()) -> {ok, non_neg_integer()}. +-spec count_users_with_status(map()) -> {ok, non_neg_integer()} | {error, resolver_error()}. count_users_with_status(#{<<"domain">> := null, <<"status">> := Status}) -> - Number = mongoose_session_api:num_status_users(Status), - {ok, Number}; + format_result(mongoose_session_api:num_status_users(Status), #{status => Status}); count_users_with_status(#{<<"domain">> := Domain, <<"status">> := Status}) -> - Number = mongoose_session_api:num_status_users(Domain, Status), - {ok, Number}. + format_result(mongoose_session_api:num_status_users(Domain, Status), #{domain => Domain, status => Status}). diff --git a/src/graphql/admin/mongoose_graphql_vcard_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_vcard_admin_mutation.erl index 636dc427cff..6d60060bb9c 100644 --- a/src/graphql/admin/mongoose_graphql_vcard_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_vcard_admin_mutation.erl @@ -16,6 +16,5 @@ execute(_Ctx, vcard, <<"setVcard">>, #{<<"user">> := CallerJID, <<"vcard">> := VcardInput}) -> case mod_vcard_api:set_vcard(CallerJID, VcardInput) of {ok, _} = Vcard -> Vcard; - {ErrorCode, ErrorMessage} -> - make_error({ErrorCode, ErrorMessage}, #{user => CallerJID}) + Error -> make_error(Error, #{user => jid:to_binary(CallerJID)}) end. diff --git a/src/graphql/admin/mongoose_graphql_vcard_admin_query.erl b/src/graphql/admin/mongoose_graphql_vcard_admin_query.erl index ba02b632d92..76b3c3e3de2 100644 --- a/src/graphql/admin/mongoose_graphql_vcard_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_vcard_admin_query.erl @@ -14,6 +14,6 @@ execute(_Ctx, vcard, <<"getVcard">>, #{<<"user">> := CallerJID}) -> case mod_vcard_api:get_vcard(CallerJID) of {ok, _} = Vcard -> Vcard; - {ErrorCode, ErrorMessage} -> - make_error({ErrorCode, ErrorMessage}, #{user => CallerJID}) + Error -> + make_error(Error, #{user => jid:to_binary(CallerJID)}) end. diff --git a/src/graphql/mongoose_graphql_cowboy_handler.erl b/src/graphql/mongoose_graphql_handler.erl similarity index 99% rename from src/graphql/mongoose_graphql_cowboy_handler.erl rename to src/graphql/mongoose_graphql_handler.erl index b53dd12be50..8f6a047f924 100644 --- a/src/graphql/mongoose_graphql_cowboy_handler.erl +++ b/src/graphql/mongoose_graphql_handler.erl @@ -4,7 +4,7 @@ %% %% The graphql request is authorized, processed and then passed for execution. %% @end --module(mongoose_graphql_cowboy_handler). +-module(mongoose_graphql_handler). -behaviour(mongoose_http_handler). -behavior(cowboy_rest). diff --git a/src/graphql/mongoose_graphql_muc_helper.erl b/src/graphql/mongoose_graphql_muc_helper.erl index 447d65a0535..0f1130d59df 100644 --- a/src/graphql/mongoose_graphql_muc_helper.erl +++ b/src/graphql/mongoose_graphql_muc_helper.erl @@ -13,13 +13,15 @@ -include("mod_muc_room.hrl"). -spec add_user_resource(jid:jid(), binary() | null) -> - {ok, jid:jid()} | {no_session, iolist()}. + {ok, jid:jid()} | {atom(), binary()}. add_user_resource(JID, null) -> case mongoose_session_api:get_user_resource(JID, 1) of {ok, Resource} -> {ok, jid:replace_resource(JID, Resource)}; - {wrong_res_number, _ErrMsg}-> - {no_session, "Given user does not have any session"} + {wrong_res_number, _} -> + {no_session, <<"Given user does not have any session">>}; + Error -> + Error end; add_user_resource(JID, Resource) -> {ok, jid:replace_resource(JID, Resource)}. diff --git a/src/graphql/mongoose_graphql_muc_light_helper.erl b/src/graphql/mongoose_graphql_muc_light_helper.erl index 7bc2bfd4117..7f50344b58d 100644 --- a/src/graphql/mongoose_graphql_muc_light_helper.erl +++ b/src/graphql/mongoose_graphql_muc_light_helper.erl @@ -1,8 +1,7 @@ -module(mongoose_graphql_muc_light_helper). --export([make_room/1, make_ok_user/1, blocking_item_to_map/1, - prepare_blocking_items/1, page_size_or_max_limit/2, - null_to_default/2, options_to_map/1]). +-export([make_room/1, make_ok_user/1, blocking_item_to_map/1, prepare_blocking_items/1, + page_size_or_max_limit/2, null_to_default/2, config_to_map/3]). -spec page_size_or_max_limit(null | integer(), integer()) -> integer(). page_size_or_max_limit(null, MaxLimit) -> @@ -13,9 +12,11 @@ page_size_or_max_limit(PageSize, _MaxLimit) -> PageSize. -spec make_room(mod_muc_light_api:room()) -> map(). -make_room(#{jid := JID, name := Name, subject := Subject, aff_users := Users, options := Options}) -> +make_room(#{jid := JID, aff_users := Users, options := Options}) -> Participants = lists:map(fun make_ok_user/1, Users), - #{<<"jid">> => JID, <<"name">> => Name, <<"subject">> => Subject, + Name = maps:get(<<"roomname">>, Options, null), + Subject = maps:get(<<"subject">>, Options, null), + #{<<"jid">> => jid:to_binary(JID), <<"name">> => Name, <<"subject">> => Subject, <<"participants">> => Participants, <<"options">> => make_options(Options)}. make_ok_user({JID, Aff}) -> @@ -36,6 +37,11 @@ null_to_default(null, Default) -> null_to_default(Value, _Default) -> Value. +-spec config_to_map(null | binary(), null | binary(), null | [map()]) -> map(). +config_to_map(Name, Subject, Options) -> + NS = [{K, V} || {K, V} <- [{<<"roomname">>, Name}, {<<"subject">>, Subject}], V =/= null], + maps:merge(options_to_map(Options), maps:from_list(NS)). + options_to_map(null) -> #{}; options_to_map(Options) -> diff --git a/src/graphql/mongoose_graphql_scalar.erl b/src/graphql/mongoose_graphql_scalar.erl index 6a58bcc00a3..8729615a153 100644 --- a/src/graphql/mongoose_graphql_scalar.erl +++ b/src/graphql/mongoose_graphql_scalar.erl @@ -11,7 +11,7 @@ Coerced :: any(), Reason :: term(). input(<<"DateTime">>, DT) -> binary_to_microseconds(DT); -input(<<"Stanza">>, Value) -> exml:parse(Value); +input(<<"XmlElement">>, 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); @@ -33,7 +33,7 @@ input(Ty, V) -> Coerced :: any(), Reason :: term(). output(<<"DateTime">>, DT) -> {ok, microseconds_to_binary(DT)}; -output(<<"Stanza">>, Elem) -> {ok, exml:to_binary(Elem)}; +output(<<"XmlElement">>, Elem) -> {ok, exml:to_binary(Elem)}; output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)}; output(<<"UserName">>, User) -> {ok, User}; output(<<"DomainName">>, Domain) -> {ok, Domain}; diff --git a/src/graphql/mongoose_graphql_session_helper.erl b/src/graphql/mongoose_graphql_session_helper.erl index ec0412c3b37..c503325ad6c 100644 --- a/src/graphql/mongoose_graphql_session_helper.erl +++ b/src/graphql/mongoose_graphql_session_helper.erl @@ -16,21 +16,30 @@ format_sessions(Sessions) -> lists:map(fun(S) -> {ok, format_session(S)} end, Sessions). -spec format_session(mongoose_session_api:session_info()) -> session_data(). -format_session({USR, Conn, IPS, PORT, Prio, NodeS, Uptime}) -> - #{<<"user">> => iolist_to_binary(USR), - <<"connection">> => iolist_to_binary(Conn), - <<"ip">> => iolist_to_binary(IPS), - <<"port">> => PORT, +format_session({USR, Conn, Address, Prio, Node, Uptime}) -> + {IP, Port} = from_address(Address), + #{<<"user">> => jid:to_binary(USR), + <<"connection">> => from_conn(Conn), + <<"ip">> => IP, + <<"port">> => Port, <<"priority">> => Prio, - <<"node">> => iolist_to_binary(NodeS), + <<"node">> => atom_to_binary(Node), <<"uptime">> => Uptime}. --spec format_status_users([mongoose_session_api:session_info()]) -> session_list(). +from_conn(undefined) -> null; +from_conn(Conn) -> atom_to_binary(Conn). + +from_address(undefined) -> + {null, null}; +from_address({IP, Port}) -> + {iolist_to_binary(inet:ntoa(IP)), Port}. + +-spec format_status_users([mongoose_session_api:status_user_info()]) -> status_user_list(). format_status_users(Sessions) -> lists:map(fun(S) -> {ok, format_status_user(S)} end, Sessions). -spec format_status_user(mongoose_session_api:status_user_info()) -> status_user_data(). -format_status_user({User, Server, Res, Prio, StatusText}) -> - #{<<"user">> => jid:to_binary({User, Server, Res}), +format_status_user({JID, Prio, StatusText}) -> + #{<<"user">> => jid:to_binary(JID), <<"priority">> => Prio, <<"text">> => iolist_to_binary(StatusText)}. diff --git a/src/graphql/mongoose_graphql_sse_handler.erl b/src/graphql/mongoose_graphql_sse_handler.erl index 0a10bd544e4..bd19bb594a6 100644 --- a/src/graphql/mongoose_graphql_sse_handler.erl +++ b/src/graphql/mongoose_graphql_sse_handler.erl @@ -28,9 +28,9 @@ init(State, _LastEvtId, Req) -> process_flag(trap_exit, true), % needed for 'terminate' to be called case cowboy_req:method(Req) of <<"GET">> -> - case mongoose_graphql_cowboy_handler:check_auth_header(Req, State) of + case mongoose_graphql_handler:check_auth_header(Req, State) of {ok, State2} -> - case mongoose_graphql_cowboy_handler:gather(Req) of + case mongoose_graphql_handler:gather(Req) of {ok, Req2, Decoded} -> run_request(Decoded, Req2, State2); {error, Reason} -> diff --git a/src/graphql/user/mongoose_graphql_muc_light_user_mutation.erl b/src/graphql/user/mongoose_graphql_muc_light_user_mutation.erl index 571f67252c9..bfd53dea1be 100644 --- a/src/graphql/user/mongoose_graphql_muc_light_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_muc_light_user_mutation.erl @@ -9,7 +9,7 @@ -import(mongoose_graphql_helper, [make_error/2, format_result/2]). -import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1, prepare_blocking_items/1, - null_to_default/2, options_to_map/1]). + null_to_default/2, config_to_map/3]). execute(Ctx, _Obj, <<"createRoom">>, Args) -> create_room(Ctx, Args); @@ -31,7 +31,7 @@ create_room(#{user := UserJID}, #{<<"id">> := RoomID, <<"mucDomain">> := MUCDoma <<"name">> := RoomName, <<"subject">> := Subject, <<"options">> := Options}) -> case mod_muc_light_api:create_room(MUCDomain, null_to_default(RoomID, <<>>), UserJID, - RoomName, Subject, options_to_map(Options)) of + config_to_map(RoomName, Subject, Options)) of {ok, Room} -> {ok, make_room(Room)}; Err -> @@ -41,8 +41,7 @@ create_room(#{user := UserJID}, #{<<"id">> := RoomID, <<"mucDomain">> := MUCDoma -spec change_room_config(map(), map()) -> {ok, map()} | {error, resolver_error()}. change_room_config(#{user := UserJID}, #{<<"room">> := RoomJID, <<"name">> := RoomName, <<"subject">> := Subject, <<"options">> := Options}) -> - OptMap = options_to_map(Options), - Config = OptMap#{<<"roomname">> => RoomName, <<"subject">> => Subject}, + Config = config_to_map(RoomName, Subject, Options), case mod_muc_light_api:change_room_config(RoomJID, UserJID, Config) of {ok, Room} -> {ok, make_room(Room)}; @@ -63,9 +62,9 @@ invite_user(#{user := UserJID}, #{<<"room">> := RoomJID, <<"recipient">> := Reci -spec kick_user(map(), map()) -> {ok, binary()} | {error, resolver_error()}. kick_user(#{user := UserJID}, #{<<"room">> := RoomJID, <<"user">> := UserToKickJID}) -> - Result = mod_muc_light_api:remove_user_from_room(RoomJID, UserJID, - null_to_default(UserToKickJID, UserJID)), - format_result(Result, #{user => UserToKickJID}). + AffectedJID = null_to_default(UserToKickJID, UserJID), + Result = mod_muc_light_api:change_affiliation(RoomJID, UserJID, AffectedJID, remove), + format_result(Result, #{user => jid:to_binary(AffectedJID)}). -spec send_msg_to_room(map(), map()) -> {ok, binary()} | {error, resolver_error()}. send_msg_to_room(#{user := UserJID}, #{<<"room">> := RoomJID, <<"body">> := Message}) -> diff --git a/src/graphql/user/mongoose_graphql_muc_light_user_query.erl b/src/graphql/user/mongoose_graphql_muc_light_user_query.erl index 37fa37f3ae7..ed0be30b73e 100644 --- a/src/graphql/user/mongoose_graphql_muc_light_user_query.erl +++ b/src/graphql/user/mongoose_graphql_muc_light_user_query.erl @@ -35,7 +35,7 @@ list_room_users(#{user := UserJID}, #{<<"room">> := RoomJID}) -> {ok, Affs} -> {ok, [make_ok_user(A) || A <- Affs]}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_room_config(map(), map()) -> {ok, map()} | {error, resolver_error()}. @@ -44,7 +44,7 @@ get_room_config(#{user := UserJID}, #{<<"room">> := RoomJID}) -> {ok, Room} -> {ok, make_room(Room)}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_room_messages(map(), map()) -> {ok, map()} | {error, resolver_error()}. @@ -57,7 +57,7 @@ get_room_messages(#{user := UserJID}, #{<<"room">> := RoomJID, <<"pageSize">> := Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows), {ok, #{<<"stanzas">> => Maps, <<"limit">> => PageSize2}}; Err -> - make_error(Err, #{room => RoomJID}) + make_error(Err, #{room => jid:to_binary(RoomJID)}) end. -spec get_blocking_list(map(), map()) -> {ok, [{ok, map()}]}. diff --git a/src/graphql/user/mongoose_graphql_private_user_mutation.erl b/src/graphql/user/mongoose_graphql_private_user_mutation.erl index 74cdb134a8e..9484e256282 100644 --- a/src/graphql/user/mongoose_graphql_private_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_private_user_mutation.erl @@ -12,5 +12,6 @@ execute(#{user := CallerJID}, _Obj, <<"setPrivate">>, #{<<"elementString">> := Element}) -> case mod_private_api:private_set(CallerJID, Element) of {ok, _} = Result -> Result; - Error -> make_error(Error, #{user => CallerJID, element => Element}) + Error -> make_error(Error, #{user => jid:to_binary(CallerJID), + element => exml:to_binary(Element)}) end. diff --git a/src/graphql/user/mongoose_graphql_private_user_query.erl b/src/graphql/user/mongoose_graphql_private_user_query.erl index 998774cff1b..ec9defa7a07 100644 --- a/src/graphql/user/mongoose_graphql_private_user_query.erl +++ b/src/graphql/user/mongoose_graphql_private_user_query.erl @@ -14,5 +14,6 @@ execute(#{user := CallerJID}, _Obj, <<"getPrivate">>, case mod_private_api:private_get(CallerJID, Element, SubElement) of {ok, _} = Result -> Result; Error -> - make_error(Error, #{user => CallerJID, element => Element, subElement => SubElement}) + make_error(Error, #{user => jid:to_binary(CallerJID), element => Element, + subElement => SubElement}) end. diff --git a/src/graphql/user/mongoose_graphql_session_user_query.erl b/src/graphql/user/mongoose_graphql_session_user_query.erl index 381ca0576a9..09d380e32b5 100644 --- a/src/graphql/user/mongoose_graphql_session_user_query.erl +++ b/src/graphql/user/mongoose_graphql_session_user_query.erl @@ -14,17 +14,15 @@ execute(Ctx, _Obj, <<"listSessions">>, Args) -> -spec list_resources(map(), map()) -> {ok, [jid:lresource()]}. list_resources(#{user := JID}, _Args) -> - Resources = mongoose_session_api:list_user_resources(JID), - Result = lists:map(fun(R) -> {ok, R} end, Resources), - {ok, Result}. + {ok, Resources} = mongoose_session_api:list_user_resources(JID), + {ok, lists:map(fun(R) -> {ok, R} end, Resources)}. -spec count_resources(map(), map()) -> {ok, non_neg_integer()}. count_resources(#{user := JID}, _Args) -> - Number = length(mongoose_session_api:list_user_resources(JID)), - {ok, Number}. + {ok, Resources} = mongoose_session_api:list_user_resources(JID), + {ok, length(Resources)}. -spec list_sessions_info(map(), map()) -> {ok, mongoose_graphql_session_helper:session_list()}. list_sessions_info(#{user := JID}, _Args) -> - Sessions = mongoose_session_api:list_user_sessions(JID), - Result = mongoose_graphql_session_helper:format_sessions(Sessions), - {ok, Result}. + {ok, Sessions} = mongoose_session_api:list_user_sessions(JID), + {ok, mongoose_graphql_session_helper:format_sessions(Sessions)}. diff --git a/src/graphql/user/mongoose_graphql_vcard_user_mutation.erl b/src/graphql/user/mongoose_graphql_vcard_user_mutation.erl index 7dfcb169148..8e71aa6e57f 100644 --- a/src/graphql/user/mongoose_graphql_vcard_user_mutation.erl +++ b/src/graphql/user/mongoose_graphql_vcard_user_mutation.erl @@ -13,9 +13,9 @@ -include("mongoose.hrl"). -include("jlib.hrl"). -execute(#{user := CallerJID}, vcard, <<"setVcard">>, #{<<"vcard">> := VCARD}) -> - case mod_vcard_api:set_vcard(CallerJID, VCARD) of +execute(#{user := CallerJID}, vcard, <<"setVcard">>, #{<<"vcard">> := VcardInput}) -> + case mod_vcard_api:set_vcard(CallerJID, VcardInput) of {ok, _} = Vcard -> Vcard; - {ErrorCode, ErrorMessage} -> - make_error({ErrorCode, ErrorMessage}, #{user => CallerJID}) + Error -> + make_error(Error, #{user => jid:to_binary(CallerJID)}) end. diff --git a/src/graphql/user/mongoose_graphql_vcard_user_query.erl b/src/graphql/user/mongoose_graphql_vcard_user_query.erl index a2b7a9845dc..90cc7022967 100644 --- a/src/graphql/user/mongoose_graphql_vcard_user_query.erl +++ b/src/graphql/user/mongoose_graphql_vcard_user_query.erl @@ -15,6 +15,5 @@ execute(#{user := CallerJID}, vcard, <<"getVcard">>, #{<<"user">> := UserJID}) - UserJID2 = null_to_default(UserJID, CallerJID), case mod_vcard_api:get_vcard(UserJID2) of {ok, _} = Vcard -> Vcard; - {ErrorCode, ErrorMessage} -> - make_error({ErrorCode, ErrorMessage}, #{<<"user">> => UserJID2}) + Error -> make_error(Error, #{<<"user">> => jid:to_binary(UserJID2)}) end. diff --git a/src/inbox/mod_inbox.erl b/src/inbox/mod_inbox.erl index 0ef71673a4b..6025f7d1d69 100644 --- a/src/inbox/mod_inbox.erl +++ b/src/inbox/mod_inbox.erl @@ -131,6 +131,8 @@ config_spec() -> <<"bin_ttl">> => #option{type = integer, validate = non_negative}, <<"bin_clean_after">> => #option{type = integer, validate = non_negative, process = fun timer:hours/1}, + <<"delete_domain_limit">> => #option{type = int_or_infinity, + validate = positive}, <<"aff_changes">> => #option{type = boolean}, <<"remove_on_kicked">> => #option{type = boolean}, <<"iqdisc">> => mongoose_config_spec:iqdisc() @@ -140,6 +142,7 @@ config_spec() -> <<"boxes">> => [], <<"bin_ttl">> => 30, % 30 days <<"bin_clean_after">> => timer:hours(1), + <<"delete_domain_limit">> => infinity, <<"aff_changes">> => true, <<"remove_on_kicked">> => true, <<"reset_markers">> => [<<"displayed">>], @@ -293,7 +296,7 @@ remove_user(Acc, #{jid := #jid{luser = User, lserver = Server}}, _) -> -spec remove_domain(Acc, Params, Extra) -> {ok | stop, Acc} when Acc :: mongoose_domain_api:remove_domain_acc(), Params :: #{domain := jid:lserver()}, - Extra :: #{host_type := mongooseim:host_type()}. + Extra :: gen_hook:extra(). remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) -> F = fun() -> mod_inbox_backend:remove_domain(HostType, Domain), diff --git a/src/inbox/mod_inbox_backend.erl b/src/inbox/mod_inbox_backend.erl index 1180a1ee64a..1d419a63e6c 100644 --- a/src/inbox/mod_inbox_backend.erl +++ b/src/inbox/mod_inbox_backend.erl @@ -37,7 +37,7 @@ LUser :: jid:luser(), LServer :: jid:lserver(). --callback remove_domain(HostType, LServer) -> ok when +-callback remove_domain(HostType, LServer) -> term() when HostType :: mongooseim:host_type(), LServer :: jid:lserver(). @@ -128,7 +128,7 @@ clear_inbox(HostType, LUser, LServer) -> Args = [HostType, LUser, LServer], mongoose_backend:call_tracked(HostType, ?MAIN_MODULE, ?FUNCTION_NAME, Args). --spec remove_domain(HostType, LServer) -> ok when +-spec remove_domain(HostType, LServer) -> term() when HostType :: mongooseim:host_type(), LServer :: jid:lserver(). remove_domain(HostType, LServer) -> diff --git a/src/inbox/mod_inbox_rdbms.erl b/src/inbox/mod_inbox_rdbms.erl index 9a2017cc5d9..fca9ff731d6 100644 --- a/src/inbox/mod_inbox_rdbms.erl +++ b/src/inbox/mod_inbox_rdbms.erl @@ -35,7 +35,7 @@ %% ---------------------------------------------------------------------- %% TODO pools aren't multitenancy-ready yet -init(HostType, _Options) -> +init(HostType, Opts) -> RowCond = <<"WHERE luser = ? AND lserver = ? AND remote_bare_jid = ?">>, mongoose_rdbms:prepare(inbox_select_entry, inbox, [luser, lserver, remote_bare_jid], @@ -69,8 +69,7 @@ init(HostType, _Options) -> mongoose_rdbms:prepare(inbox_delete, inbox, [luser, lserver], <<"DELETE FROM inbox WHERE luser = ? AND lserver = ?">>), - mongoose_rdbms:prepare(inbox_delete_domain, inbox, - [lserver], <<"DELETE FROM inbox WHERE lserver = ?">>), + prepare_remove_domain(Opts), % upserts BoxQuery = <<"CASE WHEN ?='bin' THEN 'bin'", " WHEN inbox.box='archive' THEN 'inbox'", @@ -95,6 +94,12 @@ init(HostType, _Options) -> UniqueKeyFields, <<"timestamp">>), ok. +prepare_remove_domain(Opts) -> + {MaybeLimitSQL, MaybeLimitMSSQL} = mod_mam_utils:batch_delete_limits(Opts), + mongoose_rdbms:prepare( + inbox_delete_domain, inbox, [lserver], + <<"DELETE ", MaybeLimitMSSQL/binary, "FROM inbox WHERE lserver = ? ", MaybeLimitSQL/binary>>). + -spec get_inbox(HostType :: mongooseim:host_type(), LUser :: jid:luser(), LServer :: jid:lserver(), @@ -170,10 +175,10 @@ remove_inbox_row(HostType, {LUser, LServer, LToBareJid}) -> check_result(Res). -spec remove_domain(HostType :: mongooseim:host_type(), - LServer :: jid:lserver()) -> ok. + LServer :: jid:lserver()) -> term(). remove_domain(HostType, LServer) -> - execute_delete_domain(HostType, LServer), - ok. + DeleteDomainLimit = gen_mod:get_module_opt(HostType, mod_inbox, delete_domain_limit), + execute_delete_domain(HostType, LServer, DeleteDomainLimit). -spec set_inbox_incr_unread( mongooseim:host_type(), mod_inbox:entry_key(), exml:element(), binary(), integer()) -> @@ -471,11 +476,12 @@ execute_delete(HostType, LUser, LServer, RemBareJID) -> execute_delete(HostType, LUser, LServer) -> mongoose_rdbms:execute_successfully(HostType, inbox_delete, [LUser, LServer]). --spec execute_delete_domain(HostType :: mongooseim:host_type(), - LServer :: jid:lserver()) -> +-spec execute_delete_domain(mongooseim:host_type(), jid:lserver(), infinity | non_neg_integer()) -> mongoose_rdbms:query_result(). -execute_delete_domain(HostType, LServer) -> - mongoose_rdbms:execute_successfully(HostType, inbox_delete_domain, [LServer]). +execute_delete_domain(HostType, LServer, infinity) -> + mongoose_rdbms:execute_successfully(HostType, inbox_delete_domain, [LServer]); +execute_delete_domain(HostType, LServer, Limit) -> + mod_mam_utils:incremental_delete_domain(HostType, LServer, Limit, [inbox_delete_domain], 0). %% DB result processing -type db_return() :: {RemBareJID :: jid:luser(), diff --git a/src/inbox/mod_inbox_rdbms_async.erl b/src/inbox/mod_inbox_rdbms_async.erl index a4856396511..e45cde9f444 100644 --- a/src/inbox/mod_inbox_rdbms_async.erl +++ b/src/inbox/mod_inbox_rdbms_async.erl @@ -138,7 +138,7 @@ get_inbox(HostType, LUser, LServer, Params) -> get_inbox_unread(HostType, Entry) -> mod_inbox_rdbms:get_inbox_unread(HostType, Entry). --spec remove_domain(mongooseim:host_type(), jid:lserver()) -> ok. +-spec remove_domain(mongooseim:host_type(), jid:lserver()) -> term(). remove_domain(HostType, LServer) -> mod_inbox_rdbms:remove_domain(HostType, LServer). diff --git a/src/mam/mod_mam_utils.erl b/src/mam/mod_mam_utils.erl index 8da1275314b..027b3a7bf3b 100644 --- a/src/mam/mod_mam_utils.erl +++ b/src/mam/mod_mam_utils.erl @@ -1226,7 +1226,7 @@ db_jid_codec(HostType, Module) -> db_message_codec(HostType, Module) -> gen_mod:get_module_opt(HostType, Module, db_message_format). --spec batch_delete_limits(#{delete_domain_limit := infinity | non_neg_integer()}) -> +-spec batch_delete_limits(#{delete_domain_limit := infinity | non_neg_integer(), _ => _}) -> {binary(), binary()}. batch_delete_limits(#{delete_domain_limit := infinity}) -> {<<>>, <<>>}; diff --git a/src/metrics/mongoose_metrics_api.erl b/src/metrics/mongoose_metrics_api.erl index 53fa08941a5..725bf723a28 100644 --- a/src/metrics/mongoose_metrics_api.erl +++ b/src/metrics/mongoose_metrics_api.erl @@ -4,6 +4,7 @@ get_cluster_metrics_as_dicts/3]). -include("mongoose_logger.hrl"). +-include("mongoose.hrl"). -type name() :: [atom() | integer()]. -type key() :: atom(). @@ -18,21 +19,31 @@ -spec get_metrics(Name :: name()) -> {ok, [metric_result()]}. get_metrics(Name) -> - Values = exometer:get_values(Name), + PrepName = prepare_host_types(Name), + Values = mongoose_metrics:get_metric_values(PrepName), {ok, lists:map(fun make_metric_result/1, Values)}. -spec get_metrics_as_dicts(Name :: name(), Keys :: [key()]) -> {ok, [metric_dict_result()]}. get_metrics_as_dicts(Name, Keys) -> - Values = exometer:get_values(Name), + PrepName = prepare_host_types(Name), + Values = mongoose_metrics:get_metric_values(PrepName), {ok, [make_metric_dict_result(V, Keys) || V <- Values]}. -spec get_cluster_metrics_as_dicts(Name :: name(), Keys :: [key()], Nodes :: [node()]) -> {ok, [metric_node_dict_result()]}. get_cluster_metrics_as_dicts(Name, Keys, Nodes) -> - Nodes2 = existing_nodes(Nodes), - F = fun(Node) -> rpc:call(Node, exometer, get_values, [Name]) end, + PrepName = prepare_host_types(Name), + Nodes2 = prepare_nodes_arg(Nodes), + F = fun(Node) -> + case rpc:call(Node, mongoose_metrics, get_metric_values, [PrepName]) of + {badrpc, Reason} -> + [{[error, Reason], []}]; + Result -> + Result + end + end, Results = mongoose_lib:pmap(F, Nodes2), {ok, [make_node_result(Node, Result, Keys) || {Node, Result} <- lists:zip(Nodes2, Results)]}. @@ -50,14 +61,10 @@ filter_keys(Dict, []) -> filter_keys(Dict, Keys) -> [KV || KV = {Key, _} <- Dict, lists:member(Key, Keys)]. -existing_nodes(Nodes) -> - AllNodes = [node()|nodes()], - filter_nodes(AllNodes, Nodes). - -filter_nodes(AllNodes, []) -> - AllNodes; -filter_nodes(AllNodes, AllowedNodes) -> - [Node || Node <- AllNodes, lists:member(Node, AllowedNodes)]. +prepare_nodes_arg([]) -> + [node()|nodes()]; +prepare_nodes_arg(Nodes) -> + Nodes. make_metric_result({Name, Dict}) -> PreparedName = format_name(Name), @@ -154,3 +161,15 @@ format_vm_system_info(#{port_count := PortCount, port_limit := PortLimit, format_probe_queues(#{fsm := FSM, regular := Regular, total := Total}) -> #{<<"type">> => <<"probe_queues">>, <<"fsm">> => FSM, <<"regular">> => Regular, <<"total">> => Total}. + +prepare_host_types(Name) -> + lists:map( + fun(Ele) -> + case lists:member(atom_to_binary(Ele), ?ALL_HOST_TYPES) of + true -> + binary:replace(atom_to_binary(Ele), <<" ">>, <<"_">>); + false -> + Ele + end + end, + Name). diff --git a/src/mod_private_api.erl b/src/mod_private_api.erl index 046eaddb296..d78676455ba 100644 --- a/src/mod_private_api.erl +++ b/src/mod_private_api.erl @@ -8,26 +8,26 @@ -export([private_get/3, private_set/2]). --spec private_get(jid:jid(), binary(), binary()) -> {Res, iodata()} when - Res :: ok | not_found. +-spec private_get(jid:jid(), binary(), binary()) -> {ok, exml:element()} | {Error, string()} when + Error :: not_found. private_get(JID, Element, Ns) -> case ejabberd_auth:does_user_exist(JID) of true -> - {ok, do_private_get(JID, Element, Ns)}; + do_private_get(JID, Element, Ns); false -> - {not_found, io_lib:format("User ~s does not exist", [jid:to_binary(JID)])} + {not_found, io_lib:format("User ~ts does not exist", [jid:to_binary(JID)])} end. --spec private_set(jid:jid(), ElementString :: binary()) -> {Res, iolist()} when - Res :: ok | not_found | parse_error. -private_set(JID, ElementString) -> - case exml:parse(ElementString) of - {error, Error} -> - String = io_lib:format("Error found parsing the element:~n ~p~nError: ~p~n", - [ElementString, Error]), - {parse_error, String}; - {ok, Xml} -> - do_private_set(JID, Xml) +-spec private_set(jid:jid(), exml:element()) -> + {ok, exml:element()} | {not_found, iolist()}. +private_set(#jid{lserver = Domain} = JID, Xml) -> + case ejabberd_auth:does_user_exist(JID) of + true -> + {ok, HostType} = mongoose_domain_api:get_domain_host_type(Domain), + send_iq(set, Xml, JID, HostType), + {ok, Xml}; + false -> + {not_found, io_lib:format("User ~ts does not exist", [jid:to_binary(JID)])} end. do_private_get(JID, Element, Ns) -> @@ -37,20 +37,10 @@ do_private_get(JID, Element, Ns) -> [#xmlel{ name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_PRIVATE}], children = [SubEl] }] = ResIq#iq.sub_el, - exml:to_binary(SubEl). - -do_private_set(#jid{lserver = Domain} = JID, Xml) -> - case ejabberd_auth:does_user_exist(JID) of - true -> - {ok, HostType} = mongoose_domain_api:get_domain_host_type(Domain), - send_iq(set, Xml, JID, HostType), - {ok, ""}; - false -> - {not_found, io_lib:format("User ~s does not exist", [jid:to_binary(JID)])} - end. + {ok, SubEl}. send_iq(Method, Xml, From = To = _JID, HostType) -> - IQ = {iq, <<"">>, Method, ?NS_PRIVATE, <<"">>, + IQ = {iq, <<>>, Method, ?NS_PRIVATE, <<>>, #xmlel{ name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_PRIVATE}], children = [Xml] } }, diff --git a/src/mod_roster.erl b/src/mod_roster.erl index db63647142d..ab94b21c7bb 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -334,7 +334,15 @@ create_sub_el(Items, Version) -> 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 + NewAcc = get_user_roster(Acc, LUser, LServer, HostType), + {ok, NewAcc}. + +-spec get_user_roster(mongoose_acc:t(), + jid:luser(), + jid:lserver(), + mongooseim:host_type()) -> mongoose_acc:t(). +get_user_roster(Acc, LUser, LServer, HostType) -> + 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); @@ -345,8 +353,7 @@ get_user_roster(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_ty true end, get_roster(HostType, LUser, LServer)), mongoose_acc:append(roster, items, Roster, Acc) - end, - {ok, NewAcc}. + end. item_to_xml(Item) -> Attrs1 = [{<<"jid">>, @@ -799,8 +806,8 @@ get_user_rosters_length(HostType, JID) -> Acc :: mongoose_acc:t(), Params :: #{jid := jid:jid()}, Extra :: gen_hook:extra(). -remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, Extra = #{host_type := HostType}) -> - Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID, 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 {atomic, ok} -> @@ -810,11 +817,11 @@ remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer} = JID}, Extra = end, {ok, Acc1}. --spec try_send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid(), gen_hook:extra()) -> +-spec try_send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t(). -try_send_unsubscription_to_rosteritems(Acc, JID, Extra) -> +try_send_unsubscription_to_rosteritems(Acc, JID) -> try - send_unsubscription_to_rosteritems(Acc, JID, Extra) + send_unsubscription_to_rosteritems(Acc, JID) catch E:R:S -> ?LOG_WARNING(#{what => roster_unsubcribe_failed, @@ -825,10 +832,10 @@ try_send_unsubscription_to_rosteritems(Acc, JID, Extra) -> %% 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(), gen_hook:extra()) -> - mongoose_acc:t(). -send_unsubscription_to_rosteritems(Acc, JID, Extra) -> - {ok, Acc1} = get_user_roster(Acc, #{jid => JID}, Extra), +-spec send_unsubscription_to_rosteritems(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t(). +send_unsubscription_to_rosteritems(Acc, JID) -> + #jid{luser = LUser, lserver = LServer} = JID, + Acc1 = get_user_roster(Acc, LUser, LServer, mongoose_acc:host_type(Acc)), RosterItems = mongoose_acc:get(roster, items, [], Acc1), lists:foreach(fun(RosterItem) -> send_unsubscribing_presence(JID, RosterItem) diff --git a/src/mongoose_account_api.erl b/src/mongoose_account_api.erl index d2f2cdb1616..88b03945702 100644 --- a/src/mongoose_account_api.erl +++ b/src/mongoose_account_api.erl @@ -16,7 +16,8 @@ check_password/2, check_password/3, check_password_hash/3, - check_password_hash/4]). + check_password_hash/4, + import_users/1]). -type register_result() :: {ok | exists | invalid_jid | cannot_register, iolist()}. @@ -184,16 +185,39 @@ check_password_hash(JID, PasswordHash, HashMethod) -> {incorrect, "Password hash is incorrect"} end. +-spec import_users(file:filename()) -> {ok, #{binary() => [{ok, jid:jid() | binary()}]}} + | {file_not_found, binary()}. +import_users(Filename) -> + case mongoose_import_users:run(Filename) of + {ok, Summary} -> + {ok, maps:fold( + fun(Reason, List, Map) -> + List2 = [{ok, El} || El <- List], + maps:put(from_reason(Reason), List2, Map) + end, + #{<<"status">> => <<"Completed">>}, + Summary)}; + {error, file_not_found} -> + {file_not_found, <<"File not found">>} + end. + +-spec from_reason(mongoose_import_users:reason()) -> binary(). +from_reason(ok) -> <<"created">>; +from_reason(exists) -> <<"existing">>; +from_reason(not_allowed) -> <<"notAllowed">>; +from_reason(invalid_jid) -> <<"invalidJID">>; +from_reason(null_password) -> <<"emptyPassword">>; +from_reason(bad_csv) -> <<"invalidRecord">>. + -spec ban_account(jid:user(), jid:server(), binary()) -> change_password_result(). ban_account(User, Host, ReasonText) -> JID = jid:make(User, Host, <<>>), ban_account(JID, ReasonText). -spec ban_account(jid:jid(), binary()) -> change_password_result(). -ban_account(JID, ReasonText) -> +ban_account(JID, Reason) -> case ejabberd_auth:does_user_exist(JID) of true -> - Reason = mongoose_session_api:prepare_reason(ReasonText), mongoose_session_api:kick_sessions(JID, Reason), case set_random_password(JID, Reason) of ok -> diff --git a/src/mongoose_admin_api/mongoose_admin_api_muc_light.erl b/src/mongoose_admin_api/mongoose_admin_api_muc_light.erl index 81e71cf23fb..2244c8da1ea 100644 --- a/src/mongoose_admin_api/mongoose_admin_api_muc_light.erl +++ b/src/mongoose_admin_api/mongoose_admin_api_muc_light.erl @@ -71,6 +71,10 @@ handle_post(Req, #{suffix := participants} = State) -> {stop, Req, State}; {muc_server_not_found, Msg} -> throw_error(not_found, Msg); + {room_not_found, Msg} -> + throw_error(not_found, Msg); + {user_not_found, Msg} -> + throw_error(bad_request, Msg); {not_room_member, Msg} -> throw_error(denied, Msg) end; @@ -85,6 +89,10 @@ handle_post(Req, #{suffix := messages} = State) -> {stop, Req, State}; {muc_server_not_found, Msg} -> throw_error(not_found, Msg); + {room_not_found, Msg} -> + throw_error(not_found, Msg); + {user_not_found, Msg} -> + throw_error(bad_request, Msg); {not_room_member, Msg} -> throw_error(denied, Msg) end; @@ -94,11 +102,14 @@ handle_post(Req, State) -> OwnerJid = get_owner_jid(Args), RoomName = get_room_name(Args), Subject = get_room_subject(Args), - case mod_muc_light_api:create_room(MUCDomain, OwnerJid, RoomName, Subject) of + Config = #{<<"roomname">> => RoomName, <<"subject">> => Subject}, + case mod_muc_light_api:create_room(MUCDomain, OwnerJid, Config) of {ok, #{jid := RoomJid}} -> RoomJidBin = jid:to_binary(RoomJid), Path = [cowboy_req:uri(Req), "/", RoomJidBin], resource_created(Req, State, Path, RoomJidBin); + {user_not_found, Msg} -> + throw_error(bad_request, Msg); {muc_server_not_found, Msg} -> throw_error(not_found, Msg) end. @@ -110,7 +121,8 @@ handle_put(Req, State) -> RoomName = get_room_name(Args), RoomId = get_room_id(Args), Subject = get_room_subject(Args), - case mod_muc_light_api:create_room(MUCDomain, RoomId, OwnerJid, RoomName, Subject) of + Config = #{<<"roomname">> => RoomName, <<"subject">> => Subject}, + case mod_muc_light_api:create_room(MUCDomain, RoomId, OwnerJid, Config) of {ok, #{jid := RoomJid}} -> RoomJidBin = jid:to_binary(RoomJid), Path = [cowboy_req:uri(Req), "/", RoomJidBin], diff --git a/src/mongoose_admin_api/mongoose_admin_api_sessions.erl b/src/mongoose_admin_api/mongoose_admin_api_sessions.erl index 2da78694ebe..074f6fc8df1 100644 --- a/src/mongoose_admin_api/mongoose_admin_api_sessions.erl +++ b/src/mongoose_admin_api/mongoose_admin_api_sessions.erl @@ -55,14 +55,14 @@ delete_resource(Req, State) -> handle_get(Req, State) -> #{domain := Domain} = cowboy_req:bindings(Req), - Sessions = mongoose_session_api:list_resources(Domain), + {ok, Sessions} = mongoose_session_api:list_resources(Domain), {jiffy:encode(Sessions), Req, State}. handle_delete(Req, State) -> #{domain := Domain} = Bindings = cowboy_req:bindings(Req), UserName = get_user_name(Bindings), Resource = get_resource(Bindings), - case mongoose_session_api:kick_session(UserName, Domain, Resource, <<"kicked">>) of + case mongoose_session_api:kick_session(jid:make(UserName, Domain, Resource), <<"kicked">>) of {ok, _} -> {true, Req, State}; {no_session, Reason} -> diff --git a/src/mongoose_client_api/mongoose_client_api_rooms.erl b/src/mongoose_client_api/mongoose_client_api_rooms.erl index 02bc2fc86aa..4e893e3080e 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms.erl @@ -15,7 +15,6 @@ %% Used by mongoose_client_api_rooms_* -export([get_room_jid/3, - get_user_aff/2, get_room_name/1, get_room_subject/1]). @@ -99,7 +98,8 @@ handle_post(Req, State = #{jid := UserJid}) -> Args = parse_body(Req), Name = get_room_name(Args), Subject = get_room_subject(Args), - {ok, #{jid := RoomJid}} = mod_muc_light_api:create_room(MUCLightDomain, UserJid, Name, Subject), + Config = #{<<"roomname">> => Name, <<"subject">> => Subject}, + {ok, #{jid := RoomJid}} = mod_muc_light_api:create_room(MUCLightDomain, UserJid, Config), room_created(Req, State, RoomJid). handle_put(Req, State = #{jid := UserJid}) -> @@ -108,7 +108,8 @@ handle_put(Req, State = #{jid := UserJid}) -> Args = parse_body(Req), Name = get_room_name(Args), Subject = get_room_subject(Args), - case mod_muc_light_api:create_room(MUCLightDomain, RoomId, UserJid, Name, Subject) of + Config = #{<<"roomname">> => Name, <<"subject">> => Subject}, + case mod_muc_light_api:create_room(MUCLightDomain, RoomId, UserJid, Config) of {ok, #{jid := RoomJid}} -> room_created(Req, State, RoomJid); {already_exists, Msg} -> @@ -125,15 +126,20 @@ room_created(Req, State, RoomJid) -> room_us_to_json({RoomU, RoomS}) -> #jid{luser = RoomId} = RoomJid = jid:make_noprep(RoomU, RoomS, <<>>), case mod_muc_light_api:get_room_info(RoomJid) of - {ok, #{name := Name, subject := Subject}} -> - [#{id => RoomId, name => Name, subject => Subject}]; + {ok, Info} -> + NS = room_name_and_subject(Info), + [NS#{id => RoomId}]; {room_not_found, _} -> [] % room was removed after listing rooms, but before this query end. -spec room_info_to_json(mod_muc_light_api:room()) -> jiffy:json_value(). -room_info_to_json(#{name := Name, subject := Subject, aff_users := AffUsers}) -> - #{name => Name, subject => Subject, participants => lists:map(fun user_to_json/1, AffUsers)}. +room_info_to_json(Info = #{aff_users := AffUsers}) -> + NS = room_name_and_subject(Info), + NS#{participants => lists:map(fun user_to_json/1, AffUsers)}. + +room_name_and_subject(#{options := #{<<"roomname">> := Name, <<"subject">> := Subject}}) -> + #{name => Name, subject => Subject}. user_to_json({UserServer, Role}) -> #{user => jid:to_binary(UserServer), @@ -157,13 +163,6 @@ get_room_jid(#{id := IdOrJid}, State, _) -> get_room_jid(#{}, _State, required) -> throw_error(bad_request, <<"Missing room ID">>); get_room_jid(#{}, _State, optional) -> undefined. -get_user_aff(#{jid := UserJid, creds := Creds}, RoomJid) -> - HostType = mongoose_credentials:host_type(Creds), - case mod_muc_light_api:get_room_user_aff(HostType, RoomJid, UserJid) of - {ok, Aff} -> Aff; - {error, room_not_found} -> throw_error(not_found, <<"Room does not exist">>) - end. - muc_light_domain(#{creds := Creds}) -> HostType = mongoose_credentials:host_type(Creds), LServer = mongoose_credentials:lserver(Creds), diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl b/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl index a3db7e0945f..24a7030dc8d 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_messages.erl @@ -76,6 +76,8 @@ handle_get(Req, State = #{jid := UserJid}) -> {ok, Msgs} -> JSONData = [make_json_item(Msg) || Msg <- Msgs], {jiffy:encode(JSONData), Req, State}; + {room_not_found, Msg} -> + throw_error(not_found, Msg); {not_room_member, Msg} -> throw_error(denied, Msg) end. @@ -92,6 +94,8 @@ handle_post(Req, State = #{jid := UserJid}) -> Resp = #{id => UUID}, Req3 = cowboy_req:set_resp_body(jiffy:encode(Resp), Req), {true, Req3, State}; + {room_not_found, Msg} -> + throw_error(not_found, Msg); {not_room_member, Msg} -> throw_error(denied, Msg) end. diff --git a/src/mongoose_client_api/mongoose_client_api_rooms_users.erl b/src/mongoose_client_api/mongoose_client_api_rooms_users.erl index 7893b127076..8498d3479ae 100644 --- a/src/mongoose_client_api/mongoose_client_api_rooms_users.erl +++ b/src/mongoose_client_api/mongoose_client_api_rooms_users.erl @@ -58,23 +58,27 @@ handle_post(Req, State = #{jid := UserJid}) -> RoomJid = get_room_jid(Bindings, State, required), Args = parse_body(Req), TargetJid = get_user_jid(Args), - assert_permissions(get_user_aff(State, RoomJid), add, UserJid, TargetJid), - mod_muc_light_api:change_affiliation(RoomJid, UserJid, TargetJid, <<"member">>), + change_affiliation(RoomJid, UserJid, TargetJid, add), {true, Req, State}. handle_delete(Req, State = #{jid := UserJid}) -> Bindings = cowboy_req:bindings(Req), RoomJid = get_room_jid(Bindings, State, required), TargetJid = get_user_jid(Bindings), - assert_permissions(get_user_aff(State, RoomJid), remove, UserJid, TargetJid), - mod_muc_light_api:change_affiliation(RoomJid, UserJid, TargetJid, <<"none">>), + change_affiliation(RoomJid, UserJid, TargetJid, remove), {true, Req, State}. --spec assert_permissions(mod_muc_light_api:aff(), add | remove, jid:jid(), jid:jid()) -> ok. -assert_permissions(owner, _Op, _UserJid, _TargetJid) -> ok; -assert_permissions(member, remove, UserJid, UserJid) -> ok; -assert_permissions(_Aff, _Op, _UserJid, _TargetJid) -> - throw_error(denied, <<"Operation not permitted for this user">>). +change_affiliation(RoomJid, UserJid, TargetJid, Op) -> + case mod_muc_light_api:change_affiliation(RoomJid, UserJid, TargetJid, Op) of + {ok, _Msg} -> + ok; + {room_not_found, Msg} -> + throw_error(not_found, Msg); + {not_allowed, Msg} -> + throw_error(denied, Msg); + {not_room_member, Msg} -> + throw_error(denied, Msg) + end. get_user_jid(#{user := JidBin}) -> case jid:from_binary(JidBin) of diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index ef1afbe8fcc..467042e5783 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -5,7 +5,6 @@ %%% make sure they pass the expected arguments. -module(mongoose_hooks). --include("jlib.hrl"). -include("mod_privacy.hrl"). -include("mongoose.hrl"). @@ -14,7 +13,6 @@ anonymous_purge_hook/3, auth_failed/3, does_user_exist/3, - ejabberd_ctl_process/2, failed_to_store_message/1, filter_local_packet/1, filter_packet/1, @@ -169,9 +167,7 @@ Result :: mod_adhoc:command_hook_acc(). adhoc_local_commands(HostType, From, To, AdhocRequest) -> Params = #{from => From, to => To, adhoc_request => AdhocRequest}, - LegacyArgs = [From, To, AdhocRequest], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, LegacyArgs), - run_hook_for_host_type(adhoc_local_commands, HostType, empty, ParamsWithLegacyArgs). + run_hook_for_host_type(adhoc_local_commands, HostType, empty, Params). -spec adhoc_sm_commands(HostType, From, To, AdhocRequest) -> Result when HostType :: mongooseim:host_type(), @@ -180,7 +176,8 @@ adhoc_local_commands(HostType, From, To, AdhocRequest) -> AdhocRequest :: adhoc:request(), Result :: mod_adhoc:command_hook_acc(). adhoc_sm_commands(HostType, From, To, AdhocRequest) -> - run_hook_for_host_type(adhoc_sm_commands, HostType, empty, [From, To, AdhocRequest]). + Params = #{from => From, to => To, request => AdhocRequest}, + run_hook_for_host_type(adhoc_sm_commands, HostType, empty, Params). %%% @doc The `anonymous_purge_hook' hook is called when anonymous user's data is removed. -spec anonymous_purge_hook(LServer, Acc, LUser) -> Result when @@ -191,10 +188,8 @@ adhoc_sm_commands(HostType, From, To, AdhocRequest) -> anonymous_purge_hook(LServer, Acc, LUser) -> Jid = jid:make_bare(LUser, LServer), Params = #{jid => Jid}, - Args = [LUser, LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(anonymous_purge_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(anonymous_purge_hook, HostType, Acc, Params). -spec auth_failed(HostType, Server, Username) -> Result when HostType :: mongooseim:host_type(), @@ -203,9 +198,7 @@ anonymous_purge_hook(LServer, Acc, LUser) -> Result :: ok. auth_failed(HostType, Server, Username) -> Params = #{username => Username, server => Server}, - Args = [Username, Server], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(auth_failed, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(auth_failed, HostType, ok, Params). -spec does_user_exist(HostType, Jid, RequestType) -> Result when HostType :: mongooseim:host_type(), @@ -214,9 +207,7 @@ auth_failed(HostType, Server, Username) -> Result :: boolean(). does_user_exist(HostType, Jid, RequestType) -> Params = #{jid => Jid, request_type => RequestType}, - Args = [HostType, Jid, RequestType], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(does_user_exist, HostType, false, ParamsWithLegacyArgs). + run_hook_for_host_type(does_user_exist, HostType, false, Params). -spec remove_domain(HostType, Domain) -> Result when HostType :: mongooseim:host_type(), @@ -224,31 +215,19 @@ does_user_exist(HostType, Jid, RequestType) -> Result :: mongoose_domain_api:remove_domain_acc(). remove_domain(HostType, Domain) -> Params = #{domain => Domain}, - Args = [HostType, Domain], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(remove_domain, HostType, #{failed => []}, ParamsWithLegacyArgs). + run_hook_for_host_type(remove_domain, HostType, #{failed => []}, Params). -spec node_cleanup(Node :: node()) -> Acc :: map(). node_cleanup(Node) -> Params = #{node => Node}, - Args = [Node], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_global_hook(node_cleanup, #{}, ParamsWithLegacyArgs). - --spec ejabberd_ctl_process(Acc, Args) -> Result when - Acc :: any(), - Args :: [string()], - Result :: any(). -ejabberd_ctl_process(Acc, Args) -> - run_global_hook(ejabberd_ctl_process, Acc, [Args]). + run_global_hook(node_cleanup, #{}, Params). -spec failed_to_store_message(Acc) -> Result when Acc :: mongoose_acc:t(), Result :: mongoose_acc:t(). failed_to_store_message(Acc) -> HostType = mongoose_acc:host_type(Acc), - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(failed_to_store_message, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(failed_to_store_message, HostType, Acc, #{}). %%% @doc The `filter_local_packet' hook is called to filter out %%% stanzas routed with `mongoose_local_delivery'. @@ -257,8 +236,7 @@ failed_to_store_message(Acc) -> Result :: drop | filter_packet_acc(). filter_local_packet(FilterAcc = {_From, _To, Acc, _Packet}) -> HostType = mongoose_acc:host_type(Acc), - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(filter_local_packet, HostType, FilterAcc, ParamsWithLegacyArgs). + run_hook_for_host_type(filter_local_packet, HostType, FilterAcc, #{}). %%% @doc The `filter_packet' hook is called to filter out %%% stanzas routed with `mongoose_router_global'. @@ -266,7 +244,7 @@ filter_local_packet(FilterAcc = {_From, _To, Acc, _Packet}) -> Acc :: filter_packet_acc(), Result :: drop | filter_packet_acc(). filter_packet(Acc) -> - run_global_hook(filter_packet, Acc, []). + run_global_hook(filter_packet, Acc, #{}). %%% @doc The `inbox_unread_count' hook is called to get the number %%% of unread messages in the inbox for a user. @@ -277,16 +255,14 @@ filter_packet(Acc) -> Result :: mongoose_acc:t(). inbox_unread_count(LServer, Acc, User) -> Params = #{user => User}, - Args = [User], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(inbox_unread_count, LServer, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(inbox_unread_count, LServer, Acc, Params). -spec extend_inbox_message(mongoose_acc:t(), mod_inbox:inbox_res(), jlib:iq()) -> [exml:element()]. extend_inbox_message(MongooseAcc, InboxRes, IQ) -> HostType = mongoose_acc:host_type(MongooseAcc), HookParams = #{mongoose_acc => MongooseAcc, inbox_res => InboxRes, iq => IQ}, - run_fold(extend_inbox_message, HostType, [], HookParams). + run_hook_for_host_type(extend_inbox_message, HostType, [], HookParams). %%% @doc The `get_key' hook is called to extract a key from `mod_keystore'. -spec get_key(HostType, KeyName) -> Result when @@ -295,9 +271,7 @@ extend_inbox_message(MongooseAcc, InboxRes, IQ) -> Result :: mod_keystore:key_list(). get_key(HostType, KeyName) -> Params = #{key_id => {KeyName, HostType}}, - Args = [{KeyName, HostType}], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(get_key, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(get_key, HostType, [], Params). -spec packet_to_component(Acc, From, To) -> Result when Acc :: mongoose_acc:t(), @@ -306,9 +280,7 @@ get_key(HostType, KeyName) -> Result :: mongoose_acc:t(). packet_to_component(Acc, From, To) -> Params = #{from => From, to => To}, - Args = [From, To], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_global_hook(packet_to_component, Acc, ParamsWithLegacyArgs). + run_global_hook(packet_to_component, Acc, Params). -spec presence_probe_hook(HostType, Acc, From, To, Pid) -> Result when HostType :: mongooseim:host_type(), @@ -319,9 +291,7 @@ packet_to_component(Acc, From, To) -> Result :: mongoose_acc:t(). 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). + run_hook_for_host_type(presence_probe_hook, HostType, Acc, Params). %%% @doc The `push_notifications' hook is called to push notifications. -spec push_notifications(HostType, Acc, NotificationForms, Options) -> Result when @@ -332,9 +302,7 @@ presence_probe_hook(HostType, Acc, From, To, Pid) -> Result :: ok | {error, any()}. push_notifications(HostType, Acc, NotificationForms, Options) -> 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). + run_hook_for_host_type(push_notifications, HostType, Acc, Params). %%% @doc The `register_subhost' hook is called when a component %%% is registered for ejabberd_router or a subdomain is added to mongoose_subdomain_core. @@ -344,9 +312,7 @@ push_notifications(HostType, Acc, NotificationForms, Options) -> Result :: any(). register_subhost(LDomain, IsHidden) -> Params = #{ldomain => LDomain, is_hidden => IsHidden}, - Args = [LDomain, IsHidden], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_global_hook(register_subhost, ok, ParamsWithLegacyArgs). + run_global_hook(register_subhost, ok, Params). %%% @doc The `register_user' hook is called when a user is successfully %%% registered in an authentication backend. @@ -358,9 +324,7 @@ register_subhost(LDomain, IsHidden) -> register_user(HostType, LServer, LUser) -> Jid = jid:make_bare(LUser, LServer), Params = #{jid => Jid}, - Args = [LUser, LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(register_user, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(register_user, HostType, ok, Params). %%% @doc The `remove_user' hook is called when a user is removed. -spec remove_user(Acc, LServer, LUser) -> Result when @@ -371,10 +335,8 @@ register_user(HostType, LServer, LUser) -> remove_user(Acc, LServer, LUser) -> Jid = jid:make_bare(LUser, LServer), Params = #{jid => Jid}, - Args = [LUser, LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(remove_user, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(remove_user, HostType, Acc, Params). -spec resend_offline_messages_hook(Acc, JID) -> Result when Acc :: mongoose_acc:t(), @@ -382,10 +344,8 @@ remove_user(Acc, LServer, LUser) -> Result :: mongoose_acc:t(). resend_offline_messages_hook(Acc, JID) -> Params = #{jid => JID}, - Args = [JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(resend_offline_messages_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(resend_offline_messages_hook, HostType, Acc, Params). %%% @doc The `session_cleanup' hook is called when sm backend cleans up a user's session. -spec session_cleanup(Server, Acc, User, Resource, SID) -> Result when @@ -398,10 +358,8 @@ resend_offline_messages_hook(Acc, JID) -> session_cleanup(Server, Acc, User, Resource, 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), - run_hook_for_host_type(session_cleanup, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(session_cleanup, HostType, Acc, Params). %%% @doc The `set_vcard' hook is called when the caller wants to set the VCard. -spec set_vcard(HostType, UserJID, VCard) -> Result when @@ -411,9 +369,7 @@ session_cleanup(Server, Acc, User, Resource, SID) -> Result :: ok | {error, any()}. set_vcard(HostType, UserJID, VCard) -> Params = #{user => UserJID, vcard => VCard}, - Args = [HostType, UserJID, VCard], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(set_vcard, HostType, {error, no_handler_defined}, ParamsWithLegacyArgs). + run_hook_for_host_type(set_vcard, HostType, {error, no_handler_defined}, Params). -spec unacknowledged_message(Acc, JID) -> Result when Acc :: mongoose_acc:t(), @@ -422,9 +378,7 @@ set_vcard(HostType, UserJID, VCard) -> unacknowledged_message(Acc, JID) -> HostType = mongoose_acc:host_type(Acc), Params = #{jid => JID}, - Args = [JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(unacknowledged_message, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(unacknowledged_message, HostType, Acc, Params). -spec filter_unacknowledged_messages(HostType, Jid, Buffer) -> Result when HostType :: mongooseim:host_type(), @@ -432,7 +386,7 @@ unacknowledged_message(Acc, JID) -> Buffer :: [mongoose_acc:t()], Result :: [mongoose_acc:t()]. filter_unacknowledged_messages(HostType, Jid, Buffer) -> - run_fold(filter_unacknowledged_messages, HostType, Buffer, #{jid => Jid}). + run_hook_for_host_type(filter_unacknowledged_messages, HostType, Buffer, #{jid => Jid}). %%% @doc The `unregister_subhost' hook is called when a component %%% is unregistered from ejabberd_router or a subdomain is removed from mongoose_subdomain_core. @@ -441,9 +395,7 @@ filter_unacknowledged_messages(HostType, Jid, Buffer) -> Result :: any(). unregister_subhost(LDomain) -> Params = #{ldomain => LDomain}, - Args = [LDomain], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_global_hook(unregister_subhost, ok, ParamsWithLegacyArgs). + run_global_hook(unregister_subhost, ok, Params). -spec user_available_hook(Acc, JID) -> Result when Acc :: mongoose_acc:t(), @@ -451,10 +403,8 @@ unregister_subhost(LDomain) -> Result :: mongoose_acc:t(). user_available_hook(Acc, JID) -> Params = #{jid => JID}, - Args = [JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(user_available_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(user_available_hook, HostType, Acc, Params). %%% @doc The `user_ping_response' hook is called when a user responds to a ping, or times out -spec user_ping_response(HostType, Acc, JID, Response, TDelta) -> Result when @@ -466,9 +416,7 @@ user_available_hook(Acc, JID) -> Result :: simple_acc(). user_ping_response(HostType, Acc, JID, Response, TDelta) -> Params = #{jid => JID, response => Response, time_delta => TDelta}, - Args = [HostType, JID, Response, TDelta], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(user_ping_response, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(user_ping_response, HostType, Acc, Params). %%% @doc The `vcard_set' hook is called to inform that the vcard %%% has been set in mod_vcard backend. @@ -479,7 +427,9 @@ user_ping_response(HostType, Acc, JID, Response, TDelta) -> VCard :: exml:element(), Result :: any(). vcard_set(HostType, Server, LUser, VCard) -> - run_hook_for_host_type(vcard_set, HostType, ok, [HostType, LUser, Server, VCard]). + JID = jid:make_bare(LUser, Server), + Params = #{jid => JID, vcard => VCard}, + run_hook_for_host_type(vcard_set, HostType, ok, Params). -spec xmpp_send_element(HostType, Acc, El) -> Result when HostType :: mongooseim:host_type(), @@ -488,9 +438,7 @@ vcard_set(HostType, Server, LUser, VCard) -> Result :: mongoose_acc:t(). xmpp_send_element(HostType, Acc, El) -> Params = #{el => El}, - Args = [El], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(xmpp_send_element, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(xmpp_send_element, HostType, Acc, Params). %%% @doc The `xmpp_stanza_dropped' hook is called to inform that %%% an xmpp stanza has been dropped. @@ -502,10 +450,8 @@ xmpp_send_element(HostType, Acc, El) -> Result :: any(). xmpp_stanza_dropped(Acc, From, To, Packet) -> Params = #{from => From, to => To, packet => Packet}, - Args = [From, To, Packet], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(xmpp_stanza_dropped, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(xmpp_stanza_dropped, HostType, Acc, Params). %% C2S related hooks @@ -517,10 +463,8 @@ xmpp_stanza_dropped(Acc, From, To, Packet) -> Result :: [jid:simple_jid()]. c2s_broadcast_recipients(State, Type, From, Packet) -> Params = #{state => State, type => Type, from => From, packet => Packet}, - Args = [State, Type, From, Packet], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_c2s:get_host_type(State), - run_hook_for_host_type(c2s_broadcast_recipients, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(c2s_broadcast_recipients, HostType, [], Params). -spec c2s_stream_features(HostType, LServer) -> Result when HostType :: mongooseim:host_type(), @@ -528,15 +472,14 @@ c2s_broadcast_recipients(State, Type, From, Packet) -> Result :: [exml:element()]. c2s_stream_features(HostType, LServer) -> Params = #{lserver => LServer}, - Args = [HostType, LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(c2s_stream_features, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(c2s_stream_features, HostType, [], Params). -spec check_bl_c2s(IP) -> Result when IP :: inet:ip_address(), Result :: boolean(). check_bl_c2s(IP) -> - run_global_hook(check_bl_c2s, false, [IP]). + Params = #{ip => IP}, + run_global_hook(check_bl_c2s, false, Params). -spec forbidden_session_hook(HostType, Acc, JID) -> Result when HostType :: mongooseim:host_type(), @@ -544,7 +487,8 @@ check_bl_c2s(IP) -> JID :: jid:jid(), Result :: mongoose_acc:t(). forbidden_session_hook(HostType, Acc, JID) -> - run_hook_for_host_type(forbidden_session_hook, HostType, Acc, [JID]). + Params = #{jid => JID}, + run_hook_for_host_type(forbidden_session_hook, HostType, Acc, Params). -spec session_opening_allowed_for_user(HostType, JID) -> Result when HostType :: mongooseim:host_type(), @@ -552,7 +496,8 @@ forbidden_session_hook(HostType, Acc, JID) -> Result :: allow | any(). %% anything else than 'allow' is interpreted %% as not allowed session_opening_allowed_for_user(HostType, JID) -> - run_hook_for_host_type(session_opening_allowed_for_user, HostType, allow, [JID]). + Params = #{jid => JID}, + run_hook_for_host_type(session_opening_allowed_for_user, HostType, allow, Params). %% Privacy related hooks @@ -566,12 +511,9 @@ session_opening_allowed_for_user(HostType, JID) -> privacy_check_packet(Acc, JID, PrivacyList, FromToNameType, Dir) -> Params = #{jid => JID, privacy_list => PrivacyList, from_to_name_type => FromToNameType, dir => Dir}, - Args = [JID, PrivacyList, FromToNameType, Dir], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), AccWithRes = mongoose_acc:set(hook, result, allow, Acc), - run_hook_for_host_type(privacy_check_packet, HostType, AccWithRes, - ParamsWithLegacyArgs). + run_hook_for_host_type(privacy_check_packet, HostType, AccWithRes, Params). -spec privacy_get_user_list(HostType, JID) -> Result when HostType :: mongooseim:host_type(), @@ -579,9 +521,7 @@ privacy_check_packet(Acc, JID, PrivacyList, FromToNameType, Dir) -> Result :: mongoose_privacy:userlist(). privacy_get_user_list(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). + run_hook_for_host_type(privacy_get_user_list, HostType, #userlist{}, Params). -spec privacy_iq_get(HostType, Acc, From, To, IQ, PrivList) -> Result when HostType :: mongooseim:host_type(), @@ -593,9 +533,7 @@ privacy_get_user_list(HostType, JID) -> Result :: mongoose_acc:t(). privacy_iq_get(HostType, Acc, From, To, IQ, PrivList) -> Params = #{from => From, to => To, iq => IQ, priv_list => PrivList}, - Args = [From, To, IQ, PrivList], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(privacy_iq_get, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(privacy_iq_get, HostType, Acc, Params). -spec privacy_iq_set(HostType, Acc, From, To, IQ) -> Result when HostType :: mongooseim:host_type(), @@ -606,9 +544,7 @@ privacy_iq_get(HostType, Acc, From, To, IQ, PrivList) -> Result :: mongoose_acc:t(). privacy_iq_set(HostType, Acc, From, To, IQ) -> Params = #{from => From, to => To, iq => IQ}, - Args = [From, To, IQ], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(privacy_iq_set, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(privacy_iq_set, HostType, Acc, Params). -spec privacy_updated_list(HostType, OldList, NewList) -> Result when HostType :: mongooseim:host_type(), @@ -617,9 +553,7 @@ privacy_iq_set(HostType, Acc, From, To, IQ) -> Result :: false | mongoose_privacy:userlist(). privacy_updated_list(HostType, 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). + run_hook_for_host_type(privacy_updated_list, HostType, false, Params). %% Session management related hooks @@ -631,10 +565,8 @@ privacy_updated_list(HostType, OldList, NewList) -> Result :: mongoose_acc:t(). offline_groupchat_message_hook(Acc, From, To, Packet) -> Params = #{from => From, to => To, packet => Packet}, - Args = [From, To, Packet], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(offline_groupchat_message_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(offline_groupchat_message_hook, HostType, Acc, Params). -spec offline_message_hook(Acc, From, To, Packet) -> Result when Acc :: mongoose_acc:t(), @@ -644,10 +576,8 @@ offline_groupchat_message_hook(Acc, From, To, Packet) -> Result :: mongoose_acc:t(). offline_message_hook(Acc, From, To, Packet) -> Params = #{from => From, to => To, packet => Packet}, - Args = [From, To, Packet], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(offline_message_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(offline_message_hook, HostType, Acc, Params). -spec set_presence_hook(Acc, JID, Presence) -> Result when Acc :: mongoose_acc:t(), @@ -655,10 +585,9 @@ offline_message_hook(Acc, From, To, Packet) -> Presence :: any(), Result :: mongoose_acc:t(). set_presence_hook(Acc, JID, Presence) -> - #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, + Params = #{jid => JID, presence => Presence}, HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(set_presence_hook, HostType, Acc, - [LUser, LServer, LResource, Presence]). + run_hook_for_host_type(set_presence_hook, HostType, Acc, Params). -spec sm_broadcast(Acc, From, To, Broadcast, SessionCount) -> Result when Acc :: mongoose_acc:t(), @@ -669,10 +598,8 @@ set_presence_hook(Acc, JID, Presence) -> Result :: mongoose_acc:t(). sm_broadcast(Acc, From, To, Broadcast, SessionCount) -> Params = #{from => From, to => To, broadcast => Broadcast, session_count => SessionCount}, - Args = [From, To, Broadcast, SessionCount], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(sm_broadcast, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(sm_broadcast, HostType, Acc, Params). -spec sm_filter_offline_message(HostType, From, To, Packet) -> Result when HostType :: mongooseim:host_type(), @@ -682,10 +609,7 @@ sm_broadcast(Acc, From, To, Broadcast, SessionCount) -> Result :: boolean(). sm_filter_offline_message(HostType, From, To, Packet) -> Params = #{from => From, to => To, packet => Packet}, - Args = [From, To, Packet], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(sm_filter_offline_message, HostType, false, - ParamsWithLegacyArgs). + run_hook_for_host_type(sm_filter_offline_message, HostType, false, Params). -spec sm_register_connection_hook(HostType, SID, JID, Info) -> Result when HostType :: mongooseim:host_type(), @@ -695,10 +619,7 @@ sm_filter_offline_message(HostType, From, To, Packet) -> Result :: ok. sm_register_connection_hook(HostType, SID, JID, Info) -> Params = #{sid => SID, jid => JID, info => Info}, - Args = [HostType, SID, JID, Info], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(sm_register_connection_hook, HostType, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(sm_register_connection_hook, HostType, ok, Params). -spec sm_remove_connection_hook(Acc, SID, JID, Info, Reason) -> Result when Acc :: mongoose_acc:t(), @@ -709,11 +630,8 @@ sm_register_connection_hook(HostType, SID, JID, Info) -> Result :: mongoose_acc:t(). sm_remove_connection_hook(Acc, SID, JID, Info, Reason) -> Params = #{sid => SID, jid => JID, info => Info, reason => Reason}, - Args = [SID, JID, Info, Reason], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(sm_remove_connection_hook, HostType, Acc, - ParamsWithLegacyArgs). + run_hook_for_host_type(sm_remove_connection_hook, HostType, Acc, Params). -spec unset_presence_hook(Acc, JID, Status) -> Result when Acc :: mongoose_acc:t(), @@ -721,20 +639,16 @@ sm_remove_connection_hook(Acc, SID, JID, Info, Reason) -> Status :: binary(), Result :: mongoose_acc:t(). unset_presence_hook(Acc, JID, Status) -> - #jid{luser = LUser, lserver = LServer, lresource = LResource} = JID, Params = #{jid => JID, status => Status}, - Args = [LUser, LServer, LResource, Status], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(unset_presence_hook, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(unset_presence_hook, HostType, Acc, Params). -spec xmpp_bounce_message(Acc) -> Result when Acc :: mongoose_acc:t(), Result :: mongoose_acc:t(). xmpp_bounce_message(Acc) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(xmpp_bounce_message, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(xmpp_bounce_message, HostType, Acc, #{}). %% Roster related hooks @@ -745,17 +659,16 @@ xmpp_bounce_message(Acc) -> Result :: mongoose_acc:t(). roster_get(Acc, JID) -> Params = #{jid => JID}, - Args = [JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mongoose_acc:host_type(Acc), - run_hook_for_host_type(roster_get, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_get, HostType, Acc, Params). %%% @doc The `roster_groups' hook is called to extract roster groups. -spec roster_groups(LServer) -> Result when LServer :: jid:lserver(), Result :: list(). roster_groups(LServer) -> - run_hook_for_host_type(roster_groups, LServer, [], [LServer]). + Params = #{lserver => LServer}, + run_hook_for_host_type(roster_groups, LServer, [], Params). %%% @doc The `roster_get_jid_info' hook is called to determine the %%% subscription state between a given pair of users. @@ -772,9 +685,7 @@ roster_groups(LServer) -> Result :: {mod_roster:subscription_state(), [binary()]}. roster_get_jid_info(HostType, ToJID, 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). + run_hook_for_host_type(roster_get_jid_info, HostType, {none, []}, Params). %%% @doc The `roster_get_subscription_lists' hook is called to extract %%% user's subscription list. @@ -786,9 +697,7 @@ roster_get_jid_info(HostType, ToJID, RemBareJID) -> roster_get_subscription_lists(HostType, Acc, JID) -> BareJID = jid:to_bare(JID), Params = #{jid => BareJID}, - Args = [BareJID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(roster_get_subscription_lists, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_get_subscription_lists, HostType, Acc, Params). %%% @doc The `roster_get_versioning_feature' hook is %%% called to determine if roster versioning is enabled. @@ -796,9 +705,7 @@ roster_get_subscription_lists(HostType, Acc, JID) -> HostType :: mongooseim:host_type(), Result :: [exml:element()]. roster_get_versioning_feature(HostType) -> - Args = [HostType], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), - run_hook_for_host_type(roster_get_versioning_feature, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(roster_get_versioning_feature, HostType, [], #{}). %%% @doc The `roster_in_subscription' hook is called to determine %%% if a subscription presence is routed to a user. @@ -812,10 +719,8 @@ roster_get_versioning_feature(HostType) -> roster_in_subscription(Acc, To, From, Type, Reason) -> ToJID = jid:to_bare(To), 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), - run_hook_for_host_type(roster_in_subscription, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_in_subscription, HostType, Acc, Params). %%% @doc The `roster_out_subscription' hook is called %%% when a user sends out subscription. @@ -828,10 +733,8 @@ roster_in_subscription(Acc, To, From, Type, Reason) -> roster_out_subscription(Acc, From, To, Type) -> FromJID = jid:to_bare(From), Params = #{to => To, from => FromJID, type => Type}, - Args = [FromJID, To, Type], HostType = mongoose_acc:host_type(Acc), - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(roster_out_subscription, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_out_subscription, HostType, Acc, Params). %%% @doc The `roster_process_item' hook is called when a user's roster is set. -spec roster_process_item(HostType, LServer, Item) -> Result when @@ -841,9 +744,7 @@ roster_out_subscription(Acc, From, To, Type) -> Result :: mod_roster:roster(). roster_process_item(HostType, LServer, Item) -> Params = #{lserver => LServer}, - Args = [LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(roster_process_item, HostType, Item, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_process_item, HostType, Item, Params). %%% @doc The `roster_push' hook is called when a roster item is %%% being pushed and roster versioning is not enabled. @@ -854,9 +755,7 @@ roster_process_item(HostType, LServer, Item) -> Result :: any(). roster_push(HostType, From, Item) -> Params = #{from => From, item => Item}, - Args = [HostType, From, Item], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(roster_push, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_push, HostType, ok, Params). %%% @doc The `roster_set' hook is called when a user's roster is set through an IQ. -spec roster_set(HostType, From, To, SubEl) -> Result when @@ -867,9 +766,7 @@ roster_push(HostType, From, Item) -> Result :: any(). roster_set(HostType, From, To, SubEl) -> Params = #{from => From, to => To, sub_el => SubEl}, - Args = [From, To, SubEl], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(roster_set, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(roster_set, HostType, ok, Params). %% MUC related hooks @@ -888,9 +785,7 @@ roster_set(HostType, From, To, SubEl) -> Result :: boolean(). is_muc_room_owner(HostType, Acc, Room, User) -> Params = #{acc => Acc, room => Room, user => User}, - Args = [Acc, Room, User], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(is_muc_room_owner, HostType, false, ParamsWithLegacyArgs). + run_hook_for_host_type(is_muc_room_owner, HostType, false, Params). %%% @doc The `can_access_identity' hook is called to determine if %%% a given user can see the real identity of the people in a room. @@ -901,9 +796,7 @@ is_muc_room_owner(HostType, Acc, Room, User) -> Result :: boolean(). can_access_identity(HostType, Room, User) -> Params = #{room => Room, user => User}, - Args = [HostType, Room, User], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(can_access_identity, HostType, false, ParamsWithLegacyArgs). + run_hook_for_host_type(can_access_identity, HostType, false, Params). %%% @doc The `can_access_room' hook is called to determine %%% if a given user can access a room. @@ -915,9 +808,7 @@ can_access_identity(HostType, Room, User) -> Result :: boolean(). can_access_room(HostType, Acc, Room, User) -> Params = #{acc => Acc, room => Room, user => User}, - Args = [Acc, Room, User], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(can_access_room, HostType, false, ParamsWithLegacyArgs). + run_hook_for_host_type(can_access_room, HostType, false, Params). -spec acc_room_affiliations(Acc, Room) -> NewAcc when Acc :: mongoose_acc:t(), @@ -925,10 +816,8 @@ can_access_room(HostType, Acc, Room, User) -> NewAcc :: mongoose_acc:t(). acc_room_affiliations(Acc, Room) -> Params = #{room => Room}, - Args = [Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mod_muc_light_utils:acc_to_host_type(Acc), - run_hook_for_host_type(acc_room_affiliations, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(acc_room_affiliations, HostType, Acc, Params). -spec room_exists(HostType, Room) -> Result when HostType :: mongooseim:host_type(), @@ -936,9 +825,7 @@ acc_room_affiliations(Acc, Room) -> Result :: boolean(). room_exists(HostType, Room) -> Params = #{room => Room}, - Args = [HostType, Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(room_exists, HostType, false, ParamsWithLegacyArgs). + run_hook_for_host_type(room_exists, HostType, false, Params). -spec room_new_affiliations(Acc, Room, NewAffs, Version) -> NewAcc when Acc :: mongoose_acc:t(), @@ -948,10 +835,8 @@ room_exists(HostType, Room) -> NewAcc :: mongoose_acc:t(). room_new_affiliations(Acc, Room, NewAffs, Version) -> Params = #{room => Room, new_affs => NewAffs, version => Version}, - Args = [Room, NewAffs, Version], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), HostType = mod_muc_light_utils:acc_to_host_type(Acc), - run_hook_for_host_type(room_new_affiliations, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(room_new_affiliations, HostType, Acc, Params). %% MAM related hooks @@ -966,9 +851,7 @@ room_new_affiliations(Acc, Room, NewAffs, Version) -> Result :: undefined | mod_mam:archive_id(). mam_archive_id(HostType, Owner) -> Params = #{owner => Owner}, - Args = [HostType, Owner], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_archive_id, HostType, undefined, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_archive_id, HostType, undefined, Params). %%% @doc The `mam_archive_size' hook is called to determine the size %%% of the archive for a given JID @@ -979,10 +862,7 @@ mam_archive_id(HostType, Owner) -> Result :: integer(). mam_archive_size(HostType, ArchiveID, Owner) -> Params = #{archive_id => ArchiveID, owner => Owner}, - Args = [HostType, ArchiveID, Owner], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_archive_size, HostType, 0, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_archive_size, HostType, 0, Params). %%% @doc The `mam_get_behaviour' hooks is called to determine if a message %%% should be archived or not based on a given pair of JIDs. @@ -995,10 +875,7 @@ mam_archive_size(HostType, ArchiveID, Owner) -> Result :: mod_mam:archive_behaviour(). mam_get_behaviour(HostType, ArchiveID, Owner, Remote) -> Params = #{archive_id => ArchiveID, owner => Owner, remote => Remote}, - Args = [HostType, ArchiveID, Owner, Remote], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_get_behaviour, HostType, always, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_get_behaviour, HostType, always, Params). %%% @doc The `mam_set_prefs' hook is called to set a user's archive preferences. %%% @@ -1015,11 +892,7 @@ mam_get_behaviour(HostType, ArchiveID, Owner, Remote) -> mam_set_prefs(HostType, ArchiveID, Owner, DefaultMode, AlwaysJIDs, NeverJIDs) -> Params = #{archive_id => ArchiveID, owner => Owner, default_mode => DefaultMode, always_jids => AlwaysJIDs, never_jids => NeverJIDs}, - Args = [HostType, ArchiveID, Owner, - DefaultMode, AlwaysJIDs, NeverJIDs], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_set_prefs, HostType, {error, not_implemented}, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_set_prefs, HostType, {error, not_implemented}, Params). %%% @doc The `mam_get_prefs' hook is called to read %%% the archive settings for a given user. @@ -1031,11 +904,8 @@ mam_set_prefs(HostType, ArchiveID, Owner, DefaultMode, AlwaysJIDs, NeverJIDs) - Result :: mod_mam:preference() | {error, Reason :: term()}. mam_get_prefs(HostType, DefaultMode, ArchiveID, Owner) -> Params = #{archive_id => ArchiveID, owner => Owner}, - Args = [HostType, ArchiveID, Owner], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialAccValue = {DefaultMode, [], []}, %% mod_mam:preference() type - run_hook_for_host_type(mam_get_prefs, HostType, InitialAccValue, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_get_prefs, HostType, InitialAccValue, Params). %%% @doc The `mam_remove_archive' hook is called in order to %%% remove the entire archive for a particular user. @@ -1045,10 +915,7 @@ mam_get_prefs(HostType, DefaultMode, ArchiveID, Owner) -> Owner :: jid:jid(). mam_remove_archive(HostType, ArchiveID, Owner) -> Params = #{archive_id => ArchiveID, owner => Owner}, - Args = [HostType, ArchiveID, Owner], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_remove_archive, HostType, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_remove_archive, HostType, ok, Params). %%% @doc The `mam_lookup_messages' hook is to retrieve %%% archived messages for given search parameters. @@ -1057,11 +924,9 @@ mam_remove_archive(HostType, ArchiveID, Owner) -> Params :: map(), Result :: {ok, mod_mam:lookup_result()}. mam_lookup_messages(HostType, Params) -> - Args = [HostType, Params], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialLookupValue = {0, 0, []}, %% mod_mam:lookup_result() type run_hook_for_host_type(mam_lookup_messages, HostType, {ok, InitialLookupValue}, - ParamsWithLegacyArgs). + Params). %%% @doc The `mam_archive_message' hook is called in order %%% to store the message in the archive. @@ -1071,9 +936,7 @@ mam_lookup_messages(HostType, Params) -> Params :: mod_mam:archive_message_params(), Result :: ok | {error, timeout}. mam_archive_message(HostType, Params) -> - Args = [HostType, Params], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_archive_message, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_archive_message, HostType, ok, Params). %%% @doc The `mam_flush_messages' hook is run after the async bulk write %%% happens for messages despite the result of the write. @@ -1081,17 +944,12 @@ mam_archive_message(HostType, Params) -> MessageCount :: integer()) -> ok. mam_flush_messages(HostType, MessageCount) -> Params = #{count => MessageCount}, - Args = [HostType, MessageCount], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_flush_messages, HostType, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_flush_messages, HostType, ok, Params). %% @doc Waits until all pending messages are written -spec mam_archive_sync(HostType :: mongooseim:host_type()) -> ok. mam_archive_sync(HostType) -> - Args = [HostType], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), - run_hook_for_host_type(mam_archive_sync, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_archive_sync, HostType, ok, #{}). %% @doc Notifies of a message retraction -spec mam_retraction(mongooseim:host_type(), @@ -1099,7 +957,7 @@ mam_archive_sync(HostType) -> mod_mam:archive_message_params()) -> mod_mam_utils:retraction_info(). mam_retraction(HostType, RetractionInfo, Env) -> - run_fold(mam_retraction, HostType, RetractionInfo, Env). + run_hook_for_host_type(mam_retraction, HostType, RetractionInfo, Env). %% MAM MUC related hooks @@ -1120,10 +978,7 @@ mam_retraction(HostType, RetractionInfo, Env) -> Result :: undefined | mod_mam:archive_id(). mam_muc_archive_id(HostType, Owner) -> Params = #{owner => Owner}, - Args = [HostType, Owner], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_muc_archive_id, HostType, undefined, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_archive_id, HostType, undefined, Params). %%% @doc The `mam_muc_archive_size' hook is called to determine %%% the archive size for a given room. @@ -1134,9 +989,7 @@ mam_muc_archive_id(HostType, Owner) -> Result :: integer(). mam_muc_archive_size(HostType, ArchiveID, Room) -> Params = #{archive_id => ArchiveID, room => Room}, - Args = [HostType, ArchiveID, Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_muc_archive_size, HostType, 0, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_archive_size, HostType, 0, Params). %%% @doc The `mam_muc_get_behaviour' hooks is called to determine if a message should %%% be archived or not based on the given room and user JIDs. @@ -1149,11 +1002,8 @@ mam_muc_archive_size(HostType, ArchiveID, Room) -> Result :: mod_mam:archive_behaviour(). mam_muc_get_behaviour(HostType, ArchiveID, Room, Remote) -> Params = #{archive_id => ArchiveID, room => Room, remote => Remote}, - Args = [HostType, ArchiveID, Room, Remote], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), DefaultBehaviour = always, %% mod_mam:archive_behaviour() type - run_hook_for_host_type(mam_muc_get_behaviour, HostType, DefaultBehaviour, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_get_behaviour, HostType, DefaultBehaviour, Params). %%% @doc The `mam_muc_set_prefs' hook is called to set a room's archive preferences. %%% @@ -1170,12 +1020,8 @@ mam_muc_get_behaviour(HostType, ArchiveID, Room, Remote) -> mam_muc_set_prefs(HostType, ArchiveID, Room, DefaultMode, AlwaysJIDs, NeverJIDs) -> Params = #{archive_id => ArchiveID, room => Room, default_mode => DefaultMode, always_jids => AlwaysJIDs, never_jids => NeverJIDs}, - Args = [HostType, ArchiveID, Room, DefaultMode, - AlwaysJIDs, NeverJIDs], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialAcc = {error, not_implemented}, - run_hook_for_host_type(mam_muc_set_prefs, HostType, InitialAcc, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_set_prefs, HostType, InitialAcc, Params). %%% @doc The `mam_muc_get_prefs' hook is called to read %%% the archive settings for a given room. @@ -1187,11 +1033,8 @@ mam_muc_set_prefs(HostType, ArchiveID, Room, DefaultMode, AlwaysJIDs, NeverJIDs) Result :: mod_mam:preference() | {error, Reason :: term()}. mam_muc_get_prefs(HostType, DefaultMode, ArchiveID, Room) -> Params = #{archive_id => ArchiveID, room => Room}, - Args = [HostType, ArchiveID, Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialAcc = {DefaultMode, [], []}, %% mod_mam:preference() type - run_hook_for_host_type(mam_muc_get_prefs, HostType, InitialAcc, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_get_prefs, HostType, InitialAcc, Params). %%% @doc The `mam_muc_remove_archive' hook is called in order to remove the entire %%% archive for a particular user. @@ -1201,10 +1044,7 @@ mam_muc_get_prefs(HostType, DefaultMode, ArchiveID, Room) -> Room :: jid:jid(). mam_muc_remove_archive(HostType, ArchiveID, Room) -> Params = #{archive_id => ArchiveID, room => Room}, - Args = [HostType, ArchiveID, Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_muc_remove_archive, HostType, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_remove_archive, HostType, ok, Params). %%% @doc The `mam_muc_lookup_messages' hook is to retrieve archived %%% MUC messages for any given search parameters. @@ -1213,11 +1053,9 @@ mam_muc_remove_archive(HostType, ArchiveID, Room) -> Params :: map(), Result :: {ok, mod_mam:lookup_result()}. mam_muc_lookup_messages(HostType, Params) -> - Args = [HostType, Params], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialLookupValue = {0, 0, []}, %% mod_mam:lookup_result() type run_hook_for_host_type(mam_muc_lookup_messages, HostType, {ok, InitialLookupValue}, - ParamsWithLegacyArgs). + Params). %%% @doc The `mam_muc_archive_message' hook is called in order %%% to store the MUC message in the archive. @@ -1226,9 +1064,7 @@ mam_muc_lookup_messages(HostType, Params) -> Params :: mod_mam:archive_message_params(), Result :: ok | {error, timeout}. mam_muc_archive_message(HostType, Params) -> - Args = [HostType, Params], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_muc_archive_message, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_archive_message, HostType, ok, Params). %%% @doc The `mam_muc_flush_messages' hook is run after the async bulk write %%% happens for MUC messages despite the result of the write. @@ -1236,17 +1072,12 @@ mam_muc_archive_message(HostType, Params) -> MessageCount :: integer()) -> ok. mam_muc_flush_messages(HostType, MessageCount) -> Params = #{count => MessageCount}, - Args = [HostType, MessageCount], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mam_muc_flush_messages, HostType, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_flush_messages, HostType, ok, Params). %% @doc Waits until all pending messages are written -spec mam_muc_archive_sync(HostType :: mongooseim:host_type()) -> ok. mam_muc_archive_sync(HostType) -> - Args = [HostType], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), - run_hook_for_host_type(mam_muc_archive_sync, HostType, ok, ParamsWithLegacyArgs). + run_hook_for_host_type(mam_muc_archive_sync, HostType, ok, #{}). %% @doc Notifies of a muc message retraction -spec mam_muc_retraction(mongooseim:host_type(), @@ -1254,7 +1085,7 @@ mam_muc_archive_sync(HostType) -> mod_mam:archive_message_params()) -> mod_mam_utils:retraction_info(). mam_muc_retraction(HostType, RetractionInfo, Env) -> - run_fold(mam_muc_retraction, HostType, RetractionInfo, Env). + run_hook_for_host_type(mam_muc_retraction, HostType, RetractionInfo, Env). %% GDPR related hooks @@ -1266,9 +1097,7 @@ mam_muc_retraction(HostType, RetractionInfo, Env) -> Result :: ejabberd_gen_mam_archive:mam_pm_gdpr_data(). get_mam_pm_gdpr_data(HostType, JID) -> Params = #{jid => JID}, - Args = [HostType, JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(get_mam_pm_gdpr_data, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(get_mam_pm_gdpr_data, HostType, [], Params). %%% @doc `get_mam_muc_gdpr_data' hook is called to provide %%% a user's archive for GDPR purposes. @@ -1278,9 +1107,7 @@ get_mam_pm_gdpr_data(HostType, JID) -> Result :: ejabberd_gen_mam_archive:mam_muc_gdpr_data(). get_mam_muc_gdpr_data(HostType, JID) -> Params = #{jid => JID}, - Args = [HostType, JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(get_mam_muc_gdpr_data, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(get_mam_muc_gdpr_data, HostType, [], Params). %%% @doc `get_personal_data' hook is called to retrieve %%% a user's personal data for GDPR purposes. @@ -1290,9 +1117,7 @@ get_mam_muc_gdpr_data(HostType, JID) -> Result :: gdpr:personal_data(). get_personal_data(HostType, JID) -> Params = #{jid => JID}, - Args = [HostType, JID], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(get_personal_data, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(get_personal_data, HostType, [], Params). %% S2S related hooks @@ -1303,7 +1128,8 @@ get_personal_data(HostType, JID) -> Server :: jid:server(), Result :: any(). find_s2s_bridge(Name, Server) -> - run_global_hook(find_s2s_bridge, undefined, [Name, Server]). + Params = #{name => Name, server => Server}, + run_global_hook(find_s2s_bridge, undefined, Params). %%% @doc `s2s_allow_host' hook is called to check whether a server %%% should be allowed to be connected to. @@ -1315,7 +1141,8 @@ find_s2s_bridge(Name, Server) -> S2SHost :: jid:server(), Result :: allow | deny. s2s_allow_host(MyHost, S2SHost) -> - run_global_hook(s2s_allow_host, allow, [MyHost, S2SHost]). + Params = #{my_host => MyHost, s2s_host => S2SHost}, + run_global_hook(s2s_allow_host, allow, Params). %%% @doc `s2s_connect_hook' hook is called when a s2s connection is established. -spec s2s_connect_hook(Name, Server) -> Result when @@ -1323,7 +1150,8 @@ s2s_allow_host(MyHost, S2SHost) -> Server :: jid:server(), Result :: any(). s2s_connect_hook(Name, Server) -> - run_global_hook(s2s_connect_hook, ok, [Name, Server]). + Params = #{name => Name, server => Server}, + run_global_hook(s2s_connect_hook, ok, Params). %%% @doc `s2s_send_packet' hook is called when a message is routed. -spec s2s_send_packet(Acc, From, To, Packet) -> Result when @@ -1333,7 +1161,8 @@ s2s_connect_hook(Name, Server) -> Packet :: exml:element(), Result :: mongoose_acc:t(). s2s_send_packet(Acc, From, To, Packet) -> - run_global_hook(s2s_send_packet, Acc, [From, To, Packet]). + Params = #{from => From, to => To, packet => Packet}, + run_global_hook(s2s_send_packet, Acc, Params). %%% @doc `s2s_stream_features' hook is used to extract %%% the stream management features supported by the server. @@ -1343,9 +1172,7 @@ s2s_send_packet(Acc, From, To, Packet) -> Result :: [exml:element()]. s2s_stream_features(HostType, LServer) -> Params = #{lserver => LServer}, - Args = [HostType, LServer], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(s2s_stream_features, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(s2s_stream_features, HostType, [], Params). %%% @doc `s2s_receive_packet' hook is called when %%% an incoming stanza is routed by the server. @@ -1353,7 +1180,7 @@ s2s_stream_features(HostType, LServer) -> Acc :: mongoose_acc:t(), Result :: mongoose_acc:t(). s2s_receive_packet(Acc) -> - run_global_hook(s2s_receive_packet, Acc, []). + run_global_hook(s2s_receive_packet, Acc, #{}). %% Discovery related hooks @@ -1361,28 +1188,24 @@ s2s_receive_packet(Acc) -> -spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). disco_local_identity(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_local_identity, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_local_identity, HostType, Acc, #{}). %%% @doc `disco_sm_identity' hook is called to get the identity of the %%% client when a discovery IQ gets to session management. -spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). disco_sm_identity(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_sm_identity, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_sm_identity, HostType, Acc, #{}). %%% @doc `disco_local_items' hook is called to extract items associated with the server. -spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). disco_local_items(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_local_items, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_local_items, HostType, Acc, #{}). %%% @doc `disco_sm_items' hook is called to get the items associated %%% with the client when a discovery IQ gets to session management. -spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). disco_sm_items(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_sm_items, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_sm_items, HostType, Acc, #{}). %%% @doc `disco_local_features' hook is called to extract features %%% offered by the server. @@ -1394,21 +1217,18 @@ disco_local_features(Acc = #{host_type := HostType}) -> %%% when a discovery IQ gets to session management. -spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). disco_sm_features(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_sm_features, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_sm_features, HostType, Acc, #{}). %%% @doc `disco_muc_features' hook is called to get the features %%% supported by the MUC (Light) service. -spec disco_muc_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). disco_muc_features(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_muc_features, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_muc_features, HostType, Acc, #{}). %%% @doc `disco_info' hook is called to extract information about the server. -spec disco_info(mongoose_disco:info_acc()) -> mongoose_disco:info_acc(). disco_info(Acc = #{host_type := HostType}) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(disco_info, HostType, Acc, ParamsWithLegacyArgs). + run_hook_for_host_type(disco_info, HostType, Acc, #{}). %% AMP related hooks @@ -1421,10 +1241,8 @@ disco_info(Acc = #{host_type := HostType}) -> Result :: mod_amp:amp_match_result(). amp_check_condition(HostType, Strategy, Rule) -> Params = #{strategy => Strategy, rule => Rule}, - Args = [Strategy, Rule], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), InitialAcc = no_match, %% mod_amp:amp_match_result() type - run_hook_for_host_type(amp_check_condition, HostType, InitialAcc, ParamsWithLegacyArgs). + run_hook_for_host_type(amp_check_condition, HostType, InitialAcc, Params). %%% @doc The `amp_determine_strategy' hook is called when checking to determine %%% which strategy will be chosen when executing AMP rules. @@ -1437,11 +1255,8 @@ amp_check_condition(HostType, Strategy, Rule) -> Result :: mod_amp:amp_strategy(). amp_determine_strategy(HostType, From, To, Packet, Event) -> Params = #{from => From, to => To, packet => Packet, event => Event}, - Args = [From, To, Packet, Event], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), DefaultStrategy = amp_strategy:null_strategy(), - run_hook_for_host_type(amp_determine_strategy, HostType, DefaultStrategy, - ParamsWithLegacyArgs). + run_hook_for_host_type(amp_determine_strategy, HostType, DefaultStrategy, Params). %%% @doc The `amp_verify_support' hook is called when checking %%% whether the host supports given AMP rules. @@ -1451,9 +1266,7 @@ amp_determine_strategy(HostType, From, To, Packet, Event) -> Result :: [mod_amp:amp_rule_support()]. amp_verify_support(HostType, Rules) -> Params = #{rules => Rules}, - Args = [Rules], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(amp_verify_support, HostType, [], ParamsWithLegacyArgs). + run_hook_for_host_type(amp_verify_support, HostType, [], Params). %% MUC and MUC Light related hooks @@ -1464,9 +1277,7 @@ amp_verify_support(HostType, Rules) -> Result :: exml:element(). filter_room_packet(HostType, Packet, EventData) -> Params = #{packet => Packet, event_data => EventData}, - Args = [HostType, EventData], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(filter_room_packet, HostType, Packet, ParamsWithLegacyArgs). + run_hook_for_host_type(filter_room_packet, HostType, Packet, Params). %%% @doc The `forget_room' hook is called when a room is removed from the database. -spec forget_room(HostType, MucHost, Room) -> Result when @@ -1476,9 +1287,7 @@ filter_room_packet(HostType, Packet, EventData) -> Result :: any(). forget_room(HostType, MucHost, Room) -> Params = #{muc_host => MucHost, room => Room}, - Args = [HostType, MucHost, Room], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(forget_room, HostType, #{}, ParamsWithLegacyArgs). + run_hook_for_host_type(forget_room, HostType, #{}, Params). -spec invitation_sent(HookServer, Host, RoomJID, From, To, Reason) -> Result when HookServer :: jid:server(), @@ -1489,8 +1298,8 @@ forget_room(HostType, MucHost, Room) -> Reason :: binary(), Result :: any(). invitation_sent(HookServer, Host, RoomJID, From, To, Reason) -> - run_hook_for_host_type(invitation_sent, HookServer, ok, - [HookServer, Host, RoomJID, From, To, Reason]). + Params = #{host => Host, room_jid => RoomJID, from => From, to => To, reason => Reason}, + run_hook_for_host_type(invitation_sent, HookServer, ok, Params). %%% @doc The `join_room' hook is called when a user joins a MUC room. -spec join_room(HookServer, Room, Host, JID, MucJID) -> Result when @@ -1501,8 +1310,8 @@ invitation_sent(HookServer, Host, RoomJID, From, To, Reason) -> MucJID :: jid:jid(), Result :: any(). join_room(HookServer, Room, Host, JID, MucJID) -> - run_hook_for_host_type(join_room, HookServer, ok, - [HookServer, Room, Host, JID, MucJID]). + Params = #{room => Room, host => Host, jid => JID, muc_jid => MucJID}, + run_hook_for_host_type(join_room, HookServer, ok, Params). %%% @doc The `leave_room' hook is called when a user joins a MUC room. -spec leave_room(HookServer, Room, Host, JID, MucJID) -> Result when @@ -1513,8 +1322,8 @@ join_room(HookServer, Room, Host, JID, MucJID) -> MucJID :: jid:jid(), Result :: any(). leave_room(HookServer, Room, Host, JID, MucJID) -> - run_hook_for_host_type(leave_room, HookServer, ok, - [HookServer, Room, Host, JID, MucJID]). + Params = #{room => Room, host => Host, jid => JID, muc_jid => MucJID}, + run_hook_for_host_type(leave_room, HookServer, ok, Params). %%% @doc The `room_packet' hook is called when a message is added to room's history. -spec room_packet(Server, FromNick, FromJID, JID, Packet) -> Result when @@ -1525,15 +1334,15 @@ leave_room(HookServer, Room, Host, JID, MucJID) -> Packet :: exml:element(), Result :: any(). room_packet(Server, FromNick, FromJID, JID, Packet) -> - run_hook_for_host_type(room_packet, Server, ok, [FromNick, FromJID, JID, Packet]). + Params = #{from_nick => FromNick, from_jid => FromJID, jid => JID, packet => Packet}, + run_hook_for_host_type(room_packet, Server, ok, Params). -spec update_inbox_for_muc(HostType, Info) -> Result when HostType :: mongooseim:host_type(), Info :: mod_muc_room:update_inbox_for_muc_payload(), Result :: mod_muc_room:update_inbox_for_muc_payload(). update_inbox_for_muc(HostType, Info) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(update_inbox_for_muc, HostType, Info, ParamsWithLegacyArgs). + run_hook_for_host_type(update_inbox_for_muc, HostType, Info, #{}). %% Caps related hooks @@ -1545,10 +1354,8 @@ update_inbox_for_muc(HostType, Info) -> 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, ParamsWithLegacyArgs). + run_hook_for_host_type(caps_recognised, HostType, Acc, Params). %% PubSub related hooks @@ -1562,8 +1369,9 @@ caps_recognised(Acc, From, Pid, Features) -> NodeOptions :: list(), Result :: any(). pubsub_create_node(Server, PubSubHost, NodeId, Nidx, NodeOptions) -> - run_hook_for_host_type(pubsub_create_node, Server, ok, - [Server, PubSubHost, NodeId, Nidx, NodeOptions]). + Params = #{pub_sub_host => PubSubHost, node_id => NodeId, node_idx => Nidx, + node_options => NodeOptions}, + run_hook_for_host_type(pubsub_create_node, Server, ok, Params). %%% @doc The `pubsub_delete_node' hook is called to inform %%% that a pubsub node is deleted. @@ -1574,8 +1382,8 @@ pubsub_create_node(Server, PubSubHost, NodeId, Nidx, NodeOptions) -> Nidx :: mod_pubsub:nodeIdx(), Result :: any(). pubsub_delete_node(Server, PubSubHost, NodeId, Nidx) -> - run_hook_for_host_type(pubsub_delete_node, Server, ok, - [Server, PubSubHost, NodeId, Nidx]). + Params = #{pub_sub_host => PubSubHost, node_id => NodeId, node_idx => Nidx}, + run_hook_for_host_type(pubsub_delete_node, Server, ok, Params). %%% @doc The `pubsub_publish_item' hook is called to inform %%% that a pubsub item is published. @@ -1589,9 +1397,9 @@ pubsub_delete_node(Server, PubSubHost, NodeId, Nidx) -> BrPayload :: mod_pubsub:payload(), Result :: any(). pubsub_publish_item(Server, NodeId, Publisher, ServiceJID, ItemId, BrPayload) -> - run_hook_for_host_type(pubsub_publish_item, Server, ok, - [Server, NodeId, Publisher, ServiceJID, - ItemId, BrPayload]). + Params = #{node_id => NodeId, publisher => Publisher, service_jid => ServiceJID, + item_id => ItemId, payload => BrPayload}, + run_hook_for_host_type(pubsub_publish_item, Server, ok, Params). %% Global distribution related hooks @@ -1605,10 +1413,7 @@ pubsub_publish_item(Server, NodeId, Publisher, ServiceJID, ItemId, BrPayload) -> Result :: any(). mod_global_distrib_known_recipient(GlobalHost, From, To, LocalHost) -> Params = #{from => From, to => To, target_host => LocalHost}, - Args = [From, To, LocalHost], - ParamsWithLegacyArgs = ejabberd_hooks:add_args(Params, Args), - run_hook_for_host_type(mod_global_distrib_known_recipient, GlobalHost, ok, - ParamsWithLegacyArgs). + run_hook_for_host_type(mod_global_distrib_known_recipient, GlobalHost, ok, Params). %%% @doc The `mod_global_distrib_unknown_recipient' hook is called when %%% the recipient is unknown to `global_distrib'. @@ -1617,19 +1422,14 @@ mod_global_distrib_known_recipient(GlobalHost, From, To, LocalHost) -> Info :: filter_packet_acc(), Result :: any(). mod_global_distrib_unknown_recipient(GlobalHost, Info) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, []), - run_hook_for_host_type(mod_global_distrib_unknown_recipient, GlobalHost, Info, - ParamsWithLegacyArgs). + run_hook_for_host_type(mod_global_distrib_unknown_recipient, GlobalHost, Info, #{}). %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- run_global_hook(HookName, Acc, Params) when is_map(Params) -> - run_fold(HookName, global, Acc, Params); -run_global_hook(HookName, Acc, Args) when is_list(Args) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), - run_fold(HookName, global, Acc, ParamsWithLegacyArgs). + run_fold(HookName, global, Acc, Params). run_hook_for_host_type(HookName, undefined, Acc, Args) -> ?LOG_ERROR(#{what => undefined_host_type, @@ -1638,11 +1438,7 @@ run_hook_for_host_type(HookName, undefined, Acc, Args) -> Acc; run_hook_for_host_type(HookName, HostType, Acc, Params) when is_binary(HostType), is_map(Params) -> - run_fold(HookName, HostType, Acc, Params); -run_hook_for_host_type(HookName, HostType, Acc, Args) when is_binary(HostType), - is_list(Args) -> - ParamsWithLegacyArgs = ejabberd_hooks:add_args(#{}, Args), - run_fold(HookName, HostType, Acc, ParamsWithLegacyArgs). + run_fold(HookName, HostType, Acc, Params). run_fold(HookName, HostType, Acc, Params) when is_map(Params) -> {_, RetValue} = gen_hook:run_fold(HookName, HostType, Acc, Params), diff --git a/src/mongoose_http_handler.erl b/src/mongoose_http_handler.erl index ce8a3bb8a16..1c6ef979475 100644 --- a/src/mongoose_http_handler.erl +++ b/src/mongoose_http_handler.erl @@ -84,4 +84,4 @@ configurable_handler_modules() -> [mod_websockets, mongoose_client_api, mongoose_admin_api, - mongoose_graphql_cowboy_handler]. + mongoose_graphql_handler]. diff --git a/src/mongoose_import_users.erl b/src/mongoose_import_users.erl new file mode 100644 index 00000000000..442425ef997 --- /dev/null +++ b/src/mongoose_import_users.erl @@ -0,0 +1,97 @@ +-module(mongoose_import_users). + +-export([run/1]). + +-include("mongoose.hrl"). +-include("jlib.hrl"). + +-define(REGISTER_WORKERS_NUM, 10). + +-type summary() :: #{reason() => [jid:jid() | binary()]}. +-type reason() :: ok | exists | not_allowed | invalid_jid | null_password | bad_csv. + +-export_types([summary/0, reason/0]). + +-spec run(file:name()) -> {ok, summary()} | {error, file_not_found}. +run(Filename) -> + case filelib:is_file(Filename) of + true -> + {ok, CsvStream} = erl_csv:decode_new_s(Filename), + Workers = spawn_link_workers(), + WorkersQueue = queue:from_list(Workers), + {ok, do_import(CsvStream, WorkersQueue)}; + false -> + {error, file_not_found} + end. + +-spec do_import(erl_csv:csv_stream(), queue:queue()) -> summary(). +do_import(stream_end, WQueue) -> + Workers = queue:to_list(WQueue), + lists:foldl(fun accumulate_results/2, #{}, Workers); +do_import(Stream, WQueue) -> + {ok, Decoded, MoreStream} = erl_csv:decode_s(Stream), + WQueue1 = send_job_to_next_worker(Decoded, WQueue), + do_import(MoreStream, WQueue1). + +-spec spawn_link_workers() -> [pid()]. +spawn_link_workers() -> + Manager = self(), + [spawn_link(fun() -> registrator_proc(Manager) end) + || _ <- lists:seq(1, ?REGISTER_WORKERS_NUM)]. + +-spec accumulate_results(pid(), summary()) -> summary(). +accumulate_results(Pid, Map) -> + Results = get_results_from_registrator(Pid), + maps:merge_with( + fun(_Key, List1, List2) -> List1 ++ List2 end, + Map, + Results). + +-spec get_results_from_registrator(pid()) -> summary(). +get_results_from_registrator(Pid) -> + Pid ! get_result, + receive + {result, Result} -> Result + end. + +-spec send_job_to_next_worker([binary()], queue:queue()) -> queue:queue(). +send_job_to_next_worker([], WQueue) -> + WQueue; +send_job_to_next_worker([Record], WQueue) -> + {{value, Worker}, Q1} = queue:out(WQueue), + Worker ! {process, Record}, + queue:in(Worker, Q1). + +-spec registrator_proc(pid()) -> ok. +registrator_proc(Manager) -> + registrator_proc(Manager, #{}). + +-spec registrator_proc(pid(), summary()) -> ok. +registrator_proc(Manager, Map) -> + receive + {process, Data} -> + {Reason, User} = do_register(Data), + Map2 = maps:update_with(Reason, fun(List) -> [User | List] end, [User], Map), + registrator_proc(Manager, Map2); + get_result -> + Manager ! {result, Map} + end, + ok. + +-spec do_register([binary()]) -> {reason(), jid:user() | binary()}. +do_register([User, Host, Password] = List) -> + JID = jid:make(User, Host, <<>>), + case ejabberd_auth:try_register(JID, Password) of + {error, invalid_jid} -> {invalid_jid, join(List)}; + {error, Reason} -> {Reason, JID}; + _ -> {ok, JID} + end; +do_register(List) -> + {bad_csv, join(List)}. + +-spec join([binary()]) -> binary(). +join(Record) -> + JoinBinary = fun(Elem, <<"">>) -> Elem; + (Elem, Acc) -> <> + end, + lists:foldr(JoinBinary, <<"">>, Record). diff --git a/src/mongoose_session_api.erl b/src/mongoose_session_api.erl index 1ae46b975ac..ba5b1a6bb41 100644 --- a/src/mongoose_session_api.erl +++ b/src/mongoose_session_api.erl @@ -6,21 +6,16 @@ count_sessions/0, count_sessions/1, list_user_sessions/1, - list_user_sessions/2, list_resources/1, list_user_resources/1, num_resources/1, - num_resources/2, get_user_resource/2, - get_user_resource/3, list_status_users/1, list_status_users/2, num_status_users/1, num_status_users/2, - set_presence/7, set_presence/5, kick_session/2, - kick_session/4, kick_sessions/2, prepare_reason/1]). @@ -35,172 +30,223 @@ -type status() :: binary(). -type session() :: #session{}. --type status_user_info() :: {User :: jid:user(), - Server :: jid:server(), - Res :: jid:resource(), +-type status_user_info() :: {JID :: jid:jid(), Prio :: ejabberd_sm:priority(), Status :: status()}. --type session_info() :: {USR :: binary(), - Conn :: binary(), - IPS :: binary(), - Port :: inet:port_number(), +-type session_info() :: {USR :: jid:jid(), + Conn :: atom(), + Address :: {inet:ip_address(), inet:port_number()} | undefined, Prio :: ejabberd_sm:priority(), - NodeS :: binary(), + NodeS :: node(), Uptime :: integer()}. --type res_number_result() :: {ok | wrong_res_number, binary()}. +-type res_number_result() :: {ok | wrong_res_number | user_not_found, binary()}. --type kick_session_result() :: {ok | no_session, binary()}. +-type kick_user_result() :: #{binary() => term()}. --type set_presence_result() :: {ok | empty_resource, binary()}. +-type set_presence_result() :: {ok | empty_resource | user_not_found, binary()}. -export_type([res_number_result/0, - kick_session_result/0, set_presence_result/0, + kick_user_result/0, status_user_info/0, session_info/0, status/0]). --spec list_sessions() -> [session_info()]. +-spec list_sessions() -> {ok, [session_info()]}. list_sessions() -> USRIs = ejabberd_sm:get_full_session_list(), - lists:map(fun format_user_info/1, USRIs). + {ok, lists:map(fun format_user_info/1, USRIs)}. --spec list_sessions(jid:server()) -> [session_info()]. +-spec list_sessions(jid:server()) -> {ok, [session_info()]} | {domain_not_found, binary()}. list_sessions(Host) -> - USRIs = ejabberd_sm:get_vh_session_list(Host), - lists:map(fun format_user_info/1, USRIs). + case check_domain(Host) of + ok -> + USRIs = ejabberd_sm:get_vh_session_list(Host), + {ok, lists:map(fun format_user_info/1, USRIs)}; + Error -> + Error + end. --spec count_sessions() -> non_neg_integer(). +-spec count_sessions() -> {ok, non_neg_integer()}. count_sessions() -> - ejabberd_sm:get_total_sessions_number(). + {ok, ejabberd_sm:get_total_sessions_number()}. --spec count_sessions(jid:server()) -> non_neg_integer(). +-spec count_sessions(jid:server()) -> {ok, non_neg_integer()} | {domain_not_found, binary()}. count_sessions(Host) -> - ejabberd_sm:get_vh_session_number(Host). + case check_domain(Host) of + ok -> + {ok, ejabberd_sm:get_vh_session_number(Host)}; + Error -> + Error + end. --spec list_resources(jid:server()) -> [jid:literal_jid()]. +-spec list_resources(jid:server()) -> {ok, [jid:literal_jid()]} | {domain_not_found, binary()}. list_resources(Host) -> - Lst = ejabberd_sm:get_vh_session_list(Host), - [jid:to_binary(USR) || #session{usr = USR} <- Lst]. + case check_domain(Host) of + ok -> + Lst = ejabberd_sm:get_vh_session_list(Host), + {ok, [jid:to_binary(USR) || #session{usr = USR} <- Lst]}; + Error -> + Error + end. --spec list_user_resources(jid:jid()) -> [jid:literal_jid()]. +-spec list_user_resources(jid:jid()) -> {ok, [jid:literal_jid()]} | {user_not_found, binary()}. list_user_resources(JID) -> - ejabberd_sm:get_user_resources(JID). - --spec list_user_sessions(jid:user(), jid:server()) -> [session_info()]. -list_user_sessions(User, Host) -> - JID = jid:make(User, Host, <<>>), - list_user_sessions(JID). + case check_user(JID) of + ok -> + {ok, ejabberd_sm:get_user_resources(JID)}; + Error -> + Error + end. --spec list_user_sessions(jid:jid()) -> [session_info()]. +-spec list_user_sessions(jid:jid()) -> {ok, [session_info()]} | {user_not_found, binary()}. list_user_sessions(JID) -> - Resources = ejabberd_sm:get_user_resources(JID), - lists:foldl(fun(Res, Acc) -> - RJID = jid:replace_resource(JID, Res), - case ejabberd_sm:get_session(RJID) of - offline -> Acc; - Session -> [format_user_info(Session) | Acc] - end - end, [], Resources). - --spec num_resources(jid:user(), jid:server()) -> non_neg_integer(). -num_resources(User, Host) -> - JID = jid:make(User, Host, <<>>), - num_resources(JID). - --spec num_resources(jid:jid()) -> non_neg_integer(). -num_resources(JID) -> - length(ejabberd_sm:get_user_resources(JID)). + case check_user(JID) of + ok -> + Resources = ejabberd_sm:get_user_resources(JID), + {ok, lists:foldl( + fun(Res, Acc) -> + RJID = jid:replace_resource(JID, Res), + case ejabberd_sm:get_session(RJID) of + offline -> Acc; + Session -> [format_user_info(Session) | Acc] + end + end, + [], + Resources)}; + Error -> + Error + end. --spec get_user_resource(jid:user(), jid:server(), integer()) -> res_number_result(). -get_user_resource(User, Host, Num) -> - JID = jid:make(User, Host, <<>>), - get_user_resource(JID, Num). +-spec num_resources(jid:jid()) -> {ok, non_neg_integer()} | {user_not_found, binary()}. +num_resources(JID) -> + case check_user(JID) of + ok -> + {ok, length(ejabberd_sm:get_user_resources(JID))}; + Error -> + Error + end. -spec get_user_resource(jid:jid(), integer()) -> res_number_result(). get_user_resource(JID, Num) -> - Resources = ejabberd_sm:get_user_resources(JID), - case (0 < Num) and (Num =< length(Resources)) of - true -> - {ok, lists:nth(Num, Resources)}; - false -> - {wrong_res_number, iolist_to_binary(io_lib:format("Wrong resource number: ~p", [Num]))} + case check_user(JID) of + ok -> + Resources = ejabberd_sm:get_user_resources(JID), + case (0 < Num) and (Num =< length(Resources)) of + true -> + {ok, lists:nth(Num, Resources)}; + false -> + {wrong_res_number, + iolist_to_binary(io_lib:format("Wrong resource number: ~p", [Num]))} + end; + Error -> + Error end. --spec num_status_users(jid:server(), status()) -> non_neg_integer(). +-spec num_status_users(jid:server(), status()) -> {ok, non_neg_integer()} + | {domain_not_found, binary()}. num_status_users(Host, Status) -> - length(list_status_users(Host, Status)). + case check_domain(Host) of + ok -> + {ok, Sessions} = list_status_users(Host, Status), + {ok, length(Sessions)}; + Error -> + Error + end. --spec num_status_users(status()) -> non_neg_integer(). +-spec num_status_users(status()) -> {ok, non_neg_integer()}. num_status_users(Status) -> - length(list_status_users(Status)). + {ok, Sessions} = list_status_users(Status), + {ok, length(Sessions)}. --spec list_status_users(jid:server(), status()) -> [status_user_info()]. +-spec list_status_users(jid:server(), status()) -> {ok, [status_user_info()]} + | {domain_not_found, binary()}. list_status_users(Host, Status) -> - Sessions = ejabberd_sm:get_vh_session_list(Host), - get_status_list(Sessions, Status). + case check_domain(Host) of + ok -> + Sessions = ejabberd_sm:get_vh_session_list(Host), + {ok, get_status_list(Sessions, Status)}; + Error -> + Error + end. --spec list_status_users(status()) -> [status_user_info()]. +-spec list_status_users(status()) -> {ok, [status_user_info()]}. list_status_users(Status) -> Sessions = ejabberd_sm:get_full_session_list(), - get_status_list(Sessions, Status). - --spec set_presence(jid:user(), jid:server(), jid:resource(), - Type :: binary(), Show :: binary(), Status :: binary(), - Prio :: binary()) -> set_presence_result(). -set_presence(User, Host, Resource, Type, Show, Status, Priority) -> - JID = jid:make(User, Host, Resource), - set_presence(JID, Type, Show, Status, Priority). + {ok, get_status_list(Sessions, Status)}. -spec set_presence(jid:jid(), Type :: binary(), Show :: binary(), Status :: binary(), Prio :: binary()) -> set_presence_result(). set_presence(#jid{lresource = <<>>}, _Type, _Show, _Status, _Priority) -> {empty_resource, <<"The resource is empty. You need to provide a full JID">>}; set_presence(JID, Type, Show, Status, Priority) -> - Pid = ejabberd_sm:get_session_pid(JID), - USR = jid:to_binary(JID), - US = jid:to_binary(jid:to_bare(JID)), - - Children = maybe_pres_status(Status, - maybe_pres_priority(Priority, - maybe_pres_show(Show, []))), - Message = #xmlel{name = <<"presence">>, - attrs = [{<<"from">>, USR}, {<<"to">>, US} | maybe_type_attr(Type)], - children = Children}, - ok = mod_presence:set_presence(Pid, Message), - {ok, <<"Presence set successfully">>}. - --spec kick_sessions(jid:jid(), binary()) -> [kick_session_result()]. + case check_user(JID) of + ok -> + Pid = ejabberd_sm:get_session_pid(JID), + USR = jid:to_binary(JID), + US = jid:to_binary(jid:to_bare(JID)), + + Children = maybe_pres_status(Status, + maybe_pres_priority(Priority, + maybe_pres_show(Show, []))), + Message = #xmlel{name = <<"presence">>, + attrs = [{<<"from">>, USR}, {<<"to">>, US} | maybe_type_attr(Type)], + children = Children}, + ok = mod_presence:set_presence(Pid, Message), + {ok, <<"Presence set successfully">>}; + Error -> + Error + end. + +-spec kick_sessions(jid:jid(), binary()) -> {ok, [kick_user_result()]} | {user_not_found, binary()}. kick_sessions(JID, Reason) -> - lists:map( - fun(Resource) -> - service_admin_extra_sessions:kick_session( - jid:replace_resource(JID, Resource), Reason) - end, - ejabberd_sm:get_user_resources(JID)). - --spec kick_session(jid:user(), jid:server(), jid:resource(), binary()) -> kick_session_result(). -kick_session(User, Server, Resource, ReasonText) -> - kick_session(jid:make(User, Server, Resource), prepare_reason(ReasonText)). - --spec kick_session(jid:jid(), binary()) -> kick_session_result(). -kick_session(JID, ReasonText) -> - case ejabberd_sm:terminate_session(JID, prepare_reason(ReasonText)) of + case check_user(JID) of + ok -> + {ok, lists:map( + fun(Resource) -> + FullJID = jid:replace_resource(JID, Resource), + case kick_session_internal(FullJID, Reason) of + {ok, Result} -> + {ok, Result}; + {Code, Message} -> + {ok, #{<<"jid">> => FullJID, + <<"kicked">> => false, + <<"code">> => atom_to_binary(Code), + <<"message">> => Message}} + end + end, + ejabberd_sm:get_user_resources(JID))}; + Error -> + Error + end. + +-spec kick_session(jid:jid(), binary() | null) -> {ok, kick_user_result()} + | {no_session | user_not_found, binary()}. +kick_session(JID, Reason) -> + case check_user(JID) of + ok -> + kick_session_internal(JID, Reason); + Error -> + Error + end. + +-spec kick_session_internal(jid:jid(), binary() | null) -> {ok, kick_user_result()} + | {no_session, binary()}. +kick_session_internal(JID, Reason) -> + case ejabberd_sm:terminate_session(JID, prepare_reason(Reason)) of no_session -> {no_session, <<"No active session">>}; ok -> - {ok, <<"Session kicked">>} + {ok, #{<<"jid">> => JID, + <<"kicked">> => true, + <<"message">> => <<"Session kicked">>}} end. --spec prepare_reason(binary() | string()) -> binary(). -prepare_reason(<<>>) -> +-spec prepare_reason(binary() | null) -> binary(). +prepare_reason(Reason) when Reason == <<>>; Reason == null -> <<"Kicked by administrator">>; -prepare_reason([Reason]) -> - prepare_reason(Reason); -prepare_reason(Reason) when is_list(Reason) -> - list_to_binary(Reason); prepare_reason(Reason) when is_binary(Reason) -> Reason. @@ -209,23 +255,20 @@ prepare_reason(Reason) when is_binary(Reason) -> -spec get_status_list([session()], status()) -> [status_user_info()]. get_status_list(Sessions0, StatusRequired) -> Sessions = [ {catch mod_presence:get_presence(Pid), S, P} - || #session{sid = {_, Pid}, usr = {_, S, _}, priority = P} <- Sessions0 - ], + || #session{sid = {_, Pid}, usr = {_, S, _}, priority = P} <- Sessions0], - [{User, Server, Resource, Priority, StatusText} + [{jid:make_noprep(User, Server, Resource), Priority, StatusText} || {{User, Resource, Status, StatusText}, Server, Priority} <- Sessions, Status == StatusRequired]. -spec format_user_info(ejabberd_sm:session()) -> session_info(). format_user_info(#session{sid = {Microseconds, Pid}, usr = Usr, priority = Priority, info = Info}) -> - Conn = atom_to_binary(maps:get(conn, Info, undefined)), - {Ip, Port} = maps:get(ip, Info, undefined), - IPS = list_to_binary(inet_parse:ntoa(Ip)), - NodeS = atom_to_binary(node(Pid)), + Conn = maps:get(conn, Info, undefined), + Address = maps:get(ip, Info, undefined), + Node = node(Pid), Uptime = (erlang:system_time(microsecond) - Microseconds) div 1000000, - BinJID = jid:to_binary(Usr), - {BinJID, Conn, IPS, Port, Priority, NodeS, Uptime}. + {Usr, Conn, Address, Priority, Node, Uptime}. -spec maybe_type_attr(binary())-> list(). maybe_type_attr(<<"available">>) -> @@ -254,3 +297,22 @@ maybe_pres_status(<<>>, Children) -> maybe_pres_status(Status, Children) -> [#xmlel{name = <<"status">>, children = [#xmlcdata{content = Status}]} | Children]. + +-spec check_domain(jid:server()) -> ok | {domain_not_found, binary()}. +check_domain(Domain) -> + case mongoose_domain_api:get_domain_host_type(Domain) of + {ok, _} -> ok; + {error, not_found} -> {domain_not_found, <<"Domain not found">>} + end. + +-spec check_user(jid:jid()) -> ok | {user_not_found, binary()}. +check_user(JID = #jid{lserver = LServer}) -> + case mongoose_domain_api:get_domain_host_type(LServer) of + {ok, HostType} -> + case ejabberd_auth:does_user_exist(HostType, JID, stored) of + true -> ok; + false -> {user_not_found, <<"Given user does not exist">>} + end; + {error, not_found} -> + {user_not_found, <<"User's domain does not exist">>} + end. diff --git a/src/muc_light/mod_muc_light_api.erl b/src/muc_light/mod_muc_light_api.erl index f3502af5bc4..7264799dae2 100644 --- a/src/muc_light/mod_muc_light_api.erl +++ b/src/muc_light/mod_muc_light_api.erl @@ -1,13 +1,11 @@ %% @doc Provide an interface for frontends (like graphql or ctl) to manage MUC Light rooms. -module(mod_muc_light_api). --export([create_room/4, - create_room/5, - create_room/6, +-export([create_room/3, + create_room/4, invite_to_room/3, change_room_config/3, change_affiliation/4, - remove_user_from_room/3, send_message/3, send_message/4, delete_room/2, @@ -20,7 +18,6 @@ get_room_info/2, get_room_aff/1, get_room_aff/2, - get_room_user_aff/3, get_blocking_list/1, set_blocking/2 ]). @@ -30,12 +27,7 @@ -include("jlib.hrl"). -include("mongoose_rsm.hrl"). --type create_room_result() :: {ok, room()} | {already_exists | max_occupants_reached | - validation_error | muc_server_not_found, iolist()}. - -type room() :: #{jid := jid:jid(), - name := binary(), - subject := binary(), aff_users := aff_users(), options := map()}. @@ -44,181 +36,252 @@ -define(ROOM_DELETED_SUCC_RESULT, {ok, "Room deleted successfully"}). -define(USER_NOT_ROOM_MEMBER_RESULT, {not_room_member, "Given user does not occupy this room"}). -define(ROOM_NOT_FOUND_RESULT, {room_not_found, "Room not found"}). --define(DELETE_NOT_EXISTING_ROOM_RESULT, {room_not_found, "Cannot remove not existing room"}). -define(MUC_SERVER_NOT_FOUND_RESULT, {muc_server_not_found, "MUC Light server not found"}). -define(VALIDATION_ERROR_RESULT(Key, Reason), - {validation_error, io_lib:format("Validation failed for key: ~p with reason ~p", + {validation_error, io_lib:format("Validation failed for key: ~ts with reason ~p", [Key, Reason])}). --spec create_room(jid:lserver(), jid:jid(), binary(), binary()) -> create_room_result(). -create_room(MUCLightDomain, CreatorJID, RoomTitle, Subject) -> - create_room(MUCLightDomain, <<>>, CreatorJID, RoomTitle, Subject). - --spec create_room(jid:lserver(), jid:luser(), jid:jid(), binary(), binary()) -> - create_room_result(). -create_room(MUCLightDomain, RoomID, CreatorJID, RoomTitle, Subject) -> - create_room(MUCLightDomain, RoomID, CreatorJID, RoomTitle, Subject, #{}). +-spec create_room(jid:lserver(), jid:jid(), map()) -> + {ok, room()} | {user_not_found | muc_server_not_found | + max_occupants_reached | validation_error, iolist()}. +create_room(MUCLightDomain, CreatorJID, Config) -> + M = #{user => CreatorJID, room => jid:make_bare(<<>>, MUCLightDomain), options => Config}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun create_room_raw/1]). -create_room(MUCLightDomain, RoomID, CreatorJID, RoomTitle, Subject, Options) -> - RoomJID = jid:make_bare(RoomID, MUCLightDomain), - Options1 = Options#{<<"roomname">> => RoomTitle, <<"subject">> => Subject}, - create_room_raw(RoomJID, CreatorJID, Options1). +-spec create_room(jid:lserver(), jid:luser(), jid:jid(), map()) -> + {ok, room()} | {user_not_found | muc_server_not_found | already_exists | + max_occupants_reached | validation_error, iolist()}. +create_room(MUCLightDomain, RoomID, CreatorJID, Config) -> + M = #{user => CreatorJID, room => jid:make_bare(RoomID, MUCLightDomain), options => Config}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun create_room_raw/1]). -spec invite_to_room(jid:jid(), jid:jid(), jid:jid()) -> - {ok | not_room_member | muc_server_not_found, iolist()}. -invite_to_room(#jid{lserver = MUCServer} = RoomJID, SenderJID, RecipientJID) -> - case mongoose_domain_api:get_subdomain_host_type(MUCServer) of - {ok, HostType} -> - RecipientBin = jid:to_binary(jid:to_bare(RecipientJID)), - case is_user_room_member(HostType, jid:to_lus(SenderJID), jid:to_lus(RoomJID)) of - true -> - S = jid:to_bare(SenderJID), - R = jid:to_bare(RoomJID), - Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, - [affiliate(RecipientBin, <<"member">>)]), - ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), - <<"set">>, [Changes])), - {ok, "User invited successfully"}; - false -> - ?USER_NOT_ROOM_MEMBER_RESULT - end; - {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. - + {ok | user_not_found | muc_server_not_found | room_not_found | not_room_member, iolist()}. +invite_to_room(RoomJID, SenderJID, RecipientJID) -> + M = #{user => SenderJID, room => RoomJID, recipient => RecipientJID}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun get_user_aff/1, + fun do_invite_to_room/1]). -spec change_room_config(jid:jid(), jid:jid(), map()) -> - {ok, room()} | {not_room_member | not_allowed | room_not_found | - validation_error | muc_server_not_found, iolist()}. -change_room_config(#jid{luser = RoomID, lserver = MUCServer} = RoomJID, - UserJID, Config) -> - case mongoose_domain_api:get_subdomain_info(MUCServer) of - {ok, #{host_type := HostType, parent_domain := LServer}} -> - UserUS = jid:to_bare(UserJID), - ConfigReq = #config{ raw_config = maps:to_list(Config) }, - Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, - host_type => HostType}), - case mod_muc_light:change_room_config(UserUS, RoomID, MUCServer, ConfigReq, Acc) of - {ok, RoomJID, KV} -> - {ok, make_room(RoomJID, KV, [])}; - {error, item_not_found} -> - ?USER_NOT_ROOM_MEMBER_RESULT; - {error, not_allowed} -> - {not_allowed, "Given user does not have permission to change config"}; - {error, not_exists} -> - ?ROOM_NOT_FOUND_RESULT; - {error, {Key, Reason}} -> - ?VALIDATION_ERROR_RESULT(Key, Reason) - end; - {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. - --spec change_affiliation(jid:jid(), jid:jid(), jid:jid(), binary()) -> ok. -change_affiliation(RoomJID, SenderJID, RecipientJID, Affiliation) -> - RecipientBare = jid:to_bare(RecipientJID), - S = jid:to_bare(SenderJID), - Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, - [affiliate(jid:to_binary(RecipientBare), Affiliation)]), - ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), - <<"set">>, [Changes])), - ok. - --spec remove_user_from_room(jid:jid(), jid:jid(), jid:jid()) -> - {ok, iolist()}. -remove_user_from_room(RoomJID, SenderJID, RecipientJID) -> - ok = change_affiliation(RoomJID, SenderJID, RecipientJID, <<"none">>), - {ok, io_lib:format("Stanza kicking user ~s sent successfully", [jid:to_binary(RecipientJID)])}. + {ok, room()} | {user_not_found | muc_server_not_found | room_not_found | not_room_member | + not_allowed | validation_error, iolist()}. +change_room_config(RoomJID, UserJID, Config) -> + M = #{user => UserJID, room => RoomJID, config => Config}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun do_change_room_config/1]). + +-spec change_affiliation(jid:jid(), jid:jid(), jid:jid(), add | remove) -> + {ok | user_not_found | muc_server_not_found | room_not_found | not_room_member | + not_allowed, iolist()}. +change_affiliation(RoomJID, SenderJID, RecipientJID, Op) -> + M = #{user => SenderJID, room => RoomJID, recipient => RecipientJID, op => Op}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun get_user_aff/1, + fun check_aff_permission/1, fun do_change_affiliation/1]). -spec send_message(jid:jid(), jid:jid(), binary()) -> - {ok | not_room_member | muc_server_not_found, iolist()}. + {ok | user_not_found | muc_server_not_found | room_not_found | not_room_member, iolist()}. send_message(RoomJID, SenderJID, Text) when is_binary(Text) -> Body = #xmlel{name = <<"body">>, children = [#xmlcdata{content = Text}]}, send_message(RoomJID, SenderJID, [Body], []). -spec send_message(jid:jid(), jid:jid(), [exml:element()], [exml:attr()]) -> - {ok | not_room_member | muc_server_not_found, iolist()}. -send_message(#jid{lserver = MUCServer} = RoomJID, SenderJID, Children, ExtraAttrs) -> - case mongoose_domain_api:get_subdomain_host_type(MUCServer) of - {ok, HostType} -> - SenderBare = jid:to_bare(SenderJID), - RoomBare = jid:to_bare(RoomJID), - Stanza = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>} | ExtraAttrs], - children = Children - }, - case is_user_room_member(HostType, jid:to_lus(SenderBare), jid:to_lus(RoomJID)) of - true -> - ejabberd_router:route(SenderBare, RoomBare, Stanza), - {ok, "Message sent successfully"}; - false -> - ?USER_NOT_ROOM_MEMBER_RESULT - end; - {error, not_found}-> - ?MUC_SERVER_NOT_FOUND_RESULT - end. + {ok | user_not_found | muc_server_not_found | room_not_found | not_room_member, iolist()}. +send_message(RoomJID, SenderJID, Children, ExtraAttrs) -> + M = #{user => SenderJID, room => RoomJID, children => Children, attrs => ExtraAttrs}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun get_user_aff/1, fun do_send_message/1]). -spec delete_room(jid:jid(), jid:jid()) -> - {ok | not_allowed | room_not_found | muc_server_not_found , iolist()}. -delete_room(#jid{lserver = MUCServer} = RoomJID, UserJID) -> - case mongoose_domain_api:get_subdomain_host_type(MUCServer) of - {ok, HostType} -> - case get_room_user_aff(HostType, RoomJID, UserJID) of - {ok, owner} -> - ok = mod_muc_light_db_backend:destroy_room(HostType, jid:to_lus(RoomJID)), - ?ROOM_DELETED_SUCC_RESULT; - {ok, none} -> - ?USER_NOT_ROOM_MEMBER_RESULT; - {ok, member} -> - {not_allowed, "Given user cannot delete this room"}; - {error, room_not_found} -> - ?DELETE_NOT_EXISTING_ROOM_RESULT - end; - {error, not_found}-> - ?MUC_SERVER_NOT_FOUND_RESULT - end. + {ok | not_allowed | room_not_found | not_room_member | muc_server_not_found , iolist()}. +delete_room(RoomJID, UserJID) -> + M = #{user => UserJID, room => RoomJID}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun get_user_aff/1, + fun check_delete_permission/1, fun do_delete_room/1]). --spec delete_room(jid:jid()) -> {ok | room_not_found | muc_server_not_found, iolist()}. +-spec delete_room(jid:jid()) -> {ok | muc_server_not_found | room_not_found, iolist()}. delete_room(RoomJID) -> - try mod_muc_light:delete_room(jid:to_lus(RoomJID)) of - ok -> - ?ROOM_DELETED_SUCC_RESULT; - {error, not_exists} -> - ?DELETE_NOT_EXISTING_ROOM_RESULT - catch - error:{muc_host_to_host_type_failed, _, _} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. + M = #{room => RoomJID}, + fold(M, [fun check_muc_domain/1, fun do_delete_room/1]). + -spec get_room_messages(jid:jid(), jid:jid(), integer() | undefined, mod_mam:unix_timestamp() | undefined) -> - {ok, list()} | {muc_server_not_found | internal | not_room_member, iolist()}. + {ok, list()} | {user_not_found | muc_server_not_found | room_not_found | not_room_member | + internal, iolist()}. get_room_messages(RoomJID, UserJID, PageSize, Before) -> - case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of + M = #{user => UserJID, room => RoomJID, page_size => PageSize, before => Before}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun get_user_aff/1, + fun do_get_room_messages/1]). + +-spec get_room_messages(jid:jid(), integer() | undefined, + mod_mam:unix_timestamp() | undefined) -> + {ok, [mod_mam:message_row()]} | {muc_server_not_found | room_not_found | internal, iolist()}. +get_room_messages(RoomJID, PageSize, Before) -> + M = #{user => undefined, room => RoomJID, page_size => PageSize, before => Before}, + fold(M, [fun check_muc_domain/1, fun check_room/1, fun do_get_room_messages/1]). + +-spec get_room_info(jid:jid(), jid:jid()) -> + {ok, room()} | {user_not_found | muc_server_not_found | room_not_found | not_room_member, + iolist()}. +get_room_info(RoomJID, UserJID) -> + M = #{user => UserJID, room => RoomJID}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun do_get_room_info/1, + fun check_room_member/1, fun return_info/1]). + +-spec get_room_info(jid:jid()) -> {ok, room()} | {muc_server_not_found | room_not_found, iolist()}. +get_room_info(RoomJID) -> + M = #{room => RoomJID}, + fold(M, [fun check_muc_domain/1, fun do_get_room_info/1, fun return_info/1]). + +-spec get_room_aff(jid:jid(), jid:jid()) -> + {ok, aff_users()} | {user_not_found | muc_server_not_found | room_not_found | not_room_member, + iolist()}. +get_room_aff(RoomJID, UserJID) -> + M = #{user => UserJID, room => RoomJID}, + fold(M, [fun check_user/1, fun check_muc_domain/1, fun do_get_room_aff/1, + fun check_room_member/1, fun return_aff/1]). + +-spec get_room_aff(jid:jid()) -> + {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}. +get_room_aff(RoomJID) -> + M = #{room => RoomJID}, + fold(M, [fun check_muc_domain/1, fun do_get_room_aff/1, fun return_aff/1]). + +-spec get_user_rooms(jid:jid()) -> + {ok, [RoomUS :: jid:simple_bare_jid()]} | {user_not_found, iolist()}. +get_user_rooms(UserJID) -> + fold(#{user => UserJID}, [fun check_user/1, fun do_get_user_rooms/1]). + +-spec get_blocking_list(jid:jid()) -> {ok, [blocking_item()]} | {user_not_found, iolist()}. +get_blocking_list(UserJID) -> + fold(#{user => UserJID}, [fun check_user/1, fun do_get_blocking_list/1]). + +-spec set_blocking(jid:jid(), [blocking_item()]) -> {ok | user_not_found, iolist()}. +set_blocking(UserJID, Items) -> + fold(#{user => UserJID, items => Items}, [fun check_user/1, fun do_set_blocking_list/1]). + +%% Internal: steps used in fold/2 + +check_user(M = #{user := UserJID = #jid{lserver = LServer}}) -> + case mongoose_domain_api:get_domain_host_type(LServer) of {ok, HostType} -> - case is_user_room_member(HostType, jid:to_lus(UserJID), jid:to_lus(RoomJID)) of - true -> - get_room_messages(HostType, RoomJID, UserJID, PageSize, Before); - false -> - ?USER_NOT_ROOM_MEMBER_RESULT + case ejabberd_auth:does_user_exist(HostType, UserJID, stored) of + true -> M#{user_host_type => HostType}; + false -> {user_not_found, "Given user does not exist"} end; {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT + {user_not_found, "User's domain does not exist"} end. --spec get_room_messages(jid:jid(), integer() | undefined, - mod_mam:unix_timestamp() | undefined) -> - {ok, [mod_mam:message_row()]} | {muc_server_not_found | internal, iolist()}. -get_room_messages(RoomJID, PageSize, Before) -> - case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of +check_muc_domain(M = #{room := #jid{lserver = LServer}}) -> + case mongoose_domain_api:get_subdomain_host_type(LServer) of {ok, HostType} -> - get_room_messages(HostType, RoomJID, undefined, PageSize, Before); + M#{muc_host_type => HostType}; {error, not_found} -> ?MUC_SERVER_NOT_FOUND_RESULT end. --spec get_room_messages(mongooseim:host_type(), jid:jid(), jid:jid() | undefined, - integer() | undefined, mod_mam:unix_timestamp() | undefined) -> - {ok, list()} | {internal, iolist()}. +check_room_member(M = #{user := UserJID, aff_users := AffUsers}) -> + case get_aff(jid:to_lus(UserJID), AffUsers) of + none -> + ?USER_NOT_ROOM_MEMBER_RESULT; + _ -> + M + end. + +create_room_raw(#{room := InRoomJID, user := CreatorJID, options := Options}) -> + Config = make_room_config(Options), + case mod_muc_light:try_to_create_room(CreatorJID, InRoomJID, Config) of + {ok, RoomJID, #create{aff_users = AffUsers, raw_config = Conf}} -> + {ok, make_room(RoomJID, Conf, AffUsers)}; + {error, exists} -> + {already_exists, "Room already exists"}; + {error, max_occupants_reached} -> + {max_occupants_reached, "Max occupants number reached"}; + {error, {Key, Reason}} -> + ?VALIDATION_ERROR_RESULT(Key, Reason) + end. + +do_invite_to_room(#{user := SenderJID, room := RoomJID, recipient := RecipientJID}) -> + S = jid:to_bare(SenderJID), + R = jid:to_bare(RoomJID), + RecipientBin = jid:to_binary(jid:to_bare(RecipientJID)), + Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, [affiliate(RecipientBin, <<"member">>)]), + ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), <<"set">>, [Changes])), + {ok, "User invited successfully"}. + +do_change_room_config(#{user := UserJID, room := RoomJID, config := Config, + muc_host_type := HostType}) -> + UserUS = jid:to_bare(UserJID), + ConfigReq = #config{ raw_config = maps:to_list(Config) }, + #jid{lserver = LServer} = UserJID, + #jid{luser = RoomID, lserver = MUCServer} = RoomJID, + Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, host_type => HostType}), + case mod_muc_light:change_room_config(UserUS, RoomID, MUCServer, ConfigReq, Acc) of + {ok, RoomJID, KV} -> + {ok, make_room(RoomJID, KV, [])}; + {error, item_not_found} -> + ?USER_NOT_ROOM_MEMBER_RESULT; + {error, not_allowed} -> + {not_allowed, "Given user does not have permission to change config"}; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT; + {error, {Key, Reason}} -> + ?VALIDATION_ERROR_RESULT(Key, Reason) + end. + +check_aff_permission(M = #{user := UserJID, recipient := RecipientJID, aff := Aff, op := Op}) -> + case {Aff, Op} of + {member, remove} when RecipientJID =:= UserJID -> + M; + {owner, _} -> + M; + _ -> {not_allowed, "Given user does not have permission to change affiliations"} + end. + +check_delete_permission(M = #{aff := owner}) -> M; +check_delete_permission(#{}) -> {not_allowed, "Given user cannot delete this room"}. + +get_user_aff(M = #{muc_host_type := HostType, user := UserJID, room := RoomJID}) -> + case get_room_user_aff(HostType, RoomJID, UserJID) of + {ok, owner} -> + M#{aff => owner}; + {ok, member} -> + M#{aff => member}; + {ok, none} -> + ?USER_NOT_ROOM_MEMBER_RESULT; + {error, room_not_found} -> + ?ROOM_NOT_FOUND_RESULT + end. + +do_change_affiliation(#{user := SenderJID, room := RoomJID, recipient := RecipientJID, op := Op}) -> + RecipientBare = jid:to_bare(RecipientJID), + S = jid:to_bare(SenderJID), + Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, + [affiliate(jid:to_binary(RecipientBare), op_to_aff(Op))]), + ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID), + <<"set">>, [Changes])), + {ok, "Affiliation change request sent successfully"}. + +do_send_message(#{user := SenderJID, room := RoomJID, children := Children, attrs := ExtraAttrs}) -> + SenderBare = jid:to_bare(SenderJID), + RoomBare = jid:to_bare(RoomJID), + Stanza = #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>} | ExtraAttrs], + children = Children}, + ejabberd_router:route(SenderBare, RoomBare, Stanza), + {ok, "Message sent successfully"}. + +do_delete_room(#{room := RoomJID}) -> + case mod_muc_light:delete_room(jid:to_lus(RoomJID)) of + ok -> + ?ROOM_DELETED_SUCC_RESULT; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT + end. + +do_get_room_messages(#{user := CallerJID, room := RoomJID, page_size := PageSize, before := Before, + muc_host_type := HostType}) -> + get_room_messages(HostType, RoomJID, CallerJID, PageSize, Before). + +%% Exported for mod_muc_api get_room_messages(HostType, RoomJID, CallerJID, PageSize, Before) -> ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID), Now = os:system_time(microsecond), @@ -244,118 +307,52 @@ get_room_messages(HostType, RoomJID, CallerJID, PageSize, Before) -> {internal, io_lib:format("Internal error occured ~p", [Term])} end. --spec get_room_info(jid:jid(), jid:jid()) -> - {ok, room()} | {muc_server_not_found | room_not_found | not_room_member, iolist()}. -get_room_info(RoomJID, UserJID) -> - case get_room_info(RoomJID) of - {ok, #{aff_users := Affs} = Room} -> - case get_aff(jid:to_lus(UserJID), Affs) of - none -> - ?USER_NOT_ROOM_MEMBER_RESULT; - _ -> - {ok, Room} - end; - Error -> - Error +do_get_room_info(M = #{room := RoomJID, muc_host_type := HostType}) -> + case mod_muc_light_db_backend:get_info(HostType, jid:to_lus(RoomJID)) of + {ok, Config, AffUsers, _Version} -> + M#{aff_users => AffUsers, options => Config}; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT end. --spec get_room_info(jid:jid()) -> {ok, room()} | {muc_server_not_found | room_not_found, iolist()}. -get_room_info(#jid{lserver = MUCServer} = RoomJID) -> - case mongoose_domain_api:get_subdomain_host_type(MUCServer) of - {ok, HostType} -> - case mod_muc_light_db_backend:get_info(HostType, jid:to_lus(RoomJID)) of - {ok, Conf, AffUsers, _Version} -> - {ok, make_room(jid:to_binary(RoomJID), Conf, AffUsers)}; - {error, not_exists} -> - ?ROOM_NOT_FOUND_RESULT - end; - {error, not_found}-> - ?MUC_SERVER_NOT_FOUND_RESULT - end. +return_info(#{room := RoomJID, aff_users := AffUsers, options := Config}) -> + {ok, make_room(jid:to_binary(RoomJID), Config, AffUsers)}. --spec get_room_aff(jid:jid(), jid:jid()) -> - {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}. -get_room_aff(RoomJID, UserJID) -> - case get_room_aff(RoomJID) of - {ok, Affs} -> - case get_aff(jid:to_lus(UserJID), Affs) of - none -> - ?USER_NOT_ROOM_MEMBER_RESULT; - _ -> - {ok, Affs} - end; - Error -> - Error +do_get_room_aff(M = #{room := RoomJID, muc_host_type := HostType}) -> + case mod_muc_light_db_backend:get_aff_users(HostType, jid:to_lus(RoomJID)) of + {ok, AffUsers, _Version} -> + M#{aff_users => AffUsers}; + {error, not_exists} -> + ?ROOM_NOT_FOUND_RESULT end. --spec get_room_aff(jid:jid()) -> - {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}. -get_room_aff(#jid{lserver = MUCServer} = RoomJID) -> - case mongoose_domain_api:get_subdomain_host_type(MUCServer) of - {ok, HostType} -> - case mod_muc_light_db_backend:get_aff_users(HostType, jid:to_lus(RoomJID)) of - {ok, AffUsers, _Version} -> - {ok, AffUsers}; - {error, not_exists} -> - ?ROOM_NOT_FOUND_RESULT - end; - {error, not_found}-> - ?MUC_SERVER_NOT_FOUND_RESULT - end. +return_aff(#{aff_users := AffUsers}) -> + {ok, AffUsers}. --spec get_user_rooms(jid:jid()) -> {ok, [RoomUS :: jid:simple_bare_jid()]} | - {muc_server_not_found, iolist()}. -get_user_rooms(#jid{lserver = LServer} = UserJID) -> - case mongoose_domain_api:get_domain_host_type(LServer) of - {ok, HostType} -> - UserUS = jid:to_lus(UserJID), - MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - {ok, mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer)}; - {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT +check_room(M = #{room := RoomJID, muc_host_type := HostType}) -> + case mod_muc_light_db_backend:room_exists(HostType, jid:to_lus(RoomJID)) of + true -> + M; + false -> + ?ROOM_NOT_FOUND_RESULT end. --spec get_blocking_list(jid:jid()) -> {ok, [blocking_item()]} | {muc_server_not_found, iolist()}. -get_blocking_list(#jid{lserver = LServer} = User) -> - case mongoose_domain_api:get_domain_host_type(LServer) of - {ok, HostType} -> - MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - {ok, mod_muc_light_db_backend:get_blocking(HostType, jid:to_lus(User), MUCServer)}; - {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. +do_get_user_rooms(#{user := UserJID, user_host_type := HostType}) -> + MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, UserJID#jid.lserver), + {ok, mod_muc_light_db_backend:get_user_rooms(HostType, jid:to_lus(UserJID), MUCServer)}. --spec set_blocking(jid:jid(), [blocking_item()]) -> {ok | muc_server_not_found, iolist()}. -set_blocking(#jid{lserver = LServer} = User, Items) -> - case mongoose_domain_api:get_domain_host_type(LServer) of - {ok, HostType} -> - MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), - Q = query(?NS_MUC_LIGHT_BLOCKING, [blocking_item(I) || I <- Items]), - Iq = iq(jid:to_binary(User), MUCServer, <<"set">>, [Q]), - ejabberd_router:route(User, jid:from_binary(MUCServer), Iq), - {ok, "User blocking list updated successfully"}; - {error, not_found} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. +do_get_blocking_list(#{user := UserJID, user_host_type := HostType}) -> + MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, UserJID#jid.lserver), + {ok, mod_muc_light_db_backend:get_blocking(HostType, jid:to_lus(UserJID), MUCServer)}. - %% Internal +do_set_blocking_list(#{user := UserJID, user_host_type := HostType, items := Items}) -> + MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, UserJID#jid.lserver), + Q = query(?NS_MUC_LIGHT_BLOCKING, [blocking_item(I) || I <- Items]), + Iq = iq(jid:to_binary(UserJID), MUCServer, <<"set">>, [Q]), + ejabberd_router:route(UserJID, jid:from_binary(MUCServer), Iq), + {ok, "User blocking list updated successfully"}. --spec create_room_raw(jid:jid(), jid:jid(), map()) -> create_room_result(). -create_room_raw(InRoomJID, CreatorJID, Options) -> - Config = make_room_config(Options), - try mod_muc_light:try_to_create_room(CreatorJID, InRoomJID, Config) of - {ok, RoomJID, #create{aff_users = AffUsers, raw_config = Conf}} -> - {ok, make_room(RoomJID, Conf, AffUsers)}; - {error, exists} -> - {already_exists, "Room already exists"}; - {error, max_occupants_reached} -> - {max_occupants_reached, "Max occupants number reached"}; - {error, {Key, Reason}} -> - ?VALIDATION_ERROR_RESULT(Key, Reason) - catch - error:{muc_host_to_host_type_failed, _, _} -> - ?MUC_SERVER_NOT_FOUND_RESULT - end. +%% Internal: helpers -spec blocking_item(blocking_item()) -> exml:element(). blocking_item({What, Action, Who}) -> @@ -387,24 +384,12 @@ get_aff(UserUS, Affs) -> false -> none end. --spec is_user_room_member(mongooseim:host_type(), jid:simple_bare_jid(), - jid:simple_bare_jid()) -> boolean(). -is_user_room_member(HostType, UserUS, {_, MUCServer} = RoomLUS) -> - case mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer) of - [] -> - false; - RoomJIDs when is_list(RoomJIDs) -> - lists:any(fun(LUS) -> LUS =:= RoomLUS end, RoomJIDs) - end. - - make_room(JID, #config{ raw_config = Options}, AffUsers) -> make_room(JID, Options, AffUsers); make_room(JID, Options, AffUsers) when is_list(Options) -> make_room(JID, maps:from_list(ensure_keys_are_binaries(Options)), AffUsers); make_room(JID, Options, AffUsers) when is_map(Options) -> - #{<<"roomname">> := Name, <<"subject">> := Subject} = Options, - #{jid => JID, name => Name, subject => Subject, aff_users => AffUsers, options => Options}. + #{jid => JID, aff_users => AffUsers, options => Options}. ensure_keys_are_binaries([{K, _}|_] = Conf) when is_binary(K) -> Conf; @@ -442,3 +427,11 @@ maybe_caller_jid(undefined, Params) -> Params; maybe_caller_jid(CallerJID, Params) -> Params#{caller_jid => CallerJID}. + +op_to_aff(add) -> <<"member">>; +op_to_aff(remove) -> <<"none">>. + +fold({_, _} = Result, _) -> + Result; +fold(M, [Step | Rest]) when is_map(M) -> + fold(Step(M), Rest). diff --git a/src/vcard/mod_vcard_api.erl b/src/vcard/mod_vcard_api.erl index 17a8d73941a..53453b85f57 100644 --- a/src/vcard/mod_vcard_api.erl +++ b/src/vcard/mod_vcard_api.erl @@ -13,9 +13,9 @@ get_vcard/1]). -spec set_vcard(jid:jid(), vcard_map()) -> - {ok, vcard_map()} | {not_found, string()} | {internal, string()} | {vcard_not_found, string()}. + {ok, vcard_map()} | {user_not_found, string()} | {internal, string()} | {vcard_not_found, string()}. set_vcard(#jid{luser = LUser, lserver = LServer} = UserJID, Vcard) -> - case mongoose_domain_api:get_domain_host_type(LServer) of + case check_user(UserJID) of {ok, HostType} -> case set_vcard(HostType, UserJID, Vcard) of ok -> @@ -23,24 +23,25 @@ set_vcard(#jid{luser = LUser, lserver = LServer} = UserJID, Vcard) -> _ -> {internal, "Internal server error"} end; - _ -> - {not_found, "Host does not exist"} + Error -> + Error end. -spec get_vcard(jid:jid()) -> - {ok, vcard_map()} | {not_found, string()} | {internal, string()} | {vcard_not_found, string()}. -get_vcard(#jid{luser = LUser, lserver = LServer}) -> - case mongoose_domain_api:get_domain_host_type(LServer) of + {ok, vcard_map()} | {user_not_found, string()} | {internal, string()} | {vcard_not_found, string()} + | {vcard_not_configured_error, string()}. +get_vcard(#jid{luser = LUser, lserver = LServer} = UserJID) -> + % check if mod_vcard is loaded is needed in user's get_vcard command, when user variable is not passed + case check_user(UserJID) of {ok, HostType} -> - % check if mod_vcard is loaded is needed in user's get_vcard command, when user variable is not passed case gen_mod:is_loaded(HostType, mod_vcard) of true -> get_vcard_from_db(HostType, LUser, LServer); false -> {vcard_not_configured_error, "Mod_vcard is not loaded for this host"} end; - _ -> - {not_found, "Host does not exist"} + Error -> + Error end. set_vcard(HostType, UserJID, Vcard) -> @@ -58,6 +59,18 @@ get_vcard_from_db(HostType, LUser, LServer) -> {internal, "Internal server error"} end. +-spec check_user(jid:jid()) -> {ok, mongooseim:host_type()} | {user_not_found, binary()}. +check_user(JID = #jid{lserver = LServer}) -> + case mongoose_domain_api:get_domain_host_type(LServer) of + {ok, HostType} -> + case ejabberd_auth:does_user_exist(HostType, JID, stored) of + true -> {ok, HostType}; + false -> {user_not_found, <<"Given user does not exist">>} + end; + {error, not_found} -> + {user_not_found, <<"User's domain does not exist">>} + end. + transform_from_map(Vcard) -> #xmlel{name = <<"vCard">>, attrs = [{<<"xmlns">>, <<"vcard-temp">>}], diff --git a/test/common/config_parser_helper.erl b/test/common/config_parser_helper.erl index a0e8c89413a..0de84dce40e 100644 --- a/test/common/config_parser_helper.erl +++ b/test/common/config_parser_helper.erl @@ -558,6 +558,7 @@ all_modules() -> bin_clean_after => timer:hours(1), iqdisc => no_queue, aff_changes => true, + delete_domain_limit => infinity, groupchat => [muclight], remove_on_kicked => true, reset_markers => [<<"displayed">>]}, @@ -879,6 +880,7 @@ default_mod_config(mod_inbox) -> bin_clean_after => timer:hours(1), groupchat => [muclight], aff_changes => true, + delete_domain_limit => infinity, remove_on_kicked => true, reset_markers => [<<"displayed">>], iqdisc => no_queue}; @@ -1083,8 +1085,8 @@ default_config([listen, http, handlers, mongoose_client_api]) -> #{handlers => [sse, messages, contacts, rooms, rooms_config, rooms_users, rooms_messages], docs => true, module => mongoose_client_api}; -default_config([listen, http, handlers, mongoose_graphql_cowboy_handler]) -> - #{module => mongoose_graphql_cowboy_handler, +default_config([listen, http, handlers, mongoose_graphql_handler]) -> + #{module => mongoose_graphql_handler, schema_endpoint => admin}; default_config([listen, http, handlers, Module]) -> #{module => Module}; diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index 234b29a1239..d4e246d8291 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -612,13 +612,13 @@ listen_http_handlers_admin_api(_Config) -> listen_http_handlers_graphql(_Config) -> T = fun graphql_handler_raw/1, - {P, _} = test_listen_http_handler(mongoose_graphql_cowboy_handler, T), + {P, _} = test_listen_http_handler(mongoose_graphql_handler, T), test_listen_http_handler_creds(P, T), ?cfg(P ++ [allowed_categories], [<<"muc">>, <<"inbox">>], T(#{<<"allowed_categories">> => [<<"muc">>, <<"inbox">>]})), ?err(T(#{<<"allowed_categories">> => [<<"invalid">>]})), ?err(T(#{<<"schema_endpoint">> => <<"wrong_endpoint">>})), - ?err(http_handler_raw(mongoose_graphql_cowboy_handler, #{})). + ?err(http_handler_raw(mongoose_graphql_handler, #{})). test_listen_http_handler_creds(P, T) -> CredsRaw = #{<<"username">> => <<"user">>, <<"password">> => <<"pass">>}, @@ -1557,6 +1557,7 @@ mod_inbox(_Config) -> ?cfgh(P ++ [bin_ttl], 30, T(#{<<"bin_ttl">> => 30})), ?cfgh(P ++ [bin_clean_after], 43200000, T(#{<<"bin_clean_after">> => 12})), ?cfgh(P ++ [aff_changes], true, T(#{<<"aff_changes">> => true})), + ?cfgh(P ++ [delete_domain_limit], 1000, T(#{<<"delete_domain_limit">> => 1000})), ?cfgh(P ++ [remove_on_kicked], false, T(#{<<"remove_on_kicked">> => false})), ?errh(T(#{<<"backend">> => <<"nodejs">>})), ?errh(T(#{<<"pool_size">> => -1})), @@ -1569,6 +1570,7 @@ mod_inbox(_Config) -> ?errh(T(#{<<"bin_ttl">> => true})), ?errh(T(#{<<"bin_clean_after">> => -1})), ?errh(T(#{<<"aff_changes">> => 1})), + ?errh(T(#{<<"delete_domain_limit">> => []})), ?errh(T(#{<<"remove_on_kicked">> => 1})). mod_global_distrib(_Config) -> @@ -2980,7 +2982,7 @@ listener(Type, Opts) -> config([listen, Type], Opts). graphql_handler_raw(Opts) -> - http_handler_raw(mongoose_graphql_cowboy_handler, + http_handler_raw(mongoose_graphql_handler, maps:merge(#{<<"schema_endpoint">> => <<"admin">>}, Opts)). http_handler_raw(Type, Opts) -> diff --git a/test/ejabberd_admin_SUITE.erl b/test/ejabberd_admin_SUITE.erl index b8cf5f17958..5ade83b22bb 100644 --- a/test/ejabberd_admin_SUITE.erl +++ b/test/ejabberd_admin_SUITE.erl @@ -54,12 +54,18 @@ import_users_from_valid_csv(Config) -> % when Result = ejabberd_admin:import_users(ValidCsvPath), % then - ?assertEqual([{ok, <<"user">>}, - {exists, <<"existing_user">>}, - {null_password, <<"null_password_user">>}, - {not_allowed, <<"bad_domain_user">>}, - {invalid_jid, <<"invalid_jid_user">>}, - {bad_csv, <<"wrong,number,of,fields,line">>}], + ?assertEqual([{bad_csv, [<<"wrong,number,of,fields,line">>]}, + {exists, [#jid{luser = <<"existing_user">>, + lserver = <<"localhost">>, + lresource = <<>>}]}, + {invalid_jid, [<<"invalid_jid_user,localhost,password">>]}, + {not_allowed, [#jid{luser = <<"bad_domain_user">>, + lserver = <<"not_allowed_domain">>, + lresource = <<>>}]}, + {null_password, [#jid{luser = <<"null_password_user">>, + lserver = <<"localhost">>, + lresource = <<>>}]}, + {ok, [#jid{luser = <<"user">>, lserver = <<"localhost">>, lresource = <<>>}]}], Result). import_users_from_valid_csv_with_quoted_fields(Config) -> @@ -68,12 +74,14 @@ import_users_from_valid_csv_with_quoted_fields(Config) -> % when Result = ejabberd_admin:import_users(ValidCsvPath), % then - ?assertEqual([{ok, <<"username,with,commas">>}], + ?assertEqual([{ok, [#jid{luser = <<"username,with,commas">>, + lserver = <<"localhost">>, + lresource = <<>>}]}], Result). import_from_invalid_csv(_Config) -> % given NonExistingPath = "", % then - ?assertError({badmatch, {error, enoent}}, + ?assertError({badmatch, {error, file_not_found}}, ejabberd_admin:import_users(NonExistingPath)). diff --git a/test/ejabberd_hooks_SUITE.erl b/test/ejabberd_hooks_SUITE.erl deleted file mode 100644 index dc19574b28b..00000000000 --- a/test/ejabberd_hooks_SUITE.erl +++ /dev/null @@ -1,261 +0,0 @@ --module(ejabberd_hooks_SUITE). --compile([export_all, nowarn_export_all]). - --define(HOST, <<"localhost">>). - -all() -> - [ - a_module_fun_can_be_added, - a_module_fun_can_be_removed, - - hooks_run_launches_nullary_fun, - hooks_run_launches_unary_fun, - hooks_run_ignores_different_arity_funs, - hooks_run_stops_when_fun_returns_stop, - - hooks_run_fold_folds_with_unary_fun, - hooks_run_fold_folds_with_binary_fun, - hooks_run_fold_passes_acc_along, - hooks_run_fold_stops_when_fun_returns_stop, - hooks_run_fold_preserves_order, - - error_in_run_fold_is_ignored, - throw_in_run_fold_is_ignored, - exit_in_run_fold_is_ignored - ]. - -init_per_suite(C) -> - application:ensure_all_started(exometer_core), - mongoose_config:set_opt(all_metrics_are_global, false), - C. - -end_per_suite(_C) -> - mongoose_config:unset_opt(all_metrics_are_global), - application:stop(exometer_core). - -a_module_fun_can_be_added(_) -> - given_hooks_started(), - given_module(hook_mod, fun_a, fun(_) -> ok end), - - % when - ejabberd_hooks:add(test_run_hook, ?HOST, hook_mod, fun_a, 1), - - FunEjabberdHooksWrapper = fun ejabberd_hooks:gen_hook_fn_wrapper/3, - % then - [{{test_run_hook, <<"localhost">>}, - [{hook_handler, 1, FunEjabberdHooksWrapper, - #{function := fun_a, module := hook_mod}}]}] = get_hooks(). - -a_module_fun_can_be_removed(_) -> - given_hooks_started(), - given_module(hook_mod, fun_nullary, fun() -> success0 end), - given_hook_added(test_run_hook, hook_mod, fun_nullary, 1), - - % when - ejabberd_hooks:delete(test_run_hook, ?HOST, hook_mod, fun_nullary, 1), - - % then - [{{test_run_hook,<<"localhost">>}, []}] = get_hooks(). - - -hooks_run_launches_nullary_fun(_) -> - given_hooks_started(), - given_module(hook_mod, fun_nullary, fun(_) -> #{result => success0} end), - given_hook_added(test_run_hook, hook_mod, fun_nullary, 1), - - %% when - run_fold(test_run_hook, ?HOST, ok, []), - - %% then - H = meck:history(hook_mod), - [{_,{hook_mod,fun_nullary,[ok]}, #{result := success0}}] = H. - -hooks_run_launches_unary_fun(_) -> - given_hooks_started(), - given_module(hook_mod, fun_onearg, fun(Acc, Val) -> Val end), - given_hook_added(test_run_hook, hook_mod, fun_onearg, 1), - - %% when - run_fold(test_run_hook, ?HOST, ok, [oneval]), - - %% then - [{_,{hook_mod,fun_onearg,[ok, oneval]}, oneval}] = meck:history(hook_mod). - -hooks_run_ignores_different_arity_funs(_) -> - given_hooks_started(), - given_module(hook_mod, fun_onearg, fun(ok, unused) -> never_return end), - given_fun(hook_mod, fun_twoarg, fun(ok, one, two)-> success2 end), - - given_hook_added(test_run_hook, hook_mod, fun_onearg, 1), - given_hook_added(test_run_hook, hook_mod, fun_twoarg, 1), - - %% when - run_fold(test_run_hook, ?HOST, ok, [one, two]), - - %% then - [{_,{hook_mod, fun_twoarg, [ok, one, two]}, success2}] = meck:history(hook_mod). - -hooks_run_stops_when_fun_returns_stop(_) -> - given_hooks_started(), - given_module(hook_mod, a_fun, const(stop)), - given_fun(hook_mod, another_fun, const(success)), - - given_hook_added(test_run_hook, hook_mod, a_fun, 1), - given_hook_added(test_run_hook, hook_mod, another_fun, 2), - - %% when - run_fold(test_run_hook, ?HOST, ok, []), - - %% then - [{_,{hook_mod,a_fun,[ok]}, stop}] = meck:history(hook_mod). - - -hooks_run_fold_folds_with_unary_fun(_) -> - given_hooks_started(), - given_module(hook_mod, unary_folder, fun(initial) -> done end), - given_hook_added(test_fold_hook, hook_mod, unary_folder, 1), - - %% when - run_fold(test_fold_hook, ?HOST, initial, []), - - %% then - [{_,{hook_mod,unary_folder,[initial]}, done}] = meck:history(hook_mod). - -hooks_run_fold_folds_with_binary_fun(_) -> - given_hooks_started(), - given_module(hook_mod, binary_folder, fun(initial, arg1) -> done end), - given_hook_added(test_fold_hook, hook_mod, binary_folder, 1), - - %% when - run_fold(test_fold_hook, ?HOST, initial, [arg1]), - - %% then - [{_,{hook_mod,binary_folder,[initial, arg1]}, done}] = meck:history(hook_mod). - -hooks_run_fold_passes_acc_along(_) -> - given_hooks_started(), - given_module(hook_mod1, first_folder, fun(N, Const) -> N+Const end), - given_module(hook_mod2, second_folder, fun(N, Const) -> N-Const*2 end), - - given_hook_added(test_fold_hook, hook_mod1, first_folder, 1), - given_hook_added(test_fold_hook, hook_mod2, second_folder, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, 0, [10]), - - %% then - -10 = R. - -hooks_run_fold_stops_when_fun_returns_stop(_) -> - given_hooks_started(), - given_module(hook_mod1, stopper, const(stop)), - given_module(hook_mod2, folder, const(continue)), - - given_hook_added(test_fold_hook, hook_mod1, stopper, 1), - given_hook_added(test_fold_hook, hook_mod2, folder, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, continue, []), - - %% then - [{_,{hook_mod1,stopper,[continue]}, stop}] = meck:history(hook_mod1), - [] = meck:history(hook_mod2), - stopped = R. - - -hooks_run_fold_preserves_order(_) -> - given_hooks_started(), - given_module(hook_mod1, first_folder, const(1)), - given_module(hook_mod2, second_folder, const(2)), - - given_hook_added(test_fold_hook, hook_mod1, first_folder, 1), - given_hook_added(test_fold_hook, hook_mod2, second_folder, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, 0, []), - - %% then - 2 = R. - - -error_in_run_fold_is_ignored(_) -> - given_hooks_started(), - - given_module(failing_mod, broken, fun(_) -> error(broken) end), - given_hook_added(test_fold_hook, failing_mod, broken, 1), - - given_module(working_mod, good, const(i_was_run)), - given_hook_added(test_fold_hook, working_mod, good, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, initial, []), - - %% then - i_was_run = R, - [{_Pid, {failing_mod,broken,[initial]}, error,broken, _Stacktrace}] = - meck:history(failing_mod). - - -throw_in_run_fold_is_ignored(_) -> - given_hooks_started(), - - given_module(throwing_mod, throwing_fun, fun(_) -> throw(ball) end), - given_hook_added(test_fold_hook, throwing_mod, throwing_fun, 1), - - given_module(working_mod, good, fun(X) -> X end), - given_hook_added(test_fold_hook, working_mod, good, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, initial, []), - - %% then - initial = R, - [{_Pid, {throwing_mod,throwing_fun,[initial]}, throw, ball, _ST}] = - meck:history(throwing_mod). - - -exit_in_run_fold_is_ignored(_) -> - given_hooks_started(), - - given_module(exiting_mod, exiting_fun, - fun(_) -> meck:exception(exit,oops) end), - given_hook_added(test_fold_hook, exiting_mod, exiting_fun, 1), - - given_module(working_mod, good, fun(X) -> X end), - given_hook_added(test_fold_hook, working_mod, good, 2), - - %% when - R = run_fold(test_fold_hook, ?HOST, initial, []), - - %% then - initial = R, - [{_Pid, {exiting_mod,exiting_fun,[initial]}, exit, oops, _ST}] = - meck:history(exiting_mod). - - - - -%% Givens -const(N) -> fun(_) -> N end. - -given_hooks_started() -> - gen_hook:start_link(). - -given_hook_added(HookName, ModName, FunName, Prio) -> - ejabberd_hooks:add(HookName, ?HOST, ModName, FunName, Prio). - -given_module(ModName, FunName, Fun) -> - catch meck:unload(ModName), - meck:new(ModName, [non_strict]), - meck:expect(ModName, FunName, Fun). - -given_fun(ModName, FunName, Fun) -> - meck:expect(ModName, FunName, Fun). - -run_fold(HookName, HostType, Acc, Args) -> - Params = ejabberd_hooks:add_args(#{}, Args), - {_, RetValue} = gen_hook:run_fold(HookName, HostType, Acc, Params), - RetValue. - -get_hooks() -> - ets:tab2list(gen_hook). diff --git a/test/mongoose_graphql_SUITE.erl b/test/mongoose_graphql_SUITE.erl index 2c77faccaa4..8e8ce1a2969 100644 --- a/test/mongoose_graphql_SUITE.erl +++ b/test/mongoose_graphql_SUITE.erl @@ -1090,7 +1090,7 @@ init_ep_listener(Port, EpName, ListenerOpts, Config) -> -spec start_listener(atom(), integer(), listener_opts()) -> ok. start_listener(Ref, Port, Opts) -> Dispatch = cowboy_router:compile([ - {'_', [{"/graphql", mongoose_graphql_cowboy_handler, Opts}]} + {'_', [{"/graphql", mongoose_graphql_handler, Opts}]} ]), {ok, _} = cowboy:start_clear(Ref, [{port, Port}], diff --git a/tools/gh-actions-configure-preset.sh b/tools/gh-actions-configure-preset.sh index 87bbf09ee40..a1e4f49751c 100755 --- a/tools/gh-actions-configure-preset.sh +++ b/tools/gh-actions-configure-preset.sh @@ -22,5 +22,5 @@ case "$1" in esac if [ ! -z "$GITHUB_ENV" ]; then - env | egrep "^(DB|REL_CONFIG|TLS_DIST|TESTSPEC)=" >> $GITHUB_ENV + env | grep -E "^(DB|REL_CONFIG|TLS_DIST|TESTSPEC)=" >> $GITHUB_ENV fi