mirror of
https://github.com/valitydev/token-keeper.git
synced 2024-11-06 02:15:21 +00:00
Stop requiring exp claim when not needed (#7)
This commit is contained in:
parent
5479ffa917
commit
e8f9eecb04
@ -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"}
|
||||
}
|
||||
}
|
||||
|
@ -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) ->
|
||||
#{
|
||||
|
@ -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
|
||||
}.
|
||||
|
||||
|
@ -108,7 +108,6 @@ create_token_data(ID, #{authority_id := AuthorityID, token_type := TokenType}) -
|
||||
id => ID,
|
||||
type => TokenType,
|
||||
authority_id => AuthorityID,
|
||||
expiration => unlimited,
|
||||
payload => #{}
|
||||
}.
|
||||
|
||||
|
@ -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]).
|
||||
|
@ -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) ->
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user