Permalink
Browse files

Merge branch 'IvanMartinez-refresh_token'

  • Loading branch information...
2 parents 8e0c65d + 6f42dcd commit b5c0dfdbe21bfe175e7a232be10008277dcb2ea0 @bipthelin bipthelin committed Aug 8, 2013
Showing with 129 additions and 38 deletions.
  1. +8 −4 README.md
  2. +45 −31 src/oauth2.erl
  3. +9 −0 src/oauth2_backend.erl
  4. +6 −0 test/oauth2_mock_backend.erl
  5. +61 −3 test/oauth2_tests.erl
View
@@ -1,4 +1,4 @@
-# OAuth2 (v0.2.0) [![BuildStatus](https://travis-ci.org/kivra/oauth2.png?branch=master)](https://travis-ci.org/kivra/oauth2)
+# OAuth2 (v0.3.0) [![BuildStatus](https://travis-ci.org/kivra/oauth2.png?branch=master)](https://travis-ci.org/kivra/oauth2)
This library is designed to simplify the implementation of the server side
of OAuth2 (http://tools.ietf.org/html/rfc6749). It provides
**no** support for developing clients. See
@@ -7,18 +7,22 @@ accessing Oauth2 enabled services.
oauth2 is released under the terms of the [MIT](http://en.wikipedia.org/wiki/MIT_License) license
-Current stable version: [0.2.0](https://github.com/kivra/oauth2/tree/0.2.0)
+Current stable version: [0.3.0](https://github.com/kivra/oauth2/tree/0.3.0)
-Current α alpha version: [0.3.0](https://github.com/kivra/oauth2)
+Current α alpha version: [0.4.0](https://github.com/kivra/oauth2)
copyright 2012-2013 Kivra
## tl;dr
+### Examples
Check out the [examples](https://github.com/kivra/oauth2_example).
-There's also a webmachine server implementation by Oauth2 contributor
+### Related projects
+Webmachine server implementation by Oauth2 contributor
Ivan Martinez: [oauth2_webmachine](https://github.com/IvanMartinez/oauth2_webmachine).
+Redis backed Oauth2 [backend](https://github.com/interline/oauth2_redis_backend).
+
## Concepts
### Tokens
View
@@ -37,17 +37,17 @@
-export([verify_access_token/1]).
-export([verify_access_code/1]).
-export([verify_access_code/2]).
--export([refresh_access_token/3]).
+-export([refresh_access_token/4]).
%%% Exported types
-type context() :: proplists:proplist(binary(), term()).
-type token() :: binary().
-type lifetime() :: non_neg_integer().
-type scope() :: list(binary()) | binary().
--type error() :: access_denied | invalid_client | invalid_request
- | invalid_scope | unauthorized_client
- | unsupported_response_type | server_error
- | temporarily_unavailable.
+-type error() :: access_denied | invalid_client | invalid_grant |
+ invalid_request | invalid_scope | unauthorized_client |
+ unsupported_response_type | server_error |
+ temporarily_unavailable.
-export_type([
token/0
@@ -285,41 +285,55 @@ verify_access_code(AccessCode, Client) ->
%% @doc Verifies an refresh token RefreshToken, returning a new Access Token
%% if successful. Otherwise, an OAuth2 error code is returned.
--spec refresh_access_token(ClientId, ClientSecret, RefreshToken)
+-spec refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope)
-> {ok, Client, Response}
| {error, Reason} when
ClientId :: binary(),
ClientSecret :: binary(),
RefreshToken :: token(),
- Client :: term(),
+ Scope :: scope(),
+ Client :: term(),
Response :: oauth2_response:response(),
Reason :: error().
-refresh_access_token(ClientId, ClientSecret, RefreshToken) ->
- case ?BACKEND:resolve_refresh_token(RefreshToken) of
- {ok, Context} ->
- {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1, Context),
- case ExpiryAbsolute > seconds_since_epoch(0) of
- true ->
- {_, Client} = lists:keyfind(<<"client">>, 1, Context),
- case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
- {ok, Client} ->
- {_, ResOwner} = lists:keyfind(<<"resource_owner">>, 1, Context),
- {_, Scope} = lists:keyfind(<<"scope">>, 1, Context),
- TTL = oauth2_config:expiry_time(password_credentials),
- Response = issue_token(
- #authorization{client = Client,
- resowner = ResOwner,
- scope = Scope,
- ttl = TTL}),
- {ok, Client, Response};
- _ -> {error, access_denied}
+refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope) ->
+ case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
+ {ok, Client} ->
+ case ?BACKEND:resolve_refresh_token(RefreshToken) of
+ {ok, Context} ->
+ {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1,
+ Context),
+ case ExpiryAbsolute > seconds_since_epoch(0) of
+ true ->
+ {_, Client} = lists:keyfind(<<"client">>, 1,
+ Context),
+ {_, RegisteredScope} = lists:keyfind(<<"scope">>, 1,
+ Context),
+ case ?BACKEND:verify_scope(RegisteredScope,
+ Scope) of
+ {ok, VerifiedScope} ->
+ {_, ResOwner} = lists:keyfind(
+ <<"resource_owner">>, 1,
+ Context),
+ TTL = oauth2_config:expiry_time(
+ password_credentials),
+ Response = issue_token(
+ #authorization{
+ client = Client,
+ resowner = ResOwner,
+ scope = VerifiedScope,
+ ttl = TTL}),
+ {ok, Client, Response};
+ {error, _Reason} ->
+ {error, invalid_scope}
+ end;
+ false ->
+ ?BACKEND:revoke_refresh_token(RefreshToken),
+ {error, invalid_grant}
end;
- false ->
- ?BACKEND:revoke_refresh_token(RefreshToken),
- {error, access_denied}
+ _ ->
+ {error, invalid_grant}
end;
- _ ->
- {error, access_denied}
+ _ -> {error, invalid_client}
end.
%% @doc Verifies an access token AccessToken, returning its associated
View
@@ -158,3 +158,12 @@
Scope :: oauth2:scope(),
Scope2 :: oauth2:scope(),
Reason :: notfound | badscope.
+
+%% @doc Verifies that Scope is a valid scope of the set of scopes defined
+%% by ValidScopes.
+%% @end
+-callback verify_scope(ValidScopes, Scope) -> {ok, Scope2} | {error, Reason} when
+ ValidScopes :: oauth2:scope(),
+ Scope :: oauth2:scope(),
+ Scope2 :: oauth2:scope(),
+ Reason :: notfound | badscope.
@@ -45,6 +45,7 @@
-export([verify_redirection_uri/2]).
-export([verify_client_scope/2]).
-export([verify_resowner_scope/2]).
+-export([verify_scope/2]).
%%% mock_backend-specifics
-export([start/0]).
@@ -143,6 +144,11 @@ verify_resowner_scope({user, 31337}, ?USER_SCOPE) ->
verify_resowner_scope(_, _) ->
{error, invalid_scope}.
+verify_scope(Scope, Scope) ->
+ {ok, Scope};
+verify_scope(_, _) ->
+ {error, invalid_scope}.
+
start() ->
%% Set up the ETS table for holding access tokens.
ets:new(?ETS_TABLE, [public, named_table, {read_concurrency, true}]).
View
@@ -111,6 +111,10 @@ bad_ttl_test_() ->
{setup,
fun () ->
meck:new(oauth2_mock_backend),
+ meck:expect(oauth2_mock_backend,
+ authenticate_client,
+ fun(_, _) -> {ok, {client, 4711}}
+ end),
meck:expect(oauth2_mock_backend,
resolve_access_code,
fun(_) -> {ok, [{<<"identity">>, <<"123">>},
@@ -148,11 +152,12 @@ bad_ttl_test_() ->
?_assertMatch({error, access_denied},
oauth2:verify_access_token(
<<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>)),
- ?_assertMatch({error, access_denied},
+ ?_assertMatch({error, invalid_grant},
oauth2:refresh_access_token(
?CLIENT_ID,
?CLIENT_SECRET,
- <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>))
+ <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>,
+ ?CLIENT_SCOPE))
]
end}.
@@ -251,6 +256,58 @@ verify_access_code_test_() ->
]
end}.
+bad_refresh_token_test_() ->
+ {setup,
+ fun start/0,
+ fun stop/1,
+ fun(_) ->
+ [
+ fun() ->
+ {ok, Authorization} = oauth2:authorize_code_request(
+ ?CLIENT_ID,
+ ?CLIENT_URI,
+ ?USER_NAME,
+ ?USER_PASSWORD,
+ ?CLIENT_SCOPE),
+ Response = oauth2:issue_code(Authorization),
+ {ok, Code} = oauth2_response:access_code(Response),
+ {ok, Authorization2} = oauth2:authorize_code_grant(
+ ?CLIENT_ID,
+ ?CLIENT_SECRET,
+ Code,
+ ?CLIENT_URI),
+ Response2 = oauth2:issue_token_and_refresh(
+ Authorization2),
+ {ok, RefreshToken} = oauth2_response:refresh_token(
+ Response2),
+ ?assertMatch({error, invalid_client},
+ oauth2:refresh_access_token(
+ <<"foo">>,
+ ?CLIENT_SECRET,
+ RefreshToken,
+ ?CLIENT_SCOPE)),
+ ?assertMatch({error, invalid_client},
+ oauth2:refresh_access_token(
+ ?CLIENT_ID,
+ <<"foo">>,
+ RefreshToken,
+ ?CLIENT_SCOPE)),
+ ?assertMatch({error, invalid_grant},
+ oauth2:refresh_access_token(
+ ?CLIENT_ID,
+ ?CLIENT_SECRET,
+ <<"foo">>,
+ ?CLIENT_SCOPE)),
+ ?assertMatch({error, invalid_scope},
+ oauth2:refresh_access_token(
+ ?CLIENT_ID,
+ ?CLIENT_SECRET,
+ RefreshToken,
+ <<"foo">>))
+ end
+ ]
+ end}.
+
verify_refresh_token_test_() ->
{setup,
fun start/0,
@@ -275,7 +332,8 @@ verify_refresh_token_test_() ->
{ok, RefreshToken} = oauth2_response:refresh_token(Response2),
{ok, _, _Response3} = oauth2:refresh_access_token(?CLIENT_ID,
?CLIENT_SECRET,
- RefreshToken),
+ RefreshToken,
+ ?CLIENT_SCOPE),
{ok, Token} = oauth2_response:access_token(Response2),
?assertMatch({ok, _}, oauth2:verify_access_token(Token))
end

0 comments on commit b5c0dfd

Please sign in to comment.