diff options
Diffstat (limited to 'src/mod_http_api.erl')
-rw-r--r-- | src/mod_http_api.erl | 504 |
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(_) -> + []. |