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.erl111
1 files changed, 65 insertions, 46 deletions
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 595c121cd..ba3a14cf8 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -74,7 +74,7 @@
-behaviour(gen_mod).
--export([start/2, stop/1, process/2, mod_opt_type/1]).
+-export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -123,6 +123,9 @@ start(_Host, _Opts) ->
stop(_Host) ->
ok.
+depends(_Host, _Opts) ->
+ [].
+
%% ----------
%% basic auth
%% ----------
@@ -130,13 +133,13 @@ stop(_Host) ->
check_permissions(Request, Command) ->
case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) ->
- {ok, CommandPolicy} = ejabberd_commands:get_command_policy(Call),
- check_permissions2(Request, Call, CommandPolicy);
+ {ok, CommandPolicy, Scope} = ejabberd_commands:get_command_policy_and_scope(Call),
+ check_permissions2(Request, Call, CommandPolicy, Scope);
_ ->
- unauthorized_response()
+ json_error(404, 40, <<"Endpoint not found.">>)
end.
-check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
+check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _, ScopeList)
when HTTPAuth /= undefined ->
Admin =
case lists:keysearch(<<"X-Admin">>, 1, Headers) of
@@ -156,11 +159,9 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
false
end;
{oauth, Token, _} ->
- case oauth_check_token(Call, Token) of
+ case oauth_check_token(ScopeList, Token) of
{ok, user, {User, Server}} ->
{ok, {User, Server, {oauth, Token}, Admin}};
- {ok, server_admin} -> %% token whas generated using issue_token command line
- {ok, admin};
false ->
false
end;
@@ -171,9 +172,9 @@ check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call, _)
{ok, A} -> {allowed, Call, A};
_ -> unauthorized_response()
end;
-check_permissions2(_Request, Call, open) ->
+check_permissions2(_Request, Call, open, _Scope) ->
{allowed, Call, noauth};
-check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
+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),
@@ -193,13 +194,11 @@ check_permissions2(#request{ip={IP, _Port}}, Call, _Policy) ->
_E ->
{allowed, Call, noauth}
end;
-check_permissions2(_Request, _Call, _Policy) ->
+check_permissions2(_Request, _Call, _Policy, _Scope) ->
unauthorized_response().
-oauth_check_token(Scope, Token) when is_atom(Scope) ->
- oauth_check_token(atom_to_binary(Scope, utf8), Token);
-oauth_check_token(Scope, Token) ->
- ejabberd_oauth:check_token(Scope, Token).
+oauth_check_token(ScopeList, Token) when is_list(ScopeList) ->
+ ejabberd_oauth:check_token(ScopeList, Token).
%% ------------------
%% command processing
@@ -221,8 +220,12 @@ process([Call], #request{method = 'POST', data = Data, ip = {IP, _} = IPPort} =
log(Call, Args, IPPort),
case check_permissions(Req, Call) of
{allowed, Cmd, Auth} ->
- {Code, Result} = handle(Cmd, Auth, Args, Version, IP),
- json_response(Code, jiffy:encode(Result));
+ case handle(Cmd, Auth, Args, Version, IP) of
+ {Code, Result} ->
+ json_response(Code, jiffy:encode(Result));
+ {HTMLCode, JSONErrorCode, Message} ->
+ json_error(HTMLCode, JSONErrorCode, Message)
+ end;
%% Warning: check_permission direcly formats 401 reply if not authorized
ErrorResponse ->
ErrorResponse
@@ -265,10 +268,10 @@ 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)
+ N when is_integer(N) ->
+ N;
+ _ ->
+ get_api_version(Tail)
end;
get_api_version([_Head | Tail]) ->
get_api_version(Tail);
@@ -279,6 +282,8 @@ get_api_version([]) ->
%% command handlers
%% ----------------
+%% 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
@@ -310,8 +315,10 @@ handle(Call, Auth, Args, Version, IP) when is_atom(Call), is_list(Args) ->
{401, jlib:atom_to_binary(Why)};
throw:{not_allowed, Msg} ->
{401, iolist_to_binary(Msg)};
- throw:{error, account_unprivileged} ->
- {401, iolist_to_binary(<<"Unauthorized: Account Unpriviledged">>)};
+ 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) ->
@@ -367,28 +374,33 @@ format_args(Args, ArgsFormat) ->
L when is_list(L) -> exit({additional_unused_args, L})
end.
-format_arg({array, Elements},
- {list, {ElementDefName, ElementDefFormat}})
- when is_list(Elements) ->
- lists:map(fun ({struct, [{ElementName, ElementValue}]}) when
- ElementDefName == ElementName ->
- format_arg(ElementValue, ElementDefFormat)
- end,
- Elements);
-format_arg({array, [{struct, Elements}]},
- {list, {ElementDefName, ElementDefFormat}})
+format_arg(Elements,
+ {list, {_ElementDefName, ElementDefFormat}})
when is_list(Elements) ->
- lists:map(fun ({ElementName, ElementValue}) ->
- true = ElementDefName == ElementName,
- format_arg(ElementValue, ElementDefFormat)
- end,
- Elements);
-format_arg({array, [{struct, Elements}]},
+ [format_arg(Element, ElementDefFormat)
+ || Element <- Elements];
+format_arg({[{Name, Value}]},
+ {tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]})
+ when Tuple1S == binary;
+ Tuple1S == string ->
+ {format_arg(Name, Tuple1S), format_arg(Value, Tuple2S)};
+format_arg({Elements},
{tuple, ElementsDef})
when is_list(Elements) ->
- FormattedList = format_args(Elements, ElementsDef),
- list_to_tuple(FormattedList);
-format_arg({array, Elements}, {list, ElementsDef})
+ F = lists:map(fun({TElName, TElDef}) ->
+ case lists:keyfind(atom_to_binary(TElName, latin1), 1, Elements) of
+ {_, Value} ->
+ format_arg(Value, TElDef);
+ _ when TElDef == binary; TElDef == string ->
+ <<"">>;
+ _ ->
+ ?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
+ end, ElementsDef),
+ list_to_tuple(F);
+format_arg(Elements, {list, ElementsDef})
when is_list(Elements) and is_atom(ElementsDef) ->
[format_arg(Element, ElementsDef)
|| Element <- Elements];
@@ -402,7 +414,7 @@ format_arg(undefined, string) -> <<>>;
format_arg(Arg, Format) ->
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
throw({invalid_parameter,
- io_lib:format("Arg ~p is not in format ~p",
+ io_lib:format("Arg ~w is not in format ~w",
[Arg, Format])}).
process_unicode_codepoints(Str) ->
@@ -486,9 +498,7 @@ format_result(404, {_Name, _}) ->
"not_found".
unauthorized_response() ->
- unauthorized_response(<<"401 Unauthorized">>).
-unauthorized_response(Body) ->
- json_response(401, jiffy:encode(Body)).
+ json_error(401, 10, <<"Oauth Token is invalid or expired.">>).
badrequest_response() ->
badrequest_response(<<"400 Bad Request">>).
@@ -498,6 +508,15 @@ badrequest_response(Body) ->
json_response(Code, Body) when is_integer(Code) ->
{Code, ?HEADER(?CT_JSON), Body}.
+%% HTTPCode, JSONCode = integers
+%% message is binary
+json_error(HTTPCode, JSONCode, Message) ->
+ {HTTPCode, ?HEADER(?CT_JSON),
+ jiffy:encode({[{<<"status">>, <<"error">>},
+ {<<"code">>, JSONCode},
+ {<<"message">>, 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]);