aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_commands.erl
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2016-03-31 14:53:31 +0300
committerAlexey Shchepin <alexey@process-one.net>2016-03-31 14:53:31 +0300
commit3dc55c6d47e3093a6147ce275c7269a7d08ffc45 (patch)
tree1ff7eb63244a18f9c91dc26dd6e6845499f9f5b5 /src/ejabberd_commands.erl
parentmix 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.erl293
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],