mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-20 17:27:00 +01:00
Commands refactor, first pass.
- add API versionning - changed error handling, based on exception - commands moved/merged from mod_admin_p1 to mod_admin_extra - command bufixes - add some elixir unit test cases Squashed commit of the following: commit dd59855b3486f78a9349756e4f102e79b3accff8 Merge: 14e8ffc 506e08e Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 30 11:43:18 2015 +0100 Merge branch '3.2.x' into api commit 14e8ffce78cbea6c8605371d1fc50a0c1d1e012c Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 27 16:35:17 2015 +0100 Added OAuth tests to ejabberd_commands commit f81c550c14628edfe4861c228576cb767924366a Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 27 16:34:55 2015 +0100 Added some mod_http_api tests commit 6a64578d5b2ba532a2feb6503ed98561e56d5d53 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Oct 26 15:29:36 2015 +0100 Fix get_last command test Previous version won't work with dst. commit 27e0cde9e9c1f001effe68f8424a365ad947c068 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 17:59:34 2015 +0200 Add tests on admin command policy commit 19dad8d54f54c9fabd454280483cccfb06c8e78a Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 16:49:36 2015 +0200 Added command related tests (http api & user policy) commit e0e596ab4a3f3a70aba5f374f028939ab794de33 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 16:49:16 2015 +0200 Fix command call. commit 128cd7d1ede3c47a34f8ec3a750c980ccad2c61d Merge: 60c4c4c 447313c Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 22 14:48:39 2015 +0200 Merge branch '3.2.x' into api commit 60c4c4c0751302524c14219c6bc8c56a6069a689 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 22 14:45:57 2015 +0200 Fix ejabberd_commands spec. commit 8e145c28c5da762c2b93ee32327eff1db94ebfed Merge: 397273a f13dc94 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 18:26:07 2015 +0200 Merge branch '3.2.x' into api commit 397273a23ed415feac87aed33da6452229793387 Merge: c30e89b f289e27 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 15:27:45 2015 +0200 Merge branch '3.2.x' into api commit c30e89bb8a0013bff37e61e4c6953350c9c1f313 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 12:47:02 2015 +0200 Merge mod_http_api commit 7b0db22b4acd48ff6fabce41c1b2525e6580a3c5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 16 11:55:48 2015 +0200 Fix exunit tests to run with common_test suites commit d8b1a89800ac7379a57a7eb4a09c3c93c3e1e5eb Merge: 2879ae8 63455b3 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 15 11:39:45 2015 +0200 Merge branch '3.2.x' into api commit 2879ae87ff3eee369ef3d780136b96ecff5285d1 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 14 14:53:44 2015 +0200 Fix update_roster command. commit a1d453dd7a3afda9861a8d747494a45057ad574b Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 13 16:14:28 2015 +0200 API commands refactor Moving and/or merging commands from mod_admin_p1 to mod_admin_extra commit b709ed26b0fc0ca4f3bdd5a59fa58ec7e3db97fa Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 7 15:10:01 2015 +0200 Add tests on commands commit 6711687bee9c672cb3d5aed0744e13420ecf6dbd Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 29 15:58:16 2015 +0200 Add ejabberd_commands tests commit df8682f419cf3877e77e36a19bca0fc55dc991f8 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Sep 28 14:54:39 2015 +0200 Added API versioning for ejabberdctl and rest commands commit cd017b0e3aac431bc3ee807ceb7f8641e1523ef5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Sep 18 11:21:45 2015 +0200 Better error handling of HTTP API commands. commit ca5cb6acd8e4643f9d6c484d2277b0d7e88471e5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 15 15:03:05 2015 +0200 add commands to mod_admin_extra: - get_offline_count - get_presence - change_password commit 7f583fa099e30ac2b0915669fd8f102ac565b833 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 15 15:02:16 2015 +0200 Improve REST API error handling commit 14753b1c02cdce434a786b7f80f6c09f0d210075 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Sep 14 10:51:17 2015 +0200 Change REST API return codes for integer type.
This commit is contained in:
parent
c00cfca8e7
commit
3dc55c6d47
@ -336,6 +336,9 @@ test:
|
||||
quicktest:
|
||||
$(REBAR) skip_deps=true ct suites=elixir
|
||||
|
||||
eunit:
|
||||
$(REBAR) skip_deps=true exunit
|
||||
|
||||
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
|
||||
install uninstall uninstall-binary uninstall-all translations deps test spec \
|
||||
quicktest erlang_plt deps_plt ejabberd_plt
|
||||
|
@ -31,8 +31,10 @@
|
||||
tags = [] :: [atom()] | '_' | '$2',
|
||||
desc = "" :: string() | '_' | '$3',
|
||||
longdesc = "" :: string() | '_',
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
version = 0 :: integer(),
|
||||
jabs = 1 :: integer(),
|
||||
module :: atom() | '_',
|
||||
function :: atom() | '_',
|
||||
args = [] :: [aterm()] | '_' | '$1' | '$2',
|
||||
policy = restricted :: open | restricted | admin | user,
|
||||
result = {res, rescode} :: rterm() | '_' | '$2',
|
||||
|
@ -43,6 +43,8 @@
|
||||
{tag, "1.0.0"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.2", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.2"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "git://github.com/processone/moka.git",
|
||||
{tag, "1.0.5"}}}},
|
||||
{if_var_true, redis, {eredis, ".*", {git, "https://github.com/wooga/eredis",
|
||||
{tag, "v1.0.8"}}}}]}.
|
||||
|
||||
|
@ -90,7 +90,8 @@
|
||||
%%% PowFloat = math:pow(Base, Exponent),
|
||||
%%% round(PowFloat).</pre>
|
||||
%%%
|
||||
%%% Since this function will be called by ejabberd_commands, it must be exported.
|
||||
%%% Since this function will be called by ejabberd_commands, it must
|
||||
%%% be exported.
|
||||
%%% Add to your module:
|
||||
%%% <pre>-export([calc_power/2]).</pre>
|
||||
%%%
|
||||
@ -201,24 +202,33 @@
|
||||
%%% TODO: consider this feature:
|
||||
%%% All commands are catched. If an error happens, return the restuple:
|
||||
%%% {error, flattened error string}
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
|
||||
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
|
||||
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc)
|
||||
%%% need to allows this. And ejabberd_xmlrpc must be prepared to
|
||||
%%% handle such an unexpected response.
|
||||
|
||||
|
||||
-module(ejabberd_commands).
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-define(DEFAULT_VERSION, 1000000).
|
||||
|
||||
-export([init/0,
|
||||
list_commands/0,
|
||||
list_commands/1,
|
||||
get_command_format/1,
|
||||
get_command_format/2,
|
||||
get_command_format/2,
|
||||
get_command_format/3,
|
||||
get_command_definition/1,
|
||||
get_command_definition/2,
|
||||
get_tags_commands/0,
|
||||
get_tags_commands/1,
|
||||
get_commands/0,
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/4,
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
opt_type/1,
|
||||
get_commands_spec/0
|
||||
]).
|
||||
@ -226,6 +236,7 @@
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(POLICY_ACCESS, '$policy').
|
||||
|
||||
@ -260,23 +271,26 @@ get_commands_spec() ->
|
||||
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
|
||||
result_example = ok}].
|
||||
init() ->
|
||||
ets:new(ejabberd_commands, [named_table, set, public,
|
||||
{keypos, #ejabberd_commands.name}]),
|
||||
mnesia:delete_table(ejabberd_commands),
|
||||
mnesia:create_table(ejabberd_commands,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
|
||||
register_commands(get_commands_spec()).
|
||||
|
||||
-spec register_commands([ejabberd_commands()]) -> ok.
|
||||
|
||||
%% @doc Register ejabberd commands.
|
||||
%% If a command is already registered, a warning is printed and the old command is preserved.
|
||||
%% If a command is already registered, a warning is printed and the
|
||||
%% old command is preserved.
|
||||
register_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
case ets:insert_new(ejabberd_commands, Command) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end
|
||||
% XXX check if command exists
|
||||
mnesia:dirty_write(Command)
|
||||
% ?DEBUG("This command is already defined:~n~p", [Command])
|
||||
end,
|
||||
Commands).
|
||||
|
||||
@ -286,7 +300,7 @@ register_commands(Commands) ->
|
||||
unregister_commands(Commands) ->
|
||||
lists:foreach(
|
||||
fun(Command) ->
|
||||
ets:delete_object(ejabberd_commands, Command)
|
||||
mnesia:dirty_delete_object(Command)
|
||||
end,
|
||||
Commands).
|
||||
|
||||
@ -294,94 +308,183 @@ unregister_commands(Commands) ->
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments and description.
|
||||
list_commands() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
_ = '_'}),
|
||||
[{A, B, C} || [A, B, C] <- Commands].
|
||||
list_commands(?DEFAULT_VERSION).
|
||||
|
||||
-spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}].
|
||||
-spec list_commands(integer()) -> [{atom(), [aterm()], string()}].
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments, description, and
|
||||
%% policy.
|
||||
list_commands_policy() ->
|
||||
Commands = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = '$1',
|
||||
args = '$2',
|
||||
desc = '$3',
|
||||
policy = '$4',
|
||||
_ = '_'}),
|
||||
[{A, B, C, D} || [A, B, C, D] <- Commands].
|
||||
%% @doc Get a list of all the available commands, arguments and
|
||||
%% description in a given API verion.
|
||||
list_commands(Version) ->
|
||||
Commands = get_commands_definition(Version),
|
||||
[{Name, Args, Desc} || #ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}.
|
||||
|
||||
-spec list_commands_policy(integer()) ->
|
||||
[{atom(), [aterm()], string(), atom()}].
|
||||
|
||||
%% @doc Get a list of all the available commands, arguments,
|
||||
%% description, and policy in a given API version.
|
||||
list_commands_policy(Version) ->
|
||||
Commands = get_commands_definition(Version),
|
||||
[{Name, Args, Desc, Policy} ||
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc,
|
||||
policy = Policy} <- Commands].
|
||||
|
||||
-spec get_command_format(atom()) -> {[aterm()], rterm()}.
|
||||
|
||||
%% @doc Get the format of arguments and result of a command.
|
||||
get_command_format(Name) ->
|
||||
get_command_format(Name, noauth).
|
||||
get_command_format(Name, noauth, ?DEFAULT_VERSION).
|
||||
get_command_format(Name, Version) when is_integer(Version) ->
|
||||
get_command_format(Name, noauth, Version);
|
||||
get_command_format(Name, Auth) ->
|
||||
get_command_format(Name, Auth, ?DEFAULT_VERSION).
|
||||
|
||||
get_command_format(Name, Auth) ->
|
||||
-spec get_command_format(atom(),
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
integer()) ->
|
||||
{[aterm()], rterm()}.
|
||||
|
||||
get_command_format(Name, Auth, Version) ->
|
||||
Admin = is_admin(Name, Auth),
|
||||
Matched = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{name = Name,
|
||||
args = '$1',
|
||||
result = '$2',
|
||||
policy = '$3',
|
||||
_ = '_'}),
|
||||
case Matched of
|
||||
[] ->
|
||||
{error, command_unknown};
|
||||
[[Args, Result, user]] when Admin;
|
||||
Auth == noauth ->
|
||||
#ejabberd_commands{args = Args,
|
||||
result = Result,
|
||||
policy = Policy} =
|
||||
get_command_definition(Name, Version),
|
||||
case Policy of
|
||||
user when Admin;
|
||||
Auth == noauth ->
|
||||
{[{user, binary}, {server, binary} | Args], Result};
|
||||
[[Args, Result, _]] ->
|
||||
_ ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands() | command_not_found.
|
||||
-spec get_command_definition(atom()) -> ejabberd_commands().
|
||||
|
||||
%% @doc Get the definition record of a command.
|
||||
get_command_definition(Name) ->
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[E] -> E;
|
||||
[] -> command_not_found
|
||||
get_command_definition(Name, ?DEFAULT_VERSION).
|
||||
|
||||
-spec get_command_definition(atom(), integer()) -> ejabberd_commands().
|
||||
|
||||
%% @doc Get the definition record of a command in a given API version.
|
||||
get_command_definition(Name, Version) ->
|
||||
case lists:reverse(
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = N, version = V} = C)
|
||||
when N == Name, V =< Version ->
|
||||
{V, C}
|
||||
end)))) of
|
||||
[{_, Command} | _ ] -> Command;
|
||||
_E -> throw(unknown_command)
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
|
||||
%% @doc Execute a command.
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command([], noauth, Name, Arguments).
|
||||
-spec get_commands_definition(integer()) -> [ejabberd_commands()].
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()]
|
||||
% @doc Returns all commands for a given API version
|
||||
get_commands_definition(Version) ->
|
||||
L = lists:reverse(
|
||||
lists:sort(
|
||||
mnesia:dirty_select(
|
||||
ejabberd_commands,
|
||||
ets:fun2ms(
|
||||
fun(#ejabberd_commands{name = Name, version = V} = C)
|
||||
when V =< Version ->
|
||||
{Name, V, C}
|
||||
end)))),
|
||||
F = fun({_Name, _V, Command}, []) ->
|
||||
[Command];
|
||||
({Name, _V, _Command}, [#ejabberd_commands{name=Name}|_T] = Acc) ->
|
||||
Acc;
|
||||
({_Name, _V, Command}, Acc) -> [Command | Acc]
|
||||
end,
|
||||
lists:foldl(F, [], L).
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm
|
||||
%% where
|
||||
%% Arguments = [any()]
|
||||
%% @doc Execute a command.
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command(atom(),
|
||||
[any()],
|
||||
integer() |
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
|
||||
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
|
||||
%% where
|
||||
%% Auth = {User::string(), Server::string(), Password::string(),
|
||||
%% Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments, Version) when is_integer(Version) ->
|
||||
execute_command([], noauth, Name, Arguments, Version);
|
||||
execute_command(Name, Arguments, Auth) ->
|
||||
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
|
||||
%% ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments) ->
|
||||
%%
|
||||
%% @doc Execute a command
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()],
|
||||
integer()
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
Auth = case is_admin(Name, Auth1) of
|
||||
true -> admin;
|
||||
false -> Auth1
|
||||
end,
|
||||
case ets:lookup(ejabberd_commands, Name) of
|
||||
[Command] ->
|
||||
AccessCommands = get_access_commands(AccessCommands1),
|
||||
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
catch
|
||||
{error, Error} -> {error, Error}
|
||||
end;
|
||||
[] -> {error, command_unknown}
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_access_commands(AccessCommands1, Version),
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
|
||||
ok -> execute_command2(Auth, Command, Arguments)
|
||||
end.
|
||||
|
||||
execute_command2(
|
||||
@ -407,26 +510,25 @@ execute_command2(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
Function = Command#ejabberd_commands.function,
|
||||
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
|
||||
try apply(Module, Function, Arguments) of
|
||||
Response ->
|
||||
Response
|
||||
catch
|
||||
Problem ->
|
||||
{error, Problem}
|
||||
end.
|
||||
apply(Module, Function, Arguments).
|
||||
|
||||
-spec get_tags_commands() -> [{string(), [string()]}].
|
||||
|
||||
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands.
|
||||
get_tags_commands() ->
|
||||
CommandTags = ets:match(ejabberd_commands,
|
||||
#ejabberd_commands{
|
||||
name = '$1',
|
||||
tags = '$2',
|
||||
_ = '_'}),
|
||||
get_tags_commands(?DEFAULT_VERSION).
|
||||
|
||||
-spec get_tags_commands(integer()) -> [{string(), [string()]}].
|
||||
|
||||
%% @spec (integer) -> [{Tag::string(), [CommandName::string()]}]
|
||||
%% @doc Get all the tags and associated commands in a given API version
|
||||
get_tags_commands(Version) ->
|
||||
CommandTags = [{Name, Tags} ||
|
||||
#ejabberd_commands{name = Name, tags = Tags}
|
||||
<- get_commands_definition(Version)],
|
||||
Dict = lists:foldl(
|
||||
fun([CommandNameAtom, CTags], D) ->
|
||||
fun({CommandNameAtom, CTags}, D) ->
|
||||
CommandName = atom_to_list(CommandNameAtom),
|
||||
case CTags of
|
||||
[] ->
|
||||
@ -445,7 +547,6 @@ get_tags_commands() ->
|
||||
CommandTags),
|
||||
orddict:to_list(Dict).
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Access verification
|
||||
%% -----------------------------
|
||||
@ -479,7 +580,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
@ -488,7 +590,8 @@ check_access_commands(AccessCommands, Auth, Method, Command1, Arguments) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth) of
|
||||
true ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
@ -551,9 +654,11 @@ check_access2(Access, User, Server) ->
|
||||
deny -> false
|
||||
end.
|
||||
|
||||
check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
@ -577,18 +682,20 @@ tag_arguments(ArgsDefs, Args) ->
|
||||
Args).
|
||||
|
||||
|
||||
get_access_commands(undefined) ->
|
||||
Cmds = get_commands(),
|
||||
get_access_commands(undefined, Version) ->
|
||||
Cmds = get_commands(Version),
|
||||
[{?POLICY_ACCESS, Cmds, []}];
|
||||
get_access_commands(AccessCommands) ->
|
||||
get_access_commands(AccessCommands, _Version) ->
|
||||
AccessCommands.
|
||||
|
||||
get_commands() ->
|
||||
get_commands(?DEFAULT_VERSION).
|
||||
get_commands(Version) ->
|
||||
Opts = ejabberd_config:get_option(
|
||||
commands,
|
||||
fun(V) when is_list(V) -> V end,
|
||||
[]),
|
||||
CommandsList = list_commands_policy(),
|
||||
CommandsList = list_commands_policy(Version),
|
||||
OpenCmds = [N || {N, _, _, open} <- CommandsList],
|
||||
RestrictedCmds = [N || {N, _, _, restricted} <- CommandsList],
|
||||
AdminCmds = [N || {N, _, _, admin} <- CommandsList],
|
||||
|
@ -48,7 +48,7 @@
|
||||
-behaviour(ejabberd_config).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, init/0, process/1, process2/2,
|
||||
-export([start/0, init/0, process/1,
|
||||
register_commands/3, unregister_commands/3,
|
||||
opt_type/1]).
|
||||
|
||||
@ -57,6 +57,8 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(DEFAULT_VERSION, 1000000).
|
||||
|
||||
|
||||
%%-----------------------------
|
||||
%% Module
|
||||
@ -69,7 +71,7 @@ start() ->
|
||||
[SNode3 | Args3] ->
|
||||
[SNode3, 60000, Args3];
|
||||
_ ->
|
||||
print_usage(),
|
||||
print_usage(?DEFAULT_VERSION),
|
||||
halt(?STATUS_USAGE)
|
||||
end,
|
||||
SNode1 = case string:tokens(SNode, "@") of
|
||||
@ -93,6 +95,9 @@ start() ->
|
||||
[Node, Reason]),
|
||||
%% TODO: show minimal start help
|
||||
?STATUS_BADRPC;
|
||||
{invalid_version, V} ->
|
||||
print("Invalid API version number: ~p~n", [V]),
|
||||
?STATUS_ERROR;
|
||||
S ->
|
||||
S
|
||||
end,
|
||||
@ -126,11 +131,17 @@ unregister_commands(CmdDescs, Module, Function) ->
|
||||
%% Process
|
||||
%%-----------------------------
|
||||
|
||||
|
||||
-spec process([string()]) -> non_neg_integer().
|
||||
process(Args) ->
|
||||
process(Args, ?DEFAULT_VERSION).
|
||||
|
||||
|
||||
-spec process([string()], non_neg_integer()) -> non_neg_integer().
|
||||
|
||||
%% The commands status, stop and restart are defined here to ensure
|
||||
%% they are usable even if ejabberd is completely stopped.
|
||||
process(["status"]) ->
|
||||
process(["status"], _Version) ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
print("The node ~p is ~p with status: ~p~n",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
@ -146,24 +157,24 @@ process(["status"]) ->
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(["stop"]) ->
|
||||
process(["stop"], _Version) ->
|
||||
%%ejabberd_cover:stop(),
|
||||
init:stop(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["restart"]) ->
|
||||
process(["restart"], _Version) ->
|
||||
init:restart(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia"]) ->
|
||||
process(["mnesia"], _Version) ->
|
||||
print("~p~n", [mnesia:system_info(all)]),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", "info"]) ->
|
||||
process(["mnesia", "info"], _Version) ->
|
||||
mnesia:info(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["mnesia", Arg]) ->
|
||||
process(["mnesia", Arg], _Version) ->
|
||||
case catch mnesia:system_info(list_to_atom(Arg)) of
|
||||
{'EXIT', Error} -> print("Error: ~p~n", [Error]);
|
||||
Return -> print("~p~n", [Return])
|
||||
@ -172,23 +183,23 @@ process(["mnesia", Arg]) ->
|
||||
|
||||
%% The arguments --long and --dual are not documented because they are
|
||||
%% automatically selected depending in the number of columns of the shell
|
||||
process(["help" | Mode]) ->
|
||||
process(["help" | Mode], Version) ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
case Mode of
|
||||
[] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
print_usage(dual, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--dual"] ->
|
||||
print_usage(dual, MaxC, ShCode),
|
||||
print_usage(dual, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--long"] ->
|
||||
print_usage(long, MaxC, ShCode),
|
||||
print_usage(long, MaxC, ShCode, Version),
|
||||
?STATUS_USAGE;
|
||||
["--tags"] ->
|
||||
print_usage_tags(MaxC, ShCode),
|
||||
print_usage_tags(MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS;
|
||||
["--tags", Tag] ->
|
||||
print_usage_tags(Tag, MaxC, ShCode),
|
||||
print_usage_tags(Tag, MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS;
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
@ -196,13 +207,22 @@ process(["help" | Mode]) ->
|
||||
[CmdString | _] ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
print_usage_commands(binary_to_list(CmdStringU), MaxC, ShCode),
|
||||
print_usage_commands2(binary_to_list(CmdStringU), MaxC, ShCode, Version),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
process(["--version", Arg | Args], _) ->
|
||||
Version =
|
||||
try
|
||||
list_to_integer(Arg)
|
||||
catch _:_ ->
|
||||
throw({invalid_version, Arg})
|
||||
end,
|
||||
process(Args, Version);
|
||||
|
||||
process(Args, Version) ->
|
||||
AccessCommands = get_accesscommands(),
|
||||
{String, Code} = process2(Args, AccessCommands),
|
||||
{String, Code} = process2(Args, AccessCommands, Version),
|
||||
case String of
|
||||
[] -> ok;
|
||||
_ ->
|
||||
@ -211,18 +231,21 @@ process(Args) ->
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
|
||||
process2(["--auth", User, Server, Pass | Args], AccessCommands) ->
|
||||
process2(Args, {list_to_binary(User), list_to_binary(Server), list_to_binary(Pass), true}, AccessCommands);
|
||||
process2(Args, AccessCommands) ->
|
||||
process2(Args, noauth, AccessCommands).
|
||||
process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
|
||||
list_to_binary(Pass), true}, Version);
|
||||
process2(Args, AccessCommands, Version) ->
|
||||
process2(Args, AccessCommands, admin, Version).
|
||||
|
||||
process2(Args, Auth, AccessCommands) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands) of
|
||||
|
||||
|
||||
process2(Args, AccessCommands, Auth, Version) ->
|
||||
case try_run_ctp(Args, Auth, AccessCommands, Version) of
|
||||
{String, wrong_command_arguments}
|
||||
when is_list(String) ->
|
||||
io:format(lists:flatten(["\n" | String]++["\n"])),
|
||||
[CommandString | _] = Args,
|
||||
process(["help" | [CommandString]]),
|
||||
process(["help" | [CommandString]], Version),
|
||||
{lists:flatten(String), ?STATUS_ERROR};
|
||||
{String, Code}
|
||||
when is_list(String) and is_integer(Code) ->
|
||||
@ -246,29 +269,29 @@ get_accesscommands() ->
|
||||
%%-----------------------------
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
|
||||
try_run_ctp(Args, Auth, AccessCommands) ->
|
||||
try_run_ctp(Args, Auth, AccessCommands, Version) ->
|
||||
try ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||
false when Args /= [] ->
|
||||
try_call_command(Args, Auth, AccessCommands);
|
||||
try_call_command(Args, Auth, AccessCommands, Version);
|
||||
false ->
|
||||
print_usage(),
|
||||
print_usage(Version),
|
||||
{"", ?STATUS_USAGE};
|
||||
Status ->
|
||||
{"", Status}
|
||||
catch
|
||||
exit:Why ->
|
||||
print_usage(),
|
||||
print_usage(Version),
|
||||
{io_lib:format("Error in ejabberd ctl process: ~p", [Why]), ?STATUS_USAGE};
|
||||
Error:Why ->
|
||||
%% In this case probably ejabberd is not started, so let's show Status
|
||||
process(["status"]),
|
||||
process(["status"], Version),
|
||||
print("~n", []),
|
||||
{io_lib:format("Error in ejabberd ctl process: '~p' ~p", [Error, Why]), ?STATUS_USAGE}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()}
|
||||
try_call_command(Args, Auth, AccessCommands) ->
|
||||
try call_command(Args, Auth, AccessCommands) of
|
||||
try_call_command(Args, Auth, AccessCommands, Version) ->
|
||||
try call_command(Args, Auth, AccessCommands, Version) of
|
||||
{error, command_unknown} ->
|
||||
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
|
||||
{error, wrong_command_arguments} ->
|
||||
@ -276,24 +299,28 @@ try_call_command(Args, Auth, AccessCommands) ->
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
throw:Error ->
|
||||
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
|
||||
A:Why ->
|
||||
Stack = erlang:get_stacktrace(),
|
||||
{io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR}
|
||||
end.
|
||||
|
||||
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
|
||||
call_command([CmdString | Args], Auth, AccessCommands) ->
|
||||
call_command([CmdString | Args], Auth, AccessCommands, Version) ->
|
||||
CmdStringU = ejabberd_regexp:greplace(
|
||||
list_to_binary(CmdString), <<"-">>, <<"_">>),
|
||||
Command = list_to_atom(binary_to_list(CmdStringU)),
|
||||
case ejabberd_commands:get_command_format(Command, Auth) of
|
||||
case ejabberd_commands:get_command_format(Command, Auth, Version) of
|
||||
{error, command_unknown} ->
|
||||
{error, command_unknown};
|
||||
{ArgsFormat, ResultFormat} ->
|
||||
case (catch format_args(Args, ArgsFormat)) of
|
||||
ArgsFormatted when is_list(ArgsFormatted) ->
|
||||
Result = ejabberd_commands:execute_command(AccessCommands, Auth, Command,
|
||||
ArgsFormatted),
|
||||
Result = ejabberd_commands:execute_command(AccessCommands,
|
||||
Auth, Command,
|
||||
ArgsFormatted,
|
||||
Version),
|
||||
format_result(Result, ResultFormat);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
@ -404,8 +431,8 @@ make_status(ok) -> ?STATUS_SUCCESS;
|
||||
make_status(true) -> ?STATUS_SUCCESS;
|
||||
make_status(_Error) -> ?STATUS_ERROR.
|
||||
|
||||
get_list_commands() ->
|
||||
try ejabberd_commands:list_commands() of
|
||||
get_list_commands(Version) ->
|
||||
try ejabberd_commands:list_commands(Version) of
|
||||
Commands ->
|
||||
[tuple_command_help(Command)
|
||||
|| {N,_,_}=Command <- Commands,
|
||||
@ -458,10 +485,10 @@ get_list_ctls() ->
|
||||
-define(U2, "\e[24m").
|
||||
-define(U(S), case ShCode of true -> [?U1, S, ?U2]; false -> S end).
|
||||
|
||||
print_usage() ->
|
||||
print_usage(Version) ->
|
||||
{MaxC, ShCode} = get_shell_info(),
|
||||
print_usage(dual, MaxC, ShCode).
|
||||
print_usage(HelpMode, MaxC, ShCode) ->
|
||||
print_usage(dual, MaxC, ShCode, Version).
|
||||
print_usage(HelpMode, MaxC, ShCode, Version) ->
|
||||
AllCommands =
|
||||
[
|
||||
{"status", [], "Get ejabberd status"},
|
||||
@ -469,11 +496,11 @@ print_usage(HelpMode, MaxC, ShCode) ->
|
||||
{"restart", [], "Restart ejabberd"},
|
||||
{"help", ["[--tags [tag] | com?*]"], "Show help (try: ejabberdctl help help)"},
|
||||
{"mnesia", ["[info]"], "show information of Mnesia system"}] ++
|
||||
get_list_commands() ++
|
||||
get_list_commands(Version) ++
|
||||
get_list_ctls(),
|
||||
|
||||
print(
|
||||
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--auth ",
|
||||
["Usage: ", ?B("ejabberdctl"), " [--no-timeout] [--node ", ?U("nodename"), "] [--version ", ?U("api_version"), "] [--auth ",
|
||||
?U("user"), " ", ?U("host"), " ", ?U("password"), "] ",
|
||||
?U("command"), " [", ?U("options"), "]\n"
|
||||
"\n"
|
||||
@ -598,9 +625,9 @@ format_command_lines(CALD, _MaxCmdLen, MaxC, ShCode, long) ->
|
||||
%% Print Tags
|
||||
%%-----------------------------
|
||||
|
||||
print_usage_tags(MaxC, ShCode) ->
|
||||
print_usage_tags(MaxC, ShCode, Version) ->
|
||||
print("Available tags and commands:", []),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(Version),
|
||||
lists:foreach(
|
||||
fun({Tag, Commands} = _TagCommands) ->
|
||||
print(["\n\n ", ?B(Tag), "\n "], []),
|
||||
@ -611,10 +638,10 @@ print_usage_tags(MaxC, ShCode) ->
|
||||
TagsCommands),
|
||||
print("\n\n", []).
|
||||
|
||||
print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
print_usage_tags(Tag, MaxC, ShCode, Version) ->
|
||||
print(["Available commands with tag ", ?B(Tag), ":", "\n"], []),
|
||||
HelpMode = long,
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(),
|
||||
TagsCommands = ejabberd_commands:get_tags_commands(Version),
|
||||
CommandsNames = case lists:keysearch(Tag, 1, TagsCommands) of
|
||||
{value, {Tag, CNs}} -> CNs;
|
||||
false -> []
|
||||
@ -622,7 +649,7 @@ print_usage_tags(Tag, MaxC, ShCode) ->
|
||||
CommandsList = lists:map(
|
||||
fun(NameString) ->
|
||||
C = ejabberd_commands:get_command_definition(
|
||||
list_to_atom(NameString)),
|
||||
list_to_atom(NameString), Version),
|
||||
#ejabberd_commands{name = Name,
|
||||
args = Args,
|
||||
desc = Desc} = C,
|
||||
@ -673,20 +700,20 @@ print_usage_help(MaxC, ShCode) ->
|
||||
%%-----------------------------
|
||||
|
||||
%% @spec (CmdSubString::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_commands(CmdSubString, MaxC, ShCode) ->
|
||||
print_usage_commands2(CmdSubString, MaxC, ShCode, Version) ->
|
||||
%% Get which command names match this substring
|
||||
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands()],
|
||||
AllCommandsNames = [atom_to_list(Name) || {Name, _, _} <- ejabberd_commands:list_commands(Version)],
|
||||
Cmds = filter_commands(AllCommandsNames, CmdSubString),
|
||||
case Cmds of
|
||||
[] -> io:format("Error: not command found that match: ~p~n", [CmdSubString]);
|
||||
_ -> print_usage_commands2(lists:sort(Cmds), MaxC, ShCode)
|
||||
[] -> io:format("Error: no command found that match: ~p~n", [CmdSubString]);
|
||||
_ -> print_usage_commands3(lists:sort(Cmds), MaxC, ShCode, Version)
|
||||
end.
|
||||
|
||||
print_usage_commands2(Cmds, MaxC, ShCode) ->
|
||||
print_usage_commands3(Cmds, MaxC, ShCode, Version) ->
|
||||
%% Then for each one print it
|
||||
lists:mapfoldl(
|
||||
fun(Cmd, Remaining) ->
|
||||
print_usage_command(Cmd, MaxC, ShCode),
|
||||
print_usage_command(Cmd, MaxC, ShCode, Version),
|
||||
case Remaining > 1 of
|
||||
true -> print([" ", lists:duplicate(MaxC, 126), " \n"], []);
|
||||
false -> ok
|
||||
@ -716,16 +743,16 @@ filter_commands_regexp(All, Glob) ->
|
||||
All).
|
||||
|
||||
%% @spec (Cmd::string(), MaxC::integer(), ShCode::boolean()) -> ok
|
||||
print_usage_command(Cmd, MaxC, ShCode) ->
|
||||
print_usage_command(Cmd, MaxC, ShCode, Version) ->
|
||||
Name = list_to_atom(Cmd),
|
||||
case ejabberd_commands:get_command_definition(Name) of
|
||||
case ejabberd_commands:get_command_definition(Name, Version) of
|
||||
command_not_found ->
|
||||
io:format("Error: command ~p not known.~n", [Cmd]);
|
||||
C ->
|
||||
print_usage_command(Cmd, C, MaxC, ShCode)
|
||||
print_usage_command2(Cmd, C, MaxC, ShCode)
|
||||
end.
|
||||
|
||||
print_usage_command(Cmd, C, MaxC, ShCode) ->
|
||||
print_usage_command2(Cmd, C, MaxC, ShCode) ->
|
||||
#ejabberd_commands{
|
||||
tags = TagsAtoms,
|
||||
desc = Desc,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -29,6 +29,11 @@
|
||||
%% request_handlers:
|
||||
%% "/api": mod_http_api
|
||||
%%
|
||||
%% To use a specific API version N, add a vN element in the URL path:
|
||||
%% in ejabberd_http listener
|
||||
%% request_handlers:
|
||||
%% "/api/v2": mod_http_api
|
||||
%%
|
||||
%% Access rights are defined with:
|
||||
%% commands_admin_access: configure
|
||||
%% commands:
|
||||
@ -76,6 +81,8 @@
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-define(DEFAULT_API_VERSION, 0).
|
||||
|
||||
-define(CT_PLAIN,
|
||||
{<<"Content-Type">>, <<"text/plain">>}).
|
||||
|
||||
@ -179,7 +186,8 @@ check_permissions2(#request{ip={IP, _Port}}, Call) ->
|
||||
true -> {allowed, Call, admin};
|
||||
_ -> unauthorized_response()
|
||||
end;
|
||||
_ ->
|
||||
_E ->
|
||||
?DEBUG("Unauthorized: ~p", [_E]),
|
||||
unauthorized_response()
|
||||
end.
|
||||
|
||||
@ -192,10 +200,13 @@ oauth_check_token(Scope, Token) ->
|
||||
%% command processing
|
||||
%% ------------------
|
||||
|
||||
%process(Call, Request) ->
|
||||
% ?DEBUG("~p~n~p", [Call, Request]), ok;
|
||||
process(_, #request{method = 'POST', data = <<>>}) ->
|
||||
?DEBUG("Bad Request: no data", []),
|
||||
badrequest_response();
|
||||
badrequest_response(<<"Missing POST data">>);
|
||||
process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
Version = get_api_version(Req),
|
||||
try
|
||||
Args = case jiffy:decode(Data) of
|
||||
List when is_list(List) -> List;
|
||||
@ -205,16 +216,20 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args),
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
ErrorResponse -> %% Should we reply 403 ?
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:Error ->
|
||||
?DEBUG("Bad Request: ~p", [Error]),
|
||||
catch _:{error,{_,invalid_json}} = _Err ->
|
||||
?DEBUG("Bad Request: ~p", [_Err]),
|
||||
badrequest_response(<<"Invalid JSON input">>);
|
||||
_:_Error ->
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
Version = get_api_version(Req),
|
||||
try
|
||||
Args = case Data of
|
||||
[{nokey, <<>>}] -> [];
|
||||
@ -223,13 +238,13 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
|
||||
log(Call, Args, IP),
|
||||
case check_permissions(Req, Call) of
|
||||
{allowed, Cmd, Auth} ->
|
||||
{Code, Result} = handle(Cmd, Auth, Args),
|
||||
{Code, Result} = handle(Cmd, Auth, Args, Version),
|
||||
json_response(Code, jiffy:encode(Result));
|
||||
ErrorResponse ->
|
||||
ErrorResponse
|
||||
end
|
||||
catch _:Error ->
|
||||
?DEBUG("Bad Request: ~p", [Error]),
|
||||
catch _:_Error ->
|
||||
?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
badrequest_response()
|
||||
end;
|
||||
process([], #request{method = 'OPTIONS', data = <<>>}) ->
|
||||
@ -238,13 +253,28 @@ process(_Path, Request) ->
|
||||
?DEBUG("Bad Request: no handler ~p", [Request]),
|
||||
badrequest_response().
|
||||
|
||||
% get API version N from last "vN" element in URL path
|
||||
get_api_version(#request{path = Path}) ->
|
||||
get_api_version(lists:reverse(Path));
|
||||
get_api_version([<<"v", String/binary>> | Tail]) ->
|
||||
case catch jlib:binary_to_integer(String) of
|
||||
N when is_integer(N) ->
|
||||
N;
|
||||
_ ->
|
||||
get_api_version(Tail)
|
||||
end;
|
||||
get_api_version([_Head | Tail]) ->
|
||||
get_api_version(Tail);
|
||||
get_api_version([]) ->
|
||||
?DEFAULT_API_VERSION.
|
||||
|
||||
%% ----------------
|
||||
%% command handlers
|
||||
%% ----------------
|
||||
|
||||
% generic ejabberd command handler
|
||||
handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
case ejabberd_commands:get_command_format(Call, Auth) of
|
||||
handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
case ejabberd_commands:get_command_format(Call, Auth, Version) of
|
||||
{ArgsSpec, _} when is_list(ArgsSpec) ->
|
||||
Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
|
||||
Spec = lists:foldr(
|
||||
@ -259,22 +289,51 @@ handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
({Key, atom}, Acc) ->
|
||||
[{Key, undefined}|Acc]
|
||||
end, [], ArgsSpec),
|
||||
handle2(Call, Auth, match(Args2, Spec));
|
||||
try
|
||||
handle2(Call, Auth, match(Args2, Spec), Version)
|
||||
catch throw:not_found ->
|
||||
{404, <<"not_found">>};
|
||||
throw:{not_found, Why} when is_atom(Why) ->
|
||||
{404, jlib:atom_to_binary(Why)};
|
||||
throw:{not_found, Msg} ->
|
||||
{404, iolist_to_binary(Msg)};
|
||||
throw:not_allowed ->
|
||||
{401, <<"not_allowed">>};
|
||||
throw:{not_allowed, Why} when is_atom(Why) ->
|
||||
{401, jlib:atom_to_binary(Why)};
|
||||
throw:{not_allowed, Msg} ->
|
||||
{401, iolist_to_binary(Msg)};
|
||||
throw:{invalid_parameter, Msg} ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
throw:{error, Why} when is_atom(Why) ->
|
||||
{400, jlib:atom_to_binary(Why)};
|
||||
throw:{error, Msg} ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
throw:Error when is_atom(Error) ->
|
||||
{400, jlib:atom_to_binary(Error)};
|
||||
throw:Msg when is_list(Msg); is_binary(Msg) ->
|
||||
{400, iolist_to_binary(Msg)};
|
||||
_Error ->
|
||||
?ERROR_MSG("REST API Error: ~p ~p", [_Error, erlang:get_stacktrace()]),
|
||||
{500, <<"internal_error">>}
|
||||
end;
|
||||
{error, Msg} ->
|
||||
?ERROR_MSG("REST API Error: ~p", [Msg]),
|
||||
{400, Msg};
|
||||
_Error ->
|
||||
?ERROR_MSG("REST API Error: ~p", [_Error]),
|
||||
{400, <<"Error">>}
|
||||
end.
|
||||
|
||||
handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
|
||||
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
|
||||
handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
|
||||
{ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
|
||||
ArgsFormatted = format_args(Args, ArgsF),
|
||||
case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
|
||||
0 -> {200, <<"OK">>};
|
||||
1 -> {500, <<"500 Internal server error">>};
|
||||
400 -> {400, <<"400 Bad Request">>};
|
||||
404 -> {404, <<"404 Not found">>};
|
||||
Res -> format_command_result(Call, Auth, Res)
|
||||
case ejabberd_commands:execute_command(undefined, Auth,
|
||||
Call, ArgsFormatted, Version) of
|
||||
{error, Error} ->
|
||||
throw(Error);
|
||||
Res ->
|
||||
format_command_result(Call, Auth, Res, Version)
|
||||
end.
|
||||
|
||||
get_elem_delete(A, L) ->
|
||||
@ -339,7 +398,9 @@ format_arg(undefined, binary) -> <<>>;
|
||||
format_arg(undefined, string) -> <<>>;
|
||||
format_arg(Arg, Format) ->
|
||||
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
|
||||
error.
|
||||
throw({invalid_parameter,
|
||||
io_lib:format("Arg ~p is not in format ~p",
|
||||
[Arg, Format])}).
|
||||
|
||||
process_unicode_codepoints(Str) ->
|
||||
iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
|
||||
@ -353,36 +414,26 @@ process_unicode_codepoints(Str) ->
|
||||
match(Args, Spec) ->
|
||||
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
|
||||
|
||||
ejabberd_command(Auth, Cmd, Args, Default) ->
|
||||
Access = case Auth of
|
||||
admin -> [];
|
||||
_ -> undefined
|
||||
end,
|
||||
case catch ejabberd_commands:execute_command(Access, Auth, Cmd, Args) of
|
||||
{'EXIT', _} -> Default;
|
||||
{error, _} -> Default;
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
format_command_result(Cmd, Auth, Result) ->
|
||||
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
|
||||
format_command_result(Cmd, Auth, Result, Version) ->
|
||||
{_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
|
||||
case {ResultFormat, Result} of
|
||||
{{_, rescode}, V} when V == true; V == ok ->
|
||||
{200, <<"">>};
|
||||
{{_, rescode}, _} ->
|
||||
{500, <<"">>};
|
||||
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
|
||||
{200, iolist_to_binary(Text1)};
|
||||
{{_, restuple}, {_, Text2}} ->
|
||||
{500, iolist_to_binary(Text2)};
|
||||
{{_, {list, _}}, _V} ->
|
||||
{_, L} = format_result(Result, ResultFormat),
|
||||
{200, L};
|
||||
{{_, {tuple, _}}, _V} ->
|
||||
{_, T} = format_result(Result, ResultFormat),
|
||||
{200, T};
|
||||
_ ->
|
||||
{200, {[format_result(Result, ResultFormat)]}}
|
||||
{{_, rescode}, V} when V == true; V == ok ->
|
||||
{200, 0};
|
||||
{{_, rescode}, _} ->
|
||||
{200, 1};
|
||||
{{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
|
||||
{200, iolist_to_binary(Text1)};
|
||||
{{_, restuple}, {_, Text2}} ->
|
||||
{500, iolist_to_binary(Text2)};
|
||||
{{_, {list, _}}, _V} ->
|
||||
{_, L} = format_result(Result, ResultFormat),
|
||||
{200, L};
|
||||
{{_, {tuple, _}}, _V} ->
|
||||
{_, T} = format_result(Result, ResultFormat),
|
||||
{200, T};
|
||||
_ ->
|
||||
{200, {[format_result(Result, ResultFormat)]}}
|
||||
end.
|
||||
|
||||
format_result(Atom, {Name, atom}) ->
|
||||
@ -421,14 +472,15 @@ format_result(404, {_Name, _}) ->
|
||||
"not_found".
|
||||
|
||||
unauthorized_response() ->
|
||||
{401, ?HEADER(?CT_XML),
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
|
||||
unauthorized_response(<<"401 Unauthorized">>).
|
||||
unauthorized_response(Body) ->
|
||||
json_response(401, jiffy:encode(Body)).
|
||||
|
||||
badrequest_response() ->
|
||||
{400, ?HEADER(?CT_XML),
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
|
||||
badrequest_response(<<"400 Bad Request">>).
|
||||
badrequest_response(Body) ->
|
||||
json_response(400, jiffy:encode(Body)).
|
||||
|
||||
json_response(Code, Body) when is_integer(Code) ->
|
||||
{Code, ?HEADER(?CT_JSON), Body}.
|
||||
|
||||
|
79
test/ejabberd_admin_test.exs
Normal file
79
test/ejabberd_admin_test.exs
Normal file
@ -0,0 +1,79 @@
|
||||
# ----------------------------------------------------------------------
|
||||
#
|
||||
# ejabberd, Copyright (C) 2002-2015 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.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
defmodule EjabberdAdminTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
setup_all do
|
||||
:mnesia.start
|
||||
# For some myterious reason, :ejabberd_commands.init mays
|
||||
# sometimes fails if module is not loaded before
|
||||
{:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
|
||||
:ejabberd_commands.init
|
||||
:ejabberd_admin.start
|
||||
:ok
|
||||
end
|
||||
|
||||
setup do
|
||||
:ok
|
||||
end
|
||||
|
||||
test "Logvel can be set and retrieved" do
|
||||
:ejabberd_logger.start()
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [1])
|
||||
assert {1, :critical, 'Critical'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [2])
|
||||
assert {2, :error, 'Error'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [3])
|
||||
assert {3, :warning, 'Warning'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert {:wrong_loglevel, 6} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:set_loglevel, [6])
|
||||
assert {3, :warning, 'Warning'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [4])
|
||||
assert {4, :info, 'Info'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [5])
|
||||
assert {5, :debug, 'Debug'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [0])
|
||||
assert {0, :no_log, 'No log'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
|
||||
end
|
||||
|
||||
test "command status works with ejabberd stopped" do
|
||||
assert :ejabberd_not_running ==
|
||||
elem(:ejabberd_commands.execute_command(:status, []), 0)
|
||||
end
|
||||
|
||||
end
|
57
test/ejabberd_auth_mock.exs
Normal file
57
test/ejabberd_auth_mock.exs
Normal file
@ -0,0 +1,57 @@
|
||||
# ejabberd_auth mock
|
||||
######################
|
||||
|
||||
defmodule EjabberdAuthMock do
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
@agent __MODULE__
|
||||
|
||||
def init do
|
||||
try do
|
||||
Agent.stop(@agent)
|
||||
catch
|
||||
:exit, _e -> :ok
|
||||
end
|
||||
|
||||
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
|
||||
|
||||
mock(:ejabberd_auth, :is_user_exists,
|
||||
fn (user, domain) ->
|
||||
Agent.get(@agent, fn users -> Map.get(users, {user, domain}) end) != nil
|
||||
end)
|
||||
mock(:ejabberd_auth, :get_password_s,
|
||||
fn (user, domain) ->
|
||||
Agent.get(@agent, fn users -> Map.get(users, {user, domain}, "") end )
|
||||
end)
|
||||
mock(:ejabberd_auth, :check_password,
|
||||
fn (user, domain, password) ->
|
||||
Agent.get(@agent, fn users ->
|
||||
Map.get(users, {user, domain}) end) == password
|
||||
end)
|
||||
mock(:ejabberd_auth, :set_password,
|
||||
fn (user, domain, password) ->
|
||||
Agent.update(@agent, fn users ->
|
||||
Map.put(users, {user, domain}, password) end)
|
||||
end)
|
||||
end
|
||||
|
||||
def create_user(user, domain, password) do
|
||||
Agent.update(@agent, fn users -> Map.put(users, {user, domain}, password) end)
|
||||
end
|
||||
|
||||
####################################################################
|
||||
# Helpers
|
||||
####################################################################
|
||||
|
||||
# TODO refactor: Move to ejabberd_test_mock
|
||||
def mock(module, function, fun) do
|
||||
try do
|
||||
:meck.new(module)
|
||||
catch
|
||||
:error, {:already_started, _pid} -> :ok
|
||||
end
|
||||
|
||||
:meck.expect(module, function, fun)
|
||||
end
|
||||
|
||||
end
|
@ -19,39 +19,406 @@
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
defmodule EjabberdCommandsTest do
|
||||
@author "mremond@process-one.net"
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
require Record
|
||||
Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
|
||||
# mocked callback module
|
||||
@module :test_module
|
||||
# Admin user
|
||||
@admin "admin"
|
||||
@adminpass "adminpass"
|
||||
# Non admin user
|
||||
@user "user"
|
||||
@userpass "userpass"
|
||||
# XMPP domain
|
||||
@domain "domain"
|
||||
|
||||
setup_all do
|
||||
:ejabberd_commands.init
|
||||
end
|
||||
require Record
|
||||
Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands,
|
||||
from: "ejabberd_commands.hrl")
|
||||
|
||||
test "Check that we can register a command" do
|
||||
assert :ejabberd_commands.register_commands([user_test_command]) == :ok
|
||||
commands = :ejabberd_commands.list_commands
|
||||
assert Enum.member?(commands, {:test_user, [], "Test user"})
|
||||
end
|
||||
setup_all do
|
||||
try do
|
||||
:stringprep.start
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:mnesia.start
|
||||
EjabberdOauthMock.init
|
||||
:ok
|
||||
end
|
||||
|
||||
# test "Check that a user can use a user command" do
|
||||
# [Command] = ets:lookup(ejabberd_commands, test_user),
|
||||
# AccessCommands = ejabberd_commands:get_access_commands(undefined),
|
||||
# ejabberd_commands:check_access_commands(AccessCommands, {<<"test">>,<<"localhost">>, {oauth,<<"MyToken">>}, false}, test_user, Command, []).
|
||||
# end
|
||||
setup do
|
||||
:meck.unload
|
||||
:meck.new(@module, [:non_strict])
|
||||
:ejabberd_commands.init
|
||||
end
|
||||
|
||||
test "API command can be registered, listed and unregistered" do
|
||||
command = ejabberd_commands name: :test, module: @module,
|
||||
function: :test_command
|
||||
|
||||
assert :ok == :ejabberd_commands.register_commands [command]
|
||||
commands = :ejabberd_commands.list_commands
|
||||
assert Enum.member? commands, {:test, [], ''}
|
||||
|
||||
assert :ok == :ejabberd_commands.unregister_commands [command]
|
||||
commands = :ejabberd_commands.list_commands
|
||||
refute Enum.member? commands, {:test, [], ''}
|
||||
end
|
||||
|
||||
|
||||
test "API command with versions can be registered, listed and unregistered" do
|
||||
command1 = ejabberd_commands name: :test, module: @module,
|
||||
function: :test_command, version: 1, desc: 'version1'
|
||||
command3 = ejabberd_commands name: :test, module: @module,
|
||||
function: :test_command, version: 3, desc: 'version3'
|
||||
assert :ejabberd_commands.register_commands [command1, command3]
|
||||
|
||||
version1 = {:test, [], 'version1'}
|
||||
version3 = {:test, [], 'version3'}
|
||||
|
||||
# default version is latest one
|
||||
commands = :ejabberd_commands.list_commands
|
||||
refute Enum.member? commands, version1
|
||||
assert Enum.member? commands, version3
|
||||
|
||||
# no such command in APIv0
|
||||
commands = :ejabberd_commands.list_commands 0
|
||||
refute Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 1
|
||||
assert Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 2
|
||||
assert Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 3
|
||||
refute Enum.member? commands, version1
|
||||
assert Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 4
|
||||
refute Enum.member? commands, version1
|
||||
assert Enum.member? commands, version3
|
||||
|
||||
assert :ok == :ejabberd_commands.unregister_commands [command1]
|
||||
|
||||
commands = :ejabberd_commands.list_commands 1
|
||||
refute Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 3
|
||||
refute Enum.member? commands, version1
|
||||
assert Enum.member? commands, version3
|
||||
|
||||
assert :ok == :ejabberd_commands.unregister_commands [command3]
|
||||
|
||||
commands = :ejabberd_commands.list_commands 1
|
||||
refute Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
|
||||
commands = :ejabberd_commands.list_commands 3
|
||||
refute Enum.member? commands, version1
|
||||
refute Enum.member? commands, version3
|
||||
end
|
||||
|
||||
|
||||
test "API command can be registered and executed" do
|
||||
# Create & register a mocked command test() -> :result
|
||||
command_name = :test
|
||||
function = :test_command
|
||||
command = ejabberd_commands(name: command_name,
|
||||
module: @module,
|
||||
function: function)
|
||||
:meck.expect @module, function, fn -> :result end
|
||||
assert :ok == :ejabberd_commands.register_commands [command]
|
||||
|
||||
assert :result == :ejabberd_commands.execute_command(command_name, [])
|
||||
|
||||
assert :meck.validate @module
|
||||
end
|
||||
|
||||
test "API command with versions can be registered and executed" do
|
||||
command_name = :test
|
||||
|
||||
function1 = :test_command1
|
||||
command1 = ejabberd_commands(name: command_name,
|
||||
version: 1,
|
||||
module: @module,
|
||||
function: function1)
|
||||
:meck.expect(@module, function1, fn -> :result1 end)
|
||||
|
||||
function3 = :test_command3
|
||||
command3 = ejabberd_commands(name: command_name,
|
||||
version: 3,
|
||||
module: @module,
|
||||
function: function3)
|
||||
:meck.expect(@module, function3, fn -> :result3 end)
|
||||
|
||||
assert :ok == :ejabberd_commands.register_commands [command1, command3]
|
||||
|
||||
# default version is latest one
|
||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [])
|
||||
# no such command in APIv0
|
||||
assert :unknown_command ==
|
||||
catch_throw :ejabberd_commands.execute_command(command_name, [], 0)
|
||||
assert :result1 == :ejabberd_commands.execute_command(command_name, [], 1)
|
||||
assert :result1 == :ejabberd_commands.execute_command(command_name, [], 2)
|
||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [], 3)
|
||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [], 4)
|
||||
|
||||
assert :meck.validate @module
|
||||
end
|
||||
|
||||
|
||||
|
||||
test "API command with user policy" do
|
||||
mock_commands_config
|
||||
|
||||
# Register a command test(user, domain) -> {:versionN, user, domain}
|
||||
# with policy=user and versions 1 & 3
|
||||
command_name = :test
|
||||
command1 = ejabberd_commands(name: command_name,
|
||||
module: @module,
|
||||
function: :test_command1,
|
||||
policy: :user, version: 1)
|
||||
command3 = ejabberd_commands(name: command_name,
|
||||
module: @module,
|
||||
function: :test_command3,
|
||||
policy: :user, version: 3)
|
||||
:meck.expect(@module, :test_command1,
|
||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
||||
{:version1, user, domain}
|
||||
end)
|
||||
:meck.expect(@module, :test_command3,
|
||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
||||
{:version3, user, domain}
|
||||
end)
|
||||
assert :ok == :ejabberd_commands.register_commands [command1, command3]
|
||||
|
||||
# A normal user must not pass user info as parameter
|
||||
assert {:version1, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, false},
|
||||
command_name,
|
||||
[], 2)
|
||||
assert {:version3, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, false},
|
||||
command_name,
|
||||
[], 3)
|
||||
token = EjabberdOauthMock.get_token @user, @domain, command_name
|
||||
assert {:version3, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
{:oauth, token}, false},
|
||||
command_name,
|
||||
[], 4)
|
||||
# Expired oauth token
|
||||
token = EjabberdOauthMock.get_token @user, @domain, command_name, 1
|
||||
:timer.sleep 1500
|
||||
assert {:error, :invalid_account_data} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
{:oauth, token}, false},
|
||||
command_name,
|
||||
[], 4)
|
||||
# Wrong oauth scope
|
||||
token = EjabberdOauthMock.get_token @user, @domain, :bad_command
|
||||
assert {:error, :invalid_account_data} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
{:oauth, token}, false},
|
||||
command_name,
|
||||
[], 4)
|
||||
|
||||
|
||||
assert :function_clause ==
|
||||
catch_error :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, false},
|
||||
command_name,
|
||||
[@user, @domain], 2)
|
||||
# @user is not admin
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, true},
|
||||
command_name,
|
||||
[], 2)
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, true},
|
||||
command_name,
|
||||
[@user, @domain], 2)
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
{:oauth, token}, true},
|
||||
command_name,
|
||||
[@user, @domain], 2)
|
||||
|
||||
|
||||
# An admin must explicitely pass user info
|
||||
assert {:version1, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined, :admin,
|
||||
command_name, [@user, @domain], 2)
|
||||
assert {:version3, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined, :admin,
|
||||
command_name, [@user, @domain], 4)
|
||||
assert {:version1, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain, @adminpass, true},
|
||||
command_name, [@user, @domain], 1)
|
||||
token = EjabberdOauthMock.get_token @admin, @domain, command_name
|
||||
assert {:version3, @user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain, {:oauth, token}, true},
|
||||
command_name, [@user, @domain], 3)
|
||||
# Wrong @admin password
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
@adminpass<>"bad", true},
|
||||
command_name,
|
||||
[@user, @domain], 3)
|
||||
# @admin calling as a normal user
|
||||
assert {:version3, @admin, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
@adminpass, false},
|
||||
command_name, [], 5)
|
||||
assert {:version3, @admin, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
{:oauth, token}, false},
|
||||
command_name, [], 6)
|
||||
assert :function_clause ==
|
||||
catch_error :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
@adminpass, false},
|
||||
command_name,
|
||||
[@user, @domain], 5)
|
||||
assert :meck.validate @module
|
||||
end
|
||||
|
||||
|
||||
|
||||
test "API command with admin policy" do
|
||||
mock_commands_config
|
||||
|
||||
# Register a command test(user, domain) -> {user, domain}
|
||||
# with policy=admin
|
||||
command_name = :test
|
||||
function = :test_command
|
||||
command = ejabberd_commands(name: command_name,
|
||||
args: [{:user, :binary}, {:host, :binary}],
|
||||
module: @module,
|
||||
function: function,
|
||||
policy: :admin)
|
||||
:meck.expect(@module, function,
|
||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
||||
{user, domain}
|
||||
end)
|
||||
assert :ok == :ejabberd_commands.register_commands [command]
|
||||
|
||||
# A normal user cannot call the command
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@user, @domain,
|
||||
@userpass, false},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
# An admin can call the command
|
||||
assert {@user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
@adminpass, true},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
# An admin can call the command with oauth token
|
||||
token = EjabberdOauthMock.get_token @admin, @domain, command_name
|
||||
assert {@user, @domain} ==
|
||||
:ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
{:oauth, token}, true},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
|
||||
# An admin with bad password cannot call the command
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
"bad"<>@adminpass, false},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
# An admin cannot call the command with bad oauth token
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
{:oauth, "bad"<>token}, true},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
# An admin as a normal user cannot call the command
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
@adminpass, false},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
# An admin as a normal user cannot call the command with oauth token
|
||||
assert {:error, :account_unprivileged} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
||||
{@admin, @domain,
|
||||
{:oauth, token}, false},
|
||||
command_name,
|
||||
[@user, @domain])
|
||||
|
||||
assert :meck.validate @module
|
||||
end
|
||||
|
||||
|
||||
##########################################################
|
||||
# Utils
|
||||
|
||||
# Mock a config where only @admin user is allowed to call commands
|
||||
# as admin
|
||||
def mock_commands_config do
|
||||
EjabberdAuthMock.init
|
||||
EjabberdAuthMock.create_user @user, @domain, @userpass
|
||||
EjabberdAuthMock.create_user @admin, @domain, @adminpass
|
||||
|
||||
:meck.new :ejabberd_config
|
||||
:meck.expect(:ejabberd_config, :get_option,
|
||||
fn(:commands_admin_access, _, _) -> :commands_admin_access
|
||||
(:oauth_access, _, _) -> :all
|
||||
(_, _, default) -> default
|
||||
end)
|
||||
:meck.expect(:ejabberd_config, :get_myhosts,
|
||||
fn() -> [@domain] end)
|
||||
:meck.new :acl
|
||||
:meck.expect(:acl, :match_rule,
|
||||
fn(@domain, :commands_admin_access, user) ->
|
||||
case :jlib.make_jid(@admin, @domain, "") do
|
||||
^user -> :allow
|
||||
_ -> :deny
|
||||
end
|
||||
(@domain, :all, _user) ->
|
||||
:allow
|
||||
end)
|
||||
end
|
||||
|
||||
defp user_test_command do
|
||||
ejabberd_commands(name: :test_user, tags: [:roster],
|
||||
desc: "Test user",
|
||||
policy: :user,
|
||||
module: __MODULE__,
|
||||
function: :test_user,
|
||||
args: [],
|
||||
result: {:contacts, {:list, {:contact, {:tuple, [
|
||||
{:jid, :string},
|
||||
{:nick, :string}
|
||||
]}}}})
|
||||
end
|
||||
end
|
||||
|
@ -30,14 +30,14 @@
|
||||
# log as we are exercising hook handler recovery from that situation.
|
||||
|
||||
defmodule EjabberdHooksTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@author "mremond@process-one.net"
|
||||
@host <<"domain.net">>
|
||||
@self __MODULE__
|
||||
|
||||
setup_all do
|
||||
{:ok, _Pid} = :ejabberd_hooks.start_link
|
||||
{:ok, _pid} = :ejabberd_hooks.start_link
|
||||
:ok
|
||||
end
|
||||
|
||||
|
30
test/ejabberd_oauth_mock.exs
Normal file
30
test/ejabberd_oauth_mock.exs
Normal file
@ -0,0 +1,30 @@
|
||||
# ejabberd_oauth mock
|
||||
######################
|
||||
|
||||
defmodule EjabberdOauthMock do
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
def init() do
|
||||
:mnesia.start
|
||||
:mnesia.create_table(:oauth_token,
|
||||
[ram_copies: [node],
|
||||
attributes: [:oauth_token, :us, :scope, :expire]])
|
||||
end
|
||||
|
||||
def get_token(user, domain, command, expiration \\ 3600) do
|
||||
now = {megasecs, secs, _} = :os.timestamp
|
||||
expire = 1000000 * megasecs + secs + expiration
|
||||
:random.seed now
|
||||
token = to_string :random.uniform(100000000)
|
||||
|
||||
{:ok, _} = :ejabberd_oauth.associate_access_token(token,
|
||||
[{"resource_owner",
|
||||
{:user, user, domain}},
|
||||
{"scope", [to_string command]},
|
||||
{"expiry_time", expire}],
|
||||
:undefined)
|
||||
token
|
||||
end
|
||||
|
||||
end
|
106
test/ejabberd_sm_mock.exs
Normal file
106
test/ejabberd_sm_mock.exs
Normal file
@ -0,0 +1,106 @@
|
||||
# ejabberd_sm mock
|
||||
######################
|
||||
|
||||
defmodule EjabberdSmMock do
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
require Record
|
||||
Record.defrecord :session, Record.extract(:session,
|
||||
from: "ejabberd_sm.hrl")
|
||||
Record.defrecord :jid, Record.extract(:jid,
|
||||
from: "jlib.hrl")
|
||||
|
||||
@agent __MODULE__
|
||||
|
||||
def init do
|
||||
ModLastMock.init
|
||||
|
||||
try do
|
||||
Agent.stop(@agent)
|
||||
catch
|
||||
:exit, _e -> :ok
|
||||
end
|
||||
|
||||
{:ok, _pid} = Agent.start_link(fn -> [] end, name: @agent)
|
||||
|
||||
mock(:ejabberd_sm, :get_user_resources,
|
||||
fn (user, domain) -> for s <- get_sessions(user, domain), do: s.resource end)
|
||||
|
||||
mock(:ejabberd_sm, :route,
|
||||
fn (_from, to, {:broadcast, {:exit, _reason}}) ->
|
||||
user = jid(to, :user)
|
||||
domain = jid(to, :server)
|
||||
resource = jid(to, :resource)
|
||||
disconnect_resource(user, domain, resource)
|
||||
:ok
|
||||
(_, _, _) -> :ok
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
def connect_resource(user, domain, resource,
|
||||
opts \\ [priority: 1, conn: :c2s]) do
|
||||
Agent.update(@agent, fn sessions ->
|
||||
session = %{user: user, domain: domain, resource: resource,
|
||||
timestamp: :os.timestamp, pid: self, node: node,
|
||||
auth_module: :ejabberd_auth, ip: :undefined,
|
||||
priority: opts[:priority], conn: opts[:conn]}
|
||||
[session | sessions]
|
||||
end)
|
||||
end
|
||||
|
||||
def disconnect_resource(user, domain, resource) do
|
||||
disconnect_resource(user, domain, resource, ModLastMock.now)
|
||||
end
|
||||
|
||||
def disconnect_resource(user, domain, resource, timestamp) do
|
||||
Agent.update(@agent, fn sessions ->
|
||||
for s <- sessions,
|
||||
s.user != user or s.domain != domain or s.resource != resource, do: s
|
||||
end)
|
||||
ModLastMock.set_last user, domain, "", timestamp
|
||||
end
|
||||
|
||||
def get_sessions() do
|
||||
Agent.get(@agent, fn sessions -> sessions end)
|
||||
end
|
||||
|
||||
def get_sessions(user, domain) do
|
||||
Agent.get(@agent, fn sessions ->
|
||||
for s <- sessions, s.user == user, s.domain == domain, do: s
|
||||
end)
|
||||
end
|
||||
|
||||
def get_session(user, domain, resource) do
|
||||
Agent.get(@agent, fn sessions ->
|
||||
for s <- sessions,
|
||||
s.user == user, s.domain == domain, s.resource == resource, do: s
|
||||
end)
|
||||
end
|
||||
|
||||
def to_record(s) do
|
||||
session(usr: {s.user, s.domain, s.ressource},
|
||||
us: {s.user, s.domain},
|
||||
sid: {s.timestamp, s.pid},
|
||||
priority: s.priority,
|
||||
info: [conn: s.conn, ip: s.ip, node: s.node,
|
||||
oor: false, auth_module: s.auth_module])
|
||||
end
|
||||
|
||||
####################################################################
|
||||
# Helpers
|
||||
####################################################################
|
||||
|
||||
|
||||
# TODO refactor: Move to ejabberd_test_mock
|
||||
def mock(module, function, fun) do
|
||||
try do
|
||||
:meck.new(module)
|
||||
catch
|
||||
:error, {:already_started, _pid} -> :ok
|
||||
end
|
||||
|
||||
:meck.expect(module, function, fun)
|
||||
end
|
||||
|
||||
end
|
@ -19,6 +19,7 @@
|
||||
|
||||
init_per_suite(Config) ->
|
||||
check_meck(),
|
||||
code:add_pathz(filename:join(test_dir(), "../include")),
|
||||
Config.
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
@ -27,13 +28,13 @@ init_per_testcase(_TestCase, Config) ->
|
||||
|
||||
all() ->
|
||||
case is_elixir_available() of
|
||||
true ->
|
||||
Dir = test_dir(),
|
||||
filelib:fold_files(Dir, ".*\.exs", false,
|
||||
fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
|
||||
[]);
|
||||
false ->
|
||||
[]
|
||||
true ->
|
||||
Dir = test_dir(),
|
||||
filelib:fold_files(Dir, ".*\.exs", false,
|
||||
fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
|
||||
[]);
|
||||
false ->
|
||||
[]
|
||||
end.
|
||||
|
||||
check_meck() ->
|
||||
@ -56,16 +57,21 @@ is_elixir_available() ->
|
||||
|
||||
undefined_function(?MODULE, Func, Args) ->
|
||||
case lists:suffix(".exs", atom_to_list(Func)) of
|
||||
true ->
|
||||
run_elixir_test(Func);
|
||||
false ->
|
||||
error_handler:undefined_function(?MODULE, Func, Args)
|
||||
true ->
|
||||
run_elixir_test(Func);
|
||||
false ->
|
||||
error_handler:undefined_function(?MODULE, Func, Args)
|
||||
end;
|
||||
undefined_function(Module, Func, Args) ->
|
||||
error_handler:undefined_function(Module, Func,Args).
|
||||
|
||||
run_elixir_test(Func) ->
|
||||
'Elixir.ExUnit':start([]),
|
||||
filelib:fold_files(test_dir(), ".*\\.exs\$", true,
|
||||
fun (File, N) ->
|
||||
'Elixir.Code':require_file(list_to_binary(File)),
|
||||
N+1
|
||||
end, 0),
|
||||
'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))),
|
||||
%% I did not use map syntax, so that this file can still be build under R16
|
||||
ResultMap = 'Elixir.ExUnit':run(),
|
||||
|
699
test/mod_admin_extra_test.exs
Normal file
699
test/mod_admin_extra_test.exs
Normal file
@ -0,0 +1,699 @@
|
||||
# ----------------------------------------------------------------------
|
||||
#
|
||||
# ejabberd, Copyright (C) 2002-2015 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.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
defmodule EjabberdModAdminExtraTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
@user "user"
|
||||
@domain "domain"
|
||||
@password "password"
|
||||
@resource "resource"
|
||||
|
||||
require Record
|
||||
Record.defrecord :jid, Record.extract(:jid,
|
||||
from: "jlib.hrl")
|
||||
|
||||
setup_all do
|
||||
try do
|
||||
:stringprep.start
|
||||
:mnesia.start
|
||||
:p1_sha.load_nif
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:ejabberd_commands.init
|
||||
:mod_admin_extra.start(@domain, [])
|
||||
:sel_application.start_app(:moka)
|
||||
{:ok, _pid} = :ejabberd_hooks.start_link
|
||||
:ok
|
||||
end
|
||||
|
||||
setup do
|
||||
:meck.unload
|
||||
EjabberdAuthMock.init
|
||||
EjabberdSmMock.init
|
||||
ModRosterMock.init(@domain, :mod_admin_extra)
|
||||
:ok
|
||||
end
|
||||
|
||||
###################### Accounts
|
||||
test "check_account works" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_account, [@user, @domain])
|
||||
refute :ejabberd_commands.execute_command(:check_account, [@user, "bad_domain"])
|
||||
refute :ejabberd_commands.execute_command(:check_account, ["bad_user", @domain])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
end
|
||||
|
||||
test "check_password works" do
|
||||
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_password,
|
||||
[@user, @domain, @password])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
[@user, @domain, "bad_password"])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
[@user, "bad_domain", @password])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
["bad_user", @domain, @password])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
|
||||
end
|
||||
|
||||
test "check_password_hash works" do
|
||||
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
||||
[@user, @domain, hash, "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
[@user, @domain, "bad_hash", "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
[@user, "bad_domain", hash, "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
["bad_user", @domain, hash, "md5"])
|
||||
|
||||
hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
|
||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
||||
[@user, @domain, hash, "sha"])
|
||||
|
||||
assert :unkown_hash_method ==
|
||||
catch_throw :ejabberd_commands.execute_command(:check_password_hash,
|
||||
[@user, @domain, hash, "bad_method"])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
|
||||
end
|
||||
|
||||
test "change_password works" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:change_password,
|
||||
[@user, @domain, "new_password"])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
[@user, @domain, @password])
|
||||
assert :ejabberd_commands.execute_command(:check_password,
|
||||
[@user, @domain, "new_password"])
|
||||
assert {:not_found, 'unknown_user'} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:change_password,
|
||||
["bad_user", @domain,
|
||||
@password])
|
||||
assert :meck.validate :ejabberd_auth
|
||||
end
|
||||
|
||||
test "check_users_registration works" do
|
||||
EjabberdAuthMock.create_user @user<>"1", @domain, @password
|
||||
EjabberdAuthMock.create_user @user<>"2", @domain, @password
|
||||
EjabberdAuthMock.create_user @user<>"3", @domain, @password
|
||||
|
||||
assert [{@user<>"0", @domain, 0},
|
||||
{@user<>"1", @domain, 1},
|
||||
{@user<>"2", @domain, 1},
|
||||
{@user<>"3", @domain, 1}] ==
|
||||
:ejabberd_commands.execute_command(:check_users_registration,
|
||||
[[{@user<>"0", @domain},
|
||||
{@user<>"1", @domain},
|
||||
{@user<>"2", @domain},
|
||||
{@user<>"3", @domain}]])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
|
||||
end
|
||||
|
||||
###################### Sessions
|
||||
|
||||
test "num_resources works" do
|
||||
assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource
|
||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
end
|
||||
|
||||
test "resource_num works" do
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||
|
||||
assert :bad_argument ==
|
||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
||||
[@user, @domain, 0])), 0)
|
||||
assert @resource<>"1" ==
|
||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 1])
|
||||
assert @resource<>"3" ==
|
||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 3])
|
||||
assert :bad_argument ==
|
||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
||||
[@user, @domain, 4])), 0)
|
||||
assert :meck.validate :ejabberd_sm
|
||||
end
|
||||
|
||||
test "kick_session works" do
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
|
||||
|
||||
assert 3 == length EjabberdSmMock.get_sessions @user, @domain
|
||||
assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:kick_session,
|
||||
[@user, @domain,
|
||||
@resource<>"2", "kick"])
|
||||
|
||||
assert 2 == length EjabberdSmMock.get_sessions @user, @domain
|
||||
assert 0 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
end
|
||||
|
||||
###################### Last
|
||||
|
||||
test "get_last works" do
|
||||
|
||||
assert 'Never' ==
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||
|
||||
assert 'Online' ==
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
|
||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
|
||||
|
||||
assert 'Online' ==
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
|
||||
now = {megasecs, secs, _microsecs} = :os.timestamp
|
||||
timestamp = megasecs * 1000000 + secs
|
||||
EjabberdSmMock.disconnect_resource(@user, @domain, @resource<>"2",
|
||||
timestamp)
|
||||
{{year, month, day}, {hour, minute, second}} = :calendar.now_to_local_time now
|
||||
result = List.flatten(:io_lib.format(
|
||||
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ",
|
||||
[year, month, day, hour, minute, second]))
|
||||
assert result ==
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
|
||||
assert :meck.validate :mod_last
|
||||
end
|
||||
|
||||
###################### Roster
|
||||
|
||||
test "add_rosteritem and delete_rosteritem work" do
|
||||
# Connect user
|
||||
# Add user1 & user2 to user's roster
|
||||
# Remove user1 & user2 from user's roster
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain,
|
||||
"nick1",
|
||||
"group1",
|
||||
"both"])
|
||||
# Check that user1 is the only item of the user's roster
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 1 == length result
|
||||
[{{@user, @domain, jid}, opts}] = result
|
||||
assert @user<>"1@"<>@domain == jid
|
||||
assert "nick1" == opts.nick
|
||||
assert ["group1"] == opts.groups
|
||||
assert :both == opts.subs
|
||||
|
||||
# Check that the item roster user1 was pushed with subscription
|
||||
# 'both' to user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain,
|
||||
"nick2",
|
||||
"group2",
|
||||
"both"])
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 2 == length result
|
||||
|
||||
|
||||
# Check that the item roster user2 was pushed with subscription
|
||||
# 'both' to user online ressource
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"2", @domain, ""}, :both}}])
|
||||
|
||||
|
||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain])
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 1 == length result
|
||||
[{{@user, @domain, jid}, opts}] = result
|
||||
assert @user<>"2@"<>@domain == jid
|
||||
assert "nick2" == opts.nick
|
||||
assert ["group2"] == opts.groups
|
||||
assert :both == opts.subs
|
||||
|
||||
# Check that the item roster user1 was pushed with subscription
|
||||
# 'none' to user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
|
||||
|
||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain])
|
||||
|
||||
# Check that the item roster user2 was pushed with subscription
|
||||
# 'none' to user online ressource
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"2", @domain, ""}, :none}}])
|
||||
|
||||
# Check that nothing else was pushed to user resource
|
||||
jid = jid(user: @user, server: @domain, resource: :_,
|
||||
luser: @user, lserver: @domain, lresource: :_)
|
||||
assert 4 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
assert :meck.validate :ejabberd_sm
|
||||
|
||||
end
|
||||
|
||||
test "get_roster works" do
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
assert [] == :ejabberd_commands.execute_command(:get_roster, [@user, @domain],
|
||||
:admin)
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain,
|
||||
"nick1",
|
||||
"group1",
|
||||
"both"])
|
||||
assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
|
||||
:ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain,
|
||||
"nick2",
|
||||
"group2",
|
||||
"none"])
|
||||
result = :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
|
||||
assert 2 == length result
|
||||
assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
|
||||
assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
|
||||
|
||||
end
|
||||
|
||||
|
||||
test "link_contacts & unlink_contacts work" do
|
||||
# Create user1 and keep it offline
|
||||
EjabberdAuthMock.create_user @user<>"1", @domain, @password
|
||||
|
||||
# fail if one of the users doesn't exist locally
|
||||
assert 404 ==
|
||||
:ejabberd_commands.execute_command(:link_contacts, [@user<>"1@"<>@domain,
|
||||
"nick1",
|
||||
"group1",
|
||||
@user<>"2@"<>@domain,
|
||||
"nick2",
|
||||
"group2"])
|
||||
|
||||
# Create user2 and connect 2 resources
|
||||
EjabberdAuthMock.create_user @user<>"2", @domain, @password
|
||||
|
||||
EjabberdSmMock.connect_resource @user<>"2", @domain, @resource<>"1"
|
||||
EjabberdSmMock.connect_resource @user<>"2", @domain, @resource<>"2"
|
||||
|
||||
# Link both user1 & user2 (returns 0 if OK)
|
||||
assert 0 ==
|
||||
:ejabberd_commands.execute_command(:link_contacts, [@user<>"1@"<>@domain,
|
||||
"nick1",
|
||||
"group2",
|
||||
@user<>"2@"<>@domain,
|
||||
"nick2",
|
||||
"group1"])
|
||||
assert [{@user<>"2@"<>@domain, "", 'both', 'none', "group2"}] ==
|
||||
:ejabberd_commands.execute_command(:get_roster, [@user<>"1", @domain], :admin)
|
||||
|
||||
assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
|
||||
:ejabberd_commands.execute_command(:get_roster, [@user<>"2", @domain], :admin)
|
||||
|
||||
# Check that the item roster user1 was pushed with subscription
|
||||
# 'both' to the 2 user2 online ressources
|
||||
jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"1")
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
|
||||
jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"2")
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
|
||||
|
||||
|
||||
# Ulink both user1 & user2 (returns 0 if OK)
|
||||
assert 0 ==
|
||||
:ejabberd_commands.execute_command(:unlink_contacts, [@user<>"1@"<>@domain,
|
||||
@user<>"2@"<>@domain])
|
||||
assert [] ==
|
||||
:ejabberd_commands.execute_command(:get_roster, [@user<>"1", @domain], :admin)
|
||||
|
||||
assert [] ==
|
||||
:ejabberd_commands.execute_command(:get_roster, [@user<>"2", @domain], :admin)
|
||||
|
||||
# Check that the item roster user1 was pushed with subscription
|
||||
# 'none' to the 2 user2 online ressources
|
||||
jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"1")
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
|
||||
jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"2")
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
|
||||
|
||||
# Check that nothing else was pushed to user2 resources
|
||||
jid = jid(user: @user<>"2", server: @domain, resource: :_,
|
||||
luser: @user<>"2", lserver: @domain, lresource: :_)
|
||||
assert 4 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
# Check nothing was pushed to user1
|
||||
jid = jid(user: @user<>"1", server: @domain, resource: :_,
|
||||
luser: @user<>"1", lserver: @domain, lresource: :_)
|
||||
refute :meck.called(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
assert :meck.validate :ejabberd_auth
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
test "add_contacts and delete_contacts work" do
|
||||
# Create user, user1 & user2
|
||||
# Connect user & user1
|
||||
# Add user0, user1 & user2 to user's roster
|
||||
# Remove user0, user1 & user2 from user's roster
|
||||
|
||||
# user doesn't exists yet, command must fail
|
||||
assert 404 ==
|
||||
:ejabberd_commands.execute_command(:add_contacts, [@user, @domain,
|
||||
[{@user<>"1"<>@domain,
|
||||
"group1",
|
||||
"nick1"},
|
||||
{@user<>"2"<>@domain,
|
||||
"group2",
|
||||
"nick2"}]
|
||||
])
|
||||
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||
EjabberdAuthMock.create_user @user<>"1", @domain, @password
|
||||
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
||||
EjabberdAuthMock.create_user @user<>"2", @domain, @password
|
||||
|
||||
# Add user1 & user2 in user's roster. Try also to add user0 that
|
||||
# doesn't exists. Command is supposed to return number of added items.
|
||||
assert 2 ==
|
||||
:ejabberd_commands.execute_command(:add_contacts, [@user, @domain,
|
||||
[{@user<>"0@"<>@domain,
|
||||
"group0",
|
||||
"nick0"},
|
||||
{@user<>"1@"<>@domain,
|
||||
"group1",
|
||||
"nick1"},
|
||||
{@user<>"2@"<>@domain,
|
||||
"group2",
|
||||
"nick2"}]
|
||||
])
|
||||
# Check that user1 & user2 are the only items in user's roster
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 2 == length result
|
||||
opts1 = %{nick: "nick1", groups: ["group1"], subs: :both,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"1@"<>@domain}, opts1})
|
||||
opts2 = %{nick: "nick2", groups: ["group2"], subs: :both,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
|
||||
|
||||
# Check that user is the only item in user1's roster
|
||||
assert [{{@user<>"1", @domain, @user<>"@"<>@domain}, %{opts1|:nick => ""}}] ==
|
||||
ModRosterMock.get_roster(@user<>"1", @domain)
|
||||
|
||||
# Check that user is the only item in user2's roster
|
||||
assert [{{@user<>"2", @domain, @user<>"@"<>@domain}, %{opts2|:nick => ""}}] ==
|
||||
ModRosterMock.get_roster(@user<>"2", @domain)
|
||||
|
||||
|
||||
# Check that the roster items user1 & user2 were pushed with subscription
|
||||
# 'both' to the user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"2", @domain, ""}, :both}}])
|
||||
|
||||
# Check that the roster item user was pushed with subscription
|
||||
# 'both' to the user1 online ressource
|
||||
jid = :jlib.make_jid(@user<>"1", @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user, @domain, ""}, :both}}])
|
||||
|
||||
# Check that nothing else was pushed to online resources
|
||||
assert 3 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[:_, :_,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
# Remove user1 & user2 from user's roster. Try also to remove
|
||||
# user0 that doesn't exists. Command is supposed to return number
|
||||
# of removed items.
|
||||
assert 2 ==
|
||||
:ejabberd_commands.execute_command(:remove_contacts, [@user, @domain,
|
||||
[@user<>"0@"<>@domain,
|
||||
@user<>"1@"<>@domain,
|
||||
@user<>"2@"<>@domain]
|
||||
])
|
||||
# Check that roster of user, user1 & user2 are empty
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
assert [] == ModRosterMock.get_roster(@user<>"1", @domain)
|
||||
assert [] == ModRosterMock.get_roster(@user<>"2", @domain)
|
||||
|
||||
# Check that the roster items user1 & user2 were pushed with subscription
|
||||
# 'none' to the user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"2", @domain, ""}, :none}}])
|
||||
|
||||
# Check that the roster item user was pushed with subscription
|
||||
# 'none' to the user1 online ressource
|
||||
jid = :jlib.make_jid(@user<>"1", @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user, @domain, ""}, :none}}])
|
||||
|
||||
# Check that nothing else was pushed to online resources
|
||||
assert 6 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[:_, :_,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
assert :meck.validate :ejabberd_auth
|
||||
end
|
||||
|
||||
|
||||
test "update_roster works" do
|
||||
# user doesn't exists yet, command must fail
|
||||
result =
|
||||
:ejabberd_commands.execute_command(:update_roster,
|
||||
[@user, @domain,
|
||||
[{@user<>"1"<>@domain,
|
||||
"group1",
|
||||
"nick1"},
|
||||
{@user<>"2"<>@domain,
|
||||
"group2",
|
||||
"nick2"}],
|
||||
[]
|
||||
])
|
||||
assert :invalid_user == elem(result, 0)
|
||||
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||
EjabberdAuthMock.create_user @user<>"1", @domain, @password
|
||||
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
||||
EjabberdAuthMock.create_user @user<>"2", @domain, @password
|
||||
EjabberdAuthMock.create_user @user<>"3", @domain, @password
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:update_roster,
|
||||
[@user, @domain,
|
||||
[{[{"username", @user<>"1"},
|
||||
{"nick", "nick1"}]},
|
||||
{[{"username", @user<>"2"},
|
||||
{"nick", "nick2"},
|
||||
{"subscription", "from"}]}],
|
||||
[]])
|
||||
|
||||
|
||||
# Check that user1 & user2 are the only items in user's roster
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
|
||||
assert 2 == length result
|
||||
opts1 = %{nick: "nick1", groups: [""], subs: :both,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"1@"<>@domain}, opts1})
|
||||
opts2 = %{nick: "nick2", groups: [""], subs: :from,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
|
||||
|
||||
# Check that the roster items user1 & user2 were pushed with subscription
|
||||
# 'both' to the user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"2", @domain, ""}, :from}}])
|
||||
|
||||
# Check that nothing else was pushed to online resources
|
||||
assert 2 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[:_, :_,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
# Add user3 & remove user1
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:update_roster,
|
||||
[@user, @domain,
|
||||
[{[{"username", @user<>"3"},
|
||||
{"nick", "nick3"},
|
||||
{"subscription", "to"}]}],
|
||||
[{[{"username", @user<>"1"}]}]
|
||||
])
|
||||
|
||||
# Check that user2 & user3 are the only items in user's roster
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 2 == length result
|
||||
opts2 = %{nick: "nick2", groups: [""], subs: :from,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
|
||||
opts1 = %{nick: "nick3", groups: [""], subs: :to,
|
||||
ask: :none, askmessage: ""}
|
||||
assert Enum.member?(result, {{@user, @domain, @user<>"3@"<>@domain}, opts1})
|
||||
|
||||
# Check that the roster items user1 & user3 were pushed with subscription
|
||||
# 'none' & 'to' to the user online ressource
|
||||
jid = :jlib.make_jid(@user, @domain, @resource)
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[jid, jid,
|
||||
{:broadcast, {:item, {@user<>"3", @domain, ""}, :to}}])
|
||||
|
||||
# Check that nothing else was pushed to online resources
|
||||
assert 4 ==
|
||||
:meck.num_calls(:ejabberd_sm, :route,
|
||||
[:_, :_,
|
||||
{:broadcast, {:item, :_, :_}}])
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
assert :meck.validate :ejabberd_auth
|
||||
end
|
||||
|
||||
|
||||
# kick_user command is defined in ejabberd_sm, move to extra?
|
||||
# test "kick_user works" do
|
||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
# [@user, @domain])
|
||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
|
||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
|
||||
# assert 2 ==
|
||||
# :ejabberd_commands.execute_command(:kick_user, [@user, @domain])
|
||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
# [@user, @domain])
|
||||
# assert :meck.validate :ejabberd_sm
|
||||
# end
|
||||
|
||||
end
|
188
test/mod_http_api_test.exs
Normal file
188
test/mod_http_api_test.exs
Normal file
@ -0,0 +1,188 @@
|
||||
# ----------------------------------------------------------------------
|
||||
#
|
||||
# ejabberd, Copyright (C) 2002-2015 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.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
defmodule ModHttpApiTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
# Admin user
|
||||
@admin "admin"
|
||||
@adminpass "adminpass"
|
||||
# Non admin user
|
||||
@user "user"
|
||||
@userpass "userpass"
|
||||
# XMPP domain
|
||||
@domain "domain"
|
||||
# mocked command
|
||||
@command "command_test"
|
||||
@acommand String.to_atom(@command)
|
||||
# default API version
|
||||
@version 0
|
||||
|
||||
require Record
|
||||
Record.defrecord :request, Record.extract(:request,
|
||||
from: "ejabberd_http.hrl")
|
||||
|
||||
setup_all do
|
||||
try do
|
||||
:stringprep.start
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
:mod_http_api.start(@domain, [])
|
||||
EjabberdOauthMock.init
|
||||
:ok
|
||||
end
|
||||
|
||||
setup do
|
||||
:meck.unload
|
||||
:meck.new :ejabberd_commands
|
||||
EjabberdAuthMock.init
|
||||
:ok
|
||||
end
|
||||
|
||||
test "HTTP GET simple command call with Basic Auth" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @userpass
|
||||
|
||||
# Mock a simple command() -> :ok
|
||||
:meck.expect(:ejabberd_commands, :get_command_format,
|
||||
fn (@acommand, {@user, @domain, @userpass, false}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command,
|
||||
fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @version) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
#:ejabberd_logger.start
|
||||
#:ejabberd_logger.set 5
|
||||
|
||||
# Correct Basic Auth call
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# Basic auth
|
||||
auth: {@user<>"@"<>@domain, @userpass},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 200 == elem(result, 0) # HTTP code
|
||||
assert "0" == elem(result, 2) # command result
|
||||
|
||||
# Bad password
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# Basic auth
|
||||
auth: {@user<>"@"<>@domain, @userpass<>"bad"},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 401 == elem(result, 0) # HTTP code
|
||||
|
||||
# Check that the command was executed only once
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_commands, :execute_command, :_)
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
assert :meck.validate :ejabberd_commands
|
||||
#assert :ok = :meck.history(:ejabberd_commands)
|
||||
end
|
||||
|
||||
|
||||
test "HTTP GET simple command call with OAuth" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @userpass
|
||||
|
||||
# Mock a simple command() -> :ok
|
||||
:meck.expect(:ejabberd_commands, :get_command_format,
|
||||
fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command,
|
||||
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
|
||||
@acommand, [], @version) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
#:ejabberd_logger.start
|
||||
#:ejabberd_logger.set 5
|
||||
|
||||
# Correct OAuth call
|
||||
token = EjabberdOauthMock.get_token @user, @domain, @command
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# OAuth
|
||||
auth: {:oauth, token, []},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 200 == elem(result, 0) # HTTP code
|
||||
assert "0" == elem(result, 2) # command result
|
||||
|
||||
# Wrong OAuth token
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# OAuth
|
||||
auth: {:oauth, "bad"<>token, []},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 401 == elem(result, 0) # HTTP code
|
||||
|
||||
# Expired OAuth token
|
||||
token = EjabberdOauthMock.get_token @user, @domain, @command, 1
|
||||
:timer.sleep 1500
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# OAuth
|
||||
auth: {:oauth, token, []},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 401 == elem(result, 0) # HTTP code
|
||||
|
||||
# Wrong OAuth scope
|
||||
token = EjabberdOauthMock.get_token @user, @domain, "bad_command"
|
||||
:timer.sleep 1500
|
||||
req = request(method: :GET,
|
||||
path: ["api", @command],
|
||||
q: [nokey: ""],
|
||||
# OAuth
|
||||
auth: {:oauth, token, []},
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :mod_http_api.process([@command], req)
|
||||
assert 401 == elem(result, 0) # HTTP code
|
||||
|
||||
# Check that the command was executed only once
|
||||
assert 1 ==
|
||||
:meck.num_calls(:ejabberd_commands, :execute_command, :_)
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
assert :meck.validate :ejabberd_commands
|
||||
#assert :ok = :meck.history(:ejabberd_commands)
|
||||
end
|
||||
|
||||
|
||||
end
|
65
test/mod_last_mock.exs
Normal file
65
test/mod_last_mock.exs
Normal file
@ -0,0 +1,65 @@
|
||||
# mod_last mock
|
||||
######################
|
||||
|
||||
|
||||
defmodule ModLastMock do
|
||||
|
||||
require Record
|
||||
Record.defrecord :session, Record.extract(:session,
|
||||
from: "ejabberd_sm.hrl")
|
||||
Record.defrecord :jid, Record.extract(:jid,
|
||||
from: "jlib.hrl")
|
||||
|
||||
@author "jsautret@process-one.net"
|
||||
@agent __MODULE__
|
||||
|
||||
def init do
|
||||
try do
|
||||
Agent.stop(@agent)
|
||||
catch
|
||||
:exit, _e -> :ok
|
||||
end
|
||||
|
||||
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
|
||||
|
||||
mock(:mod_last, :get_last_info,
|
||||
fn (user, domain) ->
|
||||
Agent.get(@agent, fn last ->
|
||||
case Map.get(last, {user, domain}, :not_found) do
|
||||
{ts, status} -> {:ok, ts, status}
|
||||
result -> result
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
def set_last(user, domain, status) do
|
||||
set_last(user, domain, status, now)
|
||||
end
|
||||
|
||||
def set_last(user, domain, status, timestamp) do
|
||||
Agent.update(@agent, fn last ->
|
||||
Map.put(last, {user, domain}, {timestamp, status})
|
||||
end)
|
||||
end
|
||||
|
||||
####################################################################
|
||||
# Helpers
|
||||
####################################################################
|
||||
def now() do
|
||||
{megasecs, secs, _microsecs} = :os.timestamp
|
||||
megasecs * 1000000 + secs
|
||||
end
|
||||
|
||||
# TODO refactor: Move to ejabberd_test_mock
|
||||
def mock(module, function, fun) do
|
||||
try do
|
||||
:meck.new(module)
|
||||
catch
|
||||
:error, {:already_started, _pid} -> :ok
|
||||
end
|
||||
|
||||
:meck.expect(module, function, fun)
|
||||
end
|
||||
|
||||
end
|
192
test/mod_roster_mock.exs
Normal file
192
test/mod_roster_mock.exs
Normal file
@ -0,0 +1,192 @@
|
||||
# mod_roster mock
|
||||
######################
|
||||
|
||||
defmodule ModRosterMock do
|
||||
@author "jsautret@process-one.net"
|
||||
|
||||
require Record
|
||||
Record.defrecord :roster, Record.extract(:roster,
|
||||
from: "mod_roster.hrl")
|
||||
|
||||
@agent __MODULE__
|
||||
|
||||
def init(domain, module) do
|
||||
try do
|
||||
Agent.stop(@agent)
|
||||
catch
|
||||
:exit, _e -> :ok
|
||||
end
|
||||
|
||||
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
|
||||
|
||||
mock_with_moka module
|
||||
|
||||
#:mod_roster.stop(domain)
|
||||
:mod_roster.start(domain, [])
|
||||
end
|
||||
|
||||
def mock_with_moka(module) do
|
||||
try do
|
||||
|
||||
module_mock = :moka.start(module)
|
||||
:moka.replace(module_mock, :mod_roster, :invalidate_roster_cache,
|
||||
fn (_user, _server) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
:moka.load(module_mock)
|
||||
|
||||
roster_mock = :moka.start(:mod_roster)
|
||||
|
||||
:moka.replace(roster_mock, :gen_mod, :db_type,
|
||||
fn (_host, _opts) ->
|
||||
{:none}
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :gen_iq_handler, :add_iq_handler,
|
||||
fn (_module, _host, _ns, _m, _f, _iqdisc) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :gen_iq_handler, :remove_iq_handler,
|
||||
fn (_module, _host, _ns) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :transaction,
|
||||
fn (_server, function) ->
|
||||
{:atomic, function.()}
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :get_roster,
|
||||
fn (user, domain) ->
|
||||
to_records(get_roster(user, domain))
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :update_roster_t,
|
||||
fn (user, domain, {u, d, _r}, item) ->
|
||||
add_roster_item(user, domain, u<>"@"<>d,
|
||||
roster(item, :name),
|
||||
roster(item, :subscription),
|
||||
roster(item, :groups),
|
||||
roster(item, :ask),
|
||||
roster(item, :askmessage))
|
||||
end)
|
||||
|
||||
:moka.replace(roster_mock, :del_roster_t,
|
||||
fn (user, domain, jid) ->
|
||||
remove_roster_item(user, domain, :jlib.jid_to_string(jid))
|
||||
end)
|
||||
|
||||
:moka.load(roster_mock)
|
||||
|
||||
catch
|
||||
{:already_started, _pid} -> :ok
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def mock_with_meck do
|
||||
# mock(:gen_mod, :db_type,
|
||||
# fn (_server, :mod_roster) ->
|
||||
# :mnesia
|
||||
# end)
|
||||
#
|
||||
# mock(:mnesia, :transaction,
|
||||
# fn (_server, function) ->
|
||||
# {:atomic, function.()}
|
||||
# end)
|
||||
#
|
||||
# mock(:mnesia, :write,
|
||||
# fn (Item) ->
|
||||
# throw Item
|
||||
# {:atomic, :ok}
|
||||
# end)
|
||||
|
||||
mock(:mod_roster, :transaction,
|
||||
fn (_server, function) ->
|
||||
{:atomic, function.()}
|
||||
end)
|
||||
|
||||
mock(:mod_roster, :update_roster_t,
|
||||
fn (user, domain, {u, d, _r}, item) ->
|
||||
add_roster_item(user, domain, u<>"@"<>d,
|
||||
roster(item, :name),
|
||||
roster(item, :subscription),
|
||||
roster(item, :groups),
|
||||
roster(item, :ask),
|
||||
roster(item, :askmessage))
|
||||
end)
|
||||
|
||||
mock(:mod_roster, :invalidate_roster_cache,
|
||||
fn (_user, _server) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
def add_roster_item(user, domain, jid, nick, subs \\ :none, groups \\ [],
|
||||
ask \\ :none, askmessage \\ "")
|
||||
when is_binary(user) and byte_size(user) > 0
|
||||
and is_binary(domain) and byte_size(domain) > 0
|
||||
and is_binary(jid) and byte_size(jid) > 0
|
||||
and is_binary(nick)
|
||||
and is_atom(subs)
|
||||
and is_list(groups)
|
||||
and is_atom(ask)
|
||||
and is_binary(askmessage)
|
||||
do
|
||||
Agent.update(@agent, fn roster ->
|
||||
Map.put(roster, {user, domain, jid}, %{nick: nick,
|
||||
subs: subs, groups: groups,
|
||||
ask: ask, askmessage: askmessage})
|
||||
end)
|
||||
end
|
||||
|
||||
def remove_roster_item(user, domain, jid) do
|
||||
Agent.update(@agent, fn roster ->
|
||||
Map.delete(roster, {user, domain, jid})
|
||||
end)
|
||||
end
|
||||
|
||||
def get_rosters() do
|
||||
Agent.get(@agent, fn roster -> roster end)
|
||||
end
|
||||
|
||||
def get_roster(user, domain) do
|
||||
Agent.get(@agent, fn roster ->
|
||||
for {u, d, jid} <- Map.keys(roster), u == user, d == domain,
|
||||
do: {{u, d, jid}, Map.fetch!(roster, {u, d, jid})}
|
||||
end)
|
||||
end
|
||||
|
||||
def to_record({{user, domain, jid}, r}) do
|
||||
roster(usj: {user, domain, jid},
|
||||
us: {user, domain},
|
||||
jid: :jlib.string_to_usr(jid),
|
||||
subscription: r.subs,
|
||||
ask: r.ask,
|
||||
groups: r.groups,
|
||||
askmessage: r.askmessage
|
||||
)
|
||||
end
|
||||
def to_records(rosters) do
|
||||
for item <- rosters, do: to_record(item)
|
||||
end
|
||||
|
||||
####################################################################
|
||||
# Helpers
|
||||
####################################################################
|
||||
|
||||
# TODO refactor: Move to ejabberd_test_mock
|
||||
def mock(module, function, fun) do
|
||||
try do
|
||||
:meck.new(module, [:non_strict, :passthrough, :unstick])
|
||||
catch
|
||||
:error, {:already_started, _pid} -> :ok
|
||||
end
|
||||
|
||||
:meck.expect(module, function, fun)
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user