diff --git a/config/sys.config b/config/sys.config index e7884e8..a1308db 100644 --- a/config/sys.config +++ b/config/sys.config @@ -141,19 +141,19 @@ jwt => #{ %% Provides the mapping between key id and authority id authority_bindings => #{ - <<"com.rbkmoney.apikeymgmt">> => <<"apikeymgmt">>, - <<"com.rbkmoney.access.capi">> => <<"access.capi">>, - <<"com.rbkmoney.keycloak">> => <<"keycloak">> + <<"key.apikeymgmt">> => <<"com.rbkmoney.apikeymgmt">>, + <<"key.access.capi">> => <<"com.rbkmoney.access.capi">>, + <<"key.keycloak">> => <<"com.rbkmoney.keycloak">> }, %% Provides a set of keys keyset => #{ - <<"apikeymgmt">> => #{ + <<"key.apikeymgmt">> => #{ source => {pem_file, "keys/apikeymgmt/private.pem"} }, - <<"access.capi">> => #{ + <<"key.access.capi">> => #{ source => {pem_file, "keys/capi/private.pem"} }, - <<"keycloak">> => #{ + <<"key.keycloak">> => #{ source => {pem_file, "keys/keycloak/public.pem"} } } diff --git a/src/tk_context_extractor_user_session_token.erl b/src/tk_context_extractor_user_session_token.erl index 217ed3f..3fb8334 100644 --- a/src/tk_context_extractor_user_session_token.erl +++ b/src/tk_context_extractor_user_session_token.erl @@ -20,13 +20,14 @@ -define(CLAIM_USER_ID, <<"sub">>). -define(CLAIM_USER_EMAIL, <<"email">>). +-define(CLAIM_EXPIRES_AT, <<"exp">>). %% API functions -spec extract_context(tk_token:token_data(), opts()) -> tk_context_extractor:extracted_context() | undefined. -extract_context(#{id := TokenID, expiration := Expiration, payload := Payload}, Opts) -> - case extract_user_data(Payload) of - {ok, {UserID, UserEmail}} -> +extract_context(#{id := TokenID, payload := Payload}, Opts) -> + case extract_payload_data(Payload) of + {ok, {UserID, UserEmail, Expiration}} -> create_context_and_metadata(TokenID, Expiration, UserID, UserEmail, Opts); {error, Reason} -> _ = logger:warning("Could not extract user_session_token context, reason: ~p", [Reason]), @@ -45,13 +46,14 @@ create_context_and_metadata(TokenID, TokenExpiration, UserID, UserEmail, Opts) - ) }. -extract_user_data(#{ +extract_payload_data(#{ ?CLAIM_USER_ID := UserID, - ?CLAIM_USER_EMAIL := UserEmail + ?CLAIM_USER_EMAIL := UserEmail, + ?CLAIM_EXPIRES_AT := Expiration }) -> - {ok, {UserID, UserEmail}}; -extract_user_data(Payload) -> - RequiredKeys = [?CLAIM_USER_ID, ?CLAIM_USER_EMAIL], + {ok, {UserID, UserEmail, Expiration}}; +extract_payload_data(Payload) -> + RequiredKeys = [?CLAIM_USER_ID, ?CLAIM_USER_EMAIL, ?CLAIM_EXPIRES_AT], {error, {missing, RequiredKeys -- maps:keys(Payload)}}. create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) -> @@ -73,10 +75,10 @@ create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) -> Acc1 ). +make_auth_expiration(0) -> + undefined; make_auth_expiration(Timestamp) when is_integer(Timestamp) -> - genlib_rfc3339:format(Timestamp, second); -make_auth_expiration(Expiration) when Expiration =:= unlimited -> - undefined. + genlib_rfc3339:format(Timestamp, second). create_metadata(UserID, UserEmail, UserRealm) -> #{ diff --git a/src/tk_handler_authority_ephemeral.erl b/src/tk_handler_authority_ephemeral.erl index 223b34f..2120da7 100644 --- a/src/tk_handler_authority_ephemeral.erl +++ b/src/tk_handler_authority_ephemeral.erl @@ -67,7 +67,6 @@ create_token_data(Claims, #{authority_id := AuthorityID, token_type := TokenType id => unique_id(), type => TokenType, authority_id => AuthorityID, - expiration => unlimited, payload => Claims }. diff --git a/src/tk_handler_authority_offline.erl b/src/tk_handler_authority_offline.erl index 597a51c..bc9e0f5 100644 --- a/src/tk_handler_authority_offline.erl +++ b/src/tk_handler_authority_offline.erl @@ -108,7 +108,6 @@ create_token_data(ID, #{authority_id := AuthorityID, token_type := TokenType}) - id => ID, type => TokenType, authority_id => AuthorityID, - expiration => unlimited, payload => #{} }. diff --git a/src/tk_token.erl b/src/tk_token.erl index 8926fdb..7e5a079 100644 --- a/src/tk_token.erl +++ b/src/tk_token.erl @@ -19,7 +19,6 @@ -type token_data() :: #{ id := token_id(), type := token_type(), - expiration := expiration(), payload := payload(), authority_id := authority_id(), source_context => source_context() @@ -27,7 +26,6 @@ -type token_id() :: binary(). -type token_type() :: jwt. --type expiration() :: unlimited | non_neg_integer(). -type payload() :: map(). -type authority_id() :: tk_authdata:authority_id(). -type source_context() :: #{ @@ -39,7 +37,6 @@ -export_type([token_id/0]). -export_type([token_type/0]). --export_type([expiration/0]). -export_type([payload/0]). -export_type([authority_id/0]). -export_type([source_context/0]). diff --git a/src/tk_token_jwt.erl b/src/tk_token_jwt.erl index 76fb6a8..a030a48 100644 --- a/src/tk_token_jwt.erl +++ b/src/tk_token_jwt.erl @@ -47,7 +47,6 @@ %% -define(CLAIM_TOKEN_ID, <<"jti">>). --define(CLAIM_EXPIRES_AT, <<"exp">>). -define(PTERM_KEY(Key), {?MODULE, Key}). -define(KEY_BY_KEY_ID(KeyID), ?PTERM_KEY({key_id, KeyID})). @@ -72,6 +71,7 @@ child_spec(TokenOpts) -> init(#{keyset := KeySet, authority_bindings := AuthorityBindings}) -> Keys = load_keys(KeySet), _ = assert_keys_unique(Keys), + _ = assert_bindings_unique(AuthorityBindings), _ = store_keys(Keys), _ = store_authority_bindings(AuthorityBindings), {ok, {#{}, []}}. @@ -180,6 +180,17 @@ assert_key_unique(#{key_id := KeyID, key_name := KeyName}, SeenKeyIDs) -> %% +assert_bindings_unique(Bindings) -> + maps:fold(fun assert_bindings_unique/3, [], Bindings). + +assert_bindings_unique(Key, Value, SeenBindings) -> + case lists:member(Value, SeenBindings) of + true -> exit({import_error, {duplicate_authority_binding, Key, Value}}); + false -> [Value | SeenBindings] + end. + +%% + store_keys(KeyInfos) -> lists:foreach(fun store_key/1, KeyInfos). @@ -245,17 +256,11 @@ construct_token_data(Claims, SourceContext, AuthorityID) -> #{ id => maps:get(?CLAIM_TOKEN_ID, Claims), type => jwt, - expiration => decode_expiration(maps:get(?CLAIM_EXPIRES_AT, Claims)), payload => Claims, authority_id => AuthorityID, source_context => SourceContext }. -decode_expiration(0) -> - unlimited; -decode_expiration(Expiration) when is_integer(Expiration) -> - Expiration. - %% Signing issue_with_key(KeyName, TokenData) -> @@ -271,22 +276,14 @@ issue_with_key(KeyName, TokenData) -> {error, {key_does_not_exist, KeyName}} end. -construct_claims(#{id := TokenID, expiration := Expiration, payload := Claims}) -> +construct_claims(#{id := TokenID, payload := Claims}) -> maps:map(fun encode_claim/2, Claims#{ - ?CLAIM_TOKEN_ID => TokenID, - ?CLAIM_EXPIRES_AT => Expiration + ?CLAIM_TOKEN_ID => TokenID }). -encode_claim(?CLAIM_EXPIRES_AT, Expiration) -> - encode_expires_at(Expiration); encode_claim(_, Value) -> Value. -encode_expires_at(unlimited) -> - 0; -encode_expires_at(Dl) -> - Dl. - %% put_key(KeyID, KeyName, KeyInfo) -> diff --git a/test/token_keeper_SUITE.erl b/test/token_keeper_SUITE.erl index 7fc0eb2..f92fe84 100644 --- a/test/token_keeper_SUITE.erl +++ b/test/token_keeper_SUITE.erl @@ -24,6 +24,7 @@ -export([authenticate_user_session_token_no_payload_claims_fail/1]). -export([authenticate_phony_api_key_token_ok/1]). -export([authenticate_user_session_token_ok/1]). +-export([authenticate_user_session_token_w_exp_ok/1]). -export([authenticate_blacklisted_jti_fail/1]). -export([authenticate_non_blacklisted_jti_ok/1]). -export([authenticate_ephemeral_claim_token_ok/1]). @@ -87,7 +88,8 @@ groups() -> authenticate_no_payload_claims_fail, authenticate_user_session_token_no_payload_claims_fail, authenticate_phony_api_key_token_ok, - authenticate_user_session_token_ok + authenticate_user_session_token_ok, + authenticate_user_session_token_w_exp_ok ]}, {ephemeral, [parallel], [ authenticate_invalid_token_type_fail, @@ -370,7 +372,7 @@ authenticate_user_session_token_ok(C) -> JTI = unique_id(), SubjectID = unique_id(), SubjectEmail = <<"test@test.test">>, - Claims = get_user_session_token_claims(JTI, SubjectID, SubjectEmail), + Claims = get_user_session_token_claims(JTI, 0, SubjectID, SubjectEmail), Token = issue_token(Claims, C), #token_keeper_AuthData{ id = undefined, @@ -384,7 +386,19 @@ authenticate_user_session_token_ok(C) -> }, authority = ?TK_AUTHORITY_KEYCLOAK } = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT(?USER_TOKEN_SOURCE), C), - _ = assert_context({user_session_token, JTI, SubjectID, SubjectEmail, unlimited}, Context). + _ = assert_context({user_session_token, JTI, SubjectID, SubjectEmail, undefined}, Context). + +-spec authenticate_user_session_token_w_exp_ok(config()) -> _. +authenticate_user_session_token_w_exp_ok(C) -> + JTI = unique_id(), + SubjectID = unique_id(), + SubjectEmail = <<"test@test.test">>, + Claims = get_user_session_token_claims(JTI, 42, SubjectID, SubjectEmail), + Token = issue_token(Claims, C), + #token_keeper_AuthData{ + context = Context + } = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT(?USER_TOKEN_SOURCE), C), + _ = assert_context({user_session_token, JTI, SubjectID, SubjectEmail, make_auth_expiration(42)}, Context). -spec authenticate_user_session_token_no_payload_claims_fail(config()) -> _. authenticate_user_session_token_no_payload_claims_fail(C) -> @@ -570,17 +584,23 @@ revoke_authdata_by_id_not_found_fail(C) -> %% +make_auth_expiration(Timestamp) -> + genlib_rfc3339:format(Timestamp, second). + get_base_claims(JTI) -> + get_base_claims(JTI, 0). + +get_base_claims(JTI, Exp) -> #{ <<"jti">> => JTI, - <<"exp">> => 0 + <<"exp">> => Exp }. get_phony_api_key_claims(JTI, SubjectID) -> maps:merge(#{<<"sub">> => SubjectID}, get_base_claims(JTI)). -get_user_session_token_claims(JTI, SubjectID, SubjectEmail) -> - maps:merge(#{<<"sub">> => SubjectID, <<"email">> => SubjectEmail}, get_base_claims(JTI)). +get_user_session_token_claims(JTI, Exp, SubjectID, SubjectEmail) -> + maps:merge(#{<<"sub">> => SubjectID, <<"email">> => SubjectEmail}, get_base_claims(JTI, Exp)). create_bouncer_context(JTI) -> bouncer_context_helpers:add_auth( @@ -682,7 +702,7 @@ assert_auth({api_key_token, JTI, SubjectID}, Auth) -> assert_auth({user_session_token, JTI, _SubjectID, _SubjectEmail, Exp}, Auth) -> ?assertEqual(<<"SessionToken">>, Auth#bctx_v1_Auth.method), ?assertMatch(#bctx_v1_Token{id = JTI}, Auth#bctx_v1_Auth.token), - ?assertEqual(make_auth_expiration(Exp), Auth#bctx_v1_Auth.expiration). + ?assertEqual(Exp, Auth#bctx_v1_Auth.expiration). assert_user({claim_token, _}, undefined) -> ok; @@ -693,9 +713,6 @@ assert_user({user_session_token, _JTI, SubjectID, SubjectEmail, _Exp}, User) -> ?assertEqual(SubjectEmail, User#bctx_v1_User.email), ?assertEqual(?CTX_ENTITY(<<"external">>), User#bctx_v1_User.realm). -make_auth_expiration(unlimited) -> - undefined. - %% -include_lib("jose/include/jose_jwk.hrl"). @@ -741,6 +758,7 @@ unique_id() -> genlib_format:format_int_base(ID, 62). %% + start_keeper(Env) -> Port = 8022, Apps = genlib_app:start_application_with(