Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'IvanMartinez-separated_authorize_issue3'

  • Loading branch information...
commit a166e02a635ce0dd99fe8a0f557da688c2ef27a2 2 parents 11b8595 + ca2137e
@bipthelin bipthelin authored
Showing with 169 additions and 158 deletions.
  1. +151 −147 src/oauth2.erl
  2. +18 −11 test/oauth2_tests.erl
View
298 src/oauth2.erl
@@ -30,7 +30,10 @@
-export([authorize_password/3]).
-export([authorize_client_credentials/3]).
-export([authorize_code_grant/4]).
--export([issue_code_grant/5]).
+-export([authorize_code_request/5]).
+-export([issue_code/1]).
+-export([issue_token/1]).
+-export([issue_token_and_refresh/1]).
-export([verify_access_token/1]).
-export([verify_access_code/1]).
-export([verify_access_code/2]).
@@ -41,9 +44,9 @@
-type token() :: binary().
-type lifetime() :: non_neg_integer().
-type scope() :: list(binary()) | binary().
--type error() :: invalid_request | unauthorized_client
- | access_denied | unsupported_response_type
- | invalid_scope | server_error
+-type error() :: access_denied | invalid_client | invalid_request
+ | invalid_scope | unauthorized_client
+ | unsupported_response_type | server_error
| temporarily_unavailable.
-export_type([
@@ -58,27 +61,34 @@
-define(BACKEND, (oauth2_config:backend())).
-define(TOKEN, (oauth2_config:token_generation())).
+%%% Internal types
+-record(authorization, {
+ client = undefined :: undefined | term(),
+ resowner = undefined :: undefined | term(),
+ scope :: scope(),
+ ttl = 0 :: non_neg_integer()
+ }).
+
%%%===================================================================
%%% API functions
%%%===================================================================
%% @doc Authorizes a client via Resource Owner Password Credentials.
-spec authorize_password(Username, Password, Scope)
- -> {ok, Identity, Response} | {error, Reason} when
- Username :: binary(),
- Password :: binary(),
- Scope :: scope(),
- Identity :: term(),
- Response :: oauth2_response:response(),
- Reason :: error().
+ -> {ok, Authorization} | {error, Reason} when
+ Username :: binary(),
+ Password :: binary(),
+ Scope :: scope(),
+ Authorization :: #authorization{},
+ Reason :: error().
authorize_password(Username, Password, Scope) ->
case ?BACKEND:authenticate_username_password(Username, Password) of
- {ok, Identity} ->
- case ?BACKEND:verify_resowner_scope(Identity, Scope) of
+ {ok, ResOwner} ->
+ case ?BACKEND:verify_resowner_scope(ResOwner, Scope) of
{ok, Scope2} ->
TTL = oauth2_config:expiry_time(password_credentials),
- Response = issue_token(Identity, <<>>, Scope2, TTL),
- {ok, Identity, Response};
+ {ok, #authorization{resowner = ResOwner, scope = Scope2,
+ ttl = TTL}};
{error, _Reason} ->
{error, invalid_scope}
end;
@@ -86,43 +96,31 @@ authorize_password(Username, Password, Scope) ->
{error, access_denied}
end.
-%% @doc Issue a Code via Access Code Grant
--spec issue_code_grant(ClientId, RedirectionUri, Username, Password, Scope)
- -> {ok, Identity, Response} | {error, Reason} when
- ClientId :: binary(),
- RedirectionUri :: scope(),
- Username :: binary(),
- Password :: binary(),
- Scope :: scope(),
- Identity :: term(),
- Response :: oauth2_response:response(),
- Reason :: error().
-issue_code_grant(ClientId, RedirectionUri, Username, Password, Scope) ->
- case ?BACKEND:get_client_identity(ClientId) of
- {ok, Identity} ->
- case ?BACKEND:verify_redirection_uri(Identity, RedirectionUri) of
- ok ->
- case ?BACKEND:verify_client_scope(Identity, Scope) of
- {ok, VerifiedScope} ->
- case ?BACKEND:authenticate_username_password(
- Username, Password) of
- {ok, ResOwner} ->
- TTL = oauth2_config:expiry_time(code_grant),
- Response = issue_code(Identity,
- VerifiedScope,
- ResOwner, TTL),
- {ok, Identity, Response};
- {error, _Reason} ->
- {error, access_denied}
- end;
- {error, _Reason} ->
- {error, invalid_scope}
- end;
- _ ->
- {error, unauthorized_client}
+%% @doc Authorize client via its own credentials, i.e., a combination
+%% of a public client identifier and a shared client secret.
+%% Should only be used for confidential clients; see the OAuth2 draft
+%% for clarification.
+-spec authorize_client_credentials(ClientId, ClientSecret, Scope)
+ -> {ok, Authorization}
+ | {error, Reason} when
+ ClientId :: binary(),
+ ClientSecret :: binary(),
+ Scope :: scope(),
+ Authorization :: #authorization{},
+ Reason :: error().
+authorize_client_credentials(ClientId, ClientSecret, Scope) ->
+ case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
+ {ok, Client} ->
+ case ?BACKEND:verify_client_scope(Client, Scope) of
+ {ok, Scope2} ->
+ TTL = oauth2_config:expiry_time(client_credentials),
+ {ok, #authorization{client = Client, scope = Scope2,
+ ttl = TTL}};
+ {error, _Reason} ->
+ {error, invalid_scope}
end;
{error, _Reason} ->
- {error, unauthorized_client}
+ {error, invalid_client}
end.
%% @doc Authorize client via its own credentials, i.e., a combination
@@ -132,33 +130,31 @@ issue_code_grant(ClientId, RedirectionUri, Username, Password, Scope) ->
%%
%% Then verify the supplied RedirectionUri and Code and if valid issue
%% an Access Token and an optional Refresh Token
-%% @end
-spec authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri)
- -> {ok, Identity, Response}
+ -> {ok, Authorization}
| {error, Reason} when
ClientId :: binary(),
ClientSecret :: binary(),
AccessCode :: token(),
RedirectionUri :: binary(),
- Identity :: term(),
- Response :: oauth2_response:response(),
+ Authorization :: #authorization{},
Reason :: error().
authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri) ->
case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
- {ok, Identity} ->
- case ?BACKEND:verify_redirection_uri(Identity, RedirectionUri) of
+ {ok, Client} ->
+ case ?BACKEND:verify_redirection_uri(Client, RedirectionUri) of
ok ->
- case verify_access_code(AccessCode, Identity) of
+ case verify_access_code(AccessCode, Client) of
{ok, Context} ->
TTL = oauth2_config:expiry_time(password_credentials),
{_, Scope} = lists:keyfind(<<"scope">>, 1, Context),
{_, ResOwner} = lists:keyfind(<<"resource_owner">>,
1, Context),
- Response = issue_token_and_refresh(Identity,
- ResOwner,
- Scope, TTL),
?BACKEND:revoke_access_code(AccessCode),
- {ok, Identity, Response};
+ {ok, #authorization{client = Client,
+ resowner = ResOwner,
+ scope = Scope,
+ ttl = TTL}};
Error ->
Error
end;
@@ -169,39 +165,86 @@ authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri) ->
{error, invalid_client}
end.
-%% @doc Authorize client via its own credentials, i.e., a combination
-%% of a public client identifier and a shared client secret.
-%% Should only be used for confidential clients; see the OAuth2 draft
-%% for clarification.
-%% @end
--spec authorize_client_credentials(ClientId, ClientSecret, Scope)
- -> {ok, Identity, Response}
- | {error, Reason} when
- ClientId :: binary(),
- ClientSecret :: binary(),
- Scope :: scope(),
- Identity :: term(),
- Response :: oauth2_response:response(),
- Reason :: error().
-authorize_client_credentials(ClientId, ClientSecret, Scope) ->
- case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
- {ok, Identity} ->
- case ?BACKEND:verify_client_scope(Identity, Scope) of
- {ok, Scope2} ->
- %% NOTE: The OAuth2 draft dictates that no refresh token be issued here.
- TTL = oauth2_config:expiry_time(client_credentials),
- Response = issue_token(Identity, <<>>, Scope2, TTL),
- {ok, Identity, Response};
- {error, _Reason} ->
- {error, invalid_scope}
+%% @doc Issue a Code via Access Code Grant
+-spec authorize_code_request( ClientId, RedirectionUri
+ , Username, Password, Scope )
+ -> {ok, Authorization} | {error, Reason} when
+ ClientId :: binary(),
+ RedirectionUri :: scope(),
+ Username :: binary(),
+ Password :: binary(),
+ Scope :: scope(),
+ Authorization :: #authorization{},
+ Reason :: error().
+authorize_code_request(ClientId, RedirectionUri, Username, Password, Scope) ->
+ case ?BACKEND:get_client_identity(ClientId) of
+ {ok, Client} ->
+ case ?BACKEND:verify_redirection_uri(Client, RedirectionUri) of
+ ok ->
+ case ?BACKEND:verify_client_scope(Client, Scope) of
+ {ok, VerifiedScope} ->
+ case ?BACKEND:authenticate_username_password(
+ Username, Password) of
+ {ok, ResOwner} ->
+ TTL = oauth2_config:expiry_time(code_grant),
+ {ok, #authorization{client = Client,
+ resowner = ResOwner,
+ scope = VerifiedScope,
+ ttl = TTL}};
+ {error, _Reason} ->
+ {error, access_denied}
+ end;
+ {error, _Reason} ->
+ {error, invalid_scope}
+ end;
+ _ ->
+ {error, unauthorized_client}
end;
{error, _Reason} ->
- {error, invalid_client}
+ {error, unauthorized_client}
end.
+-spec issue_code(Authorization) -> Response when
+ Authorization :: #authorization{},
+ Response :: oauth2_response:response().
+issue_code(#authorization{client = Client, resowner = ResOwner,
+ scope = Scope, ttl = TTL}) ->
+ ExpiryAbsolute = seconds_since_epoch(TTL),
+ Context = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessCode = ?TOKEN:generate(Context),
+ ok = ?BACKEND:associate_access_code(AccessCode, Context),
+ oauth2_response:new(<<>>, TTL, ResOwner, Scope, <<>>, AccessCode).
+
+-spec issue_token(Authorization) -> Response when
+ Authorization :: #authorization{},
+ Response :: oauth2_response:response().
+issue_token(#authorization{client = Client, resowner = ResOwner,
+ scope = Scope, ttl = TTL}) ->
+ ExpiryAbsolute = seconds_since_epoch(TTL),
+ Context = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(Context),
+ ok = ?BACKEND:associate_access_token(AccessToken, Context),
+ oauth2_response:new(AccessToken, TTL, ResOwner, Scope).
+
+%% @doc Issue an Access Token and a Refresh Token.
+%% The OAuth2 specification forbids or discourages issuing a refresh token
+%% when no resource owner is authenticated (See 4.2.2 and 4.4.3)
+-spec issue_token_and_refresh(Authorization) -> Response when
+ Authorization :: #authorization{resowner :: term()},
+ Response :: oauth2_response:response().
+issue_token_and_refresh(#authorization{client = Client, resowner = ResOwner,
+ scope = Scope, ttl = TTL})
+ when ResOwner /= undefined ->
+ ExpiryAbsolute = seconds_since_epoch(TTL),
+ Context = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(Context),
+ RefreshToken = ?TOKEN:generate(Context),
+ ok = ?BACKEND:associate_access_token(AccessToken, Context),
+ ok = ?BACKEND:associate_refresh_token(RefreshToken, Context),
+ oauth2_response:new(AccessToken, TTL, ResOwner, Scope, RefreshToken).
+
%% @doc Verifies an access code AccessCode, returning its associated
%% context if successful. Otherwise, an OAuth2 error code is returned.
-%% @end
-spec verify_access_code(AccessCode) -> {ok, Context} | {error, Reason} when
AccessCode :: token(),
Context :: context(),
@@ -224,17 +267,17 @@ verify_access_code(AccessCode) ->
%% @doc Verifies an access code AccessCode and it's corresponding Identity,
%% returning its associated context if successful. Otherwise, an OAuth2
%% error code is returned.
-%% @end
--spec verify_access_code(AccessCode, Identity) -> {ok, Context} | {error, Reason} when
+-spec verify_access_code(AccessCode, Client) -> {ok, Context}
+ | {error, Reason} when
AccessCode :: token(),
- Identity :: term(),
+ Client :: term(),
Context :: context(),
Reason :: error().
-verify_access_code(AccessCode, Identity) ->
+verify_access_code(AccessCode, Client) ->
case verify_access_code(AccessCode) of
{ok, Context} ->
- case lists:keyfind(<<"identity">>, 1, Context) of
- {_, Identity} -> {ok, Context};
+ case lists:keyfind(<<"client">>, 1, Context) of
+ {_, Client} -> {ok, Context};
_ -> {error, invalid_grant}
end;
Error -> Error
@@ -242,14 +285,13 @@ verify_access_code(AccessCode, Identity) ->
%% @doc Verifies an refresh token RefreshToken, returning a new Access Token
%% if successful. Otherwise, an OAuth2 error code is returned.
-%% @end
-spec refresh_access_token(ClientId, ClientSecret, RefreshToken)
- -> {ok, Identity, Response}
+ -> {ok, Client, Response}
| {error, Reason} when
ClientId :: binary(),
ClientSecret :: binary(),
RefreshToken :: token(),
- Identity :: term(),
+ Client :: term(),
Response :: oauth2_response:response(),
Reason :: error().
refresh_access_token(ClientId, ClientSecret, RefreshToken) ->
@@ -258,14 +300,18 @@ refresh_access_token(ClientId, ClientSecret, RefreshToken) ->
{_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1, Context),
case ExpiryAbsolute > seconds_since_epoch(0) of
true ->
- {_, Identity} = lists:keyfind(<<"identity">>, 1, Context),
+ {_, Client} = lists:keyfind(<<"client">>, 1, Context),
case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
- {ok, Identity} ->
+ {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(Identity, ResOwner, Scope, TTL),
- {ok, Identity, Response};
+ Response = issue_token(
+ #authorization{client = Client,
+ resowner = ResOwner,
+ scope = Scope,
+ ttl = TTL}),
+ {ok, Client, Response};
_ -> {error, access_denied}
end;
false ->
@@ -278,7 +324,6 @@ refresh_access_token(ClientId, ClientSecret, RefreshToken) ->
%% @doc Verifies an access token AccessToken, returning its associated
%% context if successful. Otherwise, an OAuth2 error code is returned.
-%% @end
-spec verify_access_token(AccessToken) -> {ok, Context} | {error, Reason} when
AccessToken :: token(),
Context :: context(),
@@ -302,55 +347,14 @@ verify_access_token(AccessToken) ->
%%% Internal functions
%%%===================================================================
--spec issue_code(Identity, Scope, ResOwner, TTL) -> Response when
- Identity :: term(),
- Scope :: scope(),
- ResOwner :: term(),
- TTL :: non_neg_integer(),
- Response :: oauth2_response:response().
-issue_code(Identity, Scope, ResOwner, TTL) ->
- ExpiryAbsolute = seconds_since_epoch(TTL),
- Context = build_context(Identity, ExpiryAbsolute, ResOwner, Scope),
- AccessCode = ?TOKEN:generate(Context),
- ok = ?BACKEND:associate_access_code(AccessCode, Context),
- oauth2_response:new(<<>>, TTL, ResOwner, Scope, <<>>, AccessCode).
-
--spec issue_token_and_refresh(Identity, ResOwner, Scope, TTL) -> Response when
- Identity :: term(),
- ResOwner :: term(),
- Scope :: scope(),
- TTL :: non_neg_integer(),
- Response :: oauth2_response:response().
-issue_token_and_refresh(Identity, ResOwner, Scope, TTL) ->
- ExpiryAbsolute = seconds_since_epoch(TTL),
- Context = build_context(Identity, ExpiryAbsolute, ResOwner, Scope),
- AccessToken = ?TOKEN:generate(Context),
- RefreshToken = ?TOKEN:generate(Context),
- ok = ?BACKEND:associate_access_token(AccessToken, Context),
- ok = ?BACKEND:associate_refresh_token(RefreshToken, Context),
- oauth2_response:new(AccessToken, TTL, ResOwner, Scope, RefreshToken).
-
--spec issue_token(Identity, ResOwner, Scope, TTL) -> Response when
- Identity :: term(),
- ResOwner :: term(),
- Scope :: scope(),
- TTL :: non_neg_integer(),
- Response :: oauth2_response:response().
-issue_token(Identity, ResOwner, Scope, TTL) ->
- ExpiryAbsolute = seconds_since_epoch(TTL),
- Context = build_context(Identity, ExpiryAbsolute, ResOwner, Scope),
- AccessToken = ?TOKEN:generate(Context),
- ok = ?BACKEND:associate_access_token(AccessToken, Context),
- oauth2_response:new(AccessToken, TTL, ResOwner, Scope).
-
--spec build_context(Identity, ExpiryTime, ResOwner, Scope) -> Context when
- Identity :: term(),
+-spec build_context(Client, ExpiryTime, ResOwner, Scope) -> Context when
+ Client :: term(),
ExpiryTime :: non_neg_integer(),
ResOwner :: term(),
Scope :: scope(),
Context :: context().
-build_context(Identity, ExpiryTime, ResOwner, Scope) ->
- [{<<"identity">>, Identity},
+build_context(Client, ExpiryTime, ResOwner, Scope) ->
+ [{<<"client">>, Client},
{<<"resource_owner">>, ResOwner},
{<<"expiry_time">>, list_to_binary(integer_to_list(ExpiryTime))},
{<<"scope">>, Scope}].
View
29 test/oauth2_tests.erl
@@ -55,7 +55,7 @@ bad_authorize_password_test_() ->
fun stop/1,
fun(_) ->
[
- ?_assertMatch({ok, _, _},
+ ?_assertMatch({ok, _},
oauth2:authorize_password(
<<"herp">>,
<<"derp">>,
@@ -163,10 +163,11 @@ verify_access_token_test_() ->
fun(_) ->
[
fun() ->
- {ok, _, Response} = oauth2:authorize_client_credentials(
+ {ok, Authorization} = oauth2:authorize_client_credentials(
?CLIENT_ID,
?CLIENT_SECRET,
?CLIENT_SCOPE),
+ Response = oauth2:issue_token(Authorization),
{ok, Token} = oauth2_response:access_token(Response),
?assertMatch({ok, _}, oauth2:verify_access_token(Token))
end,
@@ -182,31 +183,33 @@ bad_access_code_test_() ->
fun(_) ->
[
fun() ->
- {error, unauthorized_client} = oauth2:issue_code_grant(
+ {error, unauthorized_client} =
+ oauth2:authorize_code_request(
?CLIENT_ID,
<<"http://in.val.id">>,
?USER_NAME,
?USER_PASSWORD,
?CLIENT_SCOPE),
- {error, unauthorized_client} = oauth2:issue_code_grant(
+ {error, unauthorized_client} =
+ oauth2:authorize_code_request(
<<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>,
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
?CLIENT_SCOPE),
- {error, invalid_scope} = oauth2:issue_code_grant(
+ {error, invalid_scope} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
<<"bad_scope">>),
- {error, access_denied} = oauth2:issue_code_grant(
+ {error, access_denied} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
<<"herp">>,
<<"herp">>,
?CLIENT_SCOPE),
- {error, access_denied} = oauth2:issue_code_grant(
+ {error, access_denied} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
<<"derp">>,
@@ -225,21 +228,23 @@ verify_access_code_test_() ->
fun(_) ->
[
fun() ->
- {ok, _, Response} = oauth2:issue_code_grant(
+ {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),
?assertMatch({ok, {user, 31337}},
oauth2_response:resource_owner(Response)),
?assertMatch({ok, _}, oauth2:verify_access_code(Code)),
- {ok, _, Response2} = oauth2:authorize_code_grant(
+ {ok, Authorization2} = oauth2:authorize_code_grant(
?CLIENT_ID,
?CLIENT_SECRET,
Code,
?CLIENT_URI),
+ Response2 = oauth2:issue_token_and_refresh(Authorization2),
{ok, Token} = oauth2_response:access_token(Response2),
?assertMatch({ok, _}, oauth2:verify_access_token(Token))
end
@@ -253,18 +258,20 @@ verify_refresh_token_test_() ->
fun(_) ->
[
fun() ->
- {ok, _, Response} = oauth2:issue_code_grant(
+ {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, _, Response2} = oauth2:authorize_code_grant(
+ {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),
{ok, _, _Response3} = oauth2:refresh_access_token(?CLIENT_ID,
?CLIENT_SECRET,
Please sign in to comment.
Something went wrong with that request. Please try again.