aboutsummaryrefslogtreecommitdiff
path: root/src/mod_http_api.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_http_api.erl')
-rw-r--r--src/mod_http_api.erl504
1 files changed, 236 insertions, 268 deletions
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 73e6f7e4e..34e118cc9 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -5,7 +5,7 @@
%%% Created : 15 Sep 2014 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -23,63 +23,19 @@
%%%
%%%----------------------------------------------------------------------
-%% Example config:
-%%
-%% in ejabberd_http listener
-%% 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:
-%% - add_commands: user
-%%
-%%
-%% add_commands allow exporting a class of commands, from
-%% open: methods is not risky and can be called by without any access check
-%% restricted (default): the same, but will appear only in ejabberdctl list.
-%% admin – auth is required with XMLRPC and HTTP API and checked for admin priviledges, works as usual in ejabberdctl.
-%% user - can be used through XMLRPC and HTTP API, even by user. Only admin can use the commands for other users.
-%%
-%% Then to perform an action, send a POST request to the following URL:
-%% http://localhost:5280/api/<call_name>
-%%
-%% It's also possible to enable unrestricted access to some commands from group
-%% of IP addresses by using option `admin_ip_access` by having fragment like
-%% this in configuration file:
-%% modules:
-%% mod_http_api:
-%% admin_ip_access: admin_ip_access_rule
-%%...
-%% access:
-%% admin_ip_access_rule:
-%% admin_ip_acl:
-%% - command1
-%% - command2
-%% %% use `all` to give access to all commands
-%%...
-%% acl:
-%% admin_ip_acl:
-%% ip:
-%% - "127.0.0.1/8"
-
-module(mod_http_api).
-author('cromain@process-one.net').
-behaviour(gen_mod).
--export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
+-export([start/2, stop/1, reload/3, process/2, depends/2,
+ mod_options/1]).
--include("ejabberd.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
+-include("ejabberd_stacktrace.hrl").
-define(DEFAULT_API_VERSION, 0).
@@ -101,7 +57,7 @@
-define(AC_ALLOW_HEADERS,
{<<"Access-Control-Allow-Headers">>,
- <<"Content-Type">>}).
+ <<"Content-Type, Authorization, X-Admin">>}).
-define(AC_MAX_AGE,
{<<"Access-Control-Max-Age">>, <<"86400">>}).
@@ -123,6 +79,9 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
+reload(_Host, _NewOpts, _OldOpts) ->
+ ok.
+
depends(_Host, _Opts) ->
[].
@@ -130,77 +89,41 @@ depends(_Host, _Opts) ->
%% basic auth
%% ----------
-check_permissions(Request, Command) ->
- case catch binary_to_existing_atom(Command, utf8) of
- Call when is_atom(Call) ->
- {ok, CommandPolicy, Scope} = ejabberd_commands:get_command_policy_and_scope(Call),
- check_permissions2(Request, Call, CommandPolicy, Scope);
- _ ->
- json_error(404, 40, <<"Endpoint not found.">>)
+extract_auth(#request{auth = HTTPAuth, ip = {IP, _}, opts = Opts}) ->
+ Info = case HTTPAuth of
+ {SJID, Pass} ->
+ try jid:decode(SJID) of
+ #jid{luser = User, lserver = Server} ->
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ true ->
+ #{usr => {User, Server, <<"">>}, caller_server => Server};
+ false ->
+ {error, invalid_auth}
+ end
+ catch _:{bad_jid, _} ->
+ {error, invalid_auth}
+ end;
+ {oauth, Token, _} ->
+ case ejabberd_oauth:check_token(Token) of
+ {ok, {U, S}, Scope} ->
+ #{usr => {U, S, <<"">>}, oauth_scope => Scope, caller_server => S};
+ {false, Reason} ->
+ {error, Reason}
+ end;
+ invalid ->
+ {error, invalid_auth};
+ _ ->
+ #{}
+ end,
+ case Info of
+ Map when is_map(Map) ->
+ Tag = proplists:get_value(tag, Opts, <<>>),
+ Map#{caller_module => ?MODULE, ip => IP, tag => Tag};
+ _ ->
+ ?DEBUG("Invalid auth data: ~p", [Info]),
+ Info
end.
-check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _, ScopeList)
- when HTTPAuth /= undefined ->
- Admin =
- case lists:keysearch(<<"X-Admin">>, 1, Headers) of
- {value, {_, <<"true">>}} -> true;
- _ -> false
- end,
- Auth =
- case HTTPAuth of
- {SJID, Pass} ->
- case jid:from_string(SJID) of
- #jid{user = User, server = Server} ->
- case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
- true -> {ok, {User, Server, Pass, Admin}};
- false -> false
- end;
- _ ->
- false
- end;
- {oauth, Token, _} ->
- case oauth_check_token(ScopeList, Token) of
- {ok, user, {User, Server}} ->
- {ok, {User, Server, {oauth, Token}, Admin}};
- {false, Reason} ->
- {false, Reason}
- end;
- _ ->
- false
- end,
- case Auth of
- {ok, A} -> {allowed, Call, A};
- {false, no_matching_scope} -> outofscope_response();
- _ -> unauthorized_response()
- end;
-check_permissions2(_Request, Call, open, _Scope) ->
- {allowed, Call, noauth};
-check_permissions2(#request{ip={IP, _Port}}, Call, _Policy, _Scope) ->
- Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
- fun(V) -> V end,
- none),
- Res = acl:match_rule(global, Access, IP),
- case Res of
- all ->
- {allowed, Call, admin};
- [all] ->
- {allowed, Call, admin};
- allow ->
- {allowed, Call, admin};
- Commands when is_list(Commands) ->
- case lists:member(Call, Commands) of
- true -> {allowed, Call, admin};
- _ -> outofscope_response()
- end;
- _E ->
- {allowed, Call, noauth}
- end;
-check_permissions2(_Request, _Call, _Policy, _Scope) ->
- unauthorized_response().
-
-oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
- ejabberd_oauth:check_token(ScopeList, Token).
-
%% ------------------
%% command processing
%% ------------------
@@ -210,31 +133,25 @@ oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []),
badrequest_response(<<"Missing POST data">>);
-process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} = Req) ->
+process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
Version = get_api_version(Req),
try
Args = extract_args(Data),
log(Call, Args, IPPort),
- case check_permissions(Req, Call) of
- {allowed, Cmd, Auth} ->
- Result = handle(Cmd, Auth, Args, Version, IP),
- json_format(Result);
- %% Warning: check_permission direcly formats 401 reply if not authorized
- ErrorResponse ->
- ErrorResponse
- end
+ perform_call(Call, Args, Req, Version)
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
- {404, 40, <<"Command not found.">>};
+ json_format({404, 44, <<"Command not found.">>});
_:{error,{_,invalid_json}} = _Err ->
- ?DEBUG("Bad Request: ~p", [_Err]),
- badrequest_response(<<"Invalid JSON input">>);
- _:_Error ->
- ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
+ ?DEBUG("Bad Request: ~p", [_Err]),
+ badrequest_response(<<"Invalid JSON input">>);
+ ?EX_RULE(_Class, _Error, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
+ ?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]),
badrequest_response()
end;
-process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
Version = get_api_version(Req),
try
Args = case Data of
@@ -242,29 +159,39 @@ process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
_ -> Data
end,
log(Call, Args, IP),
- case check_permissions(Req, Call) of
- {allowed, Cmd, Auth} ->
- Result = handle(Cmd, Auth, Args, Version, IP),
- json_format(Result);
- %% Warning: check_permission direcly formats 401 reply if not authorized
- ErrorResponse ->
- ErrorResponse
- end
+ perform_call(Call, Args, Req, Version)
catch
%% TODO We need to refactor to remove redundant error return formatting
throw:{error, unknown_command} ->
json_format({404, 44, <<"Command not found.">>});
- _:_Error ->
-
- ?DEBUG("Bad Request: ~p ~p", [_Error, erlang:get_stacktrace()]),
- badrequest_response()
+ ?EX_RULE(_, _Error, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
+ ?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]),
+ badrequest_response()
end;
-process([], #request{method = 'OPTIONS', data = <<>>}) ->
+process([_Call], #request{method = 'OPTIONS', data = <<>>}) ->
{200, ?OPTIONS_HEADER, []};
+process(_, #request{method = 'OPTIONS'}) ->
+ {400, ?OPTIONS_HEADER, []};
process(_Path, Request) ->
?DEBUG("Bad Request: no handler ~p", [Request]),
json_error(400, 40, <<"Missing command name.">>).
+perform_call(Command, Args, Req, Version) ->
+ case catch binary_to_existing_atom(Command, utf8) of
+ Call when is_atom(Call) ->
+ case extract_auth(Req) of
+ {error, expired} -> invalid_token_response();
+ {error, not_found} -> invalid_token_response();
+ {error, invalid_auth} -> unauthorized_response();
+ Auth when is_map(Auth) ->
+ Result = handle(Call, Auth, Args, Version),
+ json_format(Result)
+ end;
+ _ ->
+ json_error(404, 40, <<"Endpoint not found.">>)
+ end.
+
%% Be tolerant to make API more easily usable from command-line pipe.
extract_args(<<"\n">>) -> [];
extract_args(Data) ->
@@ -278,11 +205,11 @@ extract_args(Data) ->
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)
+ case catch 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);
@@ -296,85 +223,92 @@ get_api_version([]) ->
%% TODO Check accept types of request before decided format of reply.
% generic ejabberd command handler
-handle(Call, Auth, Args, Version, IP) 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(
- fun ({Key, binary}, Acc) ->
- [{Key, <<>>}|Acc];
- ({Key, string}, Acc) ->
- [{Key, <<>>}|Acc];
- ({Key, integer}, Acc) ->
- [{Key, 0}|Acc];
- ({Key, {list, _}}, Acc) ->
- [{Key, []}|Acc];
- ({Key, atom}, Acc) ->
- [{Key, undefined}|Acc]
- end, [], ArgsSpec),
- try
- handle2(Call, Auth, match(Args2, Spec), Version, IP)
- 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:{error, account_unprivileged} ->
- {403, 31, <<"Command need to be run with admin priviledge.">>};
- throw:{error, access_rules_unauthorized} ->
- {403, 32, <<"AccessRules: Account associated to token does not have the right to perform the operation.">>};
- 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">>}
+handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
+ Args2 = [{misc:binary_to_atom(Key), Value} || {Key, Value} <- Args],
+ try handle2(Call, Auth, Args2, Version)
+ catch throw:not_found ->
+ {404, <<"not_found">>};
+ throw:{not_found, Why} when is_atom(Why) ->
+ {404, misc: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, misc:atom_to_binary(Why)};
+ throw:{not_allowed, Msg} ->
+ {401, iolist_to_binary(Msg)};
+ throw:{error, account_unprivileged} ->
+ {403, 31, <<"Command need to be run with admin privilege.">>};
+ throw:{error, access_rules_unauthorized} ->
+ {403, 32, <<"AccessRules: Account does not have the right to perform the operation.">>};
+ throw:{invalid_parameter, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:{error, Why} when is_atom(Why) ->
+ {400, misc:atom_to_binary(Why)};
+ throw:{error, Msg} ->
+ {400, iolist_to_binary(Msg)};
+ throw:Error when is_atom(Error) ->
+ {400, misc:atom_to_binary(Error)};
+ throw:Msg when is_list(Msg); is_binary(Msg) ->
+ {400, iolist_to_binary(Msg)};
+ ?EX_RULE(Class, Error, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
+ ?ERROR_MSG("REST API Error: "
+ "~ts(~p) -> ~p:~p ~p",
+ [Call, hide_sensitive_args(Args),
+ Class, Error, StackTrace]),
+ {500, <<"internal_error">>}
end.
-handle2(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
- {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
- ArgsFormatted = format_args(Args, ArgsF),
- ejabberd_command(Auth, Call, ArgsFormatted, Version, IP).
+handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
+ {ArgsF, ArgsR, _ResultF} = ejabberd_commands:get_command_format(Call, Auth, Version),
+ ArgsFormatted = format_args(Call, rename_old_args(Args, ArgsR), ArgsF),
+ case ejabberd_commands:execute_command2(Call, ArgsFormatted, Auth, Version) of
+ {error, Error} ->
+ throw(Error);
+ Res ->
+ format_command_result(Call, Auth, Res, Version)
+ end.
-get_elem_delete(A, L) ->
+rename_old_args(Args, []) ->
+ Args;
+rename_old_args(Args, [{OldName, NewName} | ArgsR]) ->
+ Args2 = case lists:keytake(OldName, 1, Args) of
+ {value, {OldName, Value}, ArgsTail} ->
+ [{NewName, Value} | ArgsTail];
+ false ->
+ Args
+ end,
+ rename_old_args(Args2, ArgsR).
+
+get_elem_delete(Call, A, L, F) ->
case proplists:get_all_values(A, L) of
[Value] -> {Value, proplists:delete(A, L)};
[_, _ | _] ->
- %% Crash reporting the error
- exit({duplicated_attribute, A, L});
+ ?INFO_MSG("Command ~ts call rejected, it has duplicate attribute ~w",
+ [Call, A]),
+ throw({invalid_parameter,
+ io_lib:format("Request have duplicate argument: ~w", [A])});
[] ->
- %% Report the error and then force a crash
- exit({attribute_not_found, A, L})
+ case F of
+ {list, _} ->
+ {[], L};
+ _ ->
+ ?INFO_MSG("Command ~ts call rejected, missing attribute ~w",
+ [Call, A]),
+ throw({invalid_parameter,
+ io_lib:format("Request have missing argument: ~w", [A])})
+ end
end.
-format_args(Args, ArgsFormat) ->
+format_args(Call, Args, ArgsFormat) ->
{ArgsRemaining, R} = lists:foldl(fun ({ArgName,
ArgFormat},
{Args1, Res}) ->
{ArgValue, Args2} =
- get_elem_delete(ArgName,
- Args1),
+ get_elem_delete(Call, ArgName,
+ Args1, ArgFormat),
Formatted = format_arg(ArgValue,
ArgFormat),
{Args2, Res ++ [Formatted]}
@@ -382,9 +316,28 @@ format_args(Args, ArgsFormat) ->
{Args, []}, ArgsFormat),
case ArgsRemaining of
[] -> R;
- L when is_list(L) -> exit({additional_unused_args, L})
+ L when is_list(L) ->
+ ExtraArgs = [N || {N, _} <- L],
+ ?INFO_MSG("Command ~ts call rejected, it has unknown arguments ~w",
+ [Call, ExtraArgs]),
+ throw({invalid_parameter,
+ io_lib:format("Request have unknown arguments: ~w", [ExtraArgs])})
end.
+format_arg({Elements},
+ {list, {_ElementDefName, {tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]} = Tuple}})
+ when is_list(Elements) andalso
+ (Tuple1S == binary orelse Tuple1S == string) ->
+ lists:map(fun({F1, F2}) ->
+ {format_arg(F1, Tuple1S), format_arg(F2, Tuple2S)};
+ ({Val}) when is_list(Val) ->
+ format_arg({Val}, Tuple)
+ end, Elements);
+format_arg(Elements,
+ {list, {_ElementDefName, {list, _} = ElementDefFormat}})
+ when is_list(Elements) ->
+ [{format_arg(Element, ElementDefFormat)}
+ || Element <- Elements];
format_arg(Elements,
{list, {_ElementDefName, ElementDefFormat}})
when is_list(Elements) ->
@@ -405,7 +358,7 @@ format_arg({Elements},
_ when TElDef == binary; TElDef == string ->
<<"">>;
_ ->
- ?ERROR_MSG("missing field ~p in tuple ~p", [TElName, Elements]),
+ ?ERROR_MSG("Missing field ~p in tuple ~p", [TElName, Elements]),
throw({invalid_parameter,
io_lib:format("Missing field ~w in tuple ~w", [TElName, Elements])})
end
@@ -418,12 +371,12 @@ format_arg(Elements, {list, ElementsDef})
format_arg(Arg, integer) when is_integer(Arg) -> Arg;
format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg);
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
-format_arg(Arg, string) when is_list(Arg) -> process_unicode_codepoints(Arg);
-format_arg(Arg, string) when is_binary(Arg) -> Arg;
+format_arg(Arg, string) when is_list(Arg) -> Arg;
+format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg);
format_arg(undefined, binary) -> <<>>;
-format_arg(undefined, string) -> <<>>;
+format_arg(undefined, string) -> "";
format_arg(Arg, Format) ->
- ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
+ ?ERROR_MSG("Don't know how to format Arg ~p for format ~p", [Arg, Format]),
throw({invalid_parameter,
io_lib:format("Arg ~w is not in format ~w",
[Arg, Format])}).
@@ -437,75 +390,77 @@ process_unicode_codepoints(Str) ->
%% internal helpers
%% ----------------
-match(Args, Spec) ->
- [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
-
-ejabberd_command(Auth, Cmd, Args, Version, IP) ->
- Access = case Auth of
- admin -> [];
- _ -> undefined
- end,
- case ejabberd_commands:execute_command(Access, Auth, Cmd, Args, Version, #{ip => IP}) of
- {error, Error} ->
- throw(Error);
- Res ->
- format_command_result(Cmd, Auth, Res, Version)
- end.
-
format_command_result(Cmd, Auth, Result, Version) ->
- {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
+ {_, _, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth, Version),
case {ResultFormat, Result} of
- {{_, rescode}, V} when V == true; V == ok ->
- {200, 0};
- {{_, rescode}, _} ->
- {200, 1};
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, 0};
+ {{_, rescode}, _} ->
+ {200, 1};
{_, {error, ErrorAtom, Code, Msg}} ->
format_error_result(ErrorAtom, Code, Msg);
{{_, restuple}, {V, Text}} when V == true; V == ok ->
{200, iolist_to_binary(Text)};
{{_, restuple}, {ErrorAtom, Msg}} ->
format_error_result(ErrorAtom, 0, Msg);
- {{_, {list, _}}, _V} ->
- {_, L} = format_result(Result, ResultFormat),
- {200, L};
- {{_, {tuple, _}}, _V} ->
- {_, T} = format_result(Result, ResultFormat),
- {200, T};
- _ ->
- {200, {[format_result(Result, ResultFormat)]}}
+ {{_, {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}) ->
- {jlib:atom_to_binary(Name), jlib:atom_to_binary(Atom)};
+ {misc:atom_to_binary(Name), misc:atom_to_binary(Atom)};
format_result(Int, {Name, integer}) ->
- {jlib:atom_to_binary(Name), Int};
+ {misc:atom_to_binary(Name), Int};
+
+format_result([String | _] = StringList, {Name, string}) when is_list(String) ->
+ Binarized = iolist_to_binary(string:join(StringList, "\n")),
+ {misc:atom_to_binary(Name), Binarized};
format_result(String, {Name, string}) ->
- {jlib:atom_to_binary(Name), iolist_to_binary(String)};
+ {misc:atom_to_binary(Name), iolist_to_binary(String)};
format_result(Code, {Name, rescode}) ->
- {jlib:atom_to_binary(Name), Code == true orelse Code == ok};
+ {misc:atom_to_binary(Name), Code == true orelse Code == ok};
format_result({Code, Text}, {Name, restuple}) ->
- {jlib:atom_to_binary(Name),
+ {misc:atom_to_binary(Name),
{[{<<"res">>, Code == true orelse Code == ok},
{<<"text">>, iolist_to_binary(Text)}]}};
+format_result(Code, {Name, restuple}) ->
+ {misc:atom_to_binary(Name),
+ {[{<<"res">>, Code == true orelse Code == ok},
+ {<<"text">>, <<"">>}]}};
+
format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
- {jlib:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+ {misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+
+format_result(Els, {Name, {list, {_, {tuple, [{name, string}, {value, _}]}} = Fmt}}) ->
+ {misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
format_result(Els, {Name, {list, Def}}) ->
- {jlib:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
+ {misc:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
format_result(Tuple, {_Name, {tuple, [{_, atom}, ValFmt]}}) ->
{Name2, Val} = Tuple,
{_, Val2} = format_result(Val, ValFmt),
- {jlib:atom_to_binary(Name2), Val2};
+ {misc:atom_to_binary(Name2), Val2};
+
+format_result(Tuple, {_Name, {tuple, [{name, string}, {value, _} = ValFmt]}}) ->
+ {Name2, Val} = Tuple,
+ {_, Val2} = format_result(Val, ValFmt),
+ {iolist_to_binary(Name2), Val2};
format_result(Tuple, {Name, {tuple, Def}}) ->
Els = lists:zip(tuple_to_list(Tuple), Def),
- {jlib:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
+ {misc:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
format_result(404, {_Name, _}) ->
"not_found".
@@ -513,14 +468,19 @@ format_result(404, {_Name, _}) ->
format_error_result(conflict, Code, Msg) ->
{409, Code, iolist_to_binary(Msg)};
+format_error_result(not_exists, Code, Msg) ->
+ {404, Code, iolist_to_binary(Msg)};
format_error_result(_ErrorAtom, Code, Msg) ->
{500, Code, iolist_to_binary(Msg)}.
unauthorized_response() ->
+ json_error(401, 10, <<"You are not authorized to call this command.">>).
+
+invalid_token_response() ->
json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
-outofscope_response() ->
- json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
+%% outofscope_response() ->
+%% json_error(401, 11, <<"Token does not grant usage to command required scope.">>).
badrequest_response() ->
badrequest_response(<<"400 Bad Request">>).
@@ -545,10 +505,18 @@ json_error(HTTPCode, JSONCode, Message) ->
}.
log(Call, Args, {Addr, Port}) ->
- AddrS = jlib:ip_to_list({Addr, Port}),
- ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+ AddrS = misc:ip_to_list({Addr, Port}),
+ ?INFO_MSG("API call ~ts ~p from ~ts:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
log(Call, Args, IP) ->
- ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
+ ?INFO_MSG("API call ~ts ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
-mod_opt_type(admin_ip_access) -> fun acl:access_rules_validator/1;
-mod_opt_type(_) -> [admin_ip_access].
+hide_sensitive_args(Args=[_H|_T]) ->
+ lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
+ ({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
+ (E) -> E end,
+ Args);
+hide_sensitive_args(NonListArgs) ->
+ NonListArgs.
+
+mod_options(_) ->
+ [].