mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-20 17:27:00 +01:00
Support oauth password grant type
As in https://tools.ietf.org/html/rfc6749#section-4.3
This commit is contained in:
parent
57aeef74d5
commit
4332dddbc4
@ -497,10 +497,65 @@ process(_Handlers,
|
||||
}],
|
||||
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
|
||||
end;
|
||||
process(_Handlers,
|
||||
#request{method = 'POST', q = Q, lang = _Lang,
|
||||
path = [_, <<"token">>]}) ->
|
||||
case proplists:get_value(<<"grant_type">>, Q, <<"">>) of
|
||||
<<"password">> ->
|
||||
SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
|
||||
StringJID = proplists:get_value(<<"username">>, Q, <<"">>),
|
||||
#jid{user = Username, server = Server} = jid:from_string(StringJID),
|
||||
Password = proplists:get_value(<<"password">>, Q, <<"">>),
|
||||
Scope = str:tokens(SScope, <<" ">>),
|
||||
TTL = proplists:get_value(<<"ttl">>, Q, <<"">>),
|
||||
ExpiresIn = case TTL of
|
||||
<<>> -> undefined;
|
||||
_ -> jlib:binary_to_integer(TTL)
|
||||
end,
|
||||
case oauth2:authorize_password({Username, Server},
|
||||
Scope,
|
||||
{password, Password}) of
|
||||
{ok, {_AppContext, Authorization}} ->
|
||||
{ok, {_AppContext2, Response}} =
|
||||
oauth2:issue_token(Authorization, [{expiry_time, ExpiresIn} || ExpiresIn /= undefined ]),
|
||||
{ok, AccessToken} = oauth2_response:access_token(Response),
|
||||
{ok, Type} = oauth2_response:token_type(Response),
|
||||
%%Ugly: workardound to return the correct expirity time, given than oauth2 lib doesn't really have
|
||||
%%per-case expirity time.
|
||||
Expires = case ExpiresIn of
|
||||
undefined ->
|
||||
{ok, Ex} = oauth2_response:expires_in(Response),
|
||||
Ex;
|
||||
_ ->
|
||||
ExpiresIn
|
||||
end,
|
||||
{ok, VerifiedScope} = oauth2_response:scope(Response),
|
||||
json_response(200, {[
|
||||
{<<"access_token">>, AccessToken},
|
||||
{<<"token_type">>, Type},
|
||||
{<<"scope">>, str:join(VerifiedScope, <<" ">>)},
|
||||
{<<"expires_in">>, Expires}]});
|
||||
{error, Error} when is_atom(Error) ->
|
||||
json_response(400, {[
|
||||
{<<"error">>, <<"invalid_grant">>},
|
||||
{<<"error_description">>, Error}]})
|
||||
end;
|
||||
_OtherGrantType ->
|
||||
json_response(400, {[
|
||||
{<<"error">>, <<"unsupported_grant_type">>}]})
|
||||
end;
|
||||
|
||||
process(_Handlers, _Request) ->
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
|
||||
%% Headers as per RFC 6749
|
||||
json_response(Code, Body) ->
|
||||
{Code, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>},
|
||||
{<<"Cache-Control">>, <<"no-store">>},
|
||||
{<<"Pragma">>, <<"no-cache">>}],
|
||||
jiffy:encode(Body)}.
|
||||
|
||||
|
||||
|
||||
web_head() ->
|
||||
|
@ -58,6 +58,7 @@ defmodule ModHttpApiMockTest do
|
||||
setup do
|
||||
:meck.unload
|
||||
:meck.new :ejabberd_commands
|
||||
:meck.new(:acl, [:passthrough]) # Need to fake acl to allow oauth
|
||||
EjabberdAuthMock.init
|
||||
:ok
|
||||
end
|
||||
@ -206,5 +207,69 @@ defmodule ModHttpApiMockTest do
|
||||
#assert :ok = :meck.history(:ejabberd_commands)
|
||||
end
|
||||
|
||||
test "Request oauth token, resource owner password credentials" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @userpass
|
||||
:application.set_env(:oauth2, :backend, :ejabberd_oauth)
|
||||
:application.start(:oauth2)
|
||||
|
||||
# Mock a simple command() -> :ok
|
||||
:meck.expect(:ejabberd_commands, :get_command_format,
|
||||
fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8), "ejabberd:user"]} end)
|
||||
:meck.expect(:ejabberd_commands, :get_commands,
|
||||
fn () -> [@acommand] end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command,
|
||||
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
|
||||
@acommand, [], @version, _) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
#Mock acl to allow oauth authorizations
|
||||
:meck.expect(:acl, :match_rule, fn(_Server, _Access, _Jid) -> :allow end)
|
||||
|
||||
|
||||
# Correct password
|
||||
req = request(method: :POST,
|
||||
path: ["oauth", "token"],
|
||||
q: [{"grant_type", "password"}, {"scope", @command}, {"username", @user<>"@"<>@domain}, {"ttl", "4000"}, {"password", @userpass}],
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :ejabberd_oauth.process([], req)
|
||||
assert 200 = elem(result, 0) #http code
|
||||
{kv} = :jiffy.decode(elem(result,2))
|
||||
assert {_, "bearer"} = List.keyfind(kv, "token_type", 0)
|
||||
assert {_, @command} = List.keyfind(kv, "scope", 0)
|
||||
assert {_, 4000} = List.keyfind(kv, "expires_in", 0)
|
||||
{"access_token", _token} = List.keyfind(kv, "access_token", 0)
|
||||
|
||||
#missing grant_type
|
||||
req = request(method: :POST,
|
||||
path: ["oauth", "token"],
|
||||
q: [{"scope", @command}, {"username", @user<>"@"<>@domain}, {"password", @userpass}],
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :ejabberd_oauth.process([], req)
|
||||
assert 400 = elem(result, 0) #http code
|
||||
{kv} = :jiffy.decode(elem(result,2))
|
||||
assert {_, "unsupported_grant_type"} = List.keyfind(kv, "error", 0)
|
||||
|
||||
|
||||
# incorrect user/pass
|
||||
req = request(method: :POST,
|
||||
path: ["oauth", "token"],
|
||||
q: [{"grant_type", "password"}, {"scope", @command}, {"username", @user<>"@"<>@domain}, {"password", @userpass<>"aa"}],
|
||||
ip: {{127,0,0,1},60000},
|
||||
host: @domain)
|
||||
result = :ejabberd_oauth.process([], req)
|
||||
assert 400 = elem(result, 0) #http code
|
||||
{kv} = :jiffy.decode(elem(result,2))
|
||||
assert {_, "invalid_grant"} = List.keyfind(kv, "error", 0)
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
assert :meck.validate :ejabberd_commands
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user