diff options
author | Alexey Shchepin <alexey@process-one.net> | 2016-03-31 14:53:31 +0300 |
---|---|---|
committer | Alexey Shchepin <alexey@process-one.net> | 2016-03-31 14:53:31 +0300 |
commit | 3dc55c6d47e3093a6147ce275c7269a7d08ffc45 (patch) | |
tree | 1ff7eb63244a18f9c91dc26dd6e6845499f9f5b5 /src/ejabberd_commands.erl | |
parent | mix version updated for 16.03 release (diff) |
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.
Diffstat (limited to 'src/ejabberd_commands.erl')
-rw-r--r-- | src/ejabberd_commands.erl | 293 |
1 files changed, 200 insertions, 93 deletions
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index 21872aa33..fd8ba03fe 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -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]. - --spec list_commands_policy() -> [{atom(), [aterm()], string(), atom()}]. - -%% @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]. - --spec get_command_format(atom()) -> {[aterm()], rterm()} | {error, command_unknown}. + list_commands(?DEFAULT_VERSION). + +-spec list_commands(integer()) -> [{atom(), [aterm()], string()}]. + +%% @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 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, Auth) -> + 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). + +-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} +-spec get_commands_definition(integer()) -> [ejabberd_commands()]. + +% @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([], noauth, Name, Arguments). + execute_command(Name, Arguments, ?DEFAULT_VERSION). + +-spec execute_command(atom(), + [any()], + integer() | + {binary(), binary(), binary(), boolean()} | + noauth | admin + ) -> any(). + +%% @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 +%% Arguments = [any()] +%% +%% @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()] + [any()], + integer() ) -> any(). -%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error} +%% @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 -%% 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 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], |