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 => #{
|
jwt => #{
|
||||||
%% Provides the mapping between key id and authority id
|
%% Provides the mapping between key id and authority id
|
||||||
authority_bindings => #{
|
authority_bindings => #{
|
||||||
<<"com.rbkmoney.apikeymgmt">> => <<"apikeymgmt">>,
|
<<"key.apikeymgmt">> => <<"com.rbkmoney.apikeymgmt">>,
|
||||||
<<"com.rbkmoney.access.capi">> => <<"access.capi">>,
|
<<"key.access.capi">> => <<"com.rbkmoney.access.capi">>,
|
||||||
<<"com.rbkmoney.keycloak">> => <<"keycloak">>
|
<<"key.keycloak">> => <<"com.rbkmoney.keycloak">>
|
||||||
},
|
},
|
||||||
%% Provides a set of keys
|
%% Provides a set of keys
|
||||||
keyset => #{
|
keyset => #{
|
||||||
<<"apikeymgmt">> => #{
|
<<"key.apikeymgmt">> => #{
|
||||||
source => {pem_file, "keys/apikeymgmt/private.pem"}
|
source => {pem_file, "keys/apikeymgmt/private.pem"}
|
||||||
},
|
},
|
||||||
<<"access.capi">> => #{
|
<<"key.access.capi">> => #{
|
||||||
source => {pem_file, "keys/capi/private.pem"}
|
source => {pem_file, "keys/capi/private.pem"}
|
||||||
},
|
},
|
||||||
<<"keycloak">> => #{
|
<<"key.keycloak">> => #{
|
||||||
source => {pem_file, "keys/keycloak/public.pem"}
|
source => {pem_file, "keys/keycloak/public.pem"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,14 @@
|
|||||||
|
|
||||||
-define(CLAIM_USER_ID, <<"sub">>).
|
-define(CLAIM_USER_ID, <<"sub">>).
|
||||||
-define(CLAIM_USER_EMAIL, <<"email">>).
|
-define(CLAIM_USER_EMAIL, <<"email">>).
|
||||||
|
-define(CLAIM_EXPIRES_AT, <<"exp">>).
|
||||||
|
|
||||||
%% API functions
|
%% API functions
|
||||||
|
|
||||||
-spec extract_context(tk_token:token_data(), opts()) -> tk_context_extractor:extracted_context() | undefined.
|
-spec extract_context(tk_token:token_data(), opts()) -> tk_context_extractor:extracted_context() | undefined.
|
||||||
extract_context(#{id := TokenID, expiration := Expiration, payload := Payload}, Opts) ->
|
extract_context(#{id := TokenID, payload := Payload}, Opts) ->
|
||||||
case extract_user_data(Payload) of
|
case extract_payload_data(Payload) of
|
||||||
{ok, {UserID, UserEmail}} ->
|
{ok, {UserID, UserEmail, Expiration}} ->
|
||||||
create_context_and_metadata(TokenID, Expiration, UserID, UserEmail, Opts);
|
create_context_and_metadata(TokenID, Expiration, UserID, UserEmail, Opts);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
_ = logger:warning("Could not extract user_session_token context, reason: ~p", [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_ID := UserID,
|
||||||
?CLAIM_USER_EMAIL := UserEmail
|
?CLAIM_USER_EMAIL := UserEmail,
|
||||||
|
?CLAIM_EXPIRES_AT := Expiration
|
||||||
}) ->
|
}) ->
|
||||||
{ok, {UserID, UserEmail}};
|
{ok, {UserID, UserEmail, Expiration}};
|
||||||
extract_user_data(Payload) ->
|
extract_payload_data(Payload) ->
|
||||||
RequiredKeys = [?CLAIM_USER_ID, ?CLAIM_USER_EMAIL],
|
RequiredKeys = [?CLAIM_USER_ID, ?CLAIM_USER_EMAIL, ?CLAIM_EXPIRES_AT],
|
||||||
{error, {missing, RequiredKeys -- maps:keys(Payload)}}.
|
{error, {missing, RequiredKeys -- maps:keys(Payload)}}.
|
||||||
|
|
||||||
create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) ->
|
create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) ->
|
||||||
@ -73,10 +75,10 @@ create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) ->
|
|||||||
Acc1
|
Acc1
|
||||||
).
|
).
|
||||||
|
|
||||||
|
make_auth_expiration(0) ->
|
||||||
|
undefined;
|
||||||
make_auth_expiration(Timestamp) when is_integer(Timestamp) ->
|
make_auth_expiration(Timestamp) when is_integer(Timestamp) ->
|
||||||
genlib_rfc3339:format(Timestamp, second);
|
genlib_rfc3339:format(Timestamp, second).
|
||||||
make_auth_expiration(Expiration) when Expiration =:= unlimited ->
|
|
||||||
undefined.
|
|
||||||
|
|
||||||
create_metadata(UserID, UserEmail, UserRealm) ->
|
create_metadata(UserID, UserEmail, UserRealm) ->
|
||||||
#{
|
#{
|
||||||
|
@ -67,7 +67,6 @@ create_token_data(Claims, #{authority_id := AuthorityID, token_type := TokenType
|
|||||||
id => unique_id(),
|
id => unique_id(),
|
||||||
type => TokenType,
|
type => TokenType,
|
||||||
authority_id => AuthorityID,
|
authority_id => AuthorityID,
|
||||||
expiration => unlimited,
|
|
||||||
payload => Claims
|
payload => Claims
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
@ -108,7 +108,6 @@ create_token_data(ID, #{authority_id := AuthorityID, token_type := TokenType}) -
|
|||||||
id => ID,
|
id => ID,
|
||||||
type => TokenType,
|
type => TokenType,
|
||||||
authority_id => AuthorityID,
|
authority_id => AuthorityID,
|
||||||
expiration => unlimited,
|
|
||||||
payload => #{}
|
payload => #{}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
-type token_data() :: #{
|
-type token_data() :: #{
|
||||||
id := token_id(),
|
id := token_id(),
|
||||||
type := token_type(),
|
type := token_type(),
|
||||||
expiration := expiration(),
|
|
||||||
payload := payload(),
|
payload := payload(),
|
||||||
authority_id := authority_id(),
|
authority_id := authority_id(),
|
||||||
source_context => source_context()
|
source_context => source_context()
|
||||||
@ -27,7 +26,6 @@
|
|||||||
|
|
||||||
-type token_id() :: binary().
|
-type token_id() :: binary().
|
||||||
-type token_type() :: jwt.
|
-type token_type() :: jwt.
|
||||||
-type expiration() :: unlimited | non_neg_integer().
|
|
||||||
-type payload() :: map().
|
-type payload() :: map().
|
||||||
-type authority_id() :: tk_authdata:authority_id().
|
-type authority_id() :: tk_authdata:authority_id().
|
||||||
-type source_context() :: #{
|
-type source_context() :: #{
|
||||||
@ -39,7 +37,6 @@
|
|||||||
|
|
||||||
-export_type([token_id/0]).
|
-export_type([token_id/0]).
|
||||||
-export_type([token_type/0]).
|
-export_type([token_type/0]).
|
||||||
-export_type([expiration/0]).
|
|
||||||
-export_type([payload/0]).
|
-export_type([payload/0]).
|
||||||
-export_type([authority_id/0]).
|
-export_type([authority_id/0]).
|
||||||
-export_type([source_context/0]).
|
-export_type([source_context/0]).
|
||||||
|
@ -47,7 +47,6 @@
|
|||||||
%%
|
%%
|
||||||
|
|
||||||
-define(CLAIM_TOKEN_ID, <<"jti">>).
|
-define(CLAIM_TOKEN_ID, <<"jti">>).
|
||||||
-define(CLAIM_EXPIRES_AT, <<"exp">>).
|
|
||||||
|
|
||||||
-define(PTERM_KEY(Key), {?MODULE, Key}).
|
-define(PTERM_KEY(Key), {?MODULE, Key}).
|
||||||
-define(KEY_BY_KEY_ID(KeyID), ?PTERM_KEY({key_id, KeyID})).
|
-define(KEY_BY_KEY_ID(KeyID), ?PTERM_KEY({key_id, KeyID})).
|
||||||
@ -72,6 +71,7 @@ child_spec(TokenOpts) ->
|
|||||||
init(#{keyset := KeySet, authority_bindings := AuthorityBindings}) ->
|
init(#{keyset := KeySet, authority_bindings := AuthorityBindings}) ->
|
||||||
Keys = load_keys(KeySet),
|
Keys = load_keys(KeySet),
|
||||||
_ = assert_keys_unique(Keys),
|
_ = assert_keys_unique(Keys),
|
||||||
|
_ = assert_bindings_unique(AuthorityBindings),
|
||||||
_ = store_keys(Keys),
|
_ = store_keys(Keys),
|
||||||
_ = store_authority_bindings(AuthorityBindings),
|
_ = store_authority_bindings(AuthorityBindings),
|
||||||
{ok, {#{}, []}}.
|
{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) ->
|
store_keys(KeyInfos) ->
|
||||||
lists:foreach(fun store_key/1, 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),
|
id => maps:get(?CLAIM_TOKEN_ID, Claims),
|
||||||
type => jwt,
|
type => jwt,
|
||||||
expiration => decode_expiration(maps:get(?CLAIM_EXPIRES_AT, Claims)),
|
|
||||||
payload => Claims,
|
payload => Claims,
|
||||||
authority_id => AuthorityID,
|
authority_id => AuthorityID,
|
||||||
source_context => SourceContext
|
source_context => SourceContext
|
||||||
}.
|
}.
|
||||||
|
|
||||||
decode_expiration(0) ->
|
|
||||||
unlimited;
|
|
||||||
decode_expiration(Expiration) when is_integer(Expiration) ->
|
|
||||||
Expiration.
|
|
||||||
|
|
||||||
%% Signing
|
%% Signing
|
||||||
|
|
||||||
issue_with_key(KeyName, TokenData) ->
|
issue_with_key(KeyName, TokenData) ->
|
||||||
@ -271,22 +276,14 @@ issue_with_key(KeyName, TokenData) ->
|
|||||||
{error, {key_does_not_exist, KeyName}}
|
{error, {key_does_not_exist, KeyName}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
construct_claims(#{id := TokenID, expiration := Expiration, payload := Claims}) ->
|
construct_claims(#{id := TokenID, payload := Claims}) ->
|
||||||
maps:map(fun encode_claim/2, Claims#{
|
maps:map(fun encode_claim/2, Claims#{
|
||||||
?CLAIM_TOKEN_ID => TokenID,
|
?CLAIM_TOKEN_ID => TokenID
|
||||||
?CLAIM_EXPIRES_AT => Expiration
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
encode_claim(?CLAIM_EXPIRES_AT, Expiration) ->
|
|
||||||
encode_expires_at(Expiration);
|
|
||||||
encode_claim(_, Value) ->
|
encode_claim(_, Value) ->
|
||||||
Value.
|
Value.
|
||||||
|
|
||||||
encode_expires_at(unlimited) ->
|
|
||||||
0;
|
|
||||||
encode_expires_at(Dl) ->
|
|
||||||
Dl.
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
put_key(KeyID, KeyName, KeyInfo) ->
|
put_key(KeyID, KeyName, KeyInfo) ->
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
-export([authenticate_user_session_token_no_payload_claims_fail/1]).
|
-export([authenticate_user_session_token_no_payload_claims_fail/1]).
|
||||||
-export([authenticate_phony_api_key_token_ok/1]).
|
-export([authenticate_phony_api_key_token_ok/1]).
|
||||||
-export([authenticate_user_session_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_blacklisted_jti_fail/1]).
|
||||||
-export([authenticate_non_blacklisted_jti_ok/1]).
|
-export([authenticate_non_blacklisted_jti_ok/1]).
|
||||||
-export([authenticate_ephemeral_claim_token_ok/1]).
|
-export([authenticate_ephemeral_claim_token_ok/1]).
|
||||||
@ -87,7 +88,8 @@ groups() ->
|
|||||||
authenticate_no_payload_claims_fail,
|
authenticate_no_payload_claims_fail,
|
||||||
authenticate_user_session_token_no_payload_claims_fail,
|
authenticate_user_session_token_no_payload_claims_fail,
|
||||||
authenticate_phony_api_key_token_ok,
|
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], [
|
{ephemeral, [parallel], [
|
||||||
authenticate_invalid_token_type_fail,
|
authenticate_invalid_token_type_fail,
|
||||||
@ -370,7 +372,7 @@ authenticate_user_session_token_ok(C) ->
|
|||||||
JTI = unique_id(),
|
JTI = unique_id(),
|
||||||
SubjectID = unique_id(),
|
SubjectID = unique_id(),
|
||||||
SubjectEmail = <<"test@test.test">>,
|
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 = issue_token(Claims, C),
|
||||||
#token_keeper_AuthData{
|
#token_keeper_AuthData{
|
||||||
id = undefined,
|
id = undefined,
|
||||||
@ -384,7 +386,19 @@ authenticate_user_session_token_ok(C) ->
|
|||||||
},
|
},
|
||||||
authority = ?TK_AUTHORITY_KEYCLOAK
|
authority = ?TK_AUTHORITY_KEYCLOAK
|
||||||
} = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT(?USER_TOKEN_SOURCE), C),
|
} = 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()) -> _.
|
-spec authenticate_user_session_token_no_payload_claims_fail(config()) -> _.
|
||||||
authenticate_user_session_token_no_payload_claims_fail(C) ->
|
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) ->
|
||||||
|
get_base_claims(JTI, 0).
|
||||||
|
|
||||||
|
get_base_claims(JTI, Exp) ->
|
||||||
#{
|
#{
|
||||||
<<"jti">> => JTI,
|
<<"jti">> => JTI,
|
||||||
<<"exp">> => 0
|
<<"exp">> => Exp
|
||||||
}.
|
}.
|
||||||
|
|
||||||
get_phony_api_key_claims(JTI, SubjectID) ->
|
get_phony_api_key_claims(JTI, SubjectID) ->
|
||||||
maps:merge(#{<<"sub">> => SubjectID}, get_base_claims(JTI)).
|
maps:merge(#{<<"sub">> => SubjectID}, get_base_claims(JTI)).
|
||||||
|
|
||||||
get_user_session_token_claims(JTI, SubjectID, SubjectEmail) ->
|
get_user_session_token_claims(JTI, Exp, SubjectID, SubjectEmail) ->
|
||||||
maps:merge(#{<<"sub">> => SubjectID, <<"email">> => SubjectEmail}, get_base_claims(JTI)).
|
maps:merge(#{<<"sub">> => SubjectID, <<"email">> => SubjectEmail}, get_base_claims(JTI, Exp)).
|
||||||
|
|
||||||
create_bouncer_context(JTI) ->
|
create_bouncer_context(JTI) ->
|
||||||
bouncer_context_helpers:add_auth(
|
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) ->
|
assert_auth({user_session_token, JTI, _SubjectID, _SubjectEmail, Exp}, Auth) ->
|
||||||
?assertEqual(<<"SessionToken">>, Auth#bctx_v1_Auth.method),
|
?assertEqual(<<"SessionToken">>, Auth#bctx_v1_Auth.method),
|
||||||
?assertMatch(#bctx_v1_Token{id = JTI}, Auth#bctx_v1_Auth.token),
|
?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) ->
|
assert_user({claim_token, _}, undefined) ->
|
||||||
ok;
|
ok;
|
||||||
@ -693,9 +713,6 @@ assert_user({user_session_token, _JTI, SubjectID, SubjectEmail, _Exp}, User) ->
|
|||||||
?assertEqual(SubjectEmail, User#bctx_v1_User.email),
|
?assertEqual(SubjectEmail, User#bctx_v1_User.email),
|
||||||
?assertEqual(?CTX_ENTITY(<<"external">>), User#bctx_v1_User.realm).
|
?assertEqual(?CTX_ENTITY(<<"external">>), User#bctx_v1_User.realm).
|
||||||
|
|
||||||
make_auth_expiration(unlimited) ->
|
|
||||||
undefined.
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
-include_lib("jose/include/jose_jwk.hrl").
|
-include_lib("jose/include/jose_jwk.hrl").
|
||||||
@ -741,6 +758,7 @@ unique_id() ->
|
|||||||
genlib_format:format_int_base(ID, 62).
|
genlib_format:format_int_base(ID, 62).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
start_keeper(Env) ->
|
start_keeper(Env) ->
|
||||||
Port = 8022,
|
Port = 8022,
|
||||||
Apps = genlib_app:start_application_with(
|
Apps = genlib_app:start_application_with(
|
||||||
|
Loading…
Reference in New Issue
Block a user