Stop requiring exp claim when not needed (#7)

This commit is contained in:
Alexey S 2022-02-21 11:54:47 +03:00 committed by GitHub
parent 5479ffa917
commit e8f9eecb04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 61 additions and 49 deletions

View File

@ -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"}
}
}

View File

@ -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) ->
#{

View File

@ -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
}.

View File

@ -108,7 +108,6 @@ create_token_data(ID, #{authority_id := AuthorityID, token_type := TokenType}) -
id => ID,
type => TokenType,
authority_id => AuthorityID,
expiration => unlimited,
payload => #{}
}.

View File

@ -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]).

View File

@ -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) ->

View File

@ -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(