diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index f46860a56..15fe36364 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -43,6 +43,25 @@ %% %% Then to perform an action, send a POST request to the following URL: %% http://localhost:5280/api/ +%% +%% 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). @@ -102,46 +121,72 @@ stop(_Host) -> %% basic auth %% ---------- -check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command) - when HTTPAuth /= undefined -> +check_permissions(Request, Command) -> case catch binary_to_existing_atom(Command, utf8) of Call when is_atom(Call) -> - 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 ejabberd_oauth:check_token(Command, Token) of - {ok, User, Server} -> - {ok, {User, Server, {oauth, Token}, Admin}}; - false -> - false + check_permissions2(Request, Call); + _ -> + unauthorized_response() + end. + +check_permissions2(#request{auth = HTTPAuth, headers = Headers}, Call) + 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, - case Auth of - {ok, A} -> {allowed, Call, A}; + end; + {oauth, Token, _} -> + 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() end; _ -> unauthorized_response() - end; -check_permissions(_, _Command) -> - unauthorized_response(). + end. + +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 @@ -162,7 +207,7 @@ process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) -> {allowed, Cmd, Auth} -> {Code, Result} = handle(Cmd, Auth, Args), json_response(Code, jiffy:encode(Result)); - ErrorResponse -> + ErrorResponse -> %% Should we reply 403 ? ErrorResponse end catch _:Error -> @@ -309,7 +354,11 @@ match(Args, Spec) -> [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec]. 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; {error, _} -> Default; Result -> Result @@ -387,6 +436,6 @@ 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]). -mod_opt_type(access) -> +mod_opt_type(admin_ip_access) -> fun(Access) when is_atom(Access) -> Access end; -mod_opt_type(_) -> [access]. +mod_opt_type(_) -> [admin_ip_access].