}],
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() ->
setup do
:meck.unload
:meck.new :ejabberd_commands
+ :meck.new(:acl, [:passthrough]) # Need to fake acl to allow oauth
EjabberdAuthMock.init
:ok
end
#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