mirror of
https://github.com/valitydev/token-keeper.git
synced 2024-11-06 02:15:21 +00:00
TD-400: Extract resource access (#26)
This commit is contained in:
parent
8cc0d3036c
commit
5ae39c7d4d
4
.env
4
.env
@ -2,6 +2,6 @@
|
||||
# You SHOULD specify point releases here so that build time and run time Erlang/OTPs
|
||||
# are the same. See: https://github.com/erlware/relx/pull/902
|
||||
SERVICE_NAME=token-keeper
|
||||
OTP_VERSION=24.2.0
|
||||
OTP_VERSION=24
|
||||
REBAR_VERSION=3.18
|
||||
THRIFT_VERSION=0.14.2.2
|
||||
THRIFT_VERSION=0.14.2.3
|
||||
|
1
.github/workflows/erlang-checks.yml
vendored
1
.github/workflows/erlang-checks.yml
vendored
@ -37,3 +37,4 @@ jobs:
|
||||
use-thrift: true
|
||||
thrift-version: ${{ needs.setup.outputs.thrift-version }}
|
||||
run-ct-with-compose: true
|
||||
cache-version: v2
|
||||
|
16
rebar.lock
16
rebar.lock
@ -1,11 +1,11 @@
|
||||
{"1.2.0",
|
||||
[{<<"bouncer_client">>,
|
||||
{git,"https://github.com/valitydev/bouncer-client-erlang.git",
|
||||
{ref,"b6c7be05e24f46121f42ae5a48232b94f78c9c5c"}},
|
||||
{ref,"79d9d0144ed66537ec25302aeba8f133bddb05d7"}},
|
||||
0},
|
||||
{<<"bouncer_proto">>,
|
||||
{git,"https://github.com/valitydev/bouncer-proto",
|
||||
{ref,"633ba73e376ab06587499bd163cf807a9c34b8f7"}},
|
||||
{ref,"6ce6d33af5346c84b99fc6a3cebb5070001d0b62"}},
|
||||
1},
|
||||
{<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1},
|
||||
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2},
|
||||
@ -30,23 +30,23 @@
|
||||
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
|
||||
{<<"machinery">>,
|
||||
{git,"https://github.com/valitydev/machinery-erlang.git",
|
||||
{ref,"62c32434c80a462956ad9d50f9bce47836580d77"}},
|
||||
{ref,"19cdc6c846f0ebf65ae193ca0988e353e53fe1d5"}},
|
||||
0},
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
|
||||
{<<"mg_proto">>,
|
||||
{git,"https://github.com/valitydev/machinegun-proto.git",
|
||||
{ref,"7d780d5aa445e37b4816ac8a433bfaffe3715f63"}},
|
||||
{git,"https://github.com/valitydev/machinegun-proto",
|
||||
{ref,"96f7f11b184c29d8b7e83cd7646f3f2c13662bda"}},
|
||||
1},
|
||||
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2},
|
||||
{<<"org_management_proto">>,
|
||||
{git,"https://github.com/valitydev/org-management-proto",
|
||||
{ref,"f433223706284000694e54e839fafb10db84e2b3"}},
|
||||
{ref,"03a269df4805fa604e8fd2d04241619a739e2ae3"}},
|
||||
1},
|
||||
{<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2},
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2},
|
||||
{<<"scoper">>,
|
||||
{git,"https://github.com/valitydev/scoper.git",
|
||||
{ref,"7f3183df279bc8181efe58dafd9cae164f495e6f"}},
|
||||
{ref,"87110f5bd72c0e39ba9b7d6eca88fea91b8cd357"}},
|
||||
0},
|
||||
{<<"snowflake">>,
|
||||
{git,"https://github.com/valitydev/snowflake.git",
|
||||
@ -59,7 +59,7 @@
|
||||
0},
|
||||
{<<"token_keeper_proto">>,
|
||||
{git,"https://github.com/valitydev/token-keeper-proto.git",
|
||||
{ref,"8f7016f68692fc8e3141ba0fce2d47b6c8b6102a"}},
|
||||
{ref,"ea6e935122aa33467e53370a1509d7341be98750"}},
|
||||
0},
|
||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2},
|
||||
{<<"woody">>,
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
-type id() :: binary().
|
||||
-type status() :: active | revoked.
|
||||
-type encoded_context_fragment() :: tk_context_thrift:'ContextFragment'().
|
||||
-type encoded_context_fragment() :: tk_token_keeper_thrift:'ContextFragment'().
|
||||
-type authority_id() :: binary().
|
||||
-type metadata() :: #{binary() => binary()}.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
-module(tk_authdata_source_context_extractor).
|
||||
-behaviour(tk_authdata_source).
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
|
||||
%% Behaviour
|
||||
|
||||
@ -54,13 +54,13 @@ make_auth_data(ContextFragment, Metadata) ->
|
||||
}).
|
||||
|
||||
encode_context_fragment(ContextFragment) ->
|
||||
#bctx_ContextFragment{
|
||||
#ctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = encode_context_fragment_content(ContextFragment)
|
||||
}.
|
||||
|
||||
encode_context_fragment_content(ContextFragment) ->
|
||||
Type = {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}},
|
||||
Type = {struct, struct, {bouncer_ctx_v1_thrift, 'ContextFragment'}},
|
||||
Codec = thrift_strict_binary_codec:new(),
|
||||
case thrift_strict_binary_codec:write(Codec, Type, ContextFragment) of
|
||||
{ok, Codec1} ->
|
||||
|
@ -1,6 +1,6 @@
|
||||
-module(tk_claim_utils).
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
|
||||
-export([decode_authdata/1]).
|
||||
-export([encode_authdata/1]).
|
||||
@ -11,7 +11,7 @@
|
||||
%%
|
||||
|
||||
-type authdata() :: tk_authdata:prototype().
|
||||
-type encoded_context_fragment() :: tk_context_thrift:'ContextFragment'().
|
||||
-type encoded_context_fragment() :: tk_token_keeper_thrift:'ContextFragment'().
|
||||
|
||||
-type claim() :: term().
|
||||
-type claims() :: tk_token:payload().
|
||||
@ -58,7 +58,7 @@ decode_bouncer_claim(#{
|
||||
?CLAIM_CTX_CONTEXT := Content
|
||||
}) ->
|
||||
try
|
||||
{ok, #bctx_ContextFragment{
|
||||
{ok, #ctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = base64:decode(Content)
|
||||
}}
|
||||
@ -73,7 +73,7 @@ decode_bouncer_claim(Ctx) ->
|
||||
|
||||
-spec encode_bouncer_claim(encoded_context_fragment()) -> claims().
|
||||
encode_bouncer_claim(
|
||||
#bctx_ContextFragment{
|
||||
#ctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = Content
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
-module(tk_context_extractor_user_session_token).
|
||||
|
||||
-behaviour(tk_context_extractor).
|
||||
|
||||
-export([extract_context/2]).
|
||||
@ -21,58 +22,88 @@
|
||||
-define(CLAIM_USER_ID, <<"sub">>).
|
||||
-define(CLAIM_USER_EMAIL, <<"email">>).
|
||||
-define(CLAIM_EXPIRES_AT, <<"exp">>).
|
||||
-define(CLAIM_RESOURCE_ACCESS, <<"resource_access">>).
|
||||
|
||||
%% API functions
|
||||
|
||||
-spec extract_context(tk_token:token_data(), opts()) -> tk_context_extractor:extracted_context() | undefined.
|
||||
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} ->
|
||||
extract_context(TokenData, Opts) ->
|
||||
try
|
||||
AuthParams = extract_auth_params(TokenData),
|
||||
UserParams = add_user_realm(extract_user_params(TokenData), Opts),
|
||||
Context = create_context(UserParams, AuthParams),
|
||||
Metadata = create_metadata(UserParams),
|
||||
{Context, wrap_metadata(Metadata, Opts)}
|
||||
catch
|
||||
throw:Reason ->
|
||||
_ = logger:warning("Could not extract user_session_token context, reason: ~p", [Reason]),
|
||||
undefined
|
||||
end.
|
||||
|
||||
%% Internal functions
|
||||
|
||||
create_context_and_metadata(TokenID, TokenExpiration, UserID, UserEmail, Opts) ->
|
||||
UserRealm = maps:get(user_realm, Opts),
|
||||
{
|
||||
create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm),
|
||||
wrap_metadata(
|
||||
create_metadata(UserID, UserEmail, UserRealm),
|
||||
Opts
|
||||
)
|
||||
}.
|
||||
|
||||
extract_payload_data(#{
|
||||
?CLAIM_USER_ID := UserID,
|
||||
?CLAIM_USER_EMAIL := UserEmail,
|
||||
?CLAIM_EXPIRES_AT := Expiration
|
||||
extract_user_params(#{
|
||||
payload := #{
|
||||
?CLAIM_USER_ID := UserID,
|
||||
?CLAIM_USER_EMAIL := UserEmail
|
||||
}
|
||||
}) ->
|
||||
{ok, {UserID, UserEmail, Expiration}};
|
||||
extract_payload_data(Payload) ->
|
||||
RequiredKeys = [?CLAIM_USER_ID, ?CLAIM_USER_EMAIL, ?CLAIM_EXPIRES_AT],
|
||||
{error, {missing, RequiredKeys -- maps:keys(Payload)}}.
|
||||
#{
|
||||
id => UserID,
|
||||
email => UserEmail
|
||||
};
|
||||
extract_user_params(TokenData) ->
|
||||
RequiredKeys = [
|
||||
?CLAIM_USER_ID,
|
||||
?CLAIM_USER_EMAIL
|
||||
],
|
||||
throw({missing, RequiredKeys -- maps:keys(TokenData)}).
|
||||
|
||||
create_context(TokenID, TokenExpiration, UserID, UserEmail, UserRealm) ->
|
||||
extract_auth_params(#{
|
||||
id := TokenID,
|
||||
payload := #{
|
||||
?CLAIM_EXPIRES_AT := TokenExp
|
||||
} = Payload
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
token_id => TokenID,
|
||||
token_exp => TokenExp,
|
||||
resource_access => genlib_map:get(?CLAIM_RESOURCE_ACCESS, Payload)
|
||||
});
|
||||
extract_auth_params(TokenData) ->
|
||||
RequiredKeys = [
|
||||
?CLAIM_EXPIRES_AT
|
||||
],
|
||||
throw({missing, RequiredKeys -- maps:keys(TokenData)}).
|
||||
|
||||
add_user_realm(UserParams, Opts) ->
|
||||
UserParams#{realm => maps:get(user_realm, Opts)}.
|
||||
|
||||
create_context(UserParams, AuthParams) ->
|
||||
Acc0 = bouncer_context_helpers:empty(),
|
||||
Acc1 = bouncer_context_helpers:add_user(
|
||||
Acc1 = append_user_context(UserParams, Acc0),
|
||||
append_auth_context(AuthParams, Acc1).
|
||||
|
||||
append_user_context(UserParams, BouncerCtx) ->
|
||||
bouncer_context_helpers:add_user(
|
||||
#{
|
||||
id => UserID,
|
||||
email => UserEmail,
|
||||
realm => #{id => UserRealm}
|
||||
id => maps:get(id, UserParams),
|
||||
email => maps:get(email, UserParams),
|
||||
realm => #{id => maps:get(realm, UserParams)}
|
||||
},
|
||||
Acc0
|
||||
),
|
||||
BouncerCtx
|
||||
).
|
||||
|
||||
append_auth_context(AuthParams, BouncerCtx) ->
|
||||
bouncer_context_helpers:add_auth(
|
||||
#{
|
||||
method => <<"SessionToken">>,
|
||||
expiration => make_auth_expiration(TokenExpiration),
|
||||
token => #{id => TokenID}
|
||||
expiration => make_auth_expiration(maps:get(token_exp, AuthParams)),
|
||||
token => genlib_map:compact(#{
|
||||
id => maps:get(token_id, AuthParams),
|
||||
access => maybe_auth_access_list(AuthParams)
|
||||
})
|
||||
},
|
||||
Acc1
|
||||
BouncerCtx
|
||||
).
|
||||
|
||||
make_auth_expiration(0) ->
|
||||
@ -80,11 +111,26 @@ make_auth_expiration(0) ->
|
||||
make_auth_expiration(Timestamp) when is_integer(Timestamp) ->
|
||||
genlib_rfc3339:format(Timestamp, second).
|
||||
|
||||
create_metadata(UserID, UserEmail, UserRealm) ->
|
||||
maybe_auth_access_list(#{resource_access := ResourceAccess}) ->
|
||||
maps:fold(
|
||||
fun(Key, Value, Acc) ->
|
||||
Entry = #{
|
||||
id => Key,
|
||||
roles => maps:get(<<"roles">>, Value)
|
||||
},
|
||||
[Entry | Acc]
|
||||
end,
|
||||
[],
|
||||
ResourceAccess
|
||||
);
|
||||
maybe_auth_access_list(_) ->
|
||||
undefined.
|
||||
|
||||
create_metadata(UserParams) ->
|
||||
#{
|
||||
user_id => UserID,
|
||||
user_email => UserEmail,
|
||||
user_realm => UserRealm
|
||||
user_id => maps:get(id, UserParams),
|
||||
user_email => maps:get(email, UserParams),
|
||||
user_realm => maps:get(realm, UserParams)
|
||||
}.
|
||||
|
||||
wrap_metadata(Metadata, ExtractorOpts) ->
|
||||
|
@ -1,6 +1,6 @@
|
||||
-module(tk_handler_authenticator).
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
-include_lib("token_keeper_proto/include/tk_token_keeper_thrift.hrl").
|
||||
|
||||
-export([get_handler_spec/1]).
|
||||
|
@ -1,6 +1,6 @@
|
||||
-module(tk_handler_authority_ephemeral).
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
-include_lib("token_keeper_proto/include/tk_token_keeper_thrift.hrl").
|
||||
|
||||
-export([get_handler_spec/2]).
|
||||
|
@ -1,6 +1,6 @@
|
||||
-module(tk_handler_authority_offline).
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
-include_lib("token_keeper_proto/include/tk_token_keeper_thrift.hrl").
|
||||
|
||||
-export([get_handler_spec/2]).
|
||||
|
@ -15,6 +15,7 @@
|
||||
-export([process_repair/4]).
|
||||
-export([process_timeout/3]).
|
||||
-export([process_call/4]).
|
||||
-export([process_notification/4]).
|
||||
|
||||
-type storage_opts() :: #{
|
||||
namespace := namespace(),
|
||||
@ -108,12 +109,16 @@ process_call(revoke, #{history := History}, _, _) ->
|
||||
Events = change_status(revoked, AuthData),
|
||||
{ok, #{events => Events}}.
|
||||
|
||||
-spec process_notification(machinery:args(_), machine(), handler_args(), handler_opts()) -> no_return().
|
||||
process_notification(_Args, _Machine, _, _) ->
|
||||
erlang:error({not_implemented, process_notification}).
|
||||
|
||||
%%-------------------------------------
|
||||
%% internal
|
||||
|
||||
create_authdata(AuthData) ->
|
||||
[
|
||||
{created, #tk_events_AuthDataCreated{
|
||||
{created, #events_AuthDataCreated{
|
||||
id = maps:get(id, AuthData),
|
||||
status = maps:get(status, AuthData),
|
||||
context = maps:get(context, AuthData),
|
||||
@ -124,7 +129,7 @@ create_authdata(AuthData) ->
|
||||
change_status(NewStatus, #{status := NewStatus}) ->
|
||||
[];
|
||||
change_status(NewStatus, #{status := _OtherStatus}) ->
|
||||
[{status_changed, #tk_events_AuthDataStatusChanged{status = NewStatus}}].
|
||||
[{status_changed, #events_AuthDataStatusChanged{status = NewStatus}}].
|
||||
|
||||
%%
|
||||
|
||||
@ -158,8 +163,8 @@ collapse_history(History) ->
|
||||
collapse_history([], AuthData) when AuthData =/= undefined ->
|
||||
AuthData;
|
||||
collapse_history([{_, _, {created, AuthData}} | Rest], undefined) ->
|
||||
#tk_events_AuthDataCreated{id = ID, context = Ctx, status = Status, metadata = Meta} = AuthData,
|
||||
#events_AuthDataCreated{id = ID, context = Ctx, status = Status, metadata = Meta} = AuthData,
|
||||
collapse_history(Rest, #{id => ID, context => Ctx, status => Status, metadata => Meta});
|
||||
collapse_history([{_, _, {status_changed, StatusChanged}} | Rest], AuthData) when AuthData =/= undefined ->
|
||||
#tk_events_AuthDataStatusChanged{status = Status} = StatusChanged,
|
||||
#events_AuthDataStatusChanged{status = Status} = StatusChanged,
|
||||
collapse_history(Rest, AuthData#{status => Status}).
|
||||
|
@ -113,6 +113,7 @@ deserialize(Type, Data) ->
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("token_keeper_proto/include/tk_events_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
|
||||
-spec test() -> _.
|
||||
|
||||
@ -126,10 +127,10 @@ deserialize(Type, Data) ->
|
||||
|
||||
marshal_unmarshal_created_test() ->
|
||||
Event =
|
||||
{created, #tk_events_AuthDataCreated{
|
||||
{created, #events_AuthDataCreated{
|
||||
id = <<"TEST">>,
|
||||
status = active,
|
||||
context = #bctx_ContextFragment{type = v1_thrift_binary, content = <<"STUFF">>},
|
||||
context = #ctx_ContextFragment{type = v1_thrift_binary, content = <<"STUFF">>},
|
||||
metadata = #{}
|
||||
}},
|
||||
{Marshaled, _} = marshal_event(1, Event, ?CONTEXT),
|
||||
@ -138,7 +139,7 @@ marshal_unmarshal_created_test() ->
|
||||
|
||||
marshal_unmarshal_status_changed_test() ->
|
||||
Event =
|
||||
{status_changed, #tk_events_AuthDataStatusChanged{
|
||||
{status_changed, #events_AuthDataStatusChanged{
|
||||
status = revoked
|
||||
}},
|
||||
{Marshaled, _} = marshal_event(1, Event, ?CONTEXT),
|
||||
|
@ -4,10 +4,10 @@
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
|
||||
-include_lib("token_keeper_proto/include/tk_token_keeper_thrift.hrl").
|
||||
-include_lib("token_keeper_proto/include/tk_context_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_thrift.hrl").
|
||||
|
||||
-include_lib("bouncer_proto/include/bouncer_base_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_context_v1_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_ctx_v1_thrift.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-export([groups/0]).
|
||||
@ -25,6 +25,8 @@
|
||||
-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_user_session_token_no_exp_fail/1]).
|
||||
-export([authenticate_user_session_token_w_resource_access/1]).
|
||||
-export([authenticate_blacklisted_jti_fail/1]).
|
||||
-export([authenticate_non_blacklisted_jti_ok/1]).
|
||||
-export([authenticate_ephemeral_claim_token_ok/1]).
|
||||
@ -89,7 +91,9 @@ groups() ->
|
||||
authenticate_user_session_token_no_payload_claims_fail,
|
||||
authenticate_phony_api_key_token_ok,
|
||||
authenticate_user_session_token_ok,
|
||||
authenticate_user_session_token_w_exp_ok
|
||||
authenticate_user_session_token_w_exp_ok,
|
||||
authenticate_user_session_token_no_exp_fail,
|
||||
authenticate_user_session_token_w_resource_access
|
||||
]},
|
||||
{ephemeral, [parallel], [
|
||||
authenticate_invalid_token_type_fail,
|
||||
@ -365,7 +369,7 @@ authenticate_phony_api_key_token_ok(C) ->
|
||||
metadata = #{?META_PARTY_ID := SubjectID},
|
||||
authority = ?TK_AUTHORITY_KEYCLOAK
|
||||
} = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT, C),
|
||||
_ = assert_context({api_key_token, JTI, SubjectID}, Context).
|
||||
_ = assert_context({api_key_token, #{jti => JTI, subject_id => SubjectID}}, Context).
|
||||
|
||||
-spec authenticate_user_session_token_ok(config()) -> _.
|
||||
authenticate_user_session_token_ok(C) ->
|
||||
@ -386,7 +390,22 @@ 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, undefined}, Context).
|
||||
_ = assert_context(
|
||||
{user_session_token, #{jti => JTI, subject_id => SubjectID, subject_email => SubjectEmail}},
|
||||
Context
|
||||
).
|
||||
|
||||
-spec authenticate_user_session_token_no_exp_fail(config()) -> _.
|
||||
authenticate_user_session_token_no_exp_fail(C) ->
|
||||
JTI = unique_id(),
|
||||
SubjectID = unique_id(),
|
||||
SubjectEmail = <<"test@test.test">>,
|
||||
Claims = get_user_session_token_claims(JTI, 0, SubjectID, SubjectEmail),
|
||||
Token = issue_token(maps:remove(<<"exp">>, Claims), C),
|
||||
?assertThrow(
|
||||
#token_keeper_AuthDataNotFound{},
|
||||
call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT(?USER_TOKEN_SOURCE), C)
|
||||
).
|
||||
|
||||
-spec authenticate_user_session_token_w_exp_ok(config()) -> _.
|
||||
authenticate_user_session_token_w_exp_ok(C) ->
|
||||
@ -398,7 +417,40 @@ authenticate_user_session_token_w_exp_ok(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).
|
||||
_ = assert_context(
|
||||
{user_session_token, #{
|
||||
jti => JTI, subject_id => SubjectID, subject_email => SubjectEmail, exp => make_auth_expiration(42)
|
||||
}},
|
||||
Context
|
||||
).
|
||||
|
||||
-spec authenticate_user_session_token_w_resource_access(config()) -> _.
|
||||
authenticate_user_session_token_w_resource_access(C) ->
|
||||
JTI = unique_id(),
|
||||
SubjectID = unique_id(),
|
||||
SubjectEmail = <<"test@test.test">>,
|
||||
ResourceAccess = #{
|
||||
<<"api.test">> => #{
|
||||
<<"roles">> => [<<"do.nothing">>]
|
||||
}
|
||||
},
|
||||
Claims = get_user_session_token_claims(JTI, 42, SubjectID, SubjectEmail, ResourceAccess),
|
||||
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 => JTI,
|
||||
subject_id => SubjectID,
|
||||
subject_email => SubjectEmail,
|
||||
exp => make_auth_expiration(42),
|
||||
access => [
|
||||
{<<"api.test">>, [<<"do.nothing">>]}
|
||||
]
|
||||
}},
|
||||
Context
|
||||
).
|
||||
|
||||
-spec authenticate_user_session_token_no_payload_claims_fail(config()) -> _.
|
||||
authenticate_user_session_token_no_payload_claims_fail(C) ->
|
||||
@ -447,7 +499,7 @@ authenticate_ephemeral_claim_token_ok(C) ->
|
||||
metadata = Metadata,
|
||||
authority = AuthorityID
|
||||
} = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT, C),
|
||||
_ = assert_context({claim_token, JTI}, Context).
|
||||
_ = assert_context({claim_token, #{jti => JTI}}, Context).
|
||||
|
||||
-spec issue_ephemeral_token_ok(config()) -> _.
|
||||
issue_ephemeral_token_ok(C) ->
|
||||
@ -503,7 +555,7 @@ authenticate_offline_token_ok(C) ->
|
||||
metadata = Metadata,
|
||||
authority = AuthorityID
|
||||
} = call_authenticate(Token, ?TOKEN_SOURCE_CONTEXT, C),
|
||||
_ = assert_context({claim_token, JTI}, Context).
|
||||
_ = assert_context({claim_token, #{jti => JTI}}, Context).
|
||||
|
||||
-spec issue_offline_token_ok(config()) -> _.
|
||||
issue_offline_token_ok(C) ->
|
||||
@ -600,7 +652,17 @@ get_phony_api_key_claims(JTI, SubjectID) ->
|
||||
maps:merge(#{<<"sub">> => SubjectID}, get_base_claims(JTI)).
|
||||
|
||||
get_user_session_token_claims(JTI, Exp, SubjectID, SubjectEmail) ->
|
||||
maps:merge(#{<<"sub">> => SubjectID, <<"email">> => SubjectEmail}, get_base_claims(JTI, Exp)).
|
||||
get_user_session_token_claims(JTI, Exp, SubjectID, SubjectEmail, undefined).
|
||||
|
||||
get_user_session_token_claims(JTI, Exp, SubjectID, SubjectEmail, ResourceAccess) ->
|
||||
maps:merge(
|
||||
genlib_map:compact(#{
|
||||
<<"sub">> => SubjectID,
|
||||
<<"email">> => SubjectEmail,
|
||||
<<"resource_access">> => ResourceAccess
|
||||
}),
|
||||
get_base_claims(JTI, Exp)
|
||||
).
|
||||
|
||||
create_bouncer_context(JTI) ->
|
||||
bouncer_context_helpers:add_auth(
|
||||
@ -613,7 +675,7 @@ create_bouncer_context(JTI) ->
|
||||
|
||||
create_encoded_bouncer_context(JTI) ->
|
||||
Fragment = create_bouncer_context(JTI),
|
||||
#bctx_ContextFragment{
|
||||
#ctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = encode_context_fragment_content(Fragment)
|
||||
}.
|
||||
@ -671,47 +733,61 @@ get_service_spec({token_ephemeral_authority, _}) ->
|
||||
|
||||
%%
|
||||
|
||||
-define(CTX_ENTITY(ID), #bouncer_base_Entity{id = ID}).
|
||||
-define(CTX_ENTITY(ID), #base_Entity{id = ID}).
|
||||
|
||||
encode_context_fragment_content(ContextFragment) ->
|
||||
Type = {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}},
|
||||
Type = {struct, struct, {bouncer_ctx_v1_thrift, 'ContextFragment'}},
|
||||
Codec = thrift_strict_binary_codec:new(),
|
||||
case thrift_strict_binary_codec:write(Codec, Type, ContextFragment) of
|
||||
{ok, Codec1} ->
|
||||
thrift_strict_binary_codec:close(Codec1)
|
||||
end.
|
||||
|
||||
decode_bouncer_fragment(#bctx_ContextFragment{type = v1_thrift_binary, content = Content}) ->
|
||||
Type = {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}},
|
||||
decode_bouncer_fragment(#ctx_ContextFragment{type = v1_thrift_binary, content = Content}) ->
|
||||
Type = {struct, struct, {bouncer_ctx_v1_thrift, 'ContextFragment'}},
|
||||
Codec = thrift_strict_binary_codec:new(Content),
|
||||
{ok, Fragment, _} = thrift_strict_binary_codec:read(Codec, Type),
|
||||
Fragment.
|
||||
|
||||
assert_context(TokenInfo, EncodedContextFragment) ->
|
||||
#bctx_v1_ContextFragment{auth = Auth, user = User} = decode_bouncer_fragment(EncodedContextFragment),
|
||||
#ctx_v1_ContextFragment{auth = Auth, user = User} = decode_bouncer_fragment(EncodedContextFragment),
|
||||
_ = assert_auth(TokenInfo, Auth),
|
||||
_ = assert_user(TokenInfo, User).
|
||||
|
||||
assert_auth({claim_token, JTI}, Auth) ->
|
||||
?assertEqual(<<"ClaimToken">>, Auth#bctx_v1_Auth.method),
|
||||
?assertMatch(#bctx_v1_Token{id = JTI}, Auth#bctx_v1_Auth.token);
|
||||
assert_auth({api_key_token, JTI, SubjectID}, Auth) ->
|
||||
?assertEqual(<<"ApiKeyToken">>, Auth#bctx_v1_Auth.method),
|
||||
?assertMatch(#bctx_v1_Token{id = JTI}, Auth#bctx_v1_Auth.token),
|
||||
?assertMatch([#bctx_v1_AuthScope{party = ?CTX_ENTITY(SubjectID)}], Auth#bctx_v1_Auth.scope);
|
||||
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(Exp, Auth#bctx_v1_Auth.expiration).
|
||||
assert_auth({claim_token, #{jti := JTI}}, Auth) ->
|
||||
?assertEqual(<<"ClaimToken">>, Auth#ctx_v1_Auth.method),
|
||||
?assertMatch(#ctx_v1_Token{id = JTI}, Auth#ctx_v1_Auth.token);
|
||||
assert_auth({api_key_token, #{jti := JTI, subject_id := SubjectID}}, Auth) ->
|
||||
?assertEqual(<<"ApiKeyToken">>, Auth#ctx_v1_Auth.method),
|
||||
?assertMatch(#ctx_v1_Token{id = JTI}, Auth#ctx_v1_Auth.token),
|
||||
?assertMatch([#ctx_v1_AuthScope{party = ?CTX_ENTITY(SubjectID)}], Auth#ctx_v1_Auth.scope);
|
||||
assert_auth({user_session_token, #{jti := JTI} = TokenInfo}, Auth) ->
|
||||
?assertEqual(<<"SessionToken">>, Auth#ctx_v1_Auth.method),
|
||||
Exp = maps:get(exp, TokenInfo, undefined),
|
||||
Access =
|
||||
case maps:get(access, TokenInfo, undefined) of
|
||||
undefined ->
|
||||
undefined;
|
||||
AccessList ->
|
||||
[
|
||||
#ctx_v1_ResourceAccess{
|
||||
id = ID,
|
||||
roles = Roles
|
||||
}
|
||||
|| {ID, Roles} <- AccessList
|
||||
]
|
||||
end,
|
||||
?assertMatch(#ctx_v1_Token{id = JTI, access = Access}, Auth#ctx_v1_Auth.token),
|
||||
?assertEqual(Exp, Auth#ctx_v1_Auth.expiration).
|
||||
|
||||
assert_user({claim_token, _}, undefined) ->
|
||||
ok;
|
||||
assert_user({api_key_token, _, _}, undefined) ->
|
||||
assert_user({api_key_token, _}, undefined) ->
|
||||
ok;
|
||||
assert_user({user_session_token, _JTI, SubjectID, SubjectEmail, _Exp}, User) ->
|
||||
?assertEqual(SubjectID, User#bctx_v1_User.id),
|
||||
?assertEqual(SubjectEmail, User#bctx_v1_User.email),
|
||||
?assertEqual(?CTX_ENTITY(<<"external">>), User#bctx_v1_User.realm).
|
||||
assert_user({user_session_token, #{subject_id := SubjectID, subject_email := SubjectEmail}}, User) ->
|
||||
?assertEqual(SubjectID, User#ctx_v1_User.id),
|
||||
?assertEqual(SubjectEmail, User#ctx_v1_User.email),
|
||||
?assertEqual(?CTX_ENTITY(<<"external">>), User#ctx_v1_User.realm).
|
||||
|
||||
%%
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user