Skip to content
Browse files

Changes to how AppCtx is used

Try to harmonize the use of AppCtx in which all functions require a
context that get's passed to the underlying backend functions. They in
return respond with a updated context that get's handed back to the
caller.
  • Loading branch information...
1 parent 28dea15 commit e8f7c5b3ed45f340bedc4755847ac2631eb58e7c @bipthelin bipthelin committed Dec 6, 2013
View
28 include/oauth2.hrl
@@ -1,28 +0,0 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
-
-%% Length of binary data to use for token generation.
--define(TOKEN_LENGTH, 32).
View
45 src/oauth2.app.src
@@ -1,33 +1,26 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
{application, oauth2,
[
{description, "Erlang OAuth 2.0 implementation"},
- {vsn, "0.4.1"},
+ {vsn, "0.5.0"},
{registered, []},
{applications, [
kernel,
View
578 src/oauth2.erl
@@ -1,32 +1,28 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%_* Module declaration ===============================================
-module(oauth2).
+-compile({no_auto_import, [get/2]}).
-%%% API
+%%%_* Exports ==========================================================
+%%%_ * API -------------------------------------------------------------
-export([authorize_password/4]).
-export([authorize_client_credentials/4]).
-export([authorize_code_grant/5]).
@@ -39,371 +35,315 @@
-export([verify_access_code/3]).
-export([refresh_access_token/5]).
-%%% Exported types
--type context() :: proplists:proplist().
--type token() :: binary().
--type lifetime() :: non_neg_integer().
--type scope() :: list(binary()) | binary().
--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]).
+-export_type([context/0]).
+-export_type([lifetime/0]).
+-export_type([scope/0]).
+-export_type([error/0]).
--export_type([
- token/0
- ,context/0
- ,lifetime/0
- ,scope/0
- ,error/0
- ]).
-
-%%% Defines
+%%%_* Macros ===========================================================
-define(BACKEND, (oauth2_config:backend())).
--define(TOKEN, (oauth2_config:token_generation())).
+-define(TOKEN, (oauth2_config:token_generation())).
-%%% Internal types
+%%%_ * Types -----------------------------------------------------------
-record(authorization, {
- client = undefined :: undefined | term(),
+ client = undefined :: undefined | term(),
resowner = undefined :: undefined | term(),
scope :: scope(),
- ttl = 0 :: non_neg_integer()
+ ttl = 0 :: non_neg_integer()
}).
-%%%===================================================================
-%%% API functions
-%%%===================================================================
+-type context() :: proplists:proplist().
+-type auth() :: #authorization{}.
+-type token() :: binary().
+-type response() :: oauth2_response:response().
+-type lifetime() :: non_neg_integer().
+-type scope() :: list(binary()) | binary().
+-type error() :: access_denied | invalid_client | invalid_grant |
+ invalid_request | invalid_scope | unauthorized_client |
+ unsupported_response_type | server_error |
+ temporarily_unavailable.
+%%%_* Code =============================================================
+%%%_ * API -------------------------------------------------------------
%% @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, AppContext) ->
- case ?BACKEND:authenticate_username_password(Username, Password,
- AppContext) of
- {ok, ResOwner} ->
- case ?BACKEND:verify_resowner_scope(ResOwner, Scope, AppContext) of
- {ok, Scope2} ->
- TTL = oauth2_config:expiry_time(password_credentials),
- {ok, #authorization{resowner = ResOwner, scope = Scope2,
- ttl = TTL}};
- {error, _Reason} ->
- {error, invalid_scope}
- end;
- {error, _Reason} ->
- {error, access_denied}
+%% Resource Owner Password Credentials Grant and Implicit Grant.
+-spec authorize_password(binary(), binary(), scope(), term())
+ -> {ok, auth()} | {error, error()}.
+authorize_password(UId, Pwd, Scope, AppCtx1) ->
+ case ?BACKEND:authenticate_username_password(UId, Pwd, AppCtx1) of
+ {error, _} -> {error, access_denied};
+ {ok, {AppCtx2, ResOwner}} ->
+ case ?BACKEND:verify_resowner_scope(ResOwner, Scope, AppCtx2) of
+ {error, _} -> {error, invalid_scope};
+ {ok, {AppCtx3, Scope2}} ->
+ {ok, { AppCtx3
+ , #authorization{
+ resowner = ResOwner
+ , scope = Scope2
+ , ttl = oauth2_config:expiry_time(
+ password_credentials) } }}
+ end
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.
--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, AppContext) ->
- case ?BACKEND:authenticate_client(ClientId, ClientSecret, AppContext) of
- {ok, Client} ->
- case ?BACKEND:verify_client_scope(Client, Scope, AppContext) 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, invalid_client}
+%% 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(binary(), binary(), scope(), term())
+ -> {ok, auth()} | {error, error()}.
+authorize_client_credentials(CId, CSecret, Scope, AppCtx1) ->
+ case ?BACKEND:authenticate_client(CId, CSecret, AppCtx1) of
+ {error, _} -> {error, invalid_client};
+ {ok, {AppCtx2, Client}} ->
+ case ?BACKEND:verify_client_scope(Client, Scope, AppCtx2) of
+ {error, _} -> {error, invalid_scope};
+ {ok, {AppCtx3, Scope2}} ->
+ {ok, { AppCtx3
+ , #authorization{
+ client = Client
+ , scope = Scope2
+ , ttl = oauth2_config:expiry_time(
+ client_credentials) } }}
+ end
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.
+%% of a public client identifier and a shared client secret.
+%% Should only be used for confidential clients; see the OAuth2 draft
+%% for clarification.
%%
-%% 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,
- AppContext)
- -> {ok, Authorization}
- | {error, Reason} when
- 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,
- AppContext) of
- ok ->
- case verify_access_code(AccessCode, Client) of
- {ok, GrantContext} ->
- TTL = oauth2_config:expiry_time(
- password_credentials),
- {_, Scope} = lists:keyfind(<<"scope">>, 1,
- GrantContext),
- {_, ResOwner} = lists:keyfind(<<"resource_owner">>,
- 1, GrantContext),
- ?BACKEND:revoke_access_code(AccessCode, AppContext),
- {ok, #authorization{client = Client,
- resowner = ResOwner,
- scope = Scope,
- ttl = TTL}};
+%% Then verify the supplied RedirectionUri and Code and if valid issue
+%% an Access Token and an optional Refresh Token
+-spec authorize_code_grant(binary(), binary(), token(), binary(), term())
+ -> {ok, auth()} | {error, error()}.
+authorize_code_grant(CId, CSecret, Code, RedirUri, AppCtx1) ->
+ case ?BACKEND:authenticate_client(CId, CSecret, AppCtx1) of
+ {error, _} -> {error, invalid_client};
+ {ok, {AppCtx2, Client}} ->
+ case ?BACKEND:verify_redirection_uri(Client, RedirUri, AppCtx2) of
+ {ok, AppCtx3} ->
+ case verify_access_code(Code, Client, AppCtx3) of
+ {ok, {AppCtx4, GrantCtx}} ->
+ {ok, AppCtx5} = ?BACKEND:revoke_access_code(
+ Code
+ , AppCtx4),
+ {ok, { AppCtx5
+ , #authorization{
+ client = Client
+ , resowner = get_( GrantCtx
+ , <<"resource_owner">> )
+ , scope = get_(GrantCtx, <<"scope">>)
+ , ttl = oauth2_config:expiry_time(
+ password_credentials) }}};
Error ->
Error
end;
- _ ->
- {error, invalid_grant}
- end;
- {error, _Reason} ->
- {error, invalid_client}
+ _ -> {error, invalid_grant}
+ end
end.
%% @doc Issue a Code via Access Code Grant
--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,
- AppContext) ->
- case ?BACKEND:get_client_identity(ClientId, AppContext) of
- {ok, Client} ->
- case ?BACKEND:verify_redirection_uri(Client, RedirectionUri,
- AppContext) of
- ok ->
- case ?BACKEND:verify_client_scope(Client, Scope,
- AppContext) of
- {ok, VerifiedScope} ->
- case ?BACKEND:authenticate_username_password(
- Username, Password, AppContext) of
- {ok, ResOwner} ->
+-spec authorize_code_request( binary()
+ , binary()
+ , binary()
+ , binary()
+ , scope()
+ , term()) -> {ok, auth()} | {error, error()}.
+authorize_code_request(CId, RedirUri, UId, Pwd, Scope, AppCtx1) ->
+ case ?BACKEND:get_client_identity(CId, AppCtx1) of
+ {error, _} -> {error, unauthorized_client};
+ {ok, {AppCtx2, Client}} ->
+ case ?BACKEND:verify_redirection_uri(Client, RedirUri, AppCtx2) of
+ {ok, AppCtx3} ->
+ case ?BACKEND:authenticate_username_password(
+ UId, Pwd, AppCtx3) of
+ {error, _} -> {error, access_denied};
+ {ok, {AppCtx4, ResOwner}} ->
+ case ?BACKEND:verify_resowner_scope(
+ ResOwner, Scope, AppCtx4) of
+ {error, _} -> {error, invalid_scope};
+ {ok, {AppCtx5, Scope2}} ->
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}
+ {ok, { AppCtx5
+ , #authorization{ client = Client
+ , resowner = ResOwner
+ , scope = Scope2
+ , ttl = TTL
+ } }}
+ end
end;
_ ->
{error, unauthorized_client}
- end;
- {error, _Reason} ->
- {error, unauthorized_client}
+ end
end.
--spec issue_code(Authorization, AppContext) -> Response when
- Authorization :: #authorization{},
- AppContext :: term(),
- Response :: oauth2_response:response().
+-spec issue_code(auth(), term()) -> response().
issue_code(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL}, AppContext) ->
+ scope = Scope, ttl = TTL}, AppCtx1) ->
ExpiryAbsolute = seconds_since_epoch(TTL),
- 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).
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessCode = ?TOKEN:generate(GrantContext),
+ {ok, AppCtx2} = ?BACKEND:associate_access_code( AccessCode
+ , GrantContext
+ , AppCtx1) ,
+ {ok, {AppCtx2, oauth2_response:new( <<>>
+ , TTL
+ , ResOwner
+ , Scope
+ , <<>>
+ , AccessCode )}}.
--spec issue_token(Authorization, AppContext) -> Response when
- Authorization :: #authorization{},
- AppContext :: term(),
- Response :: oauth2_response:response().
+-spec issue_token(auth(), term()) -> response().
issue_token(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL}, AppContext) ->
+ scope = Scope, ttl = TTL}, AppCtx1) ->
ExpiryAbsolute = seconds_since_epoch(TTL),
- 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).
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(GrantContext),
+ {ok, AppCtx2} = ?BACKEND:associate_access_token( AccessToken
+ , GrantContext
+ , AppCtx1 ),
+ {ok, {AppCtx2, 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, AppContext) -> Response when
- Authorization :: #authorization{resowner :: term()},
- AppContext :: term(),
- Response :: oauth2_response:response().
+%% 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(auth(), term()) -> response().
issue_token_and_refresh(#authorization{client = Client, resowner = ResOwner,
- scope = Scope, ttl = TTL}, AppContext)
+ scope = Scope, ttl = TTL}, AppCtx1)
when ResOwner /= undefined ->
ExpiryAbsolute = seconds_since_epoch(TTL),
- 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).
+ GrantContext = build_context(Client, ExpiryAbsolute, ResOwner, Scope),
+ AccessToken = ?TOKEN:generate(GrantContext),
+ RefreshToken = ?TOKEN:generate(GrantContext),
+ {ok, AppCtx2} = ?BACKEND:associate_access_token( AccessToken
+ , GrantContext
+ , AppCtx1),
+ {ok, AppCtx3} = ?BACKEND:associate_refresh_token( RefreshToken
+ , GrantContext
+ , AppCtx2),
+ {ok, { AppCtx3
+ , 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, 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, GrantContext};
+%% context if successful. Otherwise, an OAuth2 error code is returned.
+-spec verify_access_code(token(), term()) -> {ok, context()} | {error, error()}.
+verify_access_code(AccessCode, AppCtx1) ->
+ case ?BACKEND:resolve_access_code(AccessCode, AppCtx1) of
+ {ok, {AppCtx2, GrantCtx}} ->
+ case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of
+ true -> {ok, {AppCtx2, GrantCtx}};
false ->
- ?BACKEND:revoke_access_code(AccessCode, AppContext),
+ ?BACKEND:revoke_access_code(AccessCode, AppCtx2),
{error, invalid_grant}
end;
- _ ->
- {error, invalid_grant}
+ _ -> {error, invalid_grant}
end.
%% @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, AppContext) -> {ok, GrantContext}
- | {error, Reason} when
- 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}
+%% returning its associated context if successful. Otherwise, an OAuth2
+%% error code is returned.
+-spec verify_access_code(token(), term(), term())
+ -> {ok, context()} | {error, error()}.
+verify_access_code(AccessCode, Client, AppCtx1) ->
+ case verify_access_code(AccessCode, AppCtx1) of
+ {ok, {AppCtx2, GrantCtx}} ->
+ case get(GrantCtx, <<"client">>) of
+ {ok, Client} -> {ok, {AppCtx2, GrantCtx}};
+ _ -> {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,
- AppContext)
- -> {ok, Client, Response}
- | {error, Reason} when
- 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, AppContext) of
- {ok, GrantContext} ->
- {_, ExpiryAbsolute} = lists:keyfind(<<"expiry_time">>, 1,
- GrantContext),
+%% if successful. Otherwise, an OAuth2 error code is returned.
+-spec refresh_access_token(binary(), binary(), token(), scope(), term())
+ -> {ok, {term(), response()}} | {error, error()}.
+refresh_access_token(CId, CSecret, RefreshToken, Scope, AppCtx1) ->
+ case ?BACKEND:authenticate_client(CId, CSecret, AppCtx1) of
+ {ok, {AppCtx2, Client}} ->
+ case ?BACKEND:resolve_refresh_token(RefreshToken, AppCtx2) of
+ {ok, {AppCtx3, GrantCtx}} ->
+ {ok, ExpiryAbsolute} = get(GrantCtx, <<"expiry_time">>),
case ExpiryAbsolute > seconds_since_epoch(0) of
true ->
- {_, Client} = lists:keyfind(<<"client">>, 1,
- GrantContext),
- {_, RegisteredScope} = lists:keyfind(<<"scope">>, 1,
- GrantContext),
- case ?BACKEND:verify_scope(RegisteredScope,
- Scope, AppContext) of
- {ok, VerifiedScope} ->
- {_, ResOwner} = lists:keyfind(
- <<"resource_owner">>, 1,
- GrantContext),
+ {ok, Client} = get(GrantCtx, <<"client">>),
+ {ok, RegScope} = get(GrantCtx, <<"scope">>),
+ case ?BACKEND:verify_scope( RegScope
+ , Scope
+ , AppCtx3) of
+ {ok, {AppCtx4, VerScope}} ->
+ {ok, ResOwner} = get( GrantCtx
+ , <<"resource_owner">> ),
TTL = oauth2_config:expiry_time(
password_credentials),
- Response = issue_token(
- #authorization{
- client = Client,
- resowner = ResOwner,
- scope = VerifiedScope,
- ttl = TTL}, AppContext),
- {ok, Client, Response};
- {error, _Reason} ->
- {error, invalid_scope}
+ issue_token( #authorization{
+ client = Client
+ , resowner = ResOwner
+ , scope = VerScope
+ , ttl = TTL
+ }
+ , AppCtx4);
+ {error, _Reason} -> {error, invalid_scope}
end;
false ->
?BACKEND:revoke_refresh_token(RefreshToken,
- AppContext),
+ AppCtx3),
{error, invalid_grant}
end;
- _ ->
- {error, invalid_grant}
+ _ -> {error, invalid_grant}
end;
_ -> {error, invalid_client}
end.
%% @doc Verifies an access token AccessToken, returning its associated
-%% context if successful. Otherwise, an OAuth2 error code is returned.
--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, GrantContext};
+%% context if successful. Otherwise, an OAuth2 error code is returned.
+-spec verify_access_token(token(), term())
+ -> {ok, context()} | {error, error()}.
+verify_access_token(AccessToken, AppCtx1) ->
+ case ?BACKEND:resolve_access_token(AccessToken, AppCtx1) of
+ {ok, {AppCtx2, GrantCtx}} ->
+ case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of
+ true -> {ok, {AppCtx2, GrantCtx}};
false ->
- ?BACKEND:revoke_access_token(AccessToken, AppContext),
+ ?BACKEND:revoke_access_token(AccessToken, AppCtx2),
{error, access_denied}
end;
- _ ->
- {error, access_denied}
+ _ -> {error, access_denied}
end.
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-
--spec build_context(Client, ExpiryTime, ResOwner, Scope) -> GrantContext when
- Client :: term(),
- ExpiryTime :: non_neg_integer(),
- ResOwner :: term(),
- Scope :: scope(),
- GrantContext :: context().
+%%%_* Private functions ================================================
+-spec build_context(term(), non_neg_integer(), term(), scope()) -> context().
build_context(Client, ExpiryTime, ResOwner, Scope) ->
- [{<<"client">>, Client},
- {<<"resource_owner">>, ResOwner},
- {<<"expiry_time">>, list_to_binary(integer_to_list(ExpiryTime))},
- {<<"scope">>, Scope}].
+ [ {<<"client">>, Client}
+ , {<<"resource_owner">>, ResOwner}
+ , {<<"expiry_time">>, list_to_binary(integer_to_list(ExpiryTime))}
+ , {<<"scope">>, Scope} ].
--spec seconds_since_epoch(Diff :: integer()) -> non_neg_integer().
+-spec seconds_since_epoch(integer()) -> non_neg_integer().
seconds_since_epoch(Diff) ->
- {Mega, Secs, _Micro} = os:timestamp(),
+ {Mega, Secs, _} = os:timestamp(),
Mega * 1000000 + Secs + Diff.
+
+get(O, K) ->
+ case lists:keyfind(K, 1, O) of
+ {K, V} -> {ok, V};
+ false -> {error, notfound}
+ end.
+
+get_(O, K) ->
+ case get(O, K) of
+ {ok, V} -> V;
+ {error, notfound} -> throw(notfound)
+ end.
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
290 src/oauth2_backend.erl
@@ -1,194 +1,122 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%_* Module declaration ===============================================
-module(oauth2_backend).
-%%%===================================================================
-%%% API functions
-%%%===================================================================
+%%%_ * Types -----------------------------------------------------------
+-type grantctx() :: oauth2:context().
+-type appctx() :: term().
+-type token() :: binary().
+-type scope() :: list(binary()) | binary().
+%%%_* Behaviour ========================================================
%% @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, AppContext) ->
- {ok, Identity} | {error, Reason} when
- Username :: binary(),
- Password :: binary(),
- Identity :: term(),
- AppContext :: term(),
- Reason :: notfound | badpass.
+%% Returns the resource owner identity if the credentials are valid.
+-callback authenticate_username_password(binary(), binary(), appctx()) ->
+ {ok, term()} | {error, notfound | badpass}.
%% @doc Authenticates a client's credentials for a given scope.
--callback authenticate_client(ClientId, ClientSecret, AppContext) ->
- {ok, Identity} | {error, Reason} when
- 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, GrantContext, AppContext) ->
- ok | {error, Reason} when
- 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, GrantContext, AppContext) ->
- ok | {error, Reason} when
- 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, GrantContext, AppContext) ->
- ok | {error, Reason} when
- 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, 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, 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, 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, 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, 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, AppContext) ->
- ok | {error, Reason} when
- RefreshToken :: oauth2:token(),
- AppContext :: term(),
- Reason :: notfound.
+-callback authenticate_client(binary(), binary(), appctx()) ->
+ {ok, term()} | {error, notfound | badsecret}.
+
+%% @doc Stores a new access code token(), associating it with Context.
+%% The context is a proplist carrying information about the identity
+%% with which the code is associated, when it expires, etc.
+-callback associate_access_code(token(), grantctx(), appctx()) ->
+ ok | {error, notfound}.
+
+%% @doc Stores a new access token token(), associating it with Context.
+%% The context is a proplist carrying information about the identity
+%% with which the token is associated, when it expires, etc.
+-callback associate_access_token(token(), grantctx(), appctx()) ->
+ ok | {error, notfound}.
+
+%% @doc Stores a new refresh token token(), associating it with
+%% grantctx(). The context is a proplist carrying information about the
+%% identity with which the token is associated, when it expires, etc.
+-callback associate_refresh_token(token(), grantctx(), appctx()) ->
+ ok | {error, notfound}.
+
+%% @doc Looks up an access token token(), returning the corresponding
+%% context if a match is found.
+-callback resolve_access_token(token(), appctx()) ->
+ {ok, grantctx()} | {error, notfound}.
+
+%% @doc Looks up an access code token(), returning the corresponding
+%% context if a match is found.
+-callback resolve_access_code(token(), appctx()) ->
+ {ok, grantctx()} | {error, notfound}.
+
+%% @doc Looks up an refresh token token(), returning the corresponding
+%% context if a match is found.
+-callback resolve_refresh_token(token(), appctx()) ->
+ {ok, grantctx()} | {error, notfound}.
+
+%% @doc Revokes an access token token(), so that it cannot be used again.
+-callback revoke_access_token(token(), appctx()) ->
+ ok | {error, notfound}.
+
+%% @doc Revokes an access code token(), so that it cannot be used again.
+-callback revoke_access_code(token(), appctx()) ->
+ ok | {error, notfound}.
+
+%% @doc Revokes an refresh token token(), so that it cannot be used again.
+-callback revoke_refresh_token(token(), appctx()) ->
+ ok | {error, notfound}.
%% @doc Returns the redirection URI associated with the client ClientId.
--callback get_redirection_uri(ClientId, AppContext) ->
- {error, Reason} | {ok, RedirectionUri} when
- ClientId :: binary(),
- AppContext :: term(),
- Reason :: notfound,
- RedirectionUri :: binary().
+-callback get_redirection_uri(binary(), appctx()) ->
+ {error, notfound} | {ok, binary()}.
%% @doc Returns a client identity for a given id.
--callback get_client_identity(ClientId, AppContext) ->
- {ok, Identity} | {error, Reason} when
- ClientId :: binary(),
- AppContext :: term(),
- Identity :: term(),
- Reason :: notfound | badsecret.
+-callback get_client_identity(binary(), appctx()) ->
+ {ok, term()} | {error, notfound | badsecret}.
@IvanMartinez
IvanMartinez added a note Jun 29, 2014

In which circumstance should {error, badsecret} be returned?.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
%% @doc Verifies that RedirectionUri is a valid redirection URI for the
-%% client identified by Identity.
-%% @end
--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, 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, 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.
+%% client identified by Identity.
+-callback verify_redirection_uri(term(), binary(), appctx()) ->
+ ok | {error, notfound | baduri}.
+
+%% @doc Verifies that scope() is a valid scope for the client identified
+%% by Identity.
+-callback verify_client_scope(term(), scope(), appctx()) ->
+ {ok, scope()} | {error, notfound | badscope}.
+
+%% @doc Verifies that scope() is a valid scope for the resource
+%% owner identified by Identity.
+-callback verify_resowner_scope(term(), scope(), appctx()) ->
+ {ok, scope()} | {error, notfound | badscope}.
+
+%% @doc Verifies that scope() is a valid scope of the set of scopes defined
+%% by Validscope()s.
%% @end
--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.
+-callback verify_scope(scope(), scope(), appctx()) ->
+ {ok, scope()} | {error, notfound | badscope}.
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
112 src/oauth2_config.erl
@@ -1,97 +1,83 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%_* Module declaration ===============================================
-module(oauth2_config).
-%%% API
+%%%_* Exports ==========================================================
+%%%_ * API -------------------------------------------------------------
-export([backend/0]).
-export([expiry_time/0]).
-export([expiry_time/1]).
-export([token_generation/0]).
+%%%_* Macros ===========================================================
%% Default time in seconds before an authentication token expires.
-define(DEFAULT_TOKEN_EXPIRY, 3600).
-%%%===================================================================
-%%% API
-%%%===================================================================
-
+%%%_* Code =============================================================
+%%%_ * API -------------------------------------------------------------
%% @doc Gets the default expiry time for access tokens.
--spec expiry_time() -> ExpiryTime when
- ExpiryTime :: non_neg_integer().
-expiry_time() ->
- get_optional(expiry_time, ?DEFAULT_TOKEN_EXPIRY).
-
+-spec expiry_time() -> non_neg_integer().
+expiry_time() -> get_optional(expiry_time, ?DEFAULT_TOKEN_EXPIRY).
%% @doc Gets a specific expiry time for access tokens if available
%% returns the default if non found
--spec expiry_time(Flow) -> ExpiryTime when
- Flow :: atom(),
- ExpiryTime :: non_neg_integer().
+-spec expiry_time(atom()) -> non_neg_integer().
expiry_time(Flow) ->
case application:get_env(oauth2, Flow) of
- undefined ->
- expiry_time();
+ undefined -> expiry_time();
{ok, Value} ->
case lists:keyfind(expiry_time, 1, Value) of
- false -> expiry_time();
+ false -> expiry_time();
{_Key, Val} -> Val
end
end.
-
%% @doc Gets the backend for validating passwords, storing tokens, etc.
--spec backend() -> Module when
- Module :: atom().
-backend() ->
- get_required(backend).
-
+-spec backend() -> atom().
+backend() -> get_required(backend).
%% @doc Gets the backend for generating tokens.
--spec token_generation() -> Module when
- Module :: atom().
-token_generation() ->
- get_optional(token_generation, oauth2_token).
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
+-spec token_generation() -> atom().
+token_generation() -> get_optional(token_generation, oauth2_token).
+%%%_* Private functions ================================================
get_optional(Key, Default) ->
case application:get_env(oauth2, Key) of
- undefined ->
- Default;
- {ok, Value} ->
- Value
+ undefined -> Default;
+ {ok, Value} -> Value
end.
get_required(Key) ->
case application:get_env(oauth2, Key) of
- undefined ->
- throw({missing_config, Key});
- {ok, Value} ->
- Value
+ undefined -> throw({missing_config, Key});
+ {ok, Value} -> Value
end.
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
162 src/oauth2_priv_set.erl
@@ -1,142 +1,108 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%_* Module declaration ===============================================
-module(oauth2_priv_set).
-%%% API
+%%%_* Exports ==========================================================
+%%%_ * API -------------------------------------------------------------
-export([new/1]).
-export([union/2]).
-export([is_subset/2]).
-export([is_member/2]).
-%%%===================================================================
-%%% API
-%%%===================================================================
-
+%%%_ * Types -----------------------------------------------------------
%% Invariant: Children are sorted increasingly by name.
-type priv_tree() :: {node, Name :: binary(), Children :: [priv_tree()]} | '*'.
%% Invariant:
%% The list of trees is sorted increasingly by the name of the root node.
-type priv_set() :: [priv_tree()].
+%%%_* Code =============================================================
+%%%_ * API -------------------------------------------------------------
%% @doc Constructs a new priv_set from a single path or a list of paths.
-%% A path denotes a single privilege.
-%% @end
--spec new(Paths) -> PrivSet when
- Paths :: binary() | [binary()],
- PrivSet :: priv_set().
+%% A path denotes a single privilege.
+-spec new(binary() | [binary()]) -> priv_set().
new(Paths) when is_list(Paths) ->
lists:foldl(fun union/2, [], [make_forest(Path) || Path <- Paths]);
new(Path) when is_binary(Path) ->
make_forest(Path).
%% @doc Returns the union of Set1 and Set2, i.e., a set such that
-%% any path present in either Set1 or Set2 is also present in the result.
-%% @end
--spec union(Set1, Set2) -> Union when
- Set1 :: priv_set(),
- Set2 :: priv_set(),
- Union :: priv_set().
+%% any path present in either Set1 or Set2 is also present in the result.
+-spec union(priv_set(), priv_set()) -> priv_set().
union([H1={node, Name1, _}|T1], [H2={node, Name2, _}|T2]) when Name1 < Name2 ->
[H1|union(T1, [H2|T2])];
union([H1={node, Name1, _}|T1], [H2={node, Name2, _}|T2]) when Name1 > Name2 ->
[H2|union([H1|T1], T2)];
union([{node, Name, S1}|T1], [{node, Name, S2}|T2]) ->
[{node, Name, union(S1, S2)}|union(T1, T2)];
-union(['*'|_], _) -> %% '*' in union with anything is still '*'.
- ['*'];
-union(_, ['*'|_]) ->
- ['*'];
-union([], Set) ->
- Set;
-union(Set, []) ->
- Set.
+union(['*'|_], _) -> ['*']; %% '*' in union with anything is still '*'.
+union(_, ['*'|_]) -> ['*'];
+union([], Set) -> Set;
+union(Set, []) -> Set.
%% @doc Return true if Set1 is a subset of Set2, i.e., if
-%% every privilege held by Set1 is also held by Set2.
-%% @end
--spec is_subset(Set1, Set2) -> Result when
- Set1 :: priv_set(),
- Set2 :: priv_set(),
- Result :: boolean().
-is_subset([{node, Name1, _}|_], [{node, Name2, _}|_]) when Name1 < Name2 ->
+%% every privilege held by Set1 is also held by Set2.
+-spec is_subset(priv_set(), priv_set()) -> boolean().
+is_subset([{node, N1, _}|_], [{node, N2, _}|_]) when N1 < N2 ->
false; %% This tree isn't present in Set2 as per the invariant.
-is_subset(Set1 = [{node, Name1, _}|_], [{node, Name2, _}|T2]) when Name1 > Name2 ->
+is_subset(Set1 = [{node, N1, _}|_], [{node, N2, _}|T2]) when N1 > N2 ->
is_subset(Set1, T2);
is_subset([{node, Name, S1}|T1], [{node, Name, S2}|T2]) ->
case is_subset(S1, S2) of
- true ->
- is_subset(T1, T2);
- false ->
- false
+ true -> is_subset(T1, T2);
+ false -> false
end;
-is_subset(['*'|_], ['*'|_]) -> %% '*' is only a subset of '*'.
- true;
-is_subset(_, ['*'|_]) -> %% Everything is a subset of '*'.
- true;
-is_subset([], _) -> %% The empty set is a subset of every set.
- true;
-is_subset(_, _) ->
- false.
+is_subset(['*'|_], ['*'|_]) -> true; %% '*' is only a subset of '*'.
+is_subset(_, ['*'|_]) -> true; %% Everything is a subset of '*'.
+is_subset([], _) -> true; %% The empty set is a subset of every set.
+is_subset(_, _) -> false.
%% @doc Returns true if Path is present in Set, i.e, if
-%% the privilege denoted by Path is contained within Set.
-%% @end
--spec is_member(Path, Set) -> Result when
- Path :: binary(),
- Set :: priv_set(),
- Result :: boolean().
-is_member(Path, Set) ->
- is_subset(make_forest(Path), Set).
+%% the privilege denoted by Path is contained within Set.
+-spec is_member(binary(), priv_set()) -> boolean().
+is_member(Path, Set) -> is_subset(make_forest(Path), Set).
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
--spec make_forest(Path) -> Forest when
- Path :: binary() | list(),
- Forest :: priv_set().
+%%%_* Private functions ================================================
+-spec make_forest(binary() | list()) -> priv_set().
make_forest(Path) when is_binary(Path) ->
make_forest(binary:split(Path, <<".">>, [global]));
make_forest(Path) when is_list(Path) ->
[make_tree(Path)].
--spec make_tree(Path) -> Tree when
- Path :: [binary()],
- Tree :: priv_tree().
-make_tree([<<"*">>|_]) ->
- '*';
-make_tree([N]) ->
- make_node(N, []);
-make_tree([H|T]) ->
- make_node(H, [make_tree(T)]).
+-spec make_tree([binary()]) -> priv_tree().
+make_tree([<<"*">>|_]) -> '*';
+make_tree([N]) -> make_node(N, []);
+make_tree([H|T]) -> make_node(H, [make_tree(T)]).
+
+-spec make_node(binary(), [priv_tree()]) -> priv_tree().
+make_node(Name, Children) -> {node, Name, Children}.
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
--spec make_node(Name, Children) -> Node when
- Name :: binary(),
- Children :: [priv_tree()],
- Node :: priv_tree().
-make_node(Name, Children) ->
- {node, Name, Children}.
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
227 src/oauth2_response.erl
@@ -1,36 +1,27 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
-
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%_* Module declaration ===============================================
-module(oauth2_response).
-%% Length of binary data to use for token generation.
--define(TOKEN_LENGTH, 32).
--define(TOKEN_TYPE, <<"bearer">>).
-
-%%% API
+%%%_* Exports ==========================================================
+%%%_ * API -------------------------------------------------------------
-export([new/1]).
-export([new/2]).
-export([new/4]).
@@ -51,7 +42,14 @@
-export([token_type/1]).
-export([to_proplist/1]).
+-export_type([response/0]).
+
+%%%_* Macros ===========================================================
+%% Length of binary data to use for token generation.
+-define(TOKEN_LENGTH, 32).
+-define(TOKEN_TYPE, <<"bearer">>).
+%%%_ * Types -----------------------------------------------------------
-record(response, {
access_token :: oauth2:token()
,access_code :: oauth2:token()
@@ -63,126 +61,89 @@
}).
-type response() :: #response{}.
--export_type([response/0]).
-
-%%%===================================================================
-%%% API functions
-%%%===================================================================
+-type token() :: oauth2:token().
+-type lifetime() :: oauth2:lifetime().
+-type scope() :: oauth2:scope().
--spec new(AccessToken :: oauth2:token()) -> response().
+%%%_* Code =============================================================
+%%%_ * API -------------------------------------------------------------
+-spec new(token()) -> response().
new(AccessToken) ->
#response{access_token = AccessToken}.
--spec new(AccessToken, ExpiresIn) -> response() when
- AccessToken :: oauth2:token(),
- ExpiresIn :: oauth2:lifetime().
+-spec new(token(), lifetime()) -> response().
new(AccessToken, ExpiresIn) ->
#response{access_token = AccessToken, expires_in = ExpiresIn}.
--spec new(AccessToken, ExpiresIn, ResOwner, Scope) -> response() when
- AccessToken :: oauth2:token(),
- ExpiresIn :: oauth2:lifetime(),
- ResOwner :: term(),
- Scope :: oauth2:scope().
+-spec new(token(), lifetime(), term(), scope()) -> response().
new(AccessToken, ExpiresIn, ResOwner, Scope) ->
- #response{access_token = AccessToken,
- expires_in = ExpiresIn,
- resource_owner = ResOwner,
- scope = Scope}.
-
--spec new(AccessToken, ExpiresIn, ResOwner, Scope, RefreshToken) -> response() when
- AccessToken :: oauth2:token(),
- ExpiresIn :: oauth2:lifetime(),
- ResOwner :: term(),
- Scope :: oauth2:scope(),
- RefreshToken :: oauth2:token().
+ #response{ access_token = AccessToken
+ , expires_in = ExpiresIn
+ , resource_owner = ResOwner
+ , scope = Scope
+ }.
+
+-spec new(token(), lifetime(), term(), scope(), token()) -> response().
new(AccessToken, ExpiresIn, ResOwner, Scope, RefreshToken) ->
- #response{access_token = AccessToken,
- expires_in = ExpiresIn,
- resource_owner = ResOwner,
- scope = Scope,
- refresh_token = RefreshToken}.
-
--spec new(_AccessToken, ExpiresIn, ResOwner, Scope, _RefreshToken, AccessCode) -> response() when
- _AccessToken :: oauth2:token(),
- ExpiresIn :: oauth2:lifetime(),
- ResOwner :: term(),
- Scope :: oauth2:scope(),
- _RefreshToken :: oauth2:token(),
- AccessCode :: oauth2:token().
-new(_AccessToken, ExpiresIn, ResOwner, Scope, _RefreshToken, AccessCode) ->
- #response{access_code = AccessCode,
- expires_in = ExpiresIn,
- resource_owner = ResOwner,
- scope = Scope}.
-
--spec access_token(response()) -> {ok, AccessToken} | {error, not_set} when
- AccessToken :: oauth2:token().
-access_token(#response{access_token = undefined}) ->
- {error, not_set};
-access_token(#response{access_token = AccessToken}) ->
- {ok, AccessToken}.
-
--spec access_token(response(), NewAccessToken) -> response() when
- NewAccessToken :: oauth2:token().
+ #response{ access_token = AccessToken
+ , expires_in = ExpiresIn
+ , resource_owner = ResOwner
+ , scope = Scope
+ , refresh_token = RefreshToken
+ }.
+
+-spec new(_, lifetime(), term(), scope(), _, token()) -> response().
+new(_, ExpiresIn, ResOwner, Scope, _, AccessCode) ->
+ #response{ access_code = AccessCode
+ , expires_in = ExpiresIn
+ , resource_owner = ResOwner
+ , scope = Scope
+ }.
+
+-spec access_token(response()) -> {ok, token()} | {error, not_set}.
+access_token(#response{access_token = undefined}) -> {error, not_set};
+access_token(#response{access_token = AccessToken}) -> {ok, AccessToken}.
+
+-spec access_token(response(), token()) -> response().
access_token(Response, NewAccessToken) ->
Response#response{access_token = NewAccessToken}.
--spec access_code(response()) -> {ok, AccessCode} | {error, not_set} when
- AccessCode :: oauth2:token().
-access_code(#response{access_code = undefined}) ->
- {error, not_set};
-access_code(#response{access_code = AccessCode}) ->
- {ok, AccessCode}.
+-spec access_code(response()) -> {ok, token()} | {error, not_set}.
+access_code(#response{access_code = undefined}) -> {error, not_set};
+access_code(#response{access_code = AccessCode}) -> {ok, AccessCode}.
--spec access_code(response(), NewAccessCode) -> response() when
- NewAccessCode :: oauth2:token().
+-spec access_code(response(), token()) -> response().
access_code(Response, NewAccessCode) ->
Response#response{access_code = NewAccessCode}.
--spec expires_in(response()) -> {ok, ExpiresIn} | {error, not_set} when
- ExpiresIn :: oauth2:lifetime().
-expires_in(#response{expires_in = undefined}) ->
- {error, not_set};
-expires_in(#response{expires_in = ExpiresIn}) ->
- {ok, ExpiresIn}.
+-spec expires_in(response()) -> {ok, lifetime()} | {error, not_set}.
+expires_in(#response{expires_in = undefined}) -> {error, not_set};
+expires_in(#response{expires_in = ExpiresIn}) -> {ok, ExpiresIn}.
--spec expires_in(response(), NewExpiresIn) -> response() when
- NewExpiresIn :: oauth2:lifetime().
+-spec expires_in(response(), lifetime()) -> response().
expires_in(Response, NewExpiresIn) ->
Response#response{expires_in = NewExpiresIn}.
--spec scope(response()) -> {ok, Scope} | {error, not_set} when
- Scope :: oauth2:scope().
-scope(#response{scope = undefined}) ->
- {error, not_set};
-scope(#response{scope = Scope}) ->
- {ok, Scope}.
-
--spec scope(response(), NewScope) -> response() when
- NewScope :: oauth2:scope().
-scope(Response, NewScope) ->
- Response#response{scope = NewScope}.
-
--spec refresh_token(response()) -> {ok, RefreshToken} | {error, not_set} when
- RefreshToken :: oauth2:token().
-refresh_token(#response{refresh_token = undefined}) ->
- {error, not_set};
-refresh_token(#response{refresh_token = RefreshToken}) ->
- {ok, RefreshToken}.
-
--spec refresh_token(response(), NewRefreshToken) -> response() when
- NewRefreshToken :: oauth2:token().
+-spec scope(response()) -> {ok, scope()} | {error, not_set}.
+scope(#response{scope = undefined}) -> {error, not_set};
+scope(#response{scope = Scope}) -> {ok, Scope}.
+
+-spec scope(response(), scope()) -> response().
+scope(Response, NewScope) -> Response#response{scope = NewScope}.
+
+-spec refresh_token(response()) -> {ok, token()} | {error, not_set}.
+refresh_token(#response{refresh_token = undefined}) -> {error, not_set};
+refresh_token(#response{refresh_token = RefreshToken}) -> {ok, RefreshToken}.
+
+-spec refresh_token(response(), token()) -> response().
refresh_token(Response, NewRefreshToken) ->
Response#response{refresh_token = NewRefreshToken}.
--spec resource_owner(response()) -> {ok, ResOwner} when
- ResOwner :: term().
+-spec resource_owner(response()) -> {ok, term()}.
resource_owner(#response{resource_owner = ResOwner}) ->
{ok, ResOwner}.
--spec resource_owner(response(), NewResOwner) -> response() when
- NewResOwner :: term().
+-spec resource_owner(response(), term()) -> response().
resource_owner(Response, NewResOwner) ->
Response#response{resource_owner = NewResOwner}.
@@ -196,10 +157,7 @@ to_proplist(Response) ->
Values = tl(tuple_to_list(Response)), %% Head is 'response'!
[{K, to_binary(V)} || {K , V} <- lists:zip(Keys, Values), V =/= undefined].
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-
+%%%_* Private functions ================================================
to_binary(Binary) when is_binary(Binary) ->
Binary;
to_binary(List) when is_list(List) ->
@@ -214,3 +172,14 @@ to_binary({Key, Value}) ->
{to_binary(Key), to_binary(Value)};
to_binary(Term) ->
to_binary(term_to_binary(Term)).
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
117 src/oauth2_token.erl
@@ -1,85 +1,62 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%_* Module declaration ===============================================
-module(oauth2_token).
-behaviour(oauth2_token_generation).
--include("oauth2.hrl").
-
-%%% API
+%%%_* Exports ==========================================================
+%%%_ * API -------------------------------------------------------------
-export([generate/1]).
%%% Exported for testability
-export([strong_rand_bytes_proxy/1]).
-%%%===================================================================
-%%% API functions
-%%%===================================================================
+%%%_* Macros ===========================================================
+-define(TOKEN_LENGTH, 32).
+%%%_* Code =============================================================
+%%%_ * API -------------------------------------------------------------
%% @doc Generates a random OAuth2 token.
--spec generate(Context) -> Token when
- Context :: oauth2:context(),
- Token :: oauth2:token().
-generate(_Context) ->
- generate_fragment(?TOKEN_LENGTH).
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
+-spec generate(oauth2:context()) -> oauth2:token().
+generate(_Context) -> generate_fragment(?TOKEN_LENGTH).
--spec generate_fragment(N) -> Fragment when
- N :: integer(),
- Fragment :: binary().
-generate_fragment(0) ->
- <<>>;
+%%%_* Private functions ================================================
+-spec generate_fragment(integer()) -> binary().
+generate_fragment(0) -> <<>>;
generate_fragment(N) ->
Rand = base64:encode(rand_bytes(N)),
Frag = << <<C>> || <<C>> <= <<Rand:N/bytes>>, is_alphanum(C) >>,
<<Frag/binary, (generate_fragment(N - byte_size(Frag)))/binary>>.
%% @doc Returns true for alphanumeric ASCII characters, false for all others.
--spec is_alphanum(Char) -> Result when
- Char :: char(),
- Result :: boolean().
-is_alphanum(C) when C >= 16#30 andalso C =< 16#39 ->
- true;
-is_alphanum(C) when C >= 16#41 andalso C =< 16#5A ->
- true;
-is_alphanum(C) when C >= 16#61 andalso C =< 16#7A ->
- true;
-is_alphanum(_) ->
- false.
+-spec is_alphanum(char()) -> boolean().
+is_alphanum(C) when C >= 16#30 andalso C =< 16#39 -> true;
+is_alphanum(C) when C >= 16#41 andalso C =< 16#5A -> true;
+is_alphanum(C) when C >= 16#61 andalso C =< 16#7A -> true;
+is_alphanum(_) -> false.
%% @doc Generate N random bytes, using the crypto:strong_rand_bytes
-%% function if sufficient entropy exists. If not, use crypto:rand_bytes
-%% as a fallback.
--spec rand_bytes(N) -> Result when
- N :: non_neg_integer(),
- Result :: binary().
+%% function if sufficient entropy exists. If not, use crypto:rand_bytes
+%% as a fallback.
+-spec rand_bytes(non_neg_integer()) -> binary().
rand_bytes(N) ->
try
%% NOTE: Apparently we can't meck away the crypto module,
@@ -92,8 +69,16 @@ rand_bytes(N) ->
end.
%% @equiv crypto:strong_rand_bytes(N)
--spec strong_rand_bytes_proxy(N) -> Result when
- N :: non_neg_integer(),
- Result :: binary().
-strong_rand_bytes_proxy(N) ->
- crypto:strong_rand_bytes(N).
+-spec strong_rand_bytes_proxy(non_neg_integer()) -> binary().
+strong_rand_bytes_proxy(N) -> crypto:strong_rand_bytes(N).
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
64 src/oauth2_token_generation.erl
@@ -1,36 +1,36 @@
-%% ----------------------------------------------------------------------------
-%%
-%% oauth2: Erlang OAuth 2.0 implementation
-%%
-%% Copyright (c) 2012-2013 KIVRA
-%%
-%% Permission is hereby granted, free of charge, to any person obtaining a
-%% copy of this software and associated documentation files (the "Software"),
-%% to deal in the Software without restriction, including without limitation
-%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
-%% and/or sell copies of the Software, and to permit persons to whom the
-%% Software is furnished to do so, subject to the following conditions:
-%%
-%% The above copyright notice and this permission notice shall be included in
-%% all copies or substantial portions of the Software.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-%% DEALINGS IN THE SOFTWARE.
-%%
-%% ----------------------------------------------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Copyright (c) 2012-2013 Kivra
+%%%
+%%% Permission to use, copy, modify, and/or distribute this software for any
+%%% purpose with or without fee is hereby granted, provided that the above
+%%% copyright notice and this permission notice appear in all copies.
+%%%
+%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+%%%
+%%% @doc Erlang OAuth 2.0 implementation
+%%% @end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%_* Module declaration ===============================================
-module(oauth2_token_generation).
-%%%===================================================================
-%%% API functions
-%%%===================================================================
-
+%%%_* Behaviour ========================================================
%% @doc Generates a random OAuth2 token.
--callback generate(Context) -> Token when
- Context :: oauth2:context(),
- Token :: oauth2:token().
+-callback generate(oauth2:context()) -> oauth2:token().
+
+%%%_* Tests ============================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+%%%_* Emacs ============================================================
+%%% Local Variables:
+%%% allout-layout: t
+%%% erlang-indent-level: 4
+%%% End:
View
80 test/oauth2_mock_backend.erl
@@ -68,86 +68,80 @@
%%% API
%%%===================================================================
-authenticate_username_password(?USER_NAME, ?USER_PASSWORD, _) ->
- {ok, {user, 31337}};
+authenticate_username_password(?USER_NAME, ?USER_PASSWORD, Ctx) ->
+ {ok, {Ctx, {user, 31337}}};
authenticate_username_password(?USER_NAME, _, _) ->
{error, badpass};
authenticate_username_password(_, _, _) ->
{error, notfound}.
-authenticate_client(?CLIENT_ID, ?CLIENT_SECRET, _) ->
- {ok, {client, 4711}};
+authenticate_client(?CLIENT_ID, ?CLIENT_SECRET, Ctx) ->
+ {ok, {Ctx, {client, 4711}}};
authenticate_client(?CLIENT_ID, _, _) ->
{error, badsecret};
authenticate_client(_, _, _) ->
{error, notfound}.
-get_client_identity(?CLIENT_ID, _) ->
- {ok, {client, 4711}};
+get_client_identity(?CLIENT_ID, Ctx) ->
+ {ok, {Ctx, {client, 4711}}};
get_client_identity(_, _) ->
{error, notfound}.
-associate_access_code(AccessCode, Context, _AppContext) ->
- associate_access_token(AccessCode, Context, _AppContext).
+associate_access_code(AccessCode, Context, AppContext) ->
+ associate_access_token(AccessCode, Context, AppContext).
-associate_refresh_token(RefreshToken, Context, _) ->
+associate_refresh_token(RefreshToken, Context, AppContext) ->
ets:insert(?ETS_TABLE, {RefreshToken, Context}),
- ok.
+ {ok, AppContext}.
-associate_access_token(AccessToken, Context, _) ->
+associate_access_token(AccessToken, Context, AppContext) ->
ets:insert(?ETS_TABLE, {AccessToken, Context}),
- ok.
+ {ok, AppContext}.
-resolve_access_code(AccessCode, _AppContext) ->
- resolve_access_token(AccessCode, _AppContext).
+resolve_access_code(AccessCode, AppContext) ->
+ resolve_access_token(AccessCode, AppContext).
-resolve_refresh_token(RefreshToken, _AppContext) ->
- resolve_access_token(RefreshToken, _AppContext).
+resolve_refresh_token(RefreshToken, AppContext) ->
+ resolve_access_token(RefreshToken, AppContext).
-resolve_access_token(AccessToken, _) ->
+resolve_access_token(AccessToken, AppContext) ->
case ets:lookup(?ETS_TABLE, AccessToken) of
- [] ->
- {error, notfound};
- [{_, Context}] ->
- {ok, Context}
+ [] -> {error, notfound};
+ [{_, Context}] -> {ok, {AppContext, Context}}
end.
-revoke_access_code(AccessCode, _AppContext) ->
- revoke_access_token(AccessCode, _AppContext).
+revoke_access_code(AccessCode, AppContext) ->
+ revoke_access_token(AccessCode, AppContext).
-revoke_access_token(AccessToken, _) ->
+revoke_access_token(AccessToken, AppContext) ->
ets:delete(?ETS_TABLE, AccessToken),
- ok.
+ {ok, AppContext}.
-revoke_refresh_token(_RefreshToken, _) ->
- ok.
+revoke_refresh_token(_RefreshToken, AppContext) ->
+ {ok, AppContext}.
-get_redirection_uri(?CLIENT_ID, _) ->
- {ok, ?CLIENT_URI};
-get_redirection_uri(_, _) ->
- {error, notfound}.
+get_redirection_uri(?CLIENT_ID, AppContext) -> {ok, {AppContext, ?CLIENT_URI}};
+get_redirection_uri(_, _) -> {error, notfound}.
-verify_redirection_uri({client, 4711}, ?CLIENT_URI, _) ->
- ok;
+verify_redirection_uri({client, 4711}, ?CLIENT_URI, AppContext) ->
+ {ok, AppContext};
verify_redirection_uri(_, _, _) ->
{error, mismatch}.
-verify_client_scope({client, 4711}, [], _) ->
- {ok, []};
-verify_client_scope({client, 4711}, ?CLIENT_SCOPE, _) ->
- {ok, ?CLIENT_SCOPE};
+verify_client_scope({client, 4711}, [], AppContext) ->
+ {ok, {AppContext, []}};
+verify_client_scope({client, 4711}, ?CLIENT_SCOPE, AppContext) ->
+ {ok, {AppContext, ?CLIENT_SCOPE}};
verify_client_scope(_, _, _) ->
{error, invalid_scope}.
-verify_resowner_scope({user, 31337}, ?USER_SCOPE, _) ->
- {ok, ?USER_SCOPE};
+verify_resowner_scope({user, 31337}, ?USER_SCOPE, AppContext) ->
+ {ok, {AppContext, ?USER_SCOPE}};
verify_resowner_scope(_, _, _) ->
{error, invalid_scope}.
-verify_scope(Scope, Scope, _) ->
- {ok, Scope};
-verify_scope(_, _, _) ->
- {error, invalid_scope}.
+verify_scope(Scope, Scope, AppContext) -> {ok, {AppContext, Scope}};
+verify_scope(_, _, _) -> {error, invalid_scope}.
start() ->
%% Set up the ETS table for holding access tokens.
View
82 test/oauth2_tests.erl
@@ -26,7 +26,6 @@
-module(oauth2_tests).
--include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
%%% Placeholder values that the mock backend will recognize.
@@ -44,11 +43,6 @@
%%% Test cases
%%%===================================================================
-proper_type_spec_test_() ->
- {timeout, 1200, [{?LINE,
- fun() -> proper:check_specs(oauth2,
- [{to_file, user}]) end}]}.
-
bad_authorize_password_test_() ->
{setup,
fun start/0,
@@ -183,16 +177,18 @@ verify_access_token_test_() ->
fun(_) ->
[
fun() ->
- {ok, Authorization} = oauth2:authorize_client_credentials(
+ {ok, {foo_context, Authorization}} =
+ oauth2:authorize_client_credentials(
?CLIENT_ID,
?CLIENT_SECRET,
?CLIENT_SCOPE,
foo_context),
- Response = oauth2:issue_token(Authorization,
- foo_context),
+ {ok, {foo_context, Response}} =
+ oauth2:issue_token(Authorization, foo_context),
{ok, Token} = oauth2_response:access_token(Response),
- ?assertMatch({ok, _}, oauth2:verify_access_token(
- Token, foo_context))
+ ?assertMatch( {ok, {foo_context, _}}
+ , oauth2:verify_access_token( Token
+ , foo_context ))
end,
?_assertMatch({error, access_denied},
oauth2:verify_access_token(<<"nonexistent_token">>,
@@ -207,15 +203,15 @@ bad_access_code_test_() ->
fun(_) ->
[
fun() ->
- {error, unauthorized_client} =
+ {error, unauthorized_client} =
oauth2:authorize_code_request(
?CLIENT_ID,
<<"http://in.val.id">>,
?USER_NAME,
?USER_PASSWORD,
?CLIENT_SCOPE,
foo_context),
- {error, unauthorized_client} =
+ {error, unauthorized_client} =
oauth2:authorize_code_request(
<<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>,
?CLIENT_URI,
@@ -257,28 +253,30 @@ verify_access_code_test_() ->
fun(_) ->
[
fun() ->
- {ok, Authorization} = oauth2:authorize_code_request(
+ {ok, {foo_context, Auth}} =
+ oauth2:authorize_code_request(
?CLIENT_ID,
?CLIENT_URI,
?USER_NAME,
?USER_PASSWORD,
- ?CLIENT_SCOPE,
+ ?USER_SCOPE,
foo_context),
- Response = oauth2:issue_code(Authorization,
- foo_context),
+ {ok, {foo_context, Response}} =
+ oauth2:issue_code(Auth, 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, foo_context)),
- {ok, Authorization2} = oauth2:authorize_code_grant(
+ {ok, {foo_context, Auth2}} =
+ oauth2:authorize_code_grant(
?CLIENT_ID,
?CLIENT_SECRET,
Code,
?CLIENT_URI,
foo_context),
- Response2 = oauth2:issue_token_and_refresh(Authorization2,
- foo_context),
+ {ok, {foo_context, Response2}} =
+ oauth2:issue_token_and_refresh(Auth2, foo_context),
{ok, Token} = oauth2_response:access_token(Response2),
?assertMatch({ok, _}, oauth2:verify_access_token(
Token,
@@ -294,26 +292,28 @@ bad_refresh_token_test_() ->