mirror of
https://github.com/valitydev/bouncer-client-erlang.git
synced 2024-11-06 00:25:18 +00:00
Add helpers API to combine parts of context (#2)
Motivation here: conceptually bouncer should see a single versioned fragment for a single context producer. So a library user should be able to combine multiple features in a single fragment. Also remove _default_ fragment constructors, since it seems risky to imply there are any defaults to begin with. Also simplify naming a bit, so there're less tautologies. * Drop unrelated includes * Enforce some most sensitive bits of context * Fix formatting
This commit is contained in:
parent
2143a16bde
commit
41f766feba
@ -2,9 +2,6 @@
|
||||
|
||||
-include_lib("bouncer_proto/include/bouncer_decisions_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_context_v1_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_context_thrift.hrl").
|
||||
|
||||
% -include_lib("org_management_proto/include/orgmgmt_auth_context_provider_thrift.hrl").
|
||||
|
||||
%% API
|
||||
|
||||
@ -16,10 +13,9 @@
|
||||
|
||||
-type context_fragment_id() :: binary().
|
||||
-type ruleset_id() :: binary().
|
||||
-type bouncer_fragment() :: bouncer_context_v1_thrift:'ContextFragment'().
|
||||
-type encoded_bouncer_fragment() :: bouncer_context_thrift:'ContextFragment'().
|
||||
-type context_fragment() ::
|
||||
{fragment, bouncer_fragment()}
|
||||
bouncer_context_helpers:context_fragment()
|
||||
| {encoded_fragment, encoded_bouncer_fragment()}.
|
||||
|
||||
-type judge_context() :: #{
|
||||
@ -74,11 +70,11 @@ collect_fragments(_, Context) ->
|
||||
|
||||
collect_fragments_(FragmentID, {encoded_fragment, EncodedFragment}, Acc0) ->
|
||||
Acc0#{FragmentID => EncodedFragment};
|
||||
collect_fragments_(FragmentID, {fragment, Fragment}, Acc0) ->
|
||||
collect_fragments_(FragmentID, ContextFragment = #bctx_v1_ContextFragment{}, Acc0) ->
|
||||
Acc0#{
|
||||
FragmentID => #bctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = encode_context_fragment(Fragment)
|
||||
content = encode_context_fragment(ContextFragment)
|
||||
}
|
||||
}.
|
||||
|
||||
|
@ -4,24 +4,28 @@
|
||||
-include_lib("bouncer_proto/include/bouncer_context_v1_thrift.hrl").
|
||||
-include_lib("bouncer_proto/include/bouncer_context_thrift.hrl").
|
||||
|
||||
-export([make_default_env_context_fragment/0]).
|
||||
-export([make_env_context_fragment/1]).
|
||||
-export([make_auth_context_fragment/1]).
|
||||
-export([make_default_user_context_fragment/1]).
|
||||
-export([make_user_context_fragment/1]).
|
||||
-export([make_requester_context_fragment/1]).
|
||||
-export([get_user_context_fragment/2]).
|
||||
-export([empty/0]).
|
||||
-export([make_env_fragment/1]).
|
||||
-export([add_env/2]).
|
||||
-export([make_auth_fragment/1]).
|
||||
-export([add_auth/2]).
|
||||
-export([make_user_fragment/1]).
|
||||
-export([add_user/2]).
|
||||
-export([make_requester_fragment/1]).
|
||||
-export([add_requester/2]).
|
||||
|
||||
-export([get_user_orgs_fragment/2]).
|
||||
|
||||
-type id() :: binary().
|
||||
-type method() :: binary().
|
||||
-type email() :: binary().
|
||||
-type timestamp() :: binary().
|
||||
-type ip() :: string().
|
||||
-type context_fragment() :: bouncer_client:context_fragment().
|
||||
-type context_fragment() :: bouncer_context_v1_thrift:'ContextFragment'().
|
||||
-type woody_context() :: woody_context:ctx().
|
||||
|
||||
-type entity() :: #{
|
||||
id => id()
|
||||
id := id()
|
||||
}.
|
||||
|
||||
-type environment_params() :: #{
|
||||
@ -34,7 +38,7 @@
|
||||
}.
|
||||
|
||||
-type auth_params() :: #{
|
||||
method => method(),
|
||||
method := method(),
|
||||
scope => [auth_scope()],
|
||||
expiration => timestamp()
|
||||
}.
|
||||
@ -46,8 +50,8 @@
|
||||
}.
|
||||
|
||||
-type user_params() :: #{
|
||||
id => id(),
|
||||
realm => entity(),
|
||||
id := id(),
|
||||
realm := entity(),
|
||||
email => email(),
|
||||
orgs => [user_org()]
|
||||
}.
|
||||
@ -71,81 +75,84 @@
|
||||
ip => ip()
|
||||
}.
|
||||
|
||||
-export_type([context_fragment/0]).
|
||||
|
||||
-export_type([environment_params/0]).
|
||||
-export_type([auth_params/0]).
|
||||
-export_type([user_params/0]).
|
||||
-export_type([requester_params/0]).
|
||||
|
||||
-spec make_default_env_context_fragment() -> context_fragment().
|
||||
make_default_env_context_fragment() ->
|
||||
Params = #{
|
||||
now => genlib_rfc3339:format(genlib_time:unow(), second)
|
||||
},
|
||||
make_env_context_fragment(Params).
|
||||
-spec empty() -> context_fragment().
|
||||
empty() ->
|
||||
#bctx_v1_ContextFragment{}.
|
||||
|
||||
-spec make_env_context_fragment(environment_params()) -> context_fragment().
|
||||
make_env_context_fragment(Params) ->
|
||||
Datetime = maybe_get_param(now, Params),
|
||||
-spec make_env_fragment(environment_params()) -> context_fragment().
|
||||
make_env_fragment(Params) ->
|
||||
add_env(Params, empty()).
|
||||
|
||||
-spec add_env(environment_params(), context_fragment()) -> context_fragment().
|
||||
add_env(Params, ContextFragment = #bctx_v1_ContextFragment{env = undefined}) ->
|
||||
Now = maybe_get_param(now, Params, genlib_rfc3339:format(genlib_time:unow(), second)),
|
||||
Deployment = maybe_get_param(deployment, Params),
|
||||
DeploymentID = maybe_get_param(id, Deployment),
|
||||
|
||||
{fragment, #bctx_v1_ContextFragment{
|
||||
ContextFragment#bctx_v1_ContextFragment{
|
||||
env = #bctx_v1_Environment{
|
||||
now = Datetime,
|
||||
now = Now,
|
||||
deployment = maybe_add_param(#bctx_v1_Deployment{id = DeploymentID}, Deployment)
|
||||
}
|
||||
}}.
|
||||
}.
|
||||
|
||||
-spec make_auth_context_fragment(auth_params()) -> context_fragment().
|
||||
make_auth_context_fragment(Params) ->
|
||||
Method = maybe_get_param(method, Params),
|
||||
-spec make_auth_fragment(auth_params()) -> context_fragment().
|
||||
make_auth_fragment(Params) ->
|
||||
add_auth(Params, empty()).
|
||||
|
||||
-spec add_auth(auth_params(), context_fragment()) -> context_fragment().
|
||||
add_auth(Params, ContextFragment = #bctx_v1_ContextFragment{auth = undefined}) ->
|
||||
Method = get_param(method, Params),
|
||||
Scope = maybe_get_param(scope, Params),
|
||||
Expiration = maybe_get_param(expiration, Params),
|
||||
|
||||
{fragment, #bctx_v1_ContextFragment{
|
||||
ContextFragment#bctx_v1_ContextFragment{
|
||||
auth = #bctx_v1_Auth{
|
||||
method = Method,
|
||||
scope = maybe_marshal_auth_scopes(Scope),
|
||||
expiration = Expiration
|
||||
}
|
||||
}}.
|
||||
}.
|
||||
|
||||
-spec make_default_user_context_fragment(id()) -> context_fragment().
|
||||
make_default_user_context_fragment(UserID) ->
|
||||
{fragment, #bctx_v1_ContextFragment{
|
||||
user = #bctx_v1_User{
|
||||
id = UserID
|
||||
}
|
||||
}}.
|
||||
-spec make_user_fragment(user_params()) -> context_fragment().
|
||||
make_user_fragment(Params) ->
|
||||
add_user(Params, empty()).
|
||||
|
||||
-spec make_user_context_fragment(user_params()) -> context_fragment().
|
||||
make_user_context_fragment(Params) ->
|
||||
UserID = maybe_get_param(id, Params),
|
||||
RealmEntity = maybe_get_param(realm, Params),
|
||||
-spec add_user(user_params(), context_fragment()) -> context_fragment().
|
||||
add_user(Params, ContextFragment = #bctx_v1_ContextFragment{user = undefined}) ->
|
||||
UserID = get_param(id, Params),
|
||||
RealmEntity = get_param(realm, Params),
|
||||
Email = maybe_get_param(email, Params),
|
||||
Orgs = maybe_get_param(orgs, Params),
|
||||
|
||||
{fragment, #bctx_v1_ContextFragment{
|
||||
ContextFragment#bctx_v1_ContextFragment{
|
||||
user = #bctx_v1_User{
|
||||
id = UserID,
|
||||
realm = maybe_add_param(maybe_marshal_entity(RealmEntity), RealmEntity),
|
||||
realm = marshal_entity(RealmEntity),
|
||||
email = Email,
|
||||
orgs = maybe_add_param(maybe_marshal_user_orgs(Orgs), Orgs)
|
||||
}
|
||||
}}.
|
||||
}.
|
||||
|
||||
-spec make_requester_context_fragment(requester_params()) -> context_fragment().
|
||||
make_requester_context_fragment(Params) ->
|
||||
-spec make_requester_fragment(requester_params()) -> context_fragment().
|
||||
make_requester_fragment(Params) ->
|
||||
add_requester(Params, empty()).
|
||||
|
||||
-spec add_requester(requester_params(), context_fragment()) -> context_fragment().
|
||||
add_requester(Params, ContextFragment = #bctx_v1_ContextFragment{requester = undefined}) ->
|
||||
IP = maybe_get_param(ip, Params),
|
||||
|
||||
{fragment, #bctx_v1_ContextFragment{
|
||||
ContextFragment#bctx_v1_ContextFragment{
|
||||
requester = #bctx_v1_Requester{
|
||||
ip = maybe_marshal_ip(IP)
|
||||
}
|
||||
}}.
|
||||
}.
|
||||
|
||||
-spec get_user_context_fragment(id(), woody_context()) -> {ok, context_fragment()} | {error, {user, notfound}}.
|
||||
get_user_context_fragment(UserID, WoodyContext) ->
|
||||
-spec get_user_orgs_fragment(id(), woody_context()) -> {ok, context_fragment()} | {error, {user, notfound}}.
|
||||
get_user_orgs_fragment(UserID, WoodyContext) ->
|
||||
ServiceName = org_management,
|
||||
case bouncer_client_woody:call(ServiceName, 'GetUserContext', {UserID}, WoodyContext) of
|
||||
{ok, EncodedFragment} ->
|
||||
@ -162,16 +169,28 @@ convert_fragment(org_management, {bctx_ContextFragment, Type = v1_thrift_binary,
|
||||
content = Content
|
||||
}.
|
||||
|
||||
get_param(Key, Map = #{}) ->
|
||||
maps:get(Key, Map).
|
||||
|
||||
maybe_get_param(_Key, undefined) ->
|
||||
undefined;
|
||||
maybe_get_param(Key, Map) ->
|
||||
maps:get(Key, Map, undefined).
|
||||
|
||||
maybe_get_param(_Key, undefined, Default) ->
|
||||
Default;
|
||||
maybe_get_param(Key, Map, Default) ->
|
||||
maps:get(Key, Map, Default).
|
||||
|
||||
maybe_add_param(_Value, undefined) ->
|
||||
undefined;
|
||||
maybe_add_param(Value, _Param) ->
|
||||
Value.
|
||||
|
||||
marshal_entity(Entity) ->
|
||||
EntityID = get_param(id, Entity),
|
||||
#bctx_v1_Entity{id = EntityID}.
|
||||
|
||||
maybe_marshal_entity(undefined) ->
|
||||
undefined;
|
||||
maybe_marshal_entity(Entity) ->
|
||||
|
@ -15,11 +15,11 @@
|
||||
-export([end_per_testcase/2]).
|
||||
|
||||
-export([empty_judge/1]).
|
||||
-export([validate_default_user_fragment/1]).
|
||||
-export([validate_user_fragment/1]).
|
||||
-export([validate_env_fragment/1]).
|
||||
-export([validate_auth_fragment/1]).
|
||||
-export([validate_requester_fragment/1]).
|
||||
-export([validate_complex_fragment/1]).
|
||||
-export([validate_remote_user_fragment/1]).
|
||||
|
||||
-type test_case_name() :: atom().
|
||||
@ -39,11 +39,11 @@ groups() ->
|
||||
[
|
||||
{default, [], [
|
||||
empty_judge,
|
||||
validate_default_user_fragment,
|
||||
validate_user_fragment,
|
||||
validate_env_fragment,
|
||||
validate_auth_fragment,
|
||||
validate_requester_fragment,
|
||||
validate_complex_fragment,
|
||||
validate_remote_user_fragment
|
||||
]}
|
||||
].
|
||||
@ -105,37 +105,17 @@ empty_judge(C) ->
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(?RULESET_ID, #{}, WoodyContext).
|
||||
|
||||
-spec validate_default_user_fragment(config()) -> _.
|
||||
validate_default_user_fragment(C) ->
|
||||
UserID = <<"someUser">>,
|
||||
mock_services(
|
||||
[
|
||||
{bouncer, fun('Judge', {_RulesetID, Fragments}) ->
|
||||
case get_user_id(Fragments) of
|
||||
UserID ->
|
||||
{ok, #bdcs_Judgement{resolution = allowed}};
|
||||
_ ->
|
||||
{ok, #bdcs_Judgement{resolution = forbidden}}
|
||||
end
|
||||
end}
|
||||
],
|
||||
C
|
||||
),
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"user">> => bouncer_context_helpers:make_default_user_context_fragment(UserID)}},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
-spec validate_user_fragment(config()) -> _.
|
||||
validate_user_fragment(C) ->
|
||||
UserID = <<"someUser">>,
|
||||
UserRealm = <<"external">>,
|
||||
mock_services(
|
||||
[
|
||||
{bouncer, fun('Judge', {_RulesetID, Fragments}) ->
|
||||
case get_user_id(Fragments) of
|
||||
UserID ->
|
||||
case get_fragment(<<"user">>, Fragments) of
|
||||
#bctx_v1_ContextFragment{
|
||||
user = #bctx_v1_User{id = UserID, realm = #bctx_v1_Entity{id = UserRealm}}
|
||||
} ->
|
||||
{ok, #bdcs_Judgement{resolution = allowed}};
|
||||
_ ->
|
||||
{ok, #bdcs_Judgement{resolution = forbidden}}
|
||||
@ -147,7 +127,14 @@ validate_user_fragment(C) ->
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"user">> => bouncer_context_helpers:make_user_context_fragment(#{id => UserID})}},
|
||||
#{
|
||||
fragments => #{
|
||||
<<"user">> => bouncer_context_helpers:make_user_fragment(#{
|
||||
id => UserID,
|
||||
realm => #{id => UserRealm}
|
||||
})
|
||||
}
|
||||
},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
@ -170,7 +157,7 @@ validate_env_fragment(C) ->
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"env">> => bouncer_context_helpers:make_env_context_fragment(#{now => Time})}},
|
||||
#{fragments => #{<<"env">> => bouncer_context_helpers:make_env_fragment(#{now => Time})}},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
@ -193,7 +180,7 @@ validate_auth_fragment(C) ->
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"auth">> => bouncer_context_helpers:make_auth_context_fragment(#{method => Method})}},
|
||||
#{fragments => #{<<"auth">> => bouncer_context_helpers:make_auth_fragment(#{method => Method})}},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
@ -221,7 +208,48 @@ validate_requester_fragment(C) ->
|
||||
WoodyContext = woody_context:new(),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"requester">> => bouncer_context_helpers:make_requester_context_fragment(#{ip => IP})}},
|
||||
#{fragments => #{<<"requester">> => bouncer_context_helpers:make_requester_fragment(#{ip => IP})}},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
-spec validate_complex_fragment(config()) -> _.
|
||||
validate_complex_fragment(C) ->
|
||||
mock_services(
|
||||
[
|
||||
{bouncer, fun('Judge', {_RulesetID, Fragments}) ->
|
||||
case Fragments of
|
||||
#bdcs_Context{fragments = #{<<"complex">> := Fragment}} ->
|
||||
case decode_fragment(Fragment) of
|
||||
#bctx_v1_ContextFragment{
|
||||
env = #bctx_v1_Environment{},
|
||||
auth = #bctx_v1_Auth{},
|
||||
user = #bctx_v1_User{}
|
||||
} ->
|
||||
{ok, #bdcs_Judgement{resolution = allowed}};
|
||||
_ ->
|
||||
{ok, #bdcs_Judgement{resolution = forbidden}}
|
||||
end;
|
||||
_ ->
|
||||
{ok, #bdcs_Judgement{resolution = forbidden}}
|
||||
end
|
||||
end}
|
||||
],
|
||||
C
|
||||
),
|
||||
WoodyContext = woody_context:new(),
|
||||
ComplexFragment =
|
||||
bouncer_context_helpers:add_user(
|
||||
#{id => <<"USER">>, realm => #{id => <<"external">>}, email => <<"user@example.org">>},
|
||||
bouncer_context_helpers:add_auth(
|
||||
#{method => <<"METHOD">>},
|
||||
bouncer_context_helpers:make_env_fragment(
|
||||
#{now => genlib_rfc3339:format(genlib_time:unow(), second)}
|
||||
)
|
||||
)
|
||||
),
|
||||
allowed = bouncer_client:judge(
|
||||
?RULESET_ID,
|
||||
#{fragments => #{<<"complex">> => ComplexFragment}},
|
||||
WoodyContext
|
||||
).
|
||||
|
||||
@ -250,20 +278,15 @@ validate_remote_user_fragment(C) ->
|
||||
C
|
||||
),
|
||||
WoodyContext = woody_context:new(),
|
||||
{ok, EncodedUserFragment} = bouncer_context_helpers:get_user_context_fragment(UserID, WoodyContext),
|
||||
{ok, EncodedUserFragment} = bouncer_context_helpers:get_user_orgs_fragment(UserID, WoodyContext),
|
||||
allowed = bouncer_client:judge(?RULESET_ID, #{fragments => #{<<"user">> => EncodedUserFragment}}, WoodyContext).
|
||||
|
||||
%%
|
||||
|
||||
get_ip(#bdcs_Context{
|
||||
fragments = #{
|
||||
<<"requester">> := #bctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = Fragment
|
||||
}
|
||||
}
|
||||
fragments = #{<<"requester">> := Fragment}
|
||||
}) ->
|
||||
case decode(Fragment) of
|
||||
case decode_fragment(Fragment) of
|
||||
{error, _} = Error ->
|
||||
error(Error);
|
||||
#bctx_v1_ContextFragment{requester = #bctx_v1_Requester{ip = IP}} ->
|
||||
@ -271,14 +294,9 @@ get_ip(#bdcs_Context{
|
||||
end.
|
||||
|
||||
get_auth_method(#bdcs_Context{
|
||||
fragments = #{
|
||||
<<"auth">> := #bctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = Fragment
|
||||
}
|
||||
}
|
||||
fragments = #{<<"auth">> := Fragment}
|
||||
}) ->
|
||||
case decode(Fragment) of
|
||||
case decode_fragment(Fragment) of
|
||||
{error, _} = Error ->
|
||||
error(Error);
|
||||
#bctx_v1_ContextFragment{auth = #bctx_v1_Auth{method = Method}} ->
|
||||
@ -286,14 +304,9 @@ get_auth_method(#bdcs_Context{
|
||||
end.
|
||||
|
||||
get_time(#bdcs_Context{
|
||||
fragments = #{
|
||||
<<"env">> := #bctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = Fragment
|
||||
}
|
||||
}
|
||||
fragments = #{<<"env">> := Fragment}
|
||||
}) ->
|
||||
case decode(Fragment) of
|
||||
case decode_fragment(Fragment) of
|
||||
{error, _} = Error ->
|
||||
error(Error);
|
||||
#bctx_v1_ContextFragment{env = #bctx_v1_Environment{now = Time}} ->
|
||||
@ -301,21 +314,29 @@ get_time(#bdcs_Context{
|
||||
end.
|
||||
|
||||
get_user_id(#bdcs_Context{
|
||||
fragments = #{
|
||||
<<"user">> := #bctx_ContextFragment{
|
||||
type = v1_thrift_binary,
|
||||
content = Fragment
|
||||
}
|
||||
}
|
||||
fragments = #{<<"user">> := Fragment}
|
||||
}) ->
|
||||
case decode(Fragment) of
|
||||
case decode_fragment(Fragment) of
|
||||
{error, _} = Error ->
|
||||
error(Error);
|
||||
#bctx_v1_ContextFragment{user = #bctx_v1_User{id = UserID}} ->
|
||||
UserID
|
||||
end.
|
||||
|
||||
decode(Content) ->
|
||||
get_fragment(ID, #bdcs_Context{
|
||||
fragments = Fragments
|
||||
}) ->
|
||||
case decode_fragment(maps:get(ID, Fragments)) of
|
||||
{error, _} = Error ->
|
||||
error(Error);
|
||||
Fragment = #bctx_v1_ContextFragment{} ->
|
||||
Fragment
|
||||
end.
|
||||
|
||||
decode_fragment(#bctx_ContextFragment{type = v1_thrift_binary, content = Content}) ->
|
||||
decode_fragment_content(Content).
|
||||
|
||||
decode_fragment_content(Content) ->
|
||||
Type = {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}},
|
||||
Codec = thrift_strict_binary_codec:new(Content),
|
||||
case thrift_strict_binary_codec:read(Codec, Type) of
|
||||
|
Loading…
Reference in New Issue
Block a user