mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +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">>)])}
|
ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
|
||||||
end;
|
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) ->
|
process(_Handlers, _Request) ->
|
||||||
ejabberd_web:error(not_found).
|
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() ->
|
web_head() ->
|
||||||
|
@ -58,6 +58,7 @@ defmodule ModHttpApiMockTest do
|
|||||||
setup do
|
setup do
|
||||||
:meck.unload
|
:meck.unload
|
||||||
:meck.new :ejabberd_commands
|
:meck.new :ejabberd_commands
|
||||||
|
:meck.new(:acl, [:passthrough]) # Need to fake acl to allow oauth
|
||||||
EjabberdAuthMock.init
|
EjabberdAuthMock.init
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
@ -206,5 +207,69 @@ defmodule ModHttpApiMockTest do
|
|||||||
#assert :ok = :meck.history(:ejabberd_commands)
|
#assert :ok = :meck.history(:ejabberd_commands)
|
||||||
end
|
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
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user