Browse files

Merge branch 'IvanMartinez-context'

  • Loading branch information...
2 parents b5c0dfd + c15256d commit 1f1fef240e62ddecfe954c98390f1e518a89c15e @bipthelin bipthelin committed Aug 19, 2013
Showing with 391 additions and 291 deletions.
  1. +146 −116 src/oauth2.erl
  2. +100 −75 src/oauth2_backend.erl
  3. +49 −49 test/oauth2_mock_backend.erl
  4. +96 −51 test/oauth2_tests.erl
View
262 src/oauth2.erl
@@ -27,17 +27,17 @@
-module(oauth2).
%%% API
--export([authorize_password/3]).
--export([authorize_client_credentials/3]).
--export([authorize_code_grant/4]).
--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([authorize_password/4]).
+-export([authorize_client_credentials/4]).
+-export([authorize_code_grant/5]).
+-export([authorize_code_request/6]).
+-export([issue_code/2]).
+-export([issue_token/2]).
+-export([issue_token_and_refresh/2]).
+-export([verify_access_token/2]).
-export([verify_access_code/2]).
--export([refresh_access_token/4]).
+-export([verify_access_code/3]).
+-export([refresh_access_token/5]).
%%% Exported types
-type context() :: proplists:proplist(binary(), term()).
@@ -73,18 +73,21 @@
%%% API functions
%%%===================================================================
-%% @doc Authorizes a client via Resource Owner Password Credentials.
--spec authorize_password(Username, Password, Scope)
+%% @doc Authorizes a resource owner's credentials. Useful for
+%% Resource Owner Password Credentials Grant and Implicit Grant.
+-spec authorize_password(Username, Password, Scope, AppContext)
-> {ok, Authorization} | {error, Reason} when
Username :: binary(),
Password :: binary(),
Scope :: scope(),
+ AppContext :: term(),
Authorization :: #authorization{},
Reason :: error().
-authorize_password(Username, Password, Scope) ->
- case ?BACKEND:authenticate_username_password(Username, Password) of
+authorize_password(Username, Password, Scope, AppContext) ->
+ case ?BACKEND:authenticate_username_password(Username, Password,
+ AppContext) of
{ok, ResOwner} ->
- case ?BACKEND:verify_resowner_scope(ResOwner, Scope) of
+ case ?BACKEND:verify_resowner_scope(ResOwner, Scope, AppContext) of
{ok, Scope2} ->
TTL = oauth2_config:expiry_time(password_credentials),
{ok, #authorization{resowner = ResOwner, scope = Scope2,
@@ -100,18 +103,19 @@ authorize_password(Username, Password, Scope) ->
%% 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)
+-spec authorize_client_credentials(ClientId, ClientSecret, Scope, AppContext)
-> {ok, Authorization}
| {error, Reason} when
ClientId :: binary(),
ClientSecret :: binary(),
Scope :: scope(),
+ AppContext :: term(),
Authorization :: #authorization{},
Reason :: error().
-authorize_client_credentials(ClientId, ClientSecret, Scope) ->
- case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
+authorize_client_credentials(ClientId, ClientSecret, Scope, AppContext) ->
+ case ?BACKEND:authenticate_client(ClientId, ClientSecret, AppContext) of
{ok, Client} ->
- case ?BACKEND:verify_client_scope(Client, Scope) of
+ case ?BACKEND:verify_client_scope(Client, Scope, AppContext) of
{ok, Scope2} ->
TTL = oauth2_config:expiry_time(client_credentials),
{ok, #authorization{client = Client, scope = Scope2,
@@ -130,27 +134,33 @@ authorize_client_credentials(ClientId, ClientSecret, Scope) ->
%%
%% Then verify the supplied RedirectionUri and Code and if valid issue
%% an Access Token and an optional Refresh Token
--spec authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri)
+-spec authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri,
+ AppContext)
-> {ok, Authorization}
| {error, Reason} when
- ClientId :: binary(),
- ClientSecret :: binary(),
- AccessCode :: token(),
- RedirectionUri :: binary(),
- Authorization :: #authorization{},
- Reason :: error().
-authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri) ->
- case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
+ ClientId :: binary(),
+ ClientSecret :: binary(),
+ AccessCode :: token(),
+ RedirectionUri :: binary(),
+ AppContext :: term(),
+ Authorization :: #authorization{},
+ Reason :: error().
+authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri,
+ AppContext) ->
+ case ?BACKEND:authenticate_client(ClientId, ClientSecret, AppContext) of
{ok, Client} ->
- case ?BACKEND:verify_redirection_uri(Client, RedirectionUri) of
+ case ?BACKEND:verify_redirection_uri(Client, RedirectionUri,
+ AppContext) of
ok ->
case verify_access_code(AccessCode, Client) of
- {ok, Context} ->
- TTL = oauth2_config:expiry_time(password_credentials),
- {_, Scope} = lists:keyfind(<<"scope">>, 1, Context),
+ {ok, GrantContext} ->
+ TTL = oauth2_config:expiry_time(
+ password_credentials),
+ {_, Scope} = lists:keyfind(<<"scope">>, 1,
+ GrantContext),
{_, ResOwner} = lists:keyfind(<<"resource_owner">>,
- 1, Context),
- ?BACKEND:revoke_access_code(AccessCode),
+ 1, GrantContext),
+ ?BACKEND:revoke_access_code(AccessCode, AppContext),
{ok, #authorization{client = Client,
resowner = ResOwner,
scope = Scope,
@@ -166,25 +176,29 @@ authorize_code_grant(ClientId, ClientSecret, AccessCode, RedirectionUri) ->
end.
%% @doc Issue a Code via Access Code Grant
--spec authorize_code_request( ClientId, RedirectionUri
- , Username, Password, Scope )
+-spec authorize_code_request(ClientId, RedirectionUri, Username, Password,
+ Scope, AppContext)
-> {ok, Authorization} | {error, Reason} when
ClientId :: binary(),
RedirectionUri :: scope(),
Username :: binary(),
Password :: binary(),
Scope :: scope(),
+ AppContext :: term(),
Authorization :: #authorization{},
Reason :: error().
-authorize_code_request(ClientId, RedirectionUri, Username, Password, Scope) ->
- case ?BACKEND:get_client_identity(ClientId) of
+authorize_code_request(ClientId, RedirectionUri, Username, Password, Scope,
+ AppContext) ->
+ case ?BACKEND:get_client_identity(ClientId, AppContext) of
{ok, Client} ->
- case ?BACKEND:verify_redirection_uri(Client, RedirectionUri) of
+ case ?BACKEND:verify_redirection_uri(Client, RedirectionUri,
+ AppContext) of
ok ->
- case ?BACKEND:verify_client_scope(Client, Scope) of
+ case ?BACKEND:verify_client_scope(Client, Scope,
+ AppContext) of
{ok, VerifiedScope} ->
case ?BACKEND:authenticate_username_password(
- Username, Password) of
+ Username, Password, AppContext) of
{ok, ResOwner} ->
TTL = oauth2_config:expiry_time(code_grant),
{ok, #authorization{client = Client,
@@ -204,60 +218,69 @@ authorize_code_request(ClientId, RedirectionUri, Username, Password, Scope) ->
{error, unauthorized_client}
end.
--spec issue_code(Authorization) -> Response when
+-spec issue_code(Authorization, AppContext) -> Response when
Authorization :: #authorization{},
- Response :: oauth2_response:response().
+ AppContext :: term(),
+ Response :: oauth2_response:response().
issue_code(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL}) ->
+ scope = Scope, ttl = TTL}, AppContext) ->
ExpiryAbsolute = seconds_since_epoch(TTL),
- Context = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
- AccessCode = ?TOKEN:generate(Context),
- ok = ?BACKEND:associate_access_code(AccessCode, Context),
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessCode = ?TOKEN:generate(GrantContext),
+ ok = ?BACKEND:associate_access_code(AccessCode, GrantContext, AppContext),
oauth2_response:new(<<>>, TTL, ResOwner, Scope, <<>>, AccessCode).
--spec issue_token(Authorization) -> Response when
+-spec issue_token(Authorization, AppContext) -> Response when
Authorization :: #authorization{},
- Response :: oauth2_response:response().
+ AppContext :: term(),
+ Response :: oauth2_response:response().
issue_token(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL}) ->
+ scope = Scope, ttl = TTL}, AppContext) ->
ExpiryAbsolute = seconds_since_epoch(TTL),
- Context = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
- AccessToken = ?TOKEN:generate(Context),
- ok = ?BACKEND:associate_access_token(AccessToken, Context),
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(GrantContext),
+ ok = ?BACKEND:associate_access_token(AccessToken, GrantContext,
+ AppContext),
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
+-spec issue_token_and_refresh(Authorization, AppContext) -> Response when
Authorization :: #authorization{resowner :: term()},
- Response :: oauth2_response:response().
+ AppContext :: term(),
+ Response :: oauth2_response:response().
issue_token_and_refresh(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL})
+ scope = Scope, ttl = TTL}, AppContext)
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),
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(GrantContext),
+ RefreshToken = ?TOKEN:generate(GrantContext),
+ ok = ?BACKEND:associate_access_token(AccessToken, GrantContext,
+ AppContext),
+ ok = ?BACKEND:associate_refresh_token(RefreshToken, GrantContext,
+ AppContext),
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.
--spec verify_access_code(AccessCode) -> {ok, Context} | {error, Reason} when
- AccessCode :: token(),
- Context :: context(),
- Reason :: error().
-verify_access_code(AccessCode) ->
- case ?BACKEND:resolve_access_code(AccessCode) of
- {ok, Context} ->
- {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1, Context),
+-spec verify_access_code(AccessCode, AppContext) ->
+ {ok, GrantContext} | {error, Reason} when
+ AccessCode :: token(),
+ AppContext :: term(),
+ GrantContext :: context(),
+ Reason :: error().
+verify_access_code(AccessCode, AppContext) ->
+ case ?BACKEND:resolve_access_code(AccessCode, AppContext) of
+ {ok, GrantContext} ->
+ {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1,
+ GrantContext),
case ExpiryAbsolute > seconds_since_epoch(0) of
true ->
- {ok, Context};
+ {ok, GrantContext};
false ->
- ?BACKEND:revoke_access_code(AccessCode),
+ ?BACKEND:revoke_access_code(AccessCode, AppContext),
{error, invalid_grant}
end;
_ ->
@@ -267,67 +290,71 @@ 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.
--spec verify_access_code(AccessCode, Client) -> {ok, Context}
+-spec verify_access_code(AccessCode, Client, AppContext) -> {ok, GrantContext}
| {error, Reason} when
- AccessCode :: token(),
- Client :: term(),
- Context :: context(),
- Reason :: error().
-verify_access_code(AccessCode, Client) ->
- case verify_access_code(AccessCode) of
- {ok, Context} ->
- case lists:keyfind(<<"client">>, 1, Context) of
- {_, Client} -> {ok, Context};
+ AccessCode :: token(),
+ Client :: term(),
+ AppContext :: term(),
+ GrantContext :: context(),
+ Reason :: error().
+verify_access_code(AccessCode, Client, AppContext) ->
+ case verify_access_code(AccessCode, AppContext) of
+ {ok, GrantContext} ->
+ case lists:keyfind(<<"client">>, 1, GrantContext) of
+ {_, Client} -> {ok, GrantContext};
_ -> {error, invalid_grant}
end;
Error -> Error
end.
%% @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, Scope)
+-spec refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope,
+ AppContext)
-> {ok, Client, Response}
| {error, Reason} when
- ClientId :: binary(),
- ClientSecret :: binary(),
- RefreshToken :: token(),
- Scope :: scope(),
- Client :: term(),
- Response :: oauth2_response:response(),
- Reason :: error().
-refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope) ->
- case ?BACKEND:authenticate_client(ClientId, ClientSecret) of
+ ClientId :: binary(),
+ ClientSecret :: binary(),
+ RefreshToken :: token(),
+ Scope :: scope(),
+ AppContext :: term(),
+ Client :: term(),
+ Response :: oauth2_response:response(),
+ Reason :: error().
+refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope, AppContext) ->
+ case ?BACKEND:authenticate_client(ClientId, ClientSecret, AppContext) of
{ok, Client} ->
- case ?BACKEND:resolve_refresh_token(RefreshToken) of
- {ok, Context} ->
+ case ?BACKEND:resolve_refresh_token(RefreshToken, AppContext) of
+ {ok, GrantContext} ->
{_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1,
- Context),
+ GrantContext),
case ExpiryAbsolute > seconds_since_epoch(0) of
true ->
{_, Client} = lists:keyfind(<<"client">>, 1,
- Context),
+ GrantContext),
{_, RegisteredScope} = lists:keyfind(<<"scope">>, 1,
- Context),
+ GrantContext),
case ?BACKEND:verify_scope(RegisteredScope,
- Scope) of
+ Scope, AppContext) of
{ok, VerifiedScope} ->
{_, ResOwner} = lists:keyfind(
<<"resource_owner">>, 1,
- Context),
+ GrantContext),
TTL = oauth2_config:expiry_time(
password_credentials),
Response = issue_token(
#authorization{
client = Client,
resowner = ResOwner,
scope = VerifiedScope,
- ttl = TTL}),
+ ttl = TTL}, AppContext),
{ok, Client, Response};
{error, _Reason} ->
{error, invalid_scope}
end;
false ->
- ?BACKEND:revoke_refresh_token(RefreshToken),
+ ?BACKEND:revoke_refresh_token(RefreshToken,
+ AppContext),
{error, invalid_grant}
end;
_ ->
@@ -338,19 +365,22 @@ refresh_access_token(ClientId, ClientSecret, RefreshToken, Scope) ->
%% @doc Verifies an access token AccessToken, returning its associated
%% context if successful. Otherwise, an OAuth2 error code is returned.
--spec verify_access_token(AccessToken) -> {ok, Context} | {error, Reason} when
- AccessToken :: token(),
- Context :: context(),
- Reason :: error().
-verify_access_token(AccessToken) ->
- case ?BACKEND:resolve_access_token(AccessToken) of
- {ok, Context} ->
- {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1, Context),
+-spec verify_access_token(AccessToken, AppContext) ->
+ {ok, GrantContext} | {error, Reason} when
+ AccessToken :: token(),
+ AppContext :: term(),
+ GrantContext :: context(),
+ Reason :: error().
+verify_access_token(AccessToken, AppContext) ->
+ case ?BACKEND:resolve_access_token(AccessToken, AppContext) of
+ {ok, GrantContext} ->
+ {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1,
+ GrantContext),
case ExpiryAbsolute > seconds_since_epoch(0) of
true ->
- {ok, Context};
+ {ok, GrantContext};
false ->
- ?BACKEND:revoke_access_token(AccessToken),
+ ?BACKEND:revoke_access_token(AccessToken, AppContext),
{error, access_denied}
end;
_ ->
@@ -361,12 +391,12 @@ verify_access_token(AccessToken) ->
%%% Internal functions
%%%===================================================================
--spec build_context(Client, ExpiryTime, ResOwner, Scope) -> Context when
- Client :: term(),
- ExpiryTime :: non_neg_integer(),
- ResOwner :: term(),
- Scope :: scope(),
- Context :: context().
+-spec build_context(Client, ExpiryTime, ResOwner, Scope) -> GrantContext when
+ Client :: term(),
+ ExpiryTime :: non_neg_integer(),
+ ResOwner :: term(),
+ Scope :: scope(),
+ GrantContext :: context().
build_context(Client, ExpiryTime, ResOwner, Scope) ->
[{<<"client">>, Client},
{<<"resource_owner">>, ResOwner},
View
175 src/oauth2_backend.erl
@@ -33,137 +33,162 @@
%% @doc Authenticates a combination of username and password.
%% Returns the resource owner identity if the credentials are valid.
%% @end
--callback authenticate_username_password(Username, Password) ->
+-callback authenticate_username_password(Username, Password, AppContext) ->
{ok, Identity} | {error, Reason} when
- Username :: binary(),
- Password :: binary(),
- Identity :: term(),
- Reason :: notfound | badpass.
+ Username :: binary(),
+ Password :: binary(),
+ Identity :: term(),
+ AppContext :: term(),
+ Reason :: notfound | badpass.
%% @doc Authenticates a client's credentials for a given scope.
--callback authenticate_client(ClientId, ClientSecret) ->
+-callback authenticate_client(ClientId, ClientSecret, AppContext) ->
{ok, Identity} | {error, Reason} when
- ClientId :: binary(),
- ClientSecret :: binary(),
- Identity :: term(),
- Reason :: notfound | badsecret.
+ ClientId :: binary(),
+ ClientSecret :: binary(),
+ Identity :: term(),
+ AppContext :: term(),
+ Reason :: notfound | badsecret.
%% @doc Stores a new access code AccessCode, associating it with Context.
%% The context is a proplist carrying information about the identity
%% with which the code is associated, when it expires, etc.
%% @end
--callback associate_access_code(AccessCode, Context) ->
+-callback associate_access_code(AccessCode, GrantContext, AppContext) ->
ok | {error, Reason} when
- AccessCode :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
+ AccessCode :: oauth2:token(),
+ GrantContext :: oauth2:context(),
+ AppContext :: term(),
+ Reason :: notfound.
%% @doc Stores a new access token AccessToken, associating it with Context.
%% The context is a proplist carrying information about the identity
%% with which the token is associated, when it expires, etc.
%% @end
--callback associate_access_token(AccessToken, Context) ->
+-callback associate_access_token(AccessToken, GrantContext, AppContext) ->
ok | {error, Reason} when
- AccessToken :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
-
-%% @doc Stores a new refresh token RefreshToken, associating it with Context.
-%% The context is a proplist carrying information about the identity
-%% with which the token is associated, when it expires, etc.
+ AccessToken :: oauth2:token(),
+ GrantContext :: oauth2:context(),
+ AppContext :: term(),
+ Reason :: notfound.
+
+%% @doc Stores a new refresh token RefreshToken, associating it with
+%% GrantContext. The context is a proplist carrying information about the
+%% identity with which the token is associated, when it expires, etc.
%% @end
--callback associate_refresh_token(RefreshToken, Context) ->
+-callback associate_refresh_token(RefreshToken, GrantContext, AppContext) ->
ok | {error, Reason} when
- RefreshToken :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
+ RefreshToken :: oauth2:token(),
+ GrantContext :: oauth2:context(),
+ AppContext :: term(),
+ Reason :: notfound.
%% @doc Looks up an access token AccessToken, returning the corresponding
%% context if a match is found.
%% @end
--callback resolve_access_token(AccessToken) ->
- {ok, Context} | {error, Reason} when
- AccessToken :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
+-callback resolve_access_token(AccessToken, AppContext) ->
+ {ok, GrantContext} | {error, Reason} when
+ AccessToken :: oauth2:token(),
+ AppContext :: term(),
+ GrantContext :: oauth2:context(),
+ Reason :: notfound.
%% @doc Looks up an access code AccessCode, returning the corresponding
%% context if a match is found.
%% @end
--callback resolve_access_code(AccessCode) ->
- {ok, Context} | {error, Reason} when
- AccessCode :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
+-callback resolve_access_code(AccessCode, AppContext) ->
+ {ok, GrantContext} | {error, Reason} when
+ AccessCode :: oauth2:token(),
+ AppContext :: term(),
+ GrantContext :: oauth2:context(),
+ Reason :: notfound.
%% @doc Looks up an refresh token RefreshToken, returning the corresponding
%% context if a match is found.
%% @end
--callback resolve_refresh_token(RefreshToken) ->
- {ok, Context} | {error, Reason} when
- RefreshToken :: oauth2:token(),
- Context :: oauth2:context(),
- Reason :: notfound.
+-callback resolve_refresh_token(RefreshToken, AppContext) ->
+ {ok, GrantContext} | {error, Reason} when
+ RefreshToken :: oauth2:token(),
+ AppContext :: term(),
+ GrantContext :: oauth2:context(),
+ Reason :: notfound.
%% @doc Revokes an access token AccessToken, so that it cannot be used again.
--callback revoke_access_token(AccessToken) -> ok | {error, Reason} when
- AccessToken :: oauth2:token(),
- Reason :: notfound.
+-callback revoke_access_token(AccessToken, AppContext) ->
+ ok | {error, Reason} when
+ AccessToken :: oauth2:token(),
+ AppContext :: term(),
+ Reason :: notfound.
%% @doc Revokes an access code AccessCode, so that it cannot be used again.
--callback revoke_access_code(AccessCode) -> ok | {error, Reason} when
+-callback revoke_access_code(AccessCode, AppContext) ->
+ ok | {error, Reason} when
AccessCode :: oauth2:token(),
+ AppContext :: term(),
Reason :: notfound.
%% @doc Revokes an refresh token RefreshToken, so that it cannot be used again.
--callback revoke_refresh_token(RefreshToken) -> ok | {error, Reason} when
- RefreshToken :: oauth2:token(),
- Reason :: notfound.
+-callback revoke_refresh_token(RefreshToken, AppContext) ->
+ ok | {error, Reason} when
+ RefreshToken :: oauth2:token(),
+ AppContext :: term(),
+ Reason :: notfound.
%% @doc Returns the redirection URI associated with the client ClientId.
--callback get_redirection_uri(ClientId) -> {error, Reason} | {ok, RedirectionUri} when
- ClientId :: binary(),
- Reason :: notfound,
- RedirectionUri :: binary().
+-callback get_redirection_uri(ClientId, AppContext) ->
+ {error, Reason} | {ok, RedirectionUri} when
+ ClientId :: binary(),
+ AppContext :: term(),
+ Reason :: notfound,
+ RedirectionUri :: binary().
%% @doc Returns a client identity for a given id.
--callback get_client_identity(ClientId) ->
+-callback get_client_identity(ClientId, AppContext) ->
{ok, Identity} | {error, Reason} when
- ClientId :: binary(),
- Identity :: term(),
- Reason :: notfound | badsecret.
+ ClientId :: binary(),
+ AppContext :: term(),
+ Identity :: term(),
+ Reason :: notfound | badsecret.
%% @doc Verifies that RedirectionUri is a valid redirection URI for the
%% client identified by Identity.
%% @end
--callback verify_redirection_uri(Identity, RedirectionUri) -> ok | {error, Reason} when
- Identity :: term(),
- RedirectionUri :: binary(),
- Reason :: notfound | baduri.
+-callback verify_redirection_uri(Identity, RedirectionUri, AppContext) ->
+ ok | {error, Reason} when
+ Identity :: term(),
+ RedirectionUri :: binary(),
+ AppContext :: term(),
+ Reason :: notfound | baduri.
%% @doc Verifies that Scope is a valid scope for the client identified
%% by Identity.
%% @end
--callback verify_client_scope(Identity, Scope) -> {ok, Scope2} | {error, Reason} when
- Identity :: term(),
- Scope :: oauth2:scope(),
- Scope2 :: oauth2:scope(),
- Reason :: notfound | badscope.
+-callback verify_client_scope(Identity, Scope, AppContext) ->
+ {ok, Scope2} | {error, Reason} when
+ Identity :: term(),
+ Scope :: oauth2:scope(),
+ AppContext :: term(),
+ Scope2 :: oauth2:scope(),
+ Reason :: notfound | badscope.
%% @doc Verifies that Scope is a valid scope for the resource
%% owner identified by Identity.
%% @end
--callback verify_resowner_scope(Identity, Scope) -> {ok, Scope2} | {error, Reason} when
- Identity :: term(),
- Scope :: oauth2:scope(),
- Scope2 :: oauth2:scope(),
- Reason :: notfound | badscope.
+-callback verify_resowner_scope(Identity, Scope, AppContext) ->
+ {ok, Scope2} | {error, Reason} when
+ Identity :: term(),
+ Scope :: oauth2:scope(),
+ AppContext :: term(),
+ 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.
+-callback verify_scope(ValidScopes, Scope, AppContext) ->
+ {ok, Scope2} | {error, Reason} when
+ ValidScopes :: oauth2:scope(),
+ Scope :: oauth2:scope(),
+ AppContext :: term(),
+ Scope2 :: oauth2:scope(),
+ Reason :: notfound | badscope.
View
98 test/oauth2_mock_backend.erl
@@ -29,23 +29,23 @@
-behavior(oauth2_backend).
%%% Behavior API
--export([authenticate_username_password/2]).
--export([authenticate_client/2]).
--export([get_client_identity/1]).
--export([associate_access_code/2]).
--export([associate_refresh_token/2]).
--export([associate_access_token/2]).
--export([resolve_access_code/1]).
--export([resolve_refresh_token/1]).
--export([resolve_access_token/1]).
--export([revoke_access_code/1]).
--export([revoke_access_token/1]).
--export([revoke_refresh_token/1]).
--export([get_redirection_uri/1]).
--export([verify_redirection_uri/2]).
--export([verify_client_scope/2]).
--export([verify_resowner_scope/2]).
--export([verify_scope/2]).
+-export([authenticate_username_password/3]).
+-export([authenticate_client/3]).
+-export([get_client_identity/2]).
+-export([associate_access_code/3]).
+-export([associate_refresh_token/3]).
+-export([associate_access_token/3]).
+-export([resolve_access_code/2]).
+-export([resolve_refresh_token/2]).
+-export([resolve_access_token/2]).
+-export([revoke_access_code/2]).
+-export([revoke_access_token/2]).
+-export([revoke_refresh_token/2]).
+-export([get_redirection_uri/2]).
+-export([verify_redirection_uri/3]).
+-export([verify_client_scope/3]).
+-export([verify_resowner_scope/3]).
+-export([verify_scope/3]).
%%% mock_backend-specifics
-export([start/0]).
@@ -68,85 +68,85 @@
%%% API
%%%===================================================================
-authenticate_username_password(?USER_NAME, ?USER_PASSWORD) ->
+authenticate_username_password(?USER_NAME, ?USER_PASSWORD, _) ->
{ok, {user, 31337}};
-authenticate_username_password(?USER_NAME, _) ->
+authenticate_username_password(?USER_NAME, _, _) ->
{error, badpass};
-authenticate_username_password(_, _) ->
+authenticate_username_password(_, _, _) ->
{error, notfound}.
-authenticate_client(?CLIENT_ID, ?CLIENT_SECRET) ->
+authenticate_client(?CLIENT_ID, ?CLIENT_SECRET, _) ->
{ok, {client, 4711}};
-authenticate_client(?CLIENT_ID, _) ->
+authenticate_client(?CLIENT_ID, _, _) ->
{error, badsecret};
-authenticate_client(_, _) ->
+authenticate_client(_, _, _) ->
{error, notfound}.
-get_client_identity(?CLIENT_ID) ->
+get_client_identity(?CLIENT_ID, _) ->
{ok, {client, 4711}};
-get_client_identity(_) ->
+get_client_identity(_, _) ->
{error, notfound}.
-associate_access_code(AccessCode, Context) ->
- associate_access_token(AccessCode, Context).
+associate_access_code(AccessCode, Context, _AppContext) ->
+ associate_access_token(AccessCode, Context, _AppContext).
-associate_refresh_token(RefreshToken, Context) ->
+associate_refresh_token(RefreshToken, Context, _) ->
ets:insert(?ETS_TABLE, {RefreshToken, Context}),
ok.
-associate_access_token(AccessToken, Context) ->
+associate_access_token(AccessToken, Context, _) ->
ets:insert(?ETS_TABLE, {AccessToken, Context}),
ok.
-resolve_access_code(AccessCode) ->
- resolve_access_token(AccessCode).
+resolve_access_code(AccessCode, _AppContext) ->
+ resolve_access_token(AccessCode, _AppContext).
-resolve_refresh_token(RefreshToken) ->
- resolve_access_token(RefreshToken).
+resolve_refresh_token(RefreshToken, _AppContext) ->
+ resolve_access_token(RefreshToken, _AppContext).
-resolve_access_token(AccessToken) ->
+resolve_access_token(AccessToken, _) ->
case ets:lookup(?ETS_TABLE, AccessToken) of
[] ->
{error, notfound};
[{_, Context}] ->
{ok, Context}
end.
-revoke_access_code(AccessCode) ->
- revoke_access_token(AccessCode).
+revoke_access_code(AccessCode, _AppContext) ->
+ revoke_access_token(AccessCode, _AppContext).
-revoke_access_token(AccessToken) ->
+revoke_access_token(AccessToken, _) ->
ets:delete(?ETS_TABLE, AccessToken),
ok.
-revoke_refresh_token(_RefreshToken) ->
+revoke_refresh_token(_RefreshToken, _) ->
ok.
-get_redirection_uri(?CLIENT_ID) ->
+get_redirection_uri(?CLIENT_ID, _) ->
{ok, ?CLIENT_URI};
-get_redirection_uri(_) ->
+get_redirection_uri(_, _) ->
{error, notfound}.
-verify_redirection_uri({client, 4711}, ?CLIENT_URI) ->
+verify_redirection_uri({client, 4711}, ?CLIENT_URI, _) ->
ok;
-verify_redirection_uri(_, _) ->
+verify_redirection_uri(_, _, _) ->
{error, mismatch}.
-verify_client_scope({client, 4711}, []) ->
+verify_client_scope({client, 4711}, [], _) ->
{ok, []};
-verify_client_scope({client, 4711}, ?CLIENT_SCOPE) ->
+verify_client_scope({client, 4711}, ?CLIENT_SCOPE, _) ->
{ok, ?CLIENT_SCOPE};
-verify_client_scope(_, _) ->
+verify_client_scope(_, _, _) ->
{error, invalid_scope}.
-verify_resowner_scope({user, 31337}, ?USER_SCOPE) ->
+verify_resowner_scope({user, 31337}, ?USER_SCOPE, _) ->
{ok, ?USER_SCOPE};
-verify_resowner_scope(_, _) ->
+verify_resowner_scope(_, _, _) ->
{error, invalid_scope}.
-verify_scope(Scope, Scope) ->
+verify_scope(Scope, Scope, _) ->
{ok, Scope};
-verify_scope(_, _) ->
+verify_scope(_, _, _) ->
{error, invalid_scope}.
start() ->
View
147 test/oauth2_tests.erl
@@ -59,22 +59,26 @@ bad_authorize_password_test_() ->
oauth2:authorize_password(
<<"herp">>,
<<"derp">>,
- [<<"xyz">>])),
+ [<<"xyz">>],
+ foo_context)),
?_assertMatch({error, invalid_scope},
oauth2:authorize_password(
<<"herp">>,
<<"derp">>,
- <<"bad_scope">>)),
+ <<"bad_scope">>,
+ foo_context)),
?_assertMatch({error, access_denied},
oauth2:authorize_password(
<<"herp">>,
<<"herp">>,
- <<"xyz">>)),
+ <<"xyz">>,
+ foo_context)),
?_assertMatch({error, access_denied},
oauth2:authorize_password(
<<"derp">>,
<<"derp">>,
- <<"xyz">>))
+ <<"xyz">>,
+ foo_context))
]
end}.
@@ -88,22 +92,26 @@ bad_authorize_client_credentials_test_() ->
oauth2:authorize_client_credentials(
<<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>,
<<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>,
- <<"abc">>)),
+ <<"abc">>,
+ foo_context)),
?_assertMatch({error, invalid_scope},
oauth2:authorize_client_credentials(
?CLIENT_ID,
?CLIENT_SECRET,
- <<"bad_scope">>)),
+ <<"bad_scope">>,
+ foo_context)),
?_assertMatch({error, invalid_client},
oauth2:authorize_client_credentials(
<<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>,
<<"gggDMAwklAKc9kq5FsLjKrzihCcI123">>,
- <<"abc">>)),
+ <<"abc">>,
+ foo_context)),
?_assertMatch({error, invalid_client},
oauth2:authorize_client_credentials(
<<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>,
<<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>,
- <<"cba">>))
+ <<"cba">>,
+ foo_context))
]
end}.
@@ -113,32 +121,36 @@ bad_ttl_test_() ->
meck:new(oauth2_mock_backend),
meck:expect(oauth2_mock_backend,
authenticate_client,
- fun(_, _) -> {ok, {client, 4711}}
+ fun(_, _, _) -> {ok, {client, 4711}}
end),
meck:expect(oauth2_mock_backend,
resolve_access_code,
- fun(_) -> {ok, [{<<"identity">>, <<"123">>},
+ fun(_, _) -> {ok, [{<<"identity">>, <<"123">>},
{<<"resource_owner">>, <<>>},
{<<"expiry_time">>, 123},
{<<"scope">>, <<>>}]}
end),
- meck:expect(oauth2_mock_backend, revoke_access_code, fun(_) -> ok end),
+ meck:expect(oauth2_mock_backend,
+ revoke_access_code,
+ fun(_, _) -> ok end),
meck:expect(oauth2_mock_backend,
resolve_access_token,
- fun(_) -> {ok, [{<<"identity">>, <<"123">>},
+ fun(_, _) -> {ok, [{<<"identity">>, <<"123">>},
{<<"resource_owner">>, <<>>},
{<<"expiry_time">>, 123},
{<<"scope">>, <<>>}]}
end),
- meck:expect(oauth2_mock_backend, revoke_access_token, fun(_) -> ok end),
+ meck:expect(oauth2_mock_backend,
+ revoke_access_token,
+ fun(_, _) -> ok end),
meck:expect(oauth2_mock_backend,
resolve_refresh_token,
- fun(_) -> {ok, [{<<"identity">>, <<"123">>},
+ fun(_, _) -> {ok, [{<<"identity">>, <<"123">>},
{<<"resource_owner">>, <<>>},
{<<"expiry_time">>, 123},
{<<"scope">>, <<>>}]}
end),
- meck:expect(oauth2_mock_backend, revoke_refresh_token, fun(_) -> ok end),
+ meck:expect(oauth2_mock_backend, revoke_refresh_token, fun(_, _) -> ok end),
ok
end,
fun (_) ->
@@ -148,16 +160,19 @@ bad_ttl_test_() ->
[
?_assertMatch({error, invalid_grant},
oauth2:verify_access_code(
- <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>)),
+ <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>,
+ foo_context)),
?_assertMatch({error, access_denied},
oauth2:verify_access_token(
- <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>)),
+ <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>,
+ foo_context)),
?_assertMatch({error, invalid_grant},
oauth2:refresh_access_token(
?CLIENT_ID,
?CLIENT_SECRET,
<<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">>,
- ?CLIENT_SCOPE))
+ ?CLIENT_SCOPE,
+ foo_context))
]
end}.
@@ -171,13 +186,17 @@ verify_access_token_test_() ->
{ok, Authorization} = oauth2:authorize_client_credentials(
?CLIENT_ID,
?CLIENT_SECRET,
- ?CLIENT_SCOPE),
- Response = oauth2:issue_token(Authorization),
+ ?CLIENT_SCOPE,
+ foo_context),
+ Response = oauth2:issue_token(Authorization,
+ foo_context),
{ok, Token} = oauth2_response:access_token(Response),
- ?assertMatch({ok, _}, oauth2:verify_access_token(Token))
+ ?assertMatch({ok, _}, oauth2:verify_access_token(
+ Token, foo_context))
end,
?_assertMatch({error, access_denied},
- oauth2:verify_access_token(<<"nonexistent_token">>))
+ oauth2:verify_access_token(<<"nonexistent_token">>,
+ foo_context))
]
end}.
@@ -194,32 +213,37 @@ bad_access_code_test_() ->
<<"http://in.val.id">>,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE),
+ ?CLIENT_SCOPE,
+ foo_context),
{error, unauthorized_client} =
oauth2:authorize_code_request(
<<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>,
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE),
+ ?CLIENT_SCOPE,
+ foo_context),
{error, invalid_scope} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- <<"bad_scope">>),
+ <<"bad_scope">>,
+ foo_context),
{error, access_denied} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
<<"herp">>,
<<"herp">>,
- ?CLIENT_SCOPE),
+ ?CLIENT_SCOPE,
+ foo_context),
{error, access_denied} = oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
<<"derp">>,
<<"derp">>,
- ?CLIENT_SCOPE),
+ ?CLIENT_SCOPE,
+ foo_context),
?_assertMatch({error, invalid_grant},
oauth2:verify_access_code(<<"nonexistent_token">>))
end
@@ -238,20 +262,27 @@ verify_access_code_test_() ->
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE),
- Response = oauth2:issue_code(Authorization),
+ ?CLIENT_SCOPE,
+ foo_context),
+ Response = oauth2:issue_code(Authorization,
+ foo_context),
{ok, Code} = oauth2_response:access_code(Response),
?assertMatch({ok, {user, 31337}},
oauth2_response:resource_owner(Response)),
- ?assertMatch({ok, _}, oauth2:verify_access_code(Code)),
+ ?assertMatch({ok, _}, oauth2:verify_access_code(
+ Code, foo_context)),
{ok, Authorization2} = oauth2:authorize_code_grant(
?CLIENT_ID,
?CLIENT_SECRET,
Code,
- ?CLIENT_URI),
- Response2 = oauth2:issue_token_and_refresh(Authorization2),
+ ?CLIENT_URI,
+ foo_context),
+ Response2 = oauth2:issue_token_and_refresh(Authorization2,
+ foo_context),
{ok, Token} = oauth2_response:access_token(Response2),
- ?assertMatch({ok, _}, oauth2:verify_access_token(Token))
+ ?assertMatch({ok, _}, oauth2:verify_access_token(
+ Token,
+ foo_context))
end
]
end}.
@@ -268,42 +299,48 @@ bad_refresh_token_test_() ->
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE),
- Response = oauth2:issue_code(Authorization),
+ ?CLIENT_SCOPE,
+ foo_context),
+ Response = oauth2:issue_code(Authorization, foo_context),
{ok, Code} = oauth2_response:access_code(Response),
{ok, Authorization2} = oauth2:authorize_code_grant(
?CLIENT_ID,
?CLIENT_SECRET,
Code,
- ?CLIENT_URI),
+ ?CLIENT_URI,
+ foo_context),
Response2 = oauth2:issue_token_and_refresh(
- Authorization2),
+ Authorization2, foo_context),
{ok, RefreshToken} = oauth2_response:refresh_token(
Response2),
?assertMatch({error, invalid_client},
oauth2:refresh_access_token(
<<"foo">>,
?CLIENT_SECRET,
RefreshToken,
- ?CLIENT_SCOPE)),
+ ?CLIENT_SCOPE,
+ foo_context)),
?assertMatch({error, invalid_client},
oauth2:refresh_access_token(
?CLIENT_ID,
<<"foo">>,
RefreshToken,
- ?CLIENT_SCOPE)),
+ ?CLIENT_SCOPE,
+ foo_context)),
?assertMatch({error, invalid_grant},
oauth2:refresh_access_token(
?CLIENT_ID,
?CLIENT_SECRET,
<<"foo">>,
- ?CLIENT_SCOPE)),
+ ?CLIENT_SCOPE,
+ foo_context)),
?assertMatch({error, invalid_scope},
oauth2:refresh_access_token(
?CLIENT_ID,
?CLIENT_SECRET,
RefreshToken,
- <<"foo">>))
+ <<"foo">>,
+ foo_context))
end
]
end}.
@@ -320,22 +357,30 @@ verify_refresh_token_test_() ->
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE),
- Response = oauth2:issue_code(Authorization),
+ ?CLIENT_SCOPE,
+ foo_context),
+ Response = oauth2:issue_code(Authorization, foo_context),
{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),
- {ok, _, _Response3} = oauth2:refresh_access_token(?CLIENT_ID,
- ?CLIENT_SECRET,
- RefreshToken,
- ?CLIENT_SCOPE),
+ ?CLIENT_URI,
+ foo_context),
+ Response2 = oauth2:issue_token_and_refresh(
+ Authorization2, foo_context),
+ {ok, RefreshToken} = oauth2_response:refresh_token(
+ Response2),
+ {ok, _, _Response3} = oauth2:refresh_access_token(
+ ?CLIENT_ID,
+ ?CLIENT_SECRET,
+ RefreshToken,
+ ?CLIENT_SCOPE,
+ foo_context),
{ok, Token} = oauth2_response:access_token(Response2),
- ?assertMatch({ok, _}, oauth2:verify_access_token(Token))
+ ?assertMatch({ok, _}, oauth2:verify_access_token(
+ Token,
+ foo_context))
end
]
end}.

0 comments on commit 1f1fef2

Please sign in to comment.