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 => #{ 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"}
} }
} }

View File

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

View File

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

View File

@ -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 => #{}
}. }.

View File

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

View File

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

View File

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