upgrade: +alpine +world (#18)

This commit is contained in:
dinama 2021-01-19 15:51:31 +03:00 committed by GitHub
parent 83bc982c8e
commit 02ab22aa33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 256 additions and 290 deletions

2
Jenkinsfile vendored
View File

@ -18,6 +18,6 @@ build('erlang_uac', 'docker-host', finalHook) {
pipeErlangLib = load("${env.JENKINS_LIB}/pipeErlangLib.groovy")
}
pipeErlangLib.runPipe(false)
pipeErlangLib.runPipe(false, true, 'test')
}

View File

@ -8,12 +8,13 @@ TEMPLATES_PATH := .
# Name of the service
SERVICE_NAME := erlang_uac
BUILD_IMAGE_TAG := e7eb72b7721443d88a948546da815528a96c6de9
BUILD_IMAGE_NAME := build-erlang
BUILD_IMAGE_TAG := 1333d0926b203e00c47e4fad7e10d2252a020305
CALL_ANYWHERE := \
submodules \
all compile xref lint dialyze test cover \
start clean distclean
start clean distclean check_format format
CALL_W_CONTAINER := $(CALL_ANYWHERE)
@ -36,10 +37,16 @@ xref:
$(REBAR) xref
lint:
elvis rock
elvis rock -V
check_format:
$(REBAR) fmt -c
format:
$(REBAR) fmt -w
dialyze:
$(REBAR) dialyzer
$(REBAR) as test dialyzer
start: submodules
$(REBAR) run

@ -1 +1 @@
Subproject commit 540183862bc9fd04682e226de2056a320fd44be9
Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e

View File

@ -2,16 +2,12 @@
{elvis, [
{config, [
#{
dirs => [
"src",
"test"
],
dirs => ["src/**", "test/**"],
filter => "*.erl",
ignore => ["_SUITE.erl$"],
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_text_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_text_style, no_tabs},
{elvis_text_style, no_trailing_whitespace},
{elvis_style, macro_module_names},
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
{elvis_style, nesting_level, #{level => 4}},
@ -47,9 +43,9 @@
dirs => ["."],
filter => "rebar.config",
rules => [
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace}
{elvis_text_style, line_length, #{limit => 120, skip_comments => false}},
{elvis_text_style, no_tabs},
{elvis_text_style, no_trailing_whitespace}
]
}
]}

View File

@ -1,6 +1,5 @@
%% Common project erlang options.
{erl_opts, [
% mandatory
debug_info,
warnings_as_errors,
@ -27,14 +26,9 @@
%% Common project dependencies.
{deps, [
{jsx, "2.8.2"},
{jose, "1.7.9"},
{base64url, "0.0.1"},
{genlib,
{git, "https://github.com/rbkmoney/genlib.git",
{branch, "master"}
}
}
{jsx, "3.0.0"},
{jose, "1.11.1"},
{genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}}
]}.
%% XRef checks
@ -44,6 +38,7 @@
deprecated_functions_calls,
deprecated_functions
]}.
% at will
% {xref_warnings, true}.
@ -64,11 +59,19 @@
{profiles, [
{test, [
{deps, [
{snowflake,
{git, "https://github.com/rbkmoney/snowflake.git",
{branch, "master"}
}
}
{snowflake, {git, "https://github.com/rbkmoney/snowflake.git", {branch, "master"}}}
]},
{dialyzer, [
{plt_extra_apps, [eunit, common_test, snowflake]}
]}
]}
]}.
{plugins, [
{erlfmt, "0.9.0"}
]}.
{erlfmt, [
{print_width, 120},
{files, ["{src,include,test}/*.{hrl,erl}", "rebar.config", "elvis.config"]}
]}.

View File

@ -1,14 +1,15 @@
{"1.1.0",
[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
{<<"genlib">>,
{"1.2.0",
[{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"41920d7774d119c294f3aaba4043ced12da2a815"}},
{ref,"4565a8d73f34a0b78cca32c9cd2b97d298bdadf8"}},
0},
{<<"jose">>,{pkg,<<"jose">>,<<"1.7.9">>},0},
{<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.2">>},0}]}.
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.1">>},0},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},0}]}.
[
{pkg_hash,[
{<<"base64url">>, <<"36A90125F5948E3AFD7BE97662A1504B934DD5DAC78451CA6E9ABF85A10286BE">>},
{<<"jose">>, <<"9DC5A14AB62DB4E41677FCC97993752562FB57AD0B8BA062589682EDD3ACB91F">>},
{<<"jsx">>, <<"7ACC7D785B5ABE8A6E9ADBDE926A24E481F29956DD8B4DF49E3E4E7BCC92A018">>}]}
{<<"jose">>, <<"59DA64010C69AAD6CDE2F5B9248B896B84472E99BD18F246085B7B9FE435DCDB">>},
{<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>}]},
{pkg_hash_ext,[
{<<"jose">>, <<"078F6C9FB3CD2F4CFAFC972C814261A7D1E8D2B3685C0A76EB87E158EFFF1AC5">>},
{<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>}]}
].

View File

@ -9,7 +9,6 @@
public_key,
genlib,
jose,
base64url,
jsx
]},
{env, []},

View File

@ -3,11 +3,13 @@
%% App
-behaviour(application).
-export([start/2, stop/1]).
%% Supervisor
-behaviour(supervisor).
-export([init/1]).
%%API
@ -18,7 +20,7 @@
-export([authorize_operation/3]).
-type context() :: uac_authorizer_jwt:t().
-type claims() :: uac_authorizer_jwt:claims().
-type claims() :: uac_authorizer_jwt:claims().
-type configuration() :: #{
jwt := uac_authorizer_jwt:options(),
@ -30,8 +32,8 @@
domains_to_decode => [domain_name()]
}.
-type api_key() :: binary().
-type key_type() :: bearer.
-type api_key() :: binary().
-type key_type() :: bearer.
-type domain_name() :: uac_authorizer_jwt:domain_name().
-export_type([context/0]).
@ -42,18 +44,14 @@
% API
%%
-spec configure(configuration()) ->
ok.
-spec configure(configuration()) -> ok.
configure(Config) ->
AuthorizerConfig = maps:get(jwt, Config),
AccessConfig = maps:get(access, Config),
ok = uac_authorizer_jwt:configure(AuthorizerConfig),
ok = uac_conf:configure(AccessConfig).
-spec authorize_api_key(api_key(), verification_opts()) ->
{ok, context()} | {error, Reason :: atom()}.
-spec authorize_api_key(api_key(), verification_opts()) -> {ok, context()} | {error, Reason :: atom()}.
authorize_api_key(ApiKey, VerificationOpts) ->
case parse_api_key(ApiKey) of
{ok, {Type, Credentials}} ->
@ -62,9 +60,7 @@ authorize_api_key(ApiKey, VerificationOpts) ->
{error, Error}
end.
-spec parse_api_key(ApiKey :: api_key()) ->
{ok, {bearer, uac_authorizer_jwt:token()}} | {error, Reason :: atom()}.
-spec parse_api_key(ApiKey :: api_key()) -> {ok, {bearer, uac_authorizer_jwt:token()}} | {error, Reason :: atom()}.
parse_api_key(ApiKey) ->
case ApiKey of
<<"Bearer ", Token/binary>> ->
@ -75,20 +71,16 @@ parse_api_key(ApiKey) ->
-spec authorize_api_key(key_type(), uac_authorizer_jwt:token(), verification_opts()) ->
{ok, context()} | {error, Reason :: atom()}.
authorize_api_key(bearer, Token, VerificationOpts) ->
uac_authorizer_jwt:verify(Token, VerificationOpts).
%%
-spec authorize_operation(uac_conf:operation_access_scopes(), context()) ->
ok | {error, unauthorized}.
-spec authorize_operation(uac_conf:operation_access_scopes(), context()) -> ok | {error, unauthorized}.
authorize_operation(AccessScope, Context) ->
authorize_operation(AccessScope, Context, uac_conf:get_domain_name()).
-spec authorize_operation(uac_conf:operation_access_scopes(), context(), domain_name()) ->
ok | {error, unauthorized}.
-spec authorize_operation(uac_conf:operation_access_scopes(), context(), domain_name()) -> ok | {error, unauthorized}.
authorize_operation(AccessScope, {_, _, Claims}, Domain) ->
ACL = get_acl(Claims, Domain),
authorize_operation_(AccessScope, ACL).
@ -96,12 +88,14 @@ authorize_operation(AccessScope, {_, _, Claims}, Domain) ->
authorize_operation_(_, undefined) ->
{error, unauthorized};
authorize_operation_(AccessScope, ACL) ->
case lists:all(
fun ({Scope, Permission}) ->
lists:member(Permission, uac_acl:match(Scope, ACL))
end,
AccessScope
) of
case
lists:all(
fun({Scope, Permission}) ->
lists:member(Permission, uac_acl:match(Scope, ACL))
end,
AccessScope
)
of
true ->
ok;
false ->
@ -118,13 +112,11 @@ get_acl(Claims, Domain) ->
% App
%%
-spec start(any(), any()) ->
{ok, pid()} | {error, Reason :: term()}.
-spec start(any(), any()) -> {ok, pid()} | {error, Reason :: term()}.
start(_StartType, _StartArgs) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec stop(any()) ->
ok.
-spec stop(any()) -> ok.
stop(_State) ->
ok.
@ -132,8 +124,7 @@ stop(_State) ->
% Supervisor
%%
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
AuthorizerSpec = uac_authorizer_jwt:get_child_spec(),
AccessSpec = uac_conf:get_child_spec(),

View File

@ -2,15 +2,15 @@
%%
-opaque t() :: [{{priority(), scope()}, [permission()]}].
-opaque t() :: [{{priority(), scope()}, [permission()]}].
-type priority() :: integer().
-type priority() :: integer().
-type unknown_scope() :: {unknown, binary()}.
-type known_scope() :: [resource() | {resource(), resource_id()}, ...].
-type scope() :: known_scope() | unknown_scope().
-type resource() :: atom().
-type resource_id() :: binary().
-type permission() :: read | write.
-type known_scope() :: [resource() | {resource(), resource_id()}, ...].
-type scope() :: known_scope() | unknown_scope().
-type resource() :: atom().
-type resource_id() :: binary().
-type permission() :: read | write.
-export_type([t/0]).
-export_type([scope/0]).
@ -31,27 +31,19 @@
%%
-spec new() ->
t().
-spec new() -> t().
new() ->
[].
-spec to_list(t()) ->
[{scope(), permission()}].
-spec to_list(t()) -> [{scope(), permission()}].
to_list(ACL) ->
[{S, P} || {{_, S}, P} <- ACL].
-spec from_list([{scope(), permission()}]) ->
t().
-spec from_list([{scope(), permission()}]) -> t().
from_list(L) ->
lists:foldl(fun ({S, P}, ACL) -> insert_scope(S, P, ACL) end, new(), L).
-spec insert_scope(scope(), permission(), t()) ->
t().
lists:foldl(fun({S, P}, ACL) -> insert_scope(S, P, ACL) end, new(), L).
-spec insert_scope(scope(), permission(), t()) -> t().
insert_scope({unknown, _} = Scope, Permission, ACL) ->
insert({{0, Scope}, [Permission]}, ACL);
insert_scope(Scope, Permission, ACL) ->
@ -68,9 +60,7 @@ insert({PS, _} = V, [{PS0, _} | _] = Vs) when PS > PS0 ->
insert(V, []) ->
[V].
-spec remove_scope(scope(), permission(), t()) ->
t().
-spec remove_scope(scope(), permission(), t()) -> t().
remove_scope(Scope, Permission, ACL) ->
Priority = compute_priority(Scope, Permission),
remove({{Priority, Scope}, [Permission]}, ACL).
@ -112,9 +102,7 @@ compute_permission_priority(V) ->
error({badarg, {permission, V}}).
%%
-spec match(known_scope(), t()) ->
[permission()].
-spec match(known_scope(), t()) -> [permission()].
match(Scope, ACL) when length(Scope) > 0 ->
match_rules(Scope, ACL);
match(Scope, _) ->
@ -149,9 +137,7 @@ match_scope(_, _) ->
%%
-spec decode([binary()]) ->
t().
-spec decode([binary()]) -> t().
decode(V) ->
lists:foldl(fun decode_entry/2, new(), V).
@ -190,7 +176,9 @@ decode_scope_frag_resource(V, ID, H) ->
{{R, ID}, delve(R, H)}.
decode_resource(V) ->
try binary_to_existing_atom(V, utf8) catch
try
binary_to_existing_atom(V, utf8)
catch
error:badarg ->
error({badarg, {resource, V}})
end.
@ -204,16 +192,19 @@ decode_permission(V) ->
%%
-spec encode(t()) ->
[binary()].
-spec encode(t()) -> [binary()].
encode(ACL) ->
lists:flatmap(fun encode_entry/1, ACL).
encode_entry({{_Priority, Scope}, Permissions}) ->
S = encode_scope(Scope),
[begin P = encode_permission(Permission), <<S/binary, ":", P/binary>> end
|| Permission <- Permissions].
[
begin
P = encode_permission(Permission),
<<S/binary, ":", P/binary>>
end
|| Permission <- Permissions
].
encode_scope({unknown, V}) when is_binary(V) ->
V;

View File

@ -24,19 +24,20 @@
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("jose/include/jose_jwt.hrl").
-type keyname() :: term().
-type kid() :: binary().
-type key() :: #jose_jwk{}.
-type token() :: binary().
-type claims() :: #{binary() => domains() | expiration() | term()}.
-type subject_id() :: binary().
-type t() :: {id(), subject_id(), claims()}.
-type domain_name() :: binary().
-type domains() :: #{domain_name() => uac_acl:t()}.
-type expiration() ::
{lifetime, Seconds :: pos_integer()} |
{deadline, UnixTs :: pos_integer()} |
unlimited.
-type keyname() :: term().
-type kid() :: binary().
-type key() :: #jose_jwk{}.
-type token() :: binary().
-type claims() :: #{binary() => domains() | expiration() | term()}.
-type subject_id() :: binary().
-type t() :: {id(), subject_id(), claims()}.
-type domain_name() :: binary().
-type domains() :: #{domain_name() => uac_acl:t()}.
-type expiration() ::
{lifetime, Seconds :: pos_integer()}
| {deadline, UnixTs :: pos_integer()}
| unlimited.
-type id() :: binary().
-export_type([t/0]).
@ -45,6 +46,7 @@
-export_type([expiration/0]).
-export_type([domain_name/0]).
-export_type([domains/0]).
%%
-type options() :: #{
@ -62,27 +64,24 @@
-type keysource() ::
{pem_file, file:filename()}.
-spec get_child_spec() ->
[supervisor:child_spec()].
-spec get_child_spec() -> [supervisor:child_spec()].
get_child_spec() ->
[#{
id => ?MODULE,
start => {supervisor, start_link, [?MODULE, []]},
type => supervisor
}].
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
[
#{
id => ?MODULE,
start => {supervisor, start_link, [?MODULE, []]},
type => supervisor
}
].
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
ok = create_table(),
{ok, {#{}, []}}.
%%
-spec configure(options()) ->
ok.
-spec configure(options()) -> ok.
configure(Options) ->
Keyset = parse_options(Options),
_ = maps:map(fun ensure_store_key/2, Keyset),
@ -92,7 +91,7 @@ parse_options(Options) ->
Keyset = maps:get(keyset, Options, #{}),
_ = is_map(Keyset) orelse exit({invalid_option, keyset, Keyset}),
_ = genlib_map:foreach(
fun (K, V) ->
fun(K, V) ->
is_keysource(V) orelse exit({invalid_option, K, V})
end,
Keyset
@ -114,9 +113,7 @@ ensure_store_key(Keyname, Source) ->
%%
-spec store_key(keyname(), {pem_file, file:filename()}) ->
ok | {error, file:posix() | {unknown_key, _}}.
-spec store_key(keyname(), {pem_file, file:filename()}) -> ok | {error, file:posix() | {unknown_key, _}}.
store_key(Keyname, {pem_file, Filename}) ->
store_key(Keyname, {pem_file, Filename}, #{
kid => fun derive_kid_from_public_key_pem_entry/1
@ -126,15 +123,13 @@ derive_kid_from_public_key_pem_entry(JWK) ->
JWKPublic = jose_jwk:to_public(JWK),
{_Module, PublicKey} = JWKPublic#jose_jwk.kty,
{_PemEntry, Data, _} = public_key:pem_entry_encode('SubjectPublicKeyInfo', PublicKey),
base64url:encode(crypto:hash(sha256, Data)).
jose_base64url:encode(crypto:hash(sha256, Data)).
-type store_opts() :: #{
kid => fun ((key()) -> kid())
kid => fun((key()) -> kid())
}.
-spec store_key(keyname(), {pem_file, file:filename()}, store_opts()) ->
ok | {error, file:posix() | {unknown_key, _}}.
-spec store_key(keyname(), {pem_file, file:filename()}, store_opts()) -> ok | {error, file:posix() | {unknown_key, _}}.
store_key(Keyname, {pem_file, Filename}, Opts) ->
case jose_jwk:from_pem_file(Filename) of
JWK = #jose_jwk{} ->
@ -148,24 +143,33 @@ derive_kid(JWK, #{kid := DeriveFun}) when is_function(DeriveFun, 1) ->
DeriveFun(JWK).
construct_key(KID, JWK) ->
Signer = try jose_jwk:signer(JWK) catch error:_ -> undefined end,
Verifier = try jose_jwk:verifier(JWK) catch error:_ -> undefined end,
Signer =
try
jose_jwk:signer(JWK)
catch
error:_ -> undefined
end,
Verifier =
try
jose_jwk:verifier(JWK)
catch
error:_ -> undefined
end,
#{
jwk => JWK,
kid => KID,
signer => Signer,
can_sign => Signer /= undefined,
verifier => Verifier,
jwk => JWK,
kid => KID,
signer => Signer,
can_sign => Signer /= undefined,
verifier => Verifier,
can_verify => Verifier /= undefined
}.
%%
-spec issue(id(), subject_id(), claims(), keyname()) ->
{ok, token()} |
{error, nonexistent_key} |
{error, {invalid_signee, Reason :: atom()}}.
{ok, token()}
| {error, nonexistent_key}
| {error, {invalid_signee, Reason :: atom()}}.
issue(JTI, SubjectID, Claims, Signee) ->
case try_get_key_for_sign(Signee) of
{ok, Key} ->
@ -197,8 +201,6 @@ encode_claim(<<"resource_access">>, DomainRoles) ->
encode_claim(_, Value) ->
Value.
get_expires_at({lifetime, Lt}) ->
genlib_time:unow() + Lt;
get_expires_at({deadline, Dl}) ->
@ -214,20 +216,17 @@ sign(#{kid := KID, jwk := JWK, signer := #{} = JWS}, Claims) ->
%%
-spec verify(token(), uac:verification_opts()) ->
{ok, t()} |
{error,
{ok, t()}
| {error,
{invalid_token,
badarg |
{badarg, term()} |
{missing, atom()} |
expired |
{malformed_acl, term()}
} |
{nonexistent_key, kid()} |
{invalid_operation, term()} |
invalid_signature
}.
badarg
| {badarg, term()}
| {missing, atom()}
| expired
| {malformed_acl, term()}}
| {nonexistent_key, kid()}
| {invalid_operation, term()}
| invalid_signature}.
verify(Token, VerificationOpts) ->
try
{_, ExpandedToken} = jose_jws:expand(Token),
@ -299,9 +298,9 @@ get_alg(#{}) ->
get_validators() ->
[
{token_id , <<"jti">> , fun check_presence/3},
{subject_id , <<"sub">> , fun check_presence/3},
{expires_at , <<"exp">> , fun check_expiration/3}
{token_id, <<"jti">>, fun check_presence/3},
{subject_id, <<"sub">>, fun check_presence/3},
{expires_at, <<"exp">>, fun check_expiration/3}
].
check_presence(_, V, _) when is_binary(V) ->
@ -323,7 +322,7 @@ check_expiration(_, Exp, Opts) when is_integer(Exp) ->
check_expiration(C, undefined, Opts) ->
case get_check_expiry(Opts) of
{true, _} -> throw({invalid_token, {missing, C}});
false -> undefined
false -> undefined
end;
check_expiration(C, V, _) ->
throw({invalid_token, {badarg, {C, V}}}).
@ -337,27 +336,22 @@ get_check_expiry(Opts) ->
end.
-spec get_subject_id(t()) -> binary().
get_subject_id({_Id, SubjectID, _Claims}) ->
SubjectID.
-spec get_claims(t()) -> claims().
get_claims({_Id, _Subject, Claims}) ->
Claims.
-spec get_claim(binary(), t()) -> term().
get_claim(ClaimName, {_Id, _Subject, Claims}) ->
maps:get(ClaimName, Claims).
-spec get_claim(binary(), t(), term()) -> term().
get_claim(ClaimName, {_Id, _Subject, Claims}, Default) ->
maps:get(ClaimName, Claims, Default).
-spec create_claims(claims(), expiration(), domains()) -> claims().
create_claims(Claims, Expiration, DomainRoles) ->
Claims#{
<<"exp">> => Expiration,
@ -394,7 +388,7 @@ decode_roles(Claims, VerificationOpts) ->
insert_key(Keyname, KeyInfo = #{kid := KID}) ->
insert_values(#{
{keyname, Keyname} => KeyInfo,
{kid, KID} => KeyInfo
{kid, KID} => KeyInfo
}).
get_key_by_name(Keyname) ->
@ -404,7 +398,8 @@ get_key_by_kid(KID) ->
lookup_value({kid, KID}).
base64url_to_map(Base64) when is_binary(Base64) ->
jsx:decode(base64url:decode(Base64), [return_maps]).
{ok, Json} = jose_base64url:decode(Base64),
jsx:decode(Json, [return_maps]).
%%

View File

@ -19,24 +19,23 @@
domain_name := domain_name(),
resource_hierarchy := resource_hierarchy()
}.
-export_type([options/0]).
-export_type([operation_access_scopes/0]).
%%
-spec get_child_spec() ->
[supervisor:child_spec()].
-spec get_child_spec() -> [supervisor:child_spec()].
get_child_spec() ->
[#{
id => ?MODULE,
start => {supervisor, start_link, [?MODULE, []]},
type => supervisor
}].
-spec init([]) ->
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
[
#{
id => ?MODULE,
start => {supervisor, start_link, [?MODULE, []]},
type => supervisor
}
].
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
init([]) ->
ok = create_table(),
{ok, {#{}, []}}.
@ -45,20 +44,17 @@ init([]) ->
%% API
%%
-spec get_domain_name() ->
domain_name().
-spec get_domain_name() -> domain_name().
get_domain_name() ->
lookup_value(domain_name).
-spec get_resource_hierarchy() ->
resource_hierarchy().
-spec get_resource_hierarchy() -> resource_hierarchy().
get_resource_hierarchy() ->
lookup_value(resource_hierarchy).
%%
-spec configure(options()) ->
ok.
-spec configure(options()) -> ok.
configure(Config) ->
ok = insert_values(Config).

View File

@ -21,20 +21,19 @@
match_scope_test/1
]).
-spec illegal_input_test(config()) -> _.
-spec unrecognized_resource_test(config()) -> _.
-spec empty_test(config()) -> _.
-spec illegal_input_test(config()) -> _.
-spec unrecognized_resource_test(config()) -> _.
-spec empty_test(config()) -> _.
-spec stable_encoding_test(config()) -> _.
-spec remove_scopes_test(config()) -> _.
-spec redundancy_test(config()) -> _.
-spec match_scope_test(config()) -> _.
-spec remove_scopes_test(config()) -> _.
-spec redundancy_test(config()) -> _.
-spec match_scope_test(config()) -> _.
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type group_name() :: atom().
-spec all() ->
[test_case_name()].
-spec all() -> [test_case_name()].
all() ->
[
illegal_input_test,
@ -46,8 +45,7 @@ all() ->
match_scope_test
].
-spec init_per_suite(config()) ->
config().
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
Apps = genlib_app:start_application(uac),
uac:configure(#{
@ -59,43 +57,39 @@ init_per_suite(Config) ->
access => #{
domain_name => <<"test">>,
resource_hierarchy => #{
party => #{invoice_templates => #{invoice_template_invoices => #{}}},
customers => #{bindings => #{}},
invoices => #{payments => #{}},
party => #{invoice_templates => #{invoice_template_invoices => #{}}},
customers => #{bindings => #{}},
invoices => #{payments => #{}},
payment_resources => #{}
}
}
}),
[{apps, Apps}] ++ Config.
-spec init_per_group(group_name(), config()) ->
config().
-spec init_per_group(group_name(), config()) -> config().
init_per_group(_Name, Config) ->
Config.
-spec init_per_testcase(group_name(), config()) ->
config().
-spec init_per_testcase(group_name(), config()) -> config().
init_per_testcase(_Name, Config) ->
Config.
-spec end_per_suite(config()) ->
config().
-spec end_per_suite(config()) -> config().
end_per_suite(Config) ->
Config.
-spec end_per_group(group_name(), config()) ->
config().
-spec end_per_group(group_name(), config()) -> config().
end_per_group(_Name, Config) ->
Config.
-spec end_per_testcase(test_case_name(), config()) ->
config().
-spec end_per_testcase(test_case_name(), config()) -> config().
end_per_testcase(_Name, Config) ->
Config.
-dialyzer({[no_fail_call], illegal_input_test/1}).
illegal_input_test(_C) ->
?assertError({badarg, {scope , _}}, from_list([{[], read}])),
?assertError({badarg, {scope, _}}, from_list([{[], read}])),
?assertError({badarg, {permission, _}}, from_list([{[invoices], wread}])).
unrecognized_resource_test(_C) ->
@ -143,15 +137,23 @@ remove_scopes_test(_C) ->
),
?assertEqual(
new(),
remove([party], read,
remove([party], write,
remove([party], read,
remove(
[party],
read,
remove(
[party],
write,
remove(
[party],
read,
from_list([{[party], read}, {[party], write}])
)
)
)
).
-dialyzer({[no_fail_call], match_scope_test/1}).
match_scope_test(_C) ->
ACL = from_list([
{[party], read},
@ -161,13 +163,13 @@ match_scope_test(_C) ->
{[{invoices, <<"42">>}], write},
{[{invoices, <<"42">>}, payments], read}
]),
?assertError({badarg, _} , match([], ACL)),
?assertEqual([write] , match([{invoices, <<"42">>}], ACL)),
?assertEqual([read] , match([{invoices, <<"43">>}], ACL)),
?assertEqual([read] , match([{invoices, <<"42">>}, {payments, <<"1">>}], ACL)),
?assertEqual([write] , match([{invoices, <<"43">>}, {payments, <<"1">>}], ACL)),
?assertEqual([read, write] , match([{party, <<"BLARGH">>}], ACL)),
?assertEqual([] , match([payment_resources], ACL)).
?assertError({badarg, _}, match([], ACL)),
?assertEqual([write], match([{invoices, <<"42">>}], ACL)),
?assertEqual([read], match([{invoices, <<"43">>}], ACL)),
?assertEqual([read], match([{invoices, <<"42">>}, {payments, <<"1">>}], ACL)),
?assertEqual([write], match([{invoices, <<"43">>}, {payments, <<"1">>}], ACL)),
?assertEqual([read, write], match([{party, <<"BLARGH">>}], ACL)),
?assertEqual([], match([payment_resources], ACL)).
new() ->
uac_acl:new().

View File

@ -24,21 +24,18 @@
configure_processed_domains_test/1
]).
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-type test_case_name() :: atom().
-type config() :: [{atom(), any()}].
-define(EXPIRE_AS_OF_NOW, #{
check_expired_as_of => genlib_time:unow()
}).
-define(TEST_SERVICE_ACL(Access),
[{[test_resource], Access}]
).
-define(TEST_SERVICE_ACL(Access), [{[test_resource], Access}]).
-define(TEST_DOMAIN_NAME, <<"test">>).
-spec all() ->
[test_case_name()].
-spec all() -> [test_case_name()].
all() ->
[
successful_auth_test,
@ -56,8 +53,7 @@ all() ->
configure_processed_domains_test
].
-spec init_per_suite(config()) ->
config().
-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
Apps = [
genlib_app:start_application(snowflake),
@ -78,42 +74,38 @@ init_per_suite(Config) ->
}),
[{apps, Apps}] ++ Config.
-spec end_per_suite(config()) ->
_.
-spec end_per_suite(config()) -> _.
end_per_suite(Config) ->
Config.
%%
-spec successful_auth_test(config()) ->
_.
-spec successful_auth_test(config()) -> _.
successful_auth_test(_) ->
{ok, Token} = issue_token(?TEST_SERVICE_ACL(write), unlimited),
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
ok = uac:authorize_operation(?TEST_SERVICE_ACL(write), AccessContext).
-spec multiple_domain_successful_auth_test(config()) ->
_.
-spec multiple_domain_successful_auth_test(config()) -> _.
multiple_domain_successful_auth_test(_) ->
ACL1 = ?TEST_SERVICE_ACL(write),
ACL2 = ?TEST_SERVICE_ACL(read),
Domain1 = <<"api-1">>,
Domain2 = <<"api-2">>,
{ok, Token} = issue_token(#{
Domain1 => uac_acl:from_list(ACL1),
Domain2 => uac_acl:from_list(ACL2)
}, unlimited),
Domain1 = <<"api-1">>,
Domain2 = <<"api-2">>,
{ok, Token} = issue_token(
#{
Domain1 => uac_acl:from_list(ACL1),
Domain2 => uac_acl:from_list(ACL2)
},
unlimited
),
{ok, AccessContext1} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
ok = uac:authorize_operation(ACL1, AccessContext1, Domain1),
{ok, AccessContext2} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
ok = uac:authorize_operation(ACL2, AccessContext2, Domain2).
-spec invalid_permissions_test(config()) ->
_.
-spec invalid_permissions_test(config()) -> _.
invalid_permissions_test(_) ->
{ok, Token} = issue_token(
[{[test_resource], read}, {{unknown, <<"other_test">>}, write}],
@ -122,33 +114,28 @@ invalid_permissions_test(_) ->
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
{error, _} = uac:authorize_operation(?TEST_SERVICE_ACL(write), AccessContext).
-spec bad_token_test(config()) ->
_.
-spec bad_token_test(config()) -> _.
bad_token_test(Config) ->
{ok, Token} = issue_dummy_token(?TEST_SERVICE_ACL(write), Config),
{error, _} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}).
-spec no_token_test(config()) ->
_.
-spec no_token_test(config()) -> _.
no_token_test(_) ->
Token = <<"">>,
{error, _} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}).
-spec force_expiration_test(config()) ->
_.
-spec force_expiration_test(config()) -> _.
force_expiration_test(_) ->
{ok, Token} = issue_token(?TEST_SERVICE_ACL(write), {deadline, 1}),
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
ok = uac:authorize_operation(?TEST_SERVICE_ACL(write), AccessContext).
-spec force_expiration_fail_test(config()) ->
_.
-spec force_expiration_fail_test(config()) -> _.
force_expiration_fail_test(_) ->
{ok, Token} = issue_token(?TEST_SERVICE_ACL(write), {deadline, 1}),
{error, _} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, ?EXPIRE_AS_OF_NOW).
-spec bad_signee_test(config()) ->
_.
-spec bad_signee_test(config()) -> _.
bad_signee_test(_) ->
ACL = ?TEST_SERVICE_ACL(write),
Claims = uac_authorizer_jwt:create_claims(#{}, unlimited, #{?TEST_DOMAIN_NAME => uac_acl:from_list(ACL)}),
@ -157,19 +144,20 @@ bad_signee_test(_) ->
%%
-spec unknown_resources_ok_test(config()) ->
_.
-spec unknown_resources_ok_test(config()) -> _.
unknown_resources_ok_test(_) ->
ok = uac_conf:configure(#{
domain_name => ?TEST_DOMAIN_NAME,
resource_hierarchy => #{
different_resource => #{},
test_resource => #{},
different_resource => #{},
test_resource => #{},
even_more_different_resource => #{}
}
}),
ACL = [{[different_resource], read}, {[test_resource], write}, {[even_more_different_resource], read}],
{ok, Token} = issue_token(ACL, unlimited),
ok = uac_conf:configure(#{
domain_name => ?TEST_DOMAIN_NAME,
resource_hierarchy => #{
test_resource => #{}
}
@ -177,37 +165,35 @@ unknown_resources_ok_test(_) ->
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
ok = uac:authorize_operation(?TEST_SERVICE_ACL(write), AccessContext).
-spec cant_authorize_without_resource_access(config()) ->
_.
-spec cant_authorize_without_resource_access(config()) -> _.
cant_authorize_without_resource_access(_) ->
{ok, Token} = issue_token(#{}, unlimited),
{ok, AccessContext}= uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}),
{error, unauthorized} = uac:authorize_operation([], AccessContext).
-spec unknown_resources_fail_encode_test(config()) ->
_.
-spec unknown_resources_fail_encode_test(config()) -> _.
unknown_resources_fail_encode_test(_) ->
ACL = [{[different_resource], read}, {[test_resource], write}, {[even_more_different_resource], read}],
?assertError({badarg, {resource, _}}, issue_token(ACL, unlimited)).
-spec no_expiration_claim_allowed(config()) ->
_.
-spec no_expiration_claim_allowed(config()) -> _.
no_expiration_claim_allowed(_) ->
PartyID = <<"TEST">>,
{ok, Token} = uac_authorizer_jwt:issue(unique_id(), PartyID, #{}, test),
{ok, _} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{}).
-spec configure_processed_domains_test(config()) ->
_.
-spec configure_processed_domains_test(config()) -> _.
configure_processed_domains_test(_) ->
ACL = ?TEST_SERVICE_ACL(read),
Domain1 = <<"api-1">>,
Domain2 = <<"api-2">>,
{ok, Token} = issue_token(#{
Domain1 => uac_acl:from_list(ACL),
Domain2 => uac_acl:from_list(ACL)
}, unlimited),
Domain1 = <<"api-1">>,
Domain2 = <<"api-2">>,
{ok, Token} = issue_token(
#{
Domain1 => uac_acl:from_list(ACL),
Domain2 => uac_acl:from_list(ACL)
},
unlimited
),
{ok, AccessContext} = uac:authorize_api_key(<<"Bearer ", Token/binary>>, #{domains_to_decode => [Domain1]}),
ok = uac:authorize_operation([], AccessContext, Domain1),
{error, unauthorized} = uac:authorize_operation([], AccessContext, Domain2).
@ -219,7 +205,6 @@ issue_token(DomainRoles, LifeTime) when is_map(DomainRoles) ->
Claims0 = #{<<"TEST">> => <<"TEST">>},
Claims = uac_authorizer_jwt:create_claims(Claims0, LifeTime, DomainRoles),
uac_authorizer_jwt:issue(unique_id(), PartyID, Claims, test);
issue_token(ACL, LifeTime) ->
PartyID = <<"TEST">>,
Claims0 = #{<<"TEST">> => <<"TEST">>},
@ -244,7 +229,7 @@ issue_dummy_token(ACL, Config) ->
JWKPublic = jose_jwk:to_public(GoodJWK),
{_Module, PublicKey} = JWKPublic#jose_jwk.kty,
{_PemEntry, Data, _} = public_key:pem_entry_encode('SubjectPublicKeyInfo', PublicKey),
KID = base64url:encode(crypto:hash(sha256, Data)),
KID = jose_base64url:encode(crypto:hash(sha256, Data)),
JWT = jose_jwt:sign(BadJWK, #{<<"alg">> => <<"RS256">>, <<"kid">> => KID}, Claims),
{_Modules, Token} = jose_jws:compact(JWT),
{ok, Token}.