From 2b3285349e8664130b53d468810cc28a386c7cb3 Mon Sep 17 00:00:00 2001 From: Moritz Ploss Date: Fri, 29 Dec 2023 09:41:06 +0100 Subject: [PATCH] tmp --- .github/workflows/build_and_test.yml | 33 +-- .gitignore | 3 +- TEST-file_oauth2.app.xml | 391 --------------------------- rebar.config | 2 +- src/oauth2.erl | 161 +++-------- src/oauth2_backend.erl | 12 - src/oauth2_response.erl | 14 +- test/oauth2_mock_backend.erl | 5 + 8 files changed, 67 insertions(+), 554 deletions(-) delete mode 100644 TEST-file_oauth2.app.xml diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 093b120..b3ac6dd 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -5,21 +5,27 @@ on: [push] jobs: build_and_test: runs-on: ubuntu-latest - name: OTP ${{matrix.otp}} strategy: matrix: - otp: ["23.2", "24.0"] + otp: ["24.0", "25.0"] + name: OTP ${{matrix.otp}} steps: - - uses: actions/checkout@v2.0.0 - - uses: gleam-lang/setup-erlang@v1.1.2 + - uses: actions/checkout@v4 + + - uses: erlef/setup-beam@v1.16.0 with: - otp-version: ${{matrix.otp}} + otp-version: ${{ matrix.otp }} + rebar3-version: "3.20.0" + - name: Compile run: make compile + - name: Run xref run: make xref + - name: Run dialyzer run: make dialyze + - name: Run eunit tests run: make eunit @@ -28,16 +34,7 @@ jobs: needs: build_and_test runs-on: ubuntu-latest steps: - - name: Bump version and push tag - id: tag_version - uses: mathieudutour/github-tag-action@v5.3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Create a GitHub release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.tag_version.outputs.new_tag }} - release_name: Release ${{ steps.tag_version.outputs.new_tag }} - body: ${{ steps.tag_version.outputs.changelog }} + - name: Create Tag (Release) + uses: kivra/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 3a1fc46..c54a5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ ebin deps .rebar *.plt -_build/* \ No newline at end of file +_build/* +TEST-*.xml diff --git a/TEST-file_oauth2.app.xml b/TEST-file_oauth2.app.xml deleted file mode 100644 index 0bc26db..0000000 --- a/TEST-file_oauth2.app.xml +++ /dev/null @@ -1,391 +0,0 @@ - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2_tests:'-bad_authorize_password_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 53) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505) -in call from eunit_proc:tests_inorder/3 (eunit_proc.erl, line 447) -**error:undef - - - - - - - - - - - - - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_password/4 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 114) -in call from oauth2_tests:'-bad_authorize_password_test_/0-fun-9-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 73) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505) -**error:undef - - - - - - - - - - - - - - - - - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_password/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 134) -in call from oauth2_tests:'-authorize_implicit_grant_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 113) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505) -**error:undef - - - - - - - - - - - - - - - - - - - - - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197) -in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426) -in call from oauth2_tests:'-bad_ttl_test_/0-fun-3-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 178) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -**error:undef - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197) -in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426) -in call from oauth2_tests:'-bad_ttl_test_/0-fun-5-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 187) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -**error:undef - - - - - - - - - - - - - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197) -in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426) -in call from oauth2_tests:'-verify_access_code_test_/0-fun-3-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 269) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -**error:undef - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197) -in call from oauth2_tests:'-bad_refresh_token_test_/0-fun-4-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 299) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505) -**error:undef - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197) -in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426) -in call from oauth2_tests:'-verify_refresh_token_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 352) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347) -**error:undef - - - - - - -::in function oauth2_mock_backend:jwt_issuer/0 - called as jwt_issuer() -in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450) -in call from oauth2:authorize_password/4 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 114) -in call from oauth2_tests:issue_token_and_refresh_with_user_name_and_password/2 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 448) -in call from oauth2_tests:'-verify_refresh_token_test_/0-fun-4-'/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 368) -in call from lists:foreach/2 (lists.erl, line 1342) -in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71) -in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522) -**error:undef - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rebar.config b/rebar.config index 228674b..7bdbbc5 100644 --- a/rebar.config +++ b/rebar.config @@ -13,7 +13,7 @@ {xref_checks, [undefined_function_calls]}. {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. -{profiles, [ {test, [{deps, [ {meck, {git, "git@github.com:kivra/meck.git", {tag, "0.8.13"}}}, +{profiles, [ {test, [{deps, [ {meck, "0.9.2"}, {proper, {git, "https://github.com/manopapad/proper.git", {tag, "v1.4"}}} ]}]} ]}. diff --git a/src/oauth2.erl b/src/oauth2.erl index 65bb723..397ba8a 100644 --- a/src/oauth2.erl +++ b/src/oauth2.erl @@ -35,15 +35,11 @@ -export([authorize_directly/4]). -export([issue_code/2]). -export([issue_token/2]). --export([issue_jwt/2]). -export([issue_token_and_refresh/2]). --export([issue_jwt_and_refresh/3]). -export([verify_access_token/2]). -export([verify_access_code/2]). -export([verify_access_code/3]). --export([verify_jwt/1]). -export([refresh_access_token/4]). --export([refresh_jwt/4]). -export_type([token/0]). -export_type([user/0]). @@ -63,28 +59,29 @@ %%%_ * Types ----------------------------------------------------------- %% Opaque authentication record -record(a, { client = undefined :: undefined | client() + , device_id = undefined :: undefined | device_id() , resowner = undefined :: undefined | resowner() , scope :: scope() , ttl = 0 :: non_neg_integer() - , issuer = undefined :: undefined | binary() }). --type context() :: proplists:proplist(). --type auth() :: #a{}. --type user() :: any(). %% Opaque User Object --type client() :: any(). %% Opaque Client Object --type resowner() :: any(). %% Opaque Resource Owner 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 | atom(). +-type context() :: proplists:proplist(). +-type auth() :: #a{}. +-type user() :: any(). %% Opaque User Object +-type client() :: any(). %% Opaque Client Object +-type resowner() :: any(). %% Opaque Resource Owner Object +-type rediruri() :: any(). %% Opaque Redirection URI +-type device_id() :: any(). +-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 | atom(). %%%_* Code ============================================================= %%%_ * API ------------------------------------------------------------- @@ -209,7 +206,7 @@ authorize_code_request(User, Client, RedirUri, Scope, Ctx0) -> %% @doc Sometimes one wishes to authorize directly with a specific scope and/or %% a specific TTL, and this function is for that. -spec authorize_directly(client(), resowner(), scope(), non_neg_integer()) -> - {ok, auth()}. + auth(). authorize_directly(Client, ResOwner, Scope, TTL) -> #a{ client = Client , resowner = ResOwner @@ -249,20 +246,6 @@ issue_token(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) -> , Ctx0 ), {ok, {Ctx1, oauth2_response:new(AccessToken, TTL, Owner, Scope)}}. -%% @doc Issues an JWT without refresh token from an authorization. --spec issue_jwt(auth(), appctx()) -> {ok, {appctx(), context(), response()}}. -issue_jwt(#a{ client = Client - , resowner = ResOwner - , scope = Scope - , ttl = TTL - , issuer = Issuer}, Ctx) -> - ExpiryTime = seconds_since_epoch(TTL), - IssuedAt = seconds_since_epoch(0), - AccessCtx = build_jwt_context( Issuer, ResOwner, ExpiryTime, IssuedAt - , Client, Scope), - {ok, JWT} = ?BACKEND:jwt_sign(AccessCtx, Ctx), - {ok, {Ctx, AccessCtx, oauth2_response:new(JWT, TTL)}}. - %% @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 @@ -276,7 +259,7 @@ 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} +issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL, device_id = DeviceId} , Ctx0 ) -> RTTL = oauth2_config:expiry_time(refresh_token), RefreshCtx = build_context(Client,seconds_since_epoch(RTTL),Owner,Scope), @@ -286,9 +269,11 @@ issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL} {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken , AccessCtx , Ctx0), - {ok, Ctx2} = ?BACKEND:associate_refresh_token( RefreshToken - , RefreshCtx - , Ctx1 ), + {ok, Ctx2} = + case DeviceId of + undefined -> ?BACKEND:associate_refresh_token(RefreshToken, RefreshCtx, Ctx1); + _ -> ?BACKEND:associate_refresh_token(RefreshToken, RefreshCtx, DeviceId, Ctx1) + end, {ok, {Ctx2, oauth2_response:new( AccessToken , TTL , Owner @@ -296,37 +281,6 @@ issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL} , RefreshToken , RTTL )}}. -%% @doc Issues JWT and refresh token from an authorization. --spec issue_jwt_and_refresh(auth(), binary(), appctx()) -> - {ok, {appctx(), context(), response()}} - | {error, invalid_authorization}. -issue_jwt_and_refresh(#a{client = undefined}, _, _) -> - {error, invalid_authorization}; -issue_jwt_and_refresh(#a{resowner = undefined}, _, _) -> - {error, invalid_authorization}; -issue_jwt_and_refresh( #a{ client = Client - , resowner = ResOwner - , scope = Scope - , ttl = TTL - , issuer = Issuer} - , DeviceId - , Ctx0) -> - % access_token - AccessExpiry = seconds_since_epoch(TTL), - IssuedAt = seconds_since_epoch(0), - AccessCtx = build_jwt_context( Issuer, ResOwner, AccessExpiry, IssuedAt - , Client, Scope), - {ok, JWT} = ?BACKEND:jwt_sign(AccessCtx, Ctx0), - - % refresh_token - RefreshTTL = oauth2_config:expiry_time(jwt_refresh_token), - RefreshExpiry = seconds_since_epoch(RefreshTTL), - RefreshCtx = build_context(Client, RefreshExpiry, ResOwner, Scope), - RefreshToken = ?TOKEN:generate(RefreshCtx), - {ok, Ctx1} = ?BACKEND:associate_refresh_token( RefreshToken, RefreshCtx - , DeviceId, Ctx0), - {ok, {Ctx1, AccessCtx, oauth2_response:new(JWT, TTL, RefreshToken)}}. - %% @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()}} @@ -366,46 +320,16 @@ verify_access_code(AccessCode, Client, Ctx0) -> -> {ok, {appctx(), response()}} | {error, error()}. refresh_access_token(Client, RefreshToken, Scope, Ctx0) -> case verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) of - {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, _DeviceId}} -> + {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, DeviceId}} -> issue_token(#a{ client = ClientId , resowner = ResOwner , scope = VerifiedScope , ttl = TTL + , device_id = DeviceId }, Ctx1); {error, _} = E -> E end. -%% @doc Validates a request for a JWT from a refresh token, issuing a new JWT -%% if valid. --spec refresh_jwt(client(), token(), scope(), appctx()) -> - {ok, {appctx(), context(), response()}} - | {error, error()}. -refresh_jwt(Client, RefreshToken, Scope, Ctx0) -> - case verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) of - {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, DeviceId}} -> - % RFC 6749 Section 10.4 (Security Considerations for refresh_token) - % - % Authorization server could employ refresh token rotation in which - % a new refresh token is issued with every access token refresh - % response. The previous refresh token is invalidated but retained - % by the authorization server. If a refresh token is compromised - % and subsequently used by both the attacker and the legitimate - % client, one of them will present an invalidated refresh token, - % which will inform the authorization server of the breach. - % - % TODO: implement this - ?BACKEND:revoke_refresh_token(RefreshToken, Ctx1), - issue_jwt_and_refresh( #a{ client = ClientId - , resowner = ResOwner - , scope = VerifiedScope - , ttl = TTL - , issuer = ?BACKEND:jwt_issuer() - } - , DeviceId - , Ctx1); - {error, _} = E -> E - 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()}} @@ -422,19 +346,6 @@ verify_access_token(AccessToken, Ctx0) -> end end. -%% @doc Verifies a JWT, returning its associated context if successful. -%% Otherwise, an OAuth2 error code is returned. --spec verify_jwt(token()) -> {ok, context()} | {error, error()}. -verify_jwt(JWT) -> - case ?BACKEND:jwt_verify(JWT) of - {error, _} -> {error, access_denied}; - {ok, GrantCtx} -> - case get_(GrantCtx, <<"exp">>) > seconds_since_epoch(0) of - true -> {ok, GrantCtx}; - false -> {error, access_denied} - end - end. - %%%_* Private functions ================================================ auth_user(User, Scope0, Ctx0) -> case ?BACKEND:authenticate_user(User, Ctx0) of @@ -447,7 +358,6 @@ auth_user(User, Scope0, Ctx0) -> , scope = Scope1 , ttl = oauth2_config:expiry_time( password_credentials) - , issuer = ?BACKEND:jwt_issuer() }}} end end. @@ -480,7 +390,7 @@ verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) -> {ok, {Ctx3, VerifiedScope}} -> {ok, ClientId} = get(GrantCtx, <<"client">>), {ok, ResOwner} = get(GrantCtx, <<"resource_owner">>), - {ok, DeviceId} = get(GrantCtx, <<"device_id">>), + DeviceId = get(GrantCtx, <<"device_id">>, undefined), TTL = oauth2_config:expiry_time( password_credentials), {ok, { Ctx3, ClientId, ResOwner @@ -504,15 +414,6 @@ build_context(Client, ExpiryTime, ResOwner, Scope) -> build_context(Client, ExpiryTime, ResOwner, Scope, RefreshToken) -> [{<<"refresh_token">>, RefreshToken} | build_context(Client, ExpiryTime, ResOwner, Scope)]. -build_jwt_context(Issuer, ResOwner, ExpiryTime, IssuedAt, Client, Scope) -> - [ {<<"iss">>, Issuer} - , {<<"sub">>, ResOwner} - , {<<"exp">>, ExpiryTime} - , {<<"iat">>, IssuedAt} - , {<<"client">>, Client} - , {<<"scope">>, Scope} - ]. - -spec seconds_since_epoch(integer()) -> non_neg_integer(). seconds_since_epoch(Diff) -> {Mega, Secs, _} = os:timestamp(), @@ -524,6 +425,12 @@ get(O, K) -> false -> {error, notfound} end. +get(O, K, D) -> + case get(O, K) of + {ok, V} -> V; + {error, notfound} -> D + end. + get_(O, K) -> {ok, V} = get(O, K), V. diff --git a/src/oauth2_backend.erl b/src/oauth2_backend.erl index edcbd10..1377d84 100644 --- a/src/oauth2_backend.erl +++ b/src/oauth2_backend.erl @@ -112,18 +112,6 @@ -callback verify_scope(scope(), scope(), appctx()) -> {ok, {appctx(), scope()}} | {error, notfound | badscope}. -%% @doc Sign the grant context with a private key and produce a JWT. -%% The grant context is a proplist carrying information about the identity -%% with which the token is associated, when it expires, etc. --callback jwt_sign(grantctx(), appctx()) -> {ok, token()}. - -%% @doc Verifies a JWT, returning the corresponding grant context if -%% verification succeeds. --callback jwt_verify(token()) -> {ok, grantctx()} | {error, badjwt}. - -%% @doc A case-sensitive string or URI that uniquely identifies the issuer. --callback jwt_issuer() -> binary(). - %%%_* Tests ============================================================ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/src/oauth2_response.erl b/src/oauth2_response.erl index 286f6be..f4e32d5 100644 --- a/src/oauth2_response.erl +++ b/src/oauth2_response.erl @@ -151,7 +151,7 @@ refresh_token(#response{refresh_token = RefreshToken}) -> {ok, RefreshToken}. refresh_token(Response, NewRefreshToken) -> Response#response{refresh_token = NewRefreshToken}. --spec refresh_token_expires_in(response()) -> {ok, lifetime()} | {error, not_set}. +-spec refresh_token_expires_in(response()) -> {ok, undefined | 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}) -> @@ -218,15 +218,21 @@ to_binary([Binary]) when is_binary(Binary) -> to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) -> <>; to_binary(List) when is_list(List) -> - to_binary(list_to_binary(List)); + erlang:display({list, List}), + try + to_binary(list_to_binary(List)) + catch + error:badarg -> + to_binary(lists:flatten(lists:map(fun to_binary/1, List))) + end; 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({_Key, _Value} = Tuple) -> + to_binary(tuple_to_list(Tuple)); to_binary(Term) -> to_binary(term_to_binary(Term)). diff --git a/test/oauth2_mock_backend.erl b/test/oauth2_mock_backend.erl index eb65743..6e3919d 100644 --- a/test/oauth2_mock_backend.erl +++ b/test/oauth2_mock_backend.erl @@ -34,6 +34,7 @@ -export([get_client_identity/2]). -export([associate_access_code/3]). -export([associate_refresh_token/3]). +-export([associate_refresh_token/4]). -export([associate_access_token/3]). -export([resolve_access_code/2]). -export([resolve_refresh_token/2]). @@ -86,6 +87,10 @@ associate_refresh_token(RefreshToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {RefreshToken, Context}), {ok, AppContext}. +associate_refresh_token(RefreshToken, Context, DeviceId, AppContext) -> + ets:insert(?ETS_TABLE, {RefreshToken, [{<<"device_id">>, DeviceId} | Context ]}), + {ok, AppContext}. + associate_access_token(AccessToken, Context, AppContext) -> ets:insert(?ETS_TABLE, {AccessToken, Context}), {ok, AppContext}.