Adds support for option admin_ip_access on mod_http_api

This allows granting access to admin commands to backend, by using IP address restrictions.
(Pawel Chmielowski)
This commit is contained in:
Mickael Remond 2016-03-29 19:40:20 +02:00
parent 6d7891ed16
commit 82cf7f7ca8
1 changed files with 84 additions and 35 deletions

View File

@ -43,6 +43,25 @@
%% %%
%% Then to perform an action, send a POST request to the following URL: %% Then to perform an action, send a POST request to the following URL:
%% http://localhost:5280/api/<call_name> %% 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). -module(mod_http_api).
@ -102,46 +121,72 @@ stop(_Host) ->
%% basic auth %% basic auth
%% ---------- %% ----------
check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command) check_permissions(Request, Command) ->
when HTTPAuth /= undefined ->
case catch binary_to_existing_atom(Command, utf8) of case catch binary_to_existing_atom(Command, utf8) of
Call when is_atom(Call) -> Call when is_atom(Call) ->
Admin = check_permissions2(Request, Call);
case lists:keysearch(<<"X-Admin">>, 1, Headers) of _ ->
{value, {_, <<"true">>}} -> true; unauthorized_response()
_ -> false end.
end,
Auth = check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call)
case HTTPAuth of when HTTPAuth /= undefined ->
{SJID, Pass} -> Admin =
case jid:from_string(SJID) of case lists:keysearch(<<"X-Admin">>, 1, Headers) of
#jid{user = User, server = Server} -> {value, {_, <<"true">>}} -> true;
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of _ -> false
true -> {ok, {User, Server, Pass, Admin}}; end,
false -> false Auth =
end; case HTTPAuth of
_ -> {SJID, Pass} ->
false case jid:from_string(SJID) of
end; #jid{user = User, server = Server} ->
{oauth, Token, _} -> case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
case ejabberd_oauth:check_token(Command, Token) of true -> {ok, {User, Server, Pass, Admin}};
{ok, User, Server} -> false -> false
{ok, {User, Server, {oauth, Token}, Admin}};
false ->
false
end; end;
_ -> _ ->
false false
end, end;
case Auth of {oauth, Token, _} ->
{ok, A} -> {allowed, Call, A}; case oauth_check_token(Call, Token) of
{ok, User, Server} ->
{ok, {User, Server, {oauth, Token}, Admin}};
false ->
false
end;
_ ->
false
end,
case Auth of
{ok, A} -> {allowed, Call, A};
_ -> unauthorized_response()
end;
check_permissions2(#request{ip={IP, _Port}}, Call) ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access,
mod_opt_type(admin_ip_access),
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};
_ -> unauthorized_response() _ -> unauthorized_response()
end; end;
_ -> _ ->
unauthorized_response() unauthorized_response()
end; end.
check_permissions(_, _Command) ->
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).
%% ------------------ %% ------------------
%% command processing %% command processing
@ -162,7 +207,7 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
{allowed, Cmd, Auth} -> {allowed, Cmd, Auth} ->
{Code, Result} = handle(Cmd, Auth, Args), {Code, Result} = handle(Cmd, Auth, Args),
json_response(Code, jiffy:encode(Result)); json_response(Code, jiffy:encode(Result));
ErrorResponse -> ErrorResponse -> %% Should we reply 403 ?
ErrorResponse ErrorResponse
end end
catch _:Error -> catch _:Error ->
@ -309,7 +354,11 @@ match(Args, Spec) ->
[{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec]. [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
ejabberd_command(Auth, Cmd, Args, Default) -> ejabberd_command(Auth, Cmd, Args, Default) ->
case catch ejabberd_commands:execute_command(undefined, Auth, Cmd, Args) of Access = case Auth of
admin -> [];
_ -> undefined
end,
case catch ejabberd_commands:execute_command(Access, Auth, Cmd, Args) of
{'EXIT', _} -> Default; {'EXIT', _} -> Default;
{error, _} -> Default; {error, _} -> Default;
Result -> Result Result -> Result
@ -387,6 +436,6 @@ log(Call, Args, {Addr, Port}) ->
AddrS = jlib:ip_to_list({Addr, Port}), AddrS = jlib:ip_to_list({Addr, Port}),
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]). ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
mod_opt_type(access) -> mod_opt_type(admin_ip_access) ->
fun(Access) when is_atom(Access) -> Access end; fun(Access) when is_atom(Access) -> Access end;
mod_opt_type(_) -> [access]. mod_opt_type(_) -> [admin_ip_access].