p1_oauth2-0.6.5/0000755000232200023220000000000013471467016013703 5ustar debalancedebalancep1_oauth2-0.6.5/rebar.test.config0000644000232200023220000000535113471467016017147 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- {lib_dirs, ["deps"]}. {deps_dir, ["deps"]}. {require_min_otp_vsn, "R14"}. {erl_opts, [ debug_info , warn_format , warn_export_all , warn_export_vars , warn_obsolete_guard , warn_bif_clash , nowarn_shadow_vars , {platform_define, "^R", pre17} , {platform_define, "^(R|17)", pre18} ]}. {xref_warnings, false}. {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. {cover_enabled, true}. {cover_print_enabled, true}. {cover_export_enabled, true}. {profiles, [{test, [{erl_opts, [{src_dirs, ["src", "test"]}]}]}]}. {dialyzer_opts, [{warnings, [ error_handling , race_conditions , behaviours ]} ]}. {clean_files, [".eunit", "ebin/*.beam", "test/*.beam"]}. {deps, [ {meck, ".*", {git, "https://github.com/eproxus/meck.git", {tag, "0.8.7"}}} , {proper, ".*", {git, "https://github.com/manopapad/proper.git", "a112a2d64b2b1bfbea780e139980711dfe99b431"}} ] }. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.5/test/0000755000232200023220000000000013471467016014662 5ustar debalancedebalancep1_oauth2-0.6.5/test/oauth2_tests.erl0000644000232200023220000004633113471467016020021 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- -module(oauth2_tests). -include_lib("eunit/include/eunit.hrl"). %%% Placeholder values that the mock backend will recognize. -define(USER_NAME, <<"herp">>). -define(USER_PASSWORD, <<"derp">>). -define(USER_SCOPE, [<<"xyz">>]). -define(RESOURCE_OWNER, <<"user">>). -define(CLIENT_ID, <<"TiaUdYODLOMyLkdaKkqlmhsl9QJ94a">>). -define(CLIENT_SECRET, <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>). -define(CLIENT_SCOPE, <<"abc">>). -define(CLIENT_URI, <<"https://no.where/cb">>). %%%=================================================================== %%% Test cases %%%=================================================================== bad_authorize_password_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ ?_assertMatch({ok, _}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, [<<"xyz">>], foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, access_denied}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, <<"xyz">>, foo_context)), ?_assertMatch({error, access_denied}, oauth2:authorize_password( {<<"derp">>,<<"derp">>}, <<"xyz">>, foo_context)), ?_assertMatch({ok, _}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, {?CLIENT_ID,?CLIENT_SECRET}, [<<"xyz">>], foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_password( {<<"herp">>, <<"derp">>}, {?CLIENT_ID, ?CLIENT_SECRET}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, access_denied}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {?CLIENT_ID, ?CLIENT_SECRET}, <<"xyz">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {?CLIENT_ID, <<"gggDMAwklAKc9kq5FsLjKrzi">>}, <<"xyz">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_password( {<<"herp">>, <<"herp">>}, {<<"XoaUdYODRC">>, ?CLIENT_SECRET}, <<"xyz">>, foo_context)) ] end}. authorize_implicit_grant_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, {foo_context, Auth}} = oauth2:authorize_password( {?USER_NAME,?USER_PASSWORD} , {?CLIENT_ID,?CLIENT_SECRET} , ?CLIENT_URI , ?USER_SCOPE , foo_context), {ok, {foo_context, Response}} = oauth2:issue_token(Auth, foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertMatch( {ok, _} , oauth2:verify_access_token( Token , foo_context )) end ] end}. bad_authorize_client_credentials_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">> , <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">> }, <<"abc">>, foo_context)), ?_assertMatch({error, invalid_scope}, oauth2:authorize_client_credentials( {?CLIENT_ID, ?CLIENT_SECRET}, <<"bad_scope">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">> , <<"gggDMAwklAKc9kq5FsLjKrzihCcI123">> }, <<"abc">>, foo_context)), ?_assertMatch({error, invalid_client}, oauth2:authorize_client_credentials( { <<"TiaUdYODLOMyLkdaKkqlmdhsl9QJ94a">> , <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">> }, <<"cba">>, foo_context)) ] end}. bad_ttl_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> application:set_env(oauth2, expiry_time, 0), {ok, Response} = issue_access_token(foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertEqual({error, access_denied}, oauth2:verify_access_token(Token, foo_context)) end, fun() -> application:set_env(oauth2, expiry_time, 0), {ok, Response} = issue_access_code(foo_context), {ok, Code} = oauth2_response:access_code(Response), ?assertEqual({error, invalid_grant}, oauth2:verify_access_code(Code, foo_context)) end, fun() -> application:set_env(oauth2, expiry_time, 3600), {ok, Res1} = issue_access_code(foo_context), application:set_env(oauth2, expiry_time, 0), {ok, Res2} = issue_token_and_refresh(Res1, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), ?assertEqual({error, invalid_grant}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context)) end ] end}. verify_access_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Response} = issue_access_token(foo_context), {ok, Token} = oauth2_response:access_token(Response), ?assertMatch({ok, {foo_context, _}}, oauth2:verify_access_token(Token, foo_context)) end, ?_assertMatch({error, access_denied}, oauth2:verify_access_token(<<"nonexistent_token">>, foo_context)) ] end}. bad_access_code_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {error, unauthorized_client} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, <<"http://in.val.id">>, ?USER_SCOPE, foo_context), {error, unauthorized_client} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, <<"XoaUdYODRCMyLkdaKkqlmhsl9QQJ4b">>, ?CLIENT_URI, ?CLIENT_SCOPE, foo_context), {error, invalid_scope} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, <<"bad_scope">>, foo_context), {error, access_denied} = oauth2:authorize_code_request( {<<"herp">>, <<"herp">>}, ?CLIENT_ID, ?CLIENT_URI, ?CLIENT_SCOPE, foo_context), ?_assertMatch({error, invalid_grant}, oauth2:verify_access_code(<<"nonexistent_token">>, foo_context)) end ] end}. verify_access_code_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Response} = issue_access_code(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, {foo_context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, 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, foo_context)) end ] end}. bad_refresh_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, {foo_context, Auth}} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, ?USER_SCOPE, foo_context), {ok, {foo_context, Response}} = oauth2:issue_code(Auth, foo_context), {ok, Code} = oauth2_response:access_code(Response), {ok, {foo_context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, foo_context), {ok, {foo_context, Res2}} = oauth2:issue_token_and_refresh(Auth2, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), ?assertMatch({error, invalid_client}, oauth2:refresh_access_token( {<<"foo">>, ?CLIENT_SECRET}, RefreshToken, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_client}, oauth2:refresh_access_token( {?CLIENT_ID, <<"foo">>}, RefreshToken, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_grant}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, <<"foo">>, ?CLIENT_SCOPE, foo_context)), ?assertMatch({error, invalid_scope}, oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, <<"foo">>, foo_context)) end ] end}. verify_refresh_token_test_() -> {setup, fun start/0, fun stop/1, fun(_) -> [ fun() -> {ok, Res1} = issue_access_code(foo_context), {ok, Res2} = issue_token_and_refresh(Res1, foo_context), {ok, RefreshToken} = oauth2_response:refresh_token(Res2), {ok, _} = oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context), {ok, Token} = oauth2_response:access_token(Res2), ?assertMatch({ok, _}, oauth2:verify_access_token( Token, foo_context)) end, fun() -> lists:foreach(fun(UserNameAndPasswordStrategyFun) -> {ok, Response} = issue_token_and_refresh_with_user_name_and_password( foo_context, UserNameAndPasswordStrategyFun), {ok, RefreshToken} = oauth2_response:refresh_token(Response), {ok, {foo_context, Response2}} = oauth2:refresh_access_token( {?CLIENT_ID, ?CLIENT_SECRET}, RefreshToken, ?USER_SCOPE, foo_context), {ok, NewAccessToken} = oauth2_response:access_token(Response2), ?assertMatch({ok, _}, oauth2:verify_access_token( NewAccessToken, foo_context)) end, [ fun(Context) -> oauth2:authorize_password( {?USER_NAME, ?USER_PASSWORD}, {?CLIENT_ID, ?CLIENT_SECRET}, ?USER_SCOPE, Context) end ]) end ] end}. %%%=================================================================== %%% Setup/teardown %%%=================================================================== start() -> application:set_env(oauth2, backend, oauth2_mock_backend), application:set_env(oauth2, expiry_time, 3600), oauth2_mock_backend:start(), ok. stop(_State) -> oauth2_mock_backend:stop(), ok. %%%=================================================================== %%% Helpers %%%=================================================================== issue_access_token(Context) -> {ok, {Context, Authorization}} = oauth2:authorize_client_credentials( {?CLIENT_ID, ?CLIENT_SECRET}, ?CLIENT_SCOPE, Context), {ok, {Context, Response}} = oauth2:issue_token(Authorization, Context), {ok, Response}. issue_access_code(Context) -> {ok, {Context, Auth}} = oauth2:authorize_code_request( {?USER_NAME, ?USER_PASSWORD}, ?CLIENT_ID, ?CLIENT_URI, ?USER_SCOPE, Context), {ok, {Context, Response}} = oauth2:issue_code(Auth, Context), {ok, Response}. issue_token_and_refresh(Response, Context) -> {ok, Code} = oauth2_response:access_code(Response), {ok, {Context, Auth2}} = oauth2:authorize_code_grant( {?CLIENT_ID, ?CLIENT_SECRET}, Code, ?CLIENT_URI, Context), {ok, {Context, Res2}} = oauth2:issue_token_and_refresh(Auth2, Context), {ok, Res2}. issue_token_and_refresh_with_user_name_and_password( Context, UserNameAndPasswordStrategyFun) -> {ok, {Context, Auth}} = UserNameAndPasswordStrategyFun(Context), {ok, {Context, Response}} = oauth2:issue_token_and_refresh(Auth, Context), {ok, Response}. p1_oauth2-0.6.5/test/oauth2_token_tests.erl0000644000232200023220000000466613471467016021226 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- -module(oauth2_token_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_token, [{to_file, user}]) end}]}. generate_test() -> Token = oauth2_token:generate([]), ?assertEqual(byte_size(Token), 32), ?assert(lists:all(fun is_alphanum/1, binary_to_list(Token))). %%%=================================================================== %%% Utility functions %%%=================================================================== %% @doc Returns true for alphanumeric ASCII characters, false for all others. -spec is_alphanum(Char :: 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. p1_oauth2-0.6.5/test/oauth2_mock_backend.erl0000644000232200023220000001257313471467016021260 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- -module(oauth2_mock_backend). -behavior(oauth2_backend). %%% Behavior API -export([authenticate_user/2]). -export([authenticate_client/2]). -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]). -export([stop/0]). %%% Placeholder values that the mock backend will recognize. -define(UNAME, <<"herp">>). -define(PASSWORD, <<"derp">>). -define(USCOPE, [<<"xyz">>]). -define(RES_OWNER, <<"user">>). -define(CID, <<"TiaUdYODLOMyLkdaKkqlmhsl9QJ94a">>). -define(SECRET, <<"fvfDMAwjlruC9rv5FsLjmyrihCcIKJL">>). -define(CSCOPE, <<"abc">>). -define(CLIENT_URI, <<"https://no.where/cb">>). -define(ETS_TABLE, access_tokens). %%%=================================================================== %%% API %%%=================================================================== authenticate_user({?UNAME, ?PASSWORD}, Ctx) -> {ok, {Ctx, {user, 31337}}}; authenticate_user({?UNAME, _}, _) -> {error, badpass}; authenticate_user(_, _) -> {error, notfound}. authenticate_client({?CID, ?SECRET}, Ctx) -> {ok, {Ctx, {client, 4711}}}; authenticate_client({?CID, _}, _) -> {error, badsecret}; authenticate_client(_, _) -> {error, notfound}. get_client_identity(?CID, Ctx) -> {ok, {Ctx, {client, 4711}}}; get_client_identity(_, _) -> {error, notfound}. associate_access_code(AccessCode, Context, AppContext) -> associate_access_token(AccessCode, Context, AppContext). associate_refresh_token(RefreshToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {RefreshToken, Context}), {ok, AppContext}. associate_access_token(AccessToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {AccessToken, Context}), {ok, AppContext}. resolve_access_code(AccessCode, AppContext) -> resolve_access_token(AccessCode, AppContext). resolve_refresh_token(RefreshToken, AppContext) -> resolve_access_token(RefreshToken, AppContext). resolve_access_token(AccessToken, AppContext) -> case ets:lookup(?ETS_TABLE, AccessToken) of [] -> {error, notfound}; [{_, Context}] -> {ok, {AppContext, Context}} end. revoke_access_code(AccessCode, AppContext) -> revoke_access_token(AccessCode, AppContext). revoke_access_token(AccessToken, AppContext) -> ets:delete(?ETS_TABLE, AccessToken), {ok, AppContext}. revoke_refresh_token(_RefreshToken, AppContext) -> {ok, AppContext}. get_redirection_uri({?CID, ?SECRET}, Ctx) -> {ok, {Ctx, ?CLIENT_URI}}; get_redirection_uri(_, _) -> {error, notfound}. verify_redirection_uri({client, 4711}, ?CLIENT_URI, Ctx) -> {ok, Ctx}; verify_redirection_uri(_, _, _) -> {error, mismatch}. verify_client_scope({client, 4711}, [], Ctx) -> {ok, {Ctx, []}}; verify_client_scope({client, 4711}, ?CSCOPE, Ctx) -> {ok, {Ctx, ?CSCOPE}}; verify_client_scope(_, _, _) -> {error, invalid_scope}. verify_resowner_scope({user, 31337}, ?USCOPE, Ctx) -> {ok, {Ctx, ?USCOPE}}; verify_resowner_scope(_, _, _) -> {error, invalid_scope}. verify_scope(Scope, Scope, AppContext) -> {ok, {AppContext, Scope}}; verify_scope(_, _, _) -> {error, invalid_scope}. %% Set up the ETS table for holding access tokens. start() -> ets:new(?ETS_TABLE, [public, named_table, {read_concurrency, true}]). stop() -> ets:delete(?ETS_TABLE). %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.5/test/oauth2_priv_set_tests.erl0000644000232200023220000001012213471467016021721 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- -module(oauth2_priv_set_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_priv_set, [{to_file, user}]) end}]}. new_test_() -> [ ?_assert(oauth2_priv_set:is_member( <<"x.y.z">>, oauth2_priv_set:new(<<"x.y.z">>))), ?_assert(oauth2_priv_set:is_member( <<"a.b.c">>, oauth2_priv_set:new([<<"a.b.c">>, <<"a.b.d">>]))), ?_assertNot(oauth2_priv_set:is_member( <<"a.b.c">>, oauth2_priv_set:new(<<"x.y.z">>))), ?_assertNot(oauth2_priv_set:is_member( <<"a.b.e">>, oauth2_priv_set:new([<<"a.b.c">>, <<"a.b.a">>]))) ]. is_subset_test_() -> [ ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c.d.e">>), oauth2_priv_set:new([ <<"a.b">>, <<"a.b.x.y">>, <<"a.b.z.x">>, <<"a.b.k.d.g.e">>, <<"a.b.m.n.p.q">>, <<"a.b.c.d.*">> ]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c">>), oauth2_priv_set:new([<<"a.b.c">>, <<"a.s.d.f.g.h">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new([<<"x.y">>, <<"x.*">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new([<<"x.*">>, <<"x.y">>]))), ?_assert(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.*">>), oauth2_priv_set:new([<<"a.*">>, <<"x.*">>]))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.b.c">>), oauth2_priv_set:new(<<"a.b">>))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"x.y.z">>), oauth2_priv_set:new(<<"x.z.*">>))), ?_assertNot(oauth2_priv_set:is_subset( oauth2_priv_set:new(<<"a.*">>), oauth2_priv_set:new([<<"a.b.*">>, <<"a.c.*">>, <<"a.d.*">>, <<"a.e.*">>, <<"a.f.z">>]))) ]. p1_oauth2-0.6.5/test/oauth2_response_tests.erl0000644000232200023220000002050613471467016021733 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- -module(oauth2_response_tests). -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(ACCESS, <<"9bX9iFUOsXbM12OOjfDW175IXXOELp6K">>). -define(REFRESH, <<"JVs3ZFQJBIdduJdhhWOoAt2B3qEKcHEo">>). -define(CODE, <<"Lz7Z24cKSQ28z8kem01ZP9c0aE3TEbGl">>). -define(RESOURCE_OWNER, <<"user">>). -define(EXPIRY, 3600). -define(SCOPE, <<"herp derp">>). -define(TOKEN_TYPE, <<"bearer">>). %%%=================================================================== %%% Test cases %%%=================================================================== proper_type_spec_test_() -> {timeout, 1200, [{?LINE, fun() -> proper:check_specs(oauth2_response, [{to_file, user}]) end}]}. new_1_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertMatch({error, not_set}, oauth2_response:expires_in(Response)), ?_assertMatch({error, not_set}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_2_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertMatch({error, not_set}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_4_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertMatch({error, not_set}, oauth2_response:refresh_token(Response)) ] end}. new_5_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE, ?REFRESH, ?EXPIRY) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({ok, ?ACCESS}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertEqual({ok, ?REFRESH}, oauth2_response:refresh_token(Response)) ] end}. new_6_test_() -> {setup, fun() -> oauth2_response:new(?ACCESS, ?EXPIRY, ?RESOURCE_OWNER, ?SCOPE, ?REFRESH, ?EXPIRY, ?CODE) end, fun(_) -> ok end, fun(Response) -> [ ?_assertEqual({error, not_set}, oauth2_response:access_token(Response)), ?_assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in(Response)), ?_assertEqual({ok, ?SCOPE}, oauth2_response:scope(Response)), ?_assertEqual({error, not_set}, oauth2_response:refresh_token(Response)), ?_assertEqual({ok, ?CODE}, oauth2_response:access_code(Response)) ] end}. access_token_test() -> ?assertEqual({ok, ?REFRESH}, oauth2_response:access_token( oauth2_response:access_token( oauth2_response:new(?ACCESS), ?REFRESH))). access_code_test() -> ?assertEqual({ok, ?CODE}, oauth2_response:access_code( oauth2_response:access_code( oauth2_response:new([], [], [], [], [], ?CODE), ?CODE))). expires_in_test() -> ?assertEqual({ok, ?EXPIRY}, oauth2_response:expires_in( oauth2_response:expires_in( oauth2_response:new(?ACCESS), ?EXPIRY))). scope_test() -> ?assertEqual({ok, ?SCOPE}, oauth2_response:scope( oauth2_response:scope( oauth2_response:new(?ACCESS), ?SCOPE))). refresh_token_test() -> ?assertEqual({ok, ?REFRESH}, oauth2_response:refresh_token( oauth2_response:refresh_token( oauth2_response:new(?ACCESS), ?REFRESH))). resource_owner_test() -> ?assertEqual({ok, ?RESOURCE_OWNER}, oauth2_response:resource_owner( oauth2_response:resource_owner( oauth2_response:new(?ACCESS), ?RESOURCE_OWNER))). token_type_test() -> ?assertEqual({ok, ?TOKEN_TYPE}, oauth2_response:token_type(oauth2_response:new(?ACCESS))). to_proplist_test() -> Property = ?FORALL( {AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry}, {non_empty(oauth2:token()), oauth2:lifetime(), binary(), oauth2:scope(), non_empty(oauth2:token()), oauth2:lifetime()}, begin Response = oauth2_response:new(AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry), [ {<<"access_token">>, AccessToken}, {<<"expires_in">>, Expiry}, {<<"resource_owner">>, ResourceOwner}, {<<"scope">>, scope_to_binary(Scope)}, {<<"refresh_token">>, RefreshToken}, {<<"refresh_token_expires_in">>, RefreshExpiry}, {<<"token_type">>, <<"bearer">>} ] =:= oauth2_response:to_proplist(Response) end), ?assert(proper:quickcheck(Property, [{to_file, user}])). -ifndef(pre17). to_map_test() -> Property = ?FORALL( {AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry}, {non_empty(oauth2:token()), oauth2:lifetime(), binary(), oauth2:scope(), non_empty(oauth2:token()), oauth2:lifetime()}, begin Response = oauth2_response:new(AccessToken, Expiry, ResourceOwner, Scope, RefreshToken, RefreshExpiry), #{ <<"access_token">> => AccessToken, <<"expires_in">> => Expiry, <<"resource_owner">> => ResourceOwner, <<"scope">> => scope_to_binary(Scope), <<"refresh_token">> => RefreshToken, <<"refresh_token_expires_in">> => RefreshExpiry, <<"token_type">> => <<"bearer">> } =:= oauth2_response:to_map(Response) end), ?assert(proper:quickcheck(Property, [{to_file, user}])). -endif. %%%=================================================================== %%% Helpers %%%=================================================================== scope_to_binary(Binary) when is_binary(Binary) -> Binary; scope_to_binary([]) -> <<>>; scope_to_binary([Binary]) when is_binary(Binary) -> Binary; scope_to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) -> <>. p1_oauth2-0.6.5/README.md0000644000232200023220000001343313471467016015166 0ustar debalancedebalance# P1 OAuth2 [![Build Status](https://travis-ci.org/processone/p1_oauth2.svg?branch=master)](https://travis-ci.org/processone/p1_oauth2) [![Coverage Status](https://coveralls.io/repos/processone/p1_oauth2/badge.svg?branch=master&service=github)](https://coveralls.io/github/processone/p1_oauth2?branch=master) [![Hex version](https://img.shields.io/hexpm/v/p1_oauth2.svg "Hex version")](https://hex.pm/packages/p1_oauth2) This library is designed to simplify the implementation of the server side of OAuth2 (http://tools.ietf.org/html/rfc6749). It provides **no** support for developing clients. See [oauth2_client](https://github.com/kivra/oauth2_client) for support in accessing Oauth2 enabled services. oauth2 is released under the terms of the [MIT](http://en.wikipedia.org/wiki/MIT_License) license Current stable version: [0.6.0](https://github.com/kivra/oauth2/tree/0.6.0) Current α alpha version: [0.6.x](https://github.com/kivra/oauth2) copyright 2012-2014 Kivra ## tl;dr ### Examples Check out the [examples](https://github.com/kivra/oauth2_example). ### Related projects Webmachine server implementation by Oauth2 contributor Ivan Martinez: [oauth2_webmachine](https://github.com/IvanMartinez/oauth2_webmachine). Redis backed Oauth2 [backend](https://github.com/interline/oauth2_redis_backend). ## Concepts ### Tokens A token is a (randomly generated) string provided to the client by the server in response to some form of authorization request. There are several types of tokens: * *Access Token*: An access token identifies the origin of a request for a privileged resource. * *Refresh Token*: A refresh token can be used to replace an expired access token. #### Expiry Access tokens can (optionally) be set to expire after a certain amount of time. An expired token cannot be used to gain access to resources. ### Identities A token is associated with an *identity* -- a value that uniquely identifies a user, client or agent within your system. Typically, this is a user identifier. ### Scope The scope is handled by the backend implementation. The specification outlines that the scope is a space delimetered set of parameters. This library has been developed with the following in mind. Scope is implemented as a set and loosely modeled after the Solaris RBAC priviliges, i.e. `solaris.x.*` and implemented as a [MAC](http://en.wikipedia.org/wiki/Mandatory_access_control) with the ability to narrow the scope but not extend it beyond the predefined scope. But since the scope is opaque to this Oauth2 implementation you can use the scoping strategy that best suit your workflow. There is a utility module to work with scope. The recommendation is to pass a Scope as a list of binaries, i.e. `[<<"root.a.c.b">>, <<"root.x.y.z">>]` you can then validate these against another set like: ``` erlang > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.*">>])). true > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.x.y">>])). false > oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]), oauth2_priv_set:new([<<"root.a.*">>, <<"root.x.y">>])). true ``` ### Clients If you have many diverse clients connecting to your service -- for instance, a web client and an iPhone app -- it's desirable to be able to distinguish them from one another and to be able to grant or revoke privileges based on the type the client issuing a request. As described in the OAuth2 specification, clients come in two flavors: * *Confidential* clients, which can be expected to keep their credentials from being disclosed. For instance, a web site owned and operated by you could be regarded as confidential. * *Public* clients, whose credentials are assumed to be compromised the moment the client software is released to the public. Clients are distinguished by their identifiers, and can (optionally) be authenticated using a secret key shared between the client and server. ## Testing If you want to run the EUnit test cases, you can do so with: $ make ct ## Customization The library makes no assumptions as to how you want to implement authentication and persistence of users, clients and tokens. Instead, it provides a behavior (`oauth2_backend`) with functions that needs to be implemented. To direct calls to a different backend module, simply set `{backend, your_backend_module}` in the `oauth2` section of your app.config. Look at [oauth2_mock_backend](test/oauth2_mock_backend.erl) for how a backend can be implemented. The following example demonstrates a basic app.config section for oauth2. ``` erlang [ {oauth2, [ %% Default expiry_time for access_tokens unless %% overridden per flow {expiry_time, 3600} ,{backend, backend_goes_here} %% Optional expiry_time override per flow ,{password_credentials, [ {expiry_time, 7200} ]} ,{client_credentials, [ {expiry_time, 86400} ]} ,{refresh_token, [ {expiry_time, 2592000} %% 30 Days ]} ,{code_grant, [ %% Recommended absolute expiry time from the spec {expiry_time, 600} ]} ]} ]. ``` A complete list of functions that your backend must provide is available by looking at `oauth2_backend.erl`, which contains documentation and function specifications. To implement a custom token generation backend you can change your app.config as such: ``` erlang [ {oauth2, [ {token_generation, YOUR_TOKEN_GENERATOR} ]} ]. ``` The default token generator is called oauth2_token. To implement your own you should create your own module implementing the oauth2_token_generation behavior exporting one function generate/0. p1_oauth2-0.6.5/rebar.test.config.script0000644000232200023220000000744213471467016020455 0ustar debalancedebalance%%%---------------------------------------------------------------------- %%% File : rebar.config.script %%% Author : Mickael Remond %%% Purpose : Rebar build script. Compliant with rebar and rebar3. %%% Created : 24 Nov 2015 by Mickael Remond %%% %%% Copyright (C) 2002-2019 ProcessOne, SARL. All Rights Reserved. %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%% %%%---------------------------------------------------------------------- ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) -> {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of {value, {_, V1}, V2} -> {V1, V2}; false -> {if Tail == [] -> Default; true -> [] end, Cfg} end, case Tail of [] -> [{Key, Op(OldVal)} | PartCfg]; _ -> [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg] end end, ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, Default) end. %% Rebar3 support for hex.pm support: %% - Transform dependencies specification to use hex.pm packages: %% deps of the form: {Name, _Vsn, {git, _URL, {tag, Version}}} %% are expected to refer to package and are rewritten for rebar3 as: %% {Name, Version} %% - Add rebar3_hex plugin IsRebar3 = case application:get_key(rebar, vsn) of {ok, VSN} -> [VSN1 | _] = string:tokens(VSN, "-"), [Maj, Min, Patch] = string:tokens(VSN1, "."), (list_to_integer(Maj) >= 3); undefined -> lists:keymember(mix, 1, application:loaded_applications()) end, Cfg2 = case IsRebar3 of true -> DepsFun = fun(DepsList) -> lists:map(fun({DepName,_, {git,_, {tag,Version}}}) -> {DepName, Version}; (Dep) -> Dep end, DepsList) end, RB1 = ModCfg(CONFIG, [deps], DepsFun, []), ModCfg(RB1, [plugins], fun(V) -> V ++ [rebar3_hex] end, []); false -> CONFIG end, %% When running Travis test, upload test coverage result to coveralls: Config = case os:getenv("TRAVIS") of "true" -> JobId = os:getenv("TRAVIS_JOB_ID"), Cfg3 = ModCfg(Cfg2, [deps], fun(V) -> [{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}|V] end, []), ModCfg(Cfg3, [post_hooks], fun(V) -> V ++ [{eunit, "echo '\n%%! -pa .eunit/ deps/coveralls/ebin\nmain(_)->{ok,F}=file:open(\"erlang.json\",[write]),io:fwrite(F,\"~s\",[coveralls:convert_file(\".eunit/cover.coverdata\", \""++JobId++"\", \"travis-ci\",\"\")]).' > getcover.erl"}, {eunit, "escript ./getcover.erl"}] end, []); _ -> Cfg2 end, Config. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.5/LICENSE0000644000232200023220000000203613471467016014711 0ustar debalancedebalanceCopyright (c) 2012-2014 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. p1_oauth2-0.6.5/Makefile0000644000232200023220000000116013471467016015341 0ustar debalancedebalancePROJECT = oauth2 DIALYZER = dialyzer REBAR = ./rebar .PHONY: all deps compile clean test ct build-plt dialyze all: deps compile deps: $(REBAR) -C rebar.test.config get-deps compile: $(REBAR) compile clean: $(REBAR) clean rm -f test/*.beam rm -f erl_crash.dump test: ct dialyze doc test-build: $(REBAR) -C rebar.test.config compile ct: clean deps test-build $(REBAR) -C rebar.test.config eunit skip_deps=true build-plt: $(DIALYZER) --build_plt --output_plt .$(PROJECT).plt \ --apps erts kernel stdlib sasl inets crypto public_key ssl dialyze: clean deps test-build $(DIALYZER) --plt .$(PROJECT).plt ebin p1_oauth2-0.6.5/rebar.config0000644000232200023220000000345313471467016016172 0ustar debalancedebalance%% ---------------------------------------------------------------------------- %% %% oauth2: Erlang OAuth 2.0 implementation %% %% Copyright (c) 2012-2014 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. %% %% ---------------------------------------------------------------------------- {erl_opts, [debug_info, {src_dirs, ["src"]}, {platform_define, "^R", pre17}, {platform_define, "^(R|17)", pre18} ]}. {cover_enabled, true}. {cover_export_enabled, true}. {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. {profiles, [{test, [{erl_opts, [{src_dirs, ["src", "test"]}]}]}]}. {plugins, [rebar3_hex]}. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.5/CHANGELOG.md0000644000232200023220000000064013471467016015514 0ustar debalancedebalance# Version 0.6.3 * Remove last remainig crypto:rand\_bytes() usage # Version 0.6.2 * Make tests works with erlang R20 * Fix Travis-CI compilation # Version 0.6.1 * Repository is now forked to p1_oauth2 for consistency (Mickaël Rémond) * Initial release on Hex.pm (Mickaël Rémond) * Standard ProcessOne build chain (Mickaël Rémond) * Setup Travis-CI and test coverage, tests still needed (Mickaël Rémond) p1_oauth2-0.6.5/src/0000755000232200023220000000000013471467016014472 5ustar debalancedebalancep1_oauth2-0.6.5/src/oauth2_token_generation.erl0000644000232200023220000000303013471467016022007 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_* Behaviour ======================================================== %% @doc Generates a random 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: p1_oauth2-0.6.5/src/p1_oauth2.app.src0000644000232200023220000000262513471467016017571 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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, p1_oauth2, [ {description, "Erlang OAuth 2.0 implementation"}, {vsn, "0.6.5"}, {registered, []}, {applications, [ kernel, stdlib ]}, {env, []}, %% hex.pm packaging: {licenses, ["MIT", "Apache 2.0"]}, {links, [{"Github", "https://github.com/processone/p1_oauth2"}]}]}. %% Local Variables: %% mode: erlang %% End: %% vim: set filetype=erlang tabstop=8: p1_oauth2-0.6.5/src/oauth2_backend.erl0000644000232200023220000001327713471467016020061 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_ * Types ----------------------------------------------------------- -type grantctx() :: oauth2:context(). -type appctx() :: oauth2:appctx(). -type token() :: oauth2:token(). -type scope() :: oauth2:scope(). -type user() :: oauth2:user(). -type client() :: oauth2:client(). %%%_* Behaviour ======================================================== %% @doc Authenticates a combination of username and password. %% Returns the resource owner identity if the credentials are valid. -callback authenticate_user(user(), appctx()) -> {ok, {appctx(), term()}} | {error, notfound | badpass}. %% @doc Authenticates a client's credentials for a given scope. -callback authenticate_client(client(), appctx()) -> {ok, {appctx(), client()}} | {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, appctx()} | {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, appctx()} | {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, appctx()} | {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, {appctx(), 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, {appctx(), 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, {appctx(), grantctx()}} | {error, notfound}. %% @doc Revokes an access token token(), so that it cannot be used again. -callback revoke_access_token(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% @doc Revokes an access code token(), so that it cannot be used again. -callback revoke_access_code(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% @doc Revokes an refresh token token(), so that it cannot be used again. -callback revoke_refresh_token(token(), appctx()) -> {ok, appctx()} | {error, notfound}. %% @doc Returns a client identity for a given id. -callback get_client_identity(client(), appctx()) -> {ok, {appctx(), client()}} | {error, notfound | badsecret}. %% @doc Verifies that RedirectionUri is a valid redirection URI for the %% client identified by Identity. -callback verify_redirection_uri(client(), binary(), appctx()) -> {ok, appctx()} | {error, notfound | baduri}. %% @doc Verifies that scope() is a valid scope for the client identified %% by Identity. -callback verify_client_scope(client(), scope(), appctx()) -> {ok, {appctx(), 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, {appctx(), scope()}} | {error, notfound | badscope}. %% @doc Verifies that scope() is a valid scope of the set of scopes defined %% by Validscope()s. -callback verify_scope(scope(), scope(), appctx()) -> {ok, {appctx(), 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: p1_oauth2-0.6.5/src/oauth2.erl0000644000232200023220000004610013471467016016401 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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 %%% %%% This library is designed to simplify the implementation of the %%% server side of OAuth2 (http://tools.ietf.org/html/rfc6749). %%% @end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%_* Module declaration =============================================== -module(oauth2). -compile({no_auto_import, [get/2]}). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([authorize_password/3]). -export([authorize_password/4]). -export([authorize_password/5]). -export([authorize_client_credentials/3]). -export([authorize_code_grant/4]). -export([authorize_code_request/5]). -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([verify_access_code/3]). -export([refresh_access_token/4]). -export_type([token/0]). -export_type([user/0]). -export_type([client/0]). -export_type([context/0]). -export_type([auth/0]). -export_type([lifetime/0]). -export_type([scope/0]). -export_type([appctx/0]). -export_type([error/0]). %%%_* Macros =========================================================== -define(BACKEND, (oauth2_config:backend())). -define(TOKEN, (oauth2_config:token_generation())). %%%_ * Types ----------------------------------------------------------- %% Opaque authentication record -record(a, { client = undefined :: undefined | term() , resowner = undefined :: undefined | term() , scope :: scope() , ttl = 0 :: non_neg_integer() }). -type context() :: proplists:proplist(). -type auth() :: #a{}. -type user() :: any(). %% Opaque User Object -type client() :: any(). %% Opaque Client Object -type rediruri() :: any(). %% Opaque Redirection URI -type token() :: binary(). -type response() :: oauth2_response:response(). -type lifetime() :: non_neg_integer(). -type scope() :: list(binary()) | binary(). -type appctx() :: term(). -type error() :: access_denied | invalid_client | invalid_grant | invalid_request | invalid_authorization | invalid_scope | unauthorized_client | unsupported_grant_type | unsupported_response_type | server_error | temporarily_unavailable. %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Validates a request for an access token from resource owner's %% credentials. Use it to implement the following steps of RFC 6749: %% - 4.3.2. Resource Owner Password Credentials Grant > %% Access Token Request, when the client is public. -spec authorize_password(user(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Scope, Ctx0) -> case auth_user(User, Scope, Ctx0) of {error, _}=E -> E; {ok, _}=Auth -> Auth end. %% @doc Validates a request for an access token from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.3.2. Resource Owner Password Credentials Grant > %% Access Token Request, when the client is confidential. -spec authorize_password(user(), client(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Client, Scope, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case auth_user(User, Scope, Ctx1) of {error, _} = E -> E; {ok, {Ctx2, Auth}} -> {ok, {Ctx2, Auth#a{client=C}}} end end. %% @doc Validates a request for an access token from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.2.1. Implicit Grant > Authorization Request, when the client %% is public. -spec authorize_password(user(), client(), rediruri(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_password(User, Client, RedirUri, Scope, Ctx0) -> case auth_client(Client, RedirUri, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case auth_user(User, Scope, Ctx1) of {error, _} = E -> E; {ok, {Ctx2, Auth}} -> {ok, {Ctx2, Auth#a{client=C}}} end end. %% @doc Validates a request for an access token from client's credentials. %% Use it to implement the following steps of RFC 6749: %% - 4.4.2. Client Credentials Grant > Access Token Request. -spec authorize_client_credentials(client(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_client_credentials(Client, Scope0, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case ?BACKEND:verify_client_scope(C, Scope0, Ctx1) of {error, _} -> {error, invalid_scope}; {ok, {Ctx2, Scope1}} -> {ok, {Ctx2, #a{ client=C , scope =Scope1 , ttl =oauth2_config:expiry_time( client_credentials) }}} end end. %% @doc Validates a request for an access token from an authorization code. %% Use it to implement the following steps of RFC 6749: %% - 4.1.3. Authorization Code Grant > Access Token Request. -spec authorize_code_grant(client(), binary(), rediruri(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_code_grant(Client, Code, RedirUri, Ctx0) -> case auth_client(Client, RedirUri, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case verify_access_code(Code, C, Ctx1) of {error, _}=E -> E; {ok, {Ctx2, GrantCtx}} -> {ok, Ctx3} = ?BACKEND:revoke_access_code(Code, Ctx2), {ok, {Ctx3, #a{ client =C , resowner=get_(GrantCtx,<<"resource_owner">>) , scope =get_(GrantCtx, <<"scope">>) , ttl =oauth2_config:expiry_time( password_credentials) }}} end end. %% @doc Validates a request for an authorization code from client and resource %% owner's credentials. Use it to implement the following steps of %% RFC 6749: %% - 4.1.1. Authorization Code Grant > Authorization Request. -spec authorize_code_request(user(), client(), rediruri(), scope(), appctx()) -> {ok, {appctx(), auth()}} | {error, error()}. authorize_code_request(User, Client, RedirUri, Scope, Ctx0) -> case ?BACKEND:get_client_identity(Client, Ctx0) of {error, _} -> {error, unauthorized_client}; {ok, {Ctx1, C}} -> case ?BACKEND:verify_redirection_uri(C, RedirUri, Ctx1) of {error, _} -> {error, unauthorized_client}; {ok, Ctx2} -> case auth_user(User, Scope, Ctx2) of {error, _}=E -> E; {ok, {Ctx3, Auth}} -> {ok, { Ctx3 , Auth#a{ client=C , ttl =oauth2_config:expiry_time( code_grant) } }} end end end. %% @doc Issues an authorization code from an authorization. Use it to implement %% the following steps of RFC 6749: %% - 4.1.2. Authorization Code Grant > Authorization Response, with the %% result of authorize_code_request/6. -spec issue_code(auth(), appctx()) -> {ok, {appctx(), response()}}. issue_code(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) -> GrantContext = build_context(Client, seconds_since_epoch(TTL), Owner, Scope), AccessCode = ?TOKEN:generate(GrantContext), {ok, Ctx1} = ?BACKEND:associate_access_code(AccessCode,GrantContext,Ctx0), {ok, {Ctx1, oauth2_response:new(<<>>,TTL,Owner,Scope,<<>>,<<>>,AccessCode)}}. %% @doc Issues an access token without refresh token from an authorization. %% Use it to implement the following steps of RFC 6749: %% - 4.1.4. Authorization Code Grant > Authorization Response, with the %% result of authorize_code_grant/5 when no refresh token must be issued. %% - 4.2.2. Implicit Grant > Access Token Response, with the result of %% authorize_password/7. %% - 4.3.3. Resource Owner Password Credentials Grant > %% Access Token Response, with the result of authorize_password/4 or %% authorize_password/6 when the client is public or no refresh token %% must be issued. %% - 4.4.3. Client Credentials Grant > Access Token Response, with the %% result of authorize_client_credentials/4. -spec issue_token(auth(), appctx()) -> {ok, {appctx(), response()}}. issue_token(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) -> GrantContext = build_context(Client,seconds_since_epoch(TTL),Owner,Scope), AccessToken = ?TOKEN:generate(GrantContext), {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken , GrantContext , Ctx0 ), {ok, {Ctx1, oauth2_response:new(AccessToken, TTL, Owner, Scope)}}. %% @doc Issues access and refresh tokens from an authorization. %% Use it to implement the following steps of RFC 6749: %% - 4.1.4. Authorization Code Grant > Access Token Response, with the %% result of authorize_code_grant/5 when a refresh token must be issued. %% - 4.3.3. Resource Owner Password Credentials Grant > %% Access Token Response, with the result of authorize_password/6 when %% the client is confidential and a refresh token must be issued. -spec issue_token_and_refresh(auth(), appctx()) -> {ok, {appctx(), response()}} | {error, invalid_authorization}. issue_token_and_refresh(#a{client = undefined}, _Ctx) -> {error, invalid_authorization}; issue_token_and_refresh(#a{resowner = undefined}, _Ctx) -> {error, invalid_authorization}; issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL} , Ctx0 ) -> RTTL = oauth2_config:expiry_time(refresh_token), AccessCtx = build_context(Client,seconds_since_epoch(TTL),Owner,Scope), RefreshCtx = build_context(Client,seconds_since_epoch(RTTL),Owner,Scope), AccessToken = ?TOKEN:generate(AccessCtx), RefreshToken = ?TOKEN:generate(RefreshCtx), {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken , AccessCtx , Ctx0), {ok, Ctx2} = ?BACKEND:associate_refresh_token( RefreshToken , RefreshCtx , Ctx1 ), {ok, {Ctx2, oauth2_response:new( AccessToken , TTL , Owner , Scope , RefreshToken , RTTL )}}. %% @doc Verifies an access code AccessCode, returning its associated %% context if successful. Otherwise, an OAuth2 error code is returned. -spec verify_access_code(token(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_code(AccessCode, Ctx0) -> case ?BACKEND:resolve_access_code(AccessCode, Ctx0) of {error, _} -> {error, invalid_grant}; {ok, {Ctx1, GrantCtx}} -> case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of true -> {ok, {Ctx1, GrantCtx}}; false -> ?BACKEND:revoke_access_code(AccessCode, Ctx1), {error, invalid_grant} end 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(token(), client(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_code(AccessCode, Client, Ctx0) -> case verify_access_code(AccessCode, Ctx0) of {error, _}=E -> E; {ok, {Ctx1, GrantCtx}} -> case get(GrantCtx, <<"client">>) of {ok, Client} -> {ok, {Ctx1, GrantCtx}}; _ -> {error, invalid_grant} end end. %% @doc Validates a request for an access token from a refresh token, issuing %% a new access token if valid. Use it to implement the following steps of %% RFC 6749: %% - 6. Refreshing an Access Token. -spec refresh_access_token(client(), token(), scope(), appctx()) -> {ok, {appctx(), response()}} | {error, error()}. refresh_access_token(Client, RefreshToken, Scope, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _} -> {error, invalid_client}; {ok, {Ctx1, C}} -> case ?BACKEND:resolve_refresh_token(RefreshToken, Ctx1) of {error, _} -> {error, invalid_grant}; {ok, {Ctx2, GrantCtx}} -> {ok, ExpiryAbsolute} = get(GrantCtx, <<"expiry_time">>), case ExpiryAbsolute > seconds_since_epoch(0) of true -> {ok, C} = get(GrantCtx, <<"client">>), {ok, RegScope} = get(GrantCtx, <<"scope">>), case ?BACKEND:verify_scope( RegScope , Scope , Ctx2) of {error, _} -> {error, invalid_scope}; {ok, {Ctx3, VerScope}} -> {ok, ResOwner} = get( GrantCtx , <<"resource_owner">> ), TTL = oauth2_config:expiry_time( password_credentials), issue_token(#a{ client = C , resowner = ResOwner , scope = VerScope , ttl = TTL }, Ctx3) end; false -> ?BACKEND:revoke_refresh_token(RefreshToken, Ctx2), {error, invalid_grant} end end end. %% @doc Verifies an access token AccessToken, returning its associated %% context if successful. Otherwise, an OAuth2 error code is returned. -spec verify_access_token(token(), appctx()) -> {ok, {appctx(), context()}} | {error, error()}. verify_access_token(AccessToken, Ctx0) -> case ?BACKEND:resolve_access_token(AccessToken, Ctx0) of {error, _} -> {error, access_denied}; {ok, {Ctx1, GrantCtx}} -> case get_(GrantCtx, <<"expiry_time">>) > seconds_since_epoch(0) of true -> {ok, {Ctx1, GrantCtx}}; false -> ?BACKEND:revoke_access_token(AccessToken, Ctx1), {error, access_denied} end end. %%%_* Private functions ================================================ auth_user(User, Scope0, Ctx0) -> case ?BACKEND:authenticate_user(User, Ctx0) of {error, _} -> {error, access_denied}; {ok, {Ctx1, Owner}} -> case ?BACKEND:verify_resowner_scope(Owner, Scope0, Ctx1) of {error, _} -> {error, invalid_scope}; {ok, {Ctx2, Scope1}} -> {ok, {Ctx2, #a{ resowner = Owner , scope = Scope1 , ttl = oauth2_config:expiry_time( password_credentials) }}} end end. auth_client(Client, no_redir, Ctx0) -> ?BACKEND:authenticate_client(Client, Ctx0); auth_client(Client, RedirUri, Ctx0) -> case auth_client(Client, no_redir, Ctx0) of {error, _}=E -> E; {ok, {Ctx1, C}} -> case ?BACKEND:verify_redirection_uri(C, RedirUri, Ctx1) of {error, _} -> {error, invalid_grant}; {ok, Ctx2} -> {ok, {Ctx2, C}} end end. -spec build_context(term(), non_neg_integer(), term(), scope()) -> context(). build_context(Client, ExpiryTime, ResOwner, Scope) -> [ {<<"client">>, Client} , {<<"resource_owner">>, ResOwner} , {<<"expiry_time">>, ExpiryTime} , {<<"scope">>, Scope} ]. -spec seconds_since_epoch(integer()) -> non_neg_integer(). seconds_since_epoch(Diff) -> {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) -> {ok, V} = get(O, K), V. %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.5/src/oauth2_response.erl0000644000232200023220000002173013471467016020321 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([new/1]). -export([new/2]). -export([new/4]). -export([new/6]). -export([new/7]). -export([access_token/1]). -export([access_token/2]). -export([access_code/1]). -export([access_code/2]). -export([refresh_token/1]). -export([refresh_token/2]). -export([refresh_token_expires_in/1]). -export([refresh_token_expires_in/2]). -export([resource_owner/1]). -export([resource_owner/2]). -export([expires_in/1]). -export([expires_in/2]). -export([scope/1]). -export([scope/2]). -export([token_type/1]). -export([to_proplist/1]). -ifndef(pre17). -export([to_map/1]). -endif. -export_type([response/0]). %%%_* Macros =========================================================== -define(TOKEN_TYPE, <<"bearer">>). %%%_ * Types ----------------------------------------------------------- -record(response, { access_token :: oauth2:token() ,access_code :: oauth2:token() ,expires_in :: oauth2:lifetime() ,resource_owner :: term() ,scope :: oauth2:scope() ,refresh_token :: oauth2:token() ,refresh_token_expires_in :: oauth2:lifetime() ,token_type = ?TOKEN_TYPE :: binary() }). -type response() :: #response{}. -type token() :: oauth2:token(). -type lifetime() :: oauth2:lifetime(). -type scope() :: oauth2:scope(). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- -spec new(token()) -> response(). new(AccessToken) -> #response{access_token = AccessToken}. -spec new(token(), lifetime()) -> response(). new(AccessToken, ExpiresIn) -> #response{access_token = AccessToken, expires_in = ExpiresIn}. -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(token(), lifetime(), term(), scope(), token(), lifetime()) -> response(). new(AccessToken, ExpiresIn, ResOwner, Scope, RefreshToken, RExpiresIn) -> #response{ access_token = AccessToken , expires_in = ExpiresIn , resource_owner = ResOwner , scope = Scope , refresh_token = RefreshToken , refresh_token_expires_in = RExpiresIn }. -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, 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(), token()) -> response(). access_code(Response, NewAccessCode) -> Response#response{access_code = NewAccessCode}. -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(), lifetime()) -> response(). expires_in(Response, NewExpiresIn) -> Response#response{expires_in = NewExpiresIn}. -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 refresh_token_expires_in(response()) -> {ok, lifetime()} | {error, not_set}. refresh_token_expires_in(#response{refresh_token = undefined}) -> {error, not_set}; refresh_token_expires_in(#response{refresh_token_expires_in = RefreshTokenExpiresIn}) -> {ok, RefreshTokenExpiresIn}. -spec refresh_token_expires_in(response(), lifetime()) -> response(). refresh_token_expires_in(Response, NewRefreshTokenExpiresIn) -> Response#response{refresh_token_expires_in = NewRefreshTokenExpiresIn}. -spec resource_owner(response()) -> {ok, term()}. resource_owner(#response{resource_owner = ResOwner}) -> {ok, ResOwner}. -spec resource_owner(response(), term()) -> response(). resource_owner(Response, NewResOwner) -> Response#response{resource_owner = NewResOwner}. -spec token_type(response()) -> {ok, binary()}. token_type(#response{}) -> {ok, ?TOKEN_TYPE}. -spec to_proplist(response()) -> proplists:proplist(). to_proplist(Response) -> response_foldr(Response, fun(Key, Value, Acc) -> [{Key, Value} | Acc] end, []). -ifndef(pre17). -ifdef(pre18). -spec to_map(response()) -> map(binary(), any()). -else. -spec to_map(response()) -> #{binary() => any()}. -endif. to_map(Response) -> response_foldr(Response, fun(Key, Value, Acc) -> maps:put(Key, Value, Acc) end, maps:new()). -endif. %%%_* Private functions ================================================ -spec response_foldr(Response, Fun, Acc0) -> Return when Response :: response(), Fun :: fun((Key::binary(), Value::any(), Acc::any()) -> Acc::any()), Acc0 :: any(), Return :: any(). response_foldr(Record, Fun, Acc0) -> Keys = record_info(fields, response), Values = tl(tuple_to_list(Record)), %% Head is 'response'! response_foldr(Keys, Values, Fun, Acc0). response_foldr([], [], _Fun, Acc0) -> Acc0; response_foldr([_ | Ks], [undefined | Vs], Fun, Acc) -> response_foldr(Ks, Vs, Fun, Acc); response_foldr([refresh_token_expires_in | Ks], [V | Vs], Fun, Acc) -> Fun(<<"refresh_token_expires_in">>, V, response_foldr(Ks, Vs, Fun, Acc)); response_foldr([expires_in | Ks], [V | Vs], Fun, Acc) -> Fun(<<"expires_in">>, V, response_foldr(Ks, Vs, Fun, Acc)); response_foldr([K | Ks], [V | Vs], Fun, Acc) -> Key = atom_to_binary(K, latin1), Value = to_binary(V), Fun(Key, Value, response_foldr(Ks, Vs, Fun, Acc)). to_binary(Binary) when is_binary(Binary) -> Binary; to_binary([Binary]) when is_binary(Binary) -> Binary; to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) -> <>; to_binary(List) when is_list(List) -> to_binary(list_to_binary(List)); to_binary(Atom) when is_atom(Atom) -> to_binary(atom_to_list(Atom)); to_binary(Float) when is_float(Float) -> to_binary(float_to_list(Float)); to_binary(Integer) when is_integer(Integer) -> to_binary(integer_to_list(Integer)); 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: p1_oauth2-0.6.5/src/oauth2_config.erl0000644000232200023220000000624313471467016017732 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_* 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). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Gets the default expiry time for access tokens. -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(atom()) -> non_neg_integer(). expiry_time(Flow) -> case application:get_env(oauth2, Flow) of undefined -> expiry_time(); {ok, Value} -> case lists:keyfind(expiry_time, 1, Value) of false -> expiry_time(); {_Key, Val} -> Val end end. %% @doc Gets the backend for validating passwords, storing tokens, etc. -spec backend() -> atom(). backend() -> get_required(backend). %% @doc Gets the backend for generating tokens. -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 end. get_required(Key) -> case application:get_env(oauth2, Key) of 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: p1_oauth2-0.6.5/src/oauth2_priv_set.erl0000644000232200023220000001121013471467016020306 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([new/1]). -export([union/2]). -export([is_subset/2]). -export([is_member/2]). -export_type([priv_set/0]). %%%_ * 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. -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. -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. %% @doc Return true if Set1 is a subset of Set2, i.e., if %% 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, 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 end; 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. -spec is_member(binary(), priv_set()) -> boolean(). is_member(Path, Set) -> is_subset(make_forest(Path), 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([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. %%%_* Emacs ============================================================ %%% Local Variables: %%% allout-layout: t %%% erlang-indent-level: 4 %%% End: p1_oauth2-0.6.5/src/oauth2_token.erl0000644000232200023220000000545713471467016017613 0ustar debalancedebalance%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Copyright (c) 2012-2014 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). %%%_* Exports ========================================================== %%%_ * API ------------------------------------------------------------- -export([generate/1]). %%%_* Macros =========================================================== -define(TOKEN_LENGTH, 32). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- %% @doc Generates a random OAuth2 token. -spec generate(oauth2:context()) -> oauth2:token(). generate(_Context) -> generate_fragment(?TOKEN_LENGTH). %%%_* Private functions ================================================ -spec generate_fragment(integer()) -> binary(). generate_fragment(0) -> <<>>; generate_fragment(N) -> Rand = base64:encode(rand_bytes(N)), Frag = << <> || <> <= <>, is_alphanum(C) >>, <>. %% @doc Returns true for alphanumeric ASCII characters, false for all others. -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 -spec rand_bytes(non_neg_integer()) -> binary(). rand_bytes(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: