mirror of
https://github.com/valitydev/erlang_uac.git
synced 2024-11-06 01:35:23 +00:00
upgrade: +alpine +world (#18)
This commit is contained in:
parent
83bc982c8e
commit
02ab22aa33
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -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')
|
||||
}
|
||||
|
||||
|
15
Makefile
15
Makefile
@ -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
|
18
elvis.config
18
elvis.config
@ -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}
|
||||
]
|
||||
}
|
||||
]}
|
||||
|
31
rebar.config
31
rebar.config
@ -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"]}
|
||||
]}.
|
||||
|
19
rebar.lock
19
rebar.lock
@ -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">>}]}
|
||||
].
|
||||
|
@ -9,7 +9,6 @@
|
||||
public_key,
|
||||
genlib,
|
||||
jose,
|
||||
base64url,
|
||||
jsx
|
||||
]},
|
||||
{env, []},
|
||||
|
39
src/uac.erl
39
src/uac.erl
@ -3,11 +3,13 @@
|
||||
%% App
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
%% Supervisor
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
%%API
|
||||
@ -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}) ->
|
||||
case
|
||||
lists:all(
|
||||
fun({Scope, Permission}) ->
|
||||
lists:member(Permission, uac_acl:match(Scope, ACL))
|
||||
end,
|
||||
AccessScope
|
||||
) of
|
||||
)
|
||||
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(),
|
||||
|
@ -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;
|
||||
|
@ -34,9 +34,10 @@
|
||||
-type domain_name() :: binary().
|
||||
-type domains() :: #{domain_name() => uac_acl:t()}.
|
||||
-type expiration() ::
|
||||
{lifetime, Seconds :: pos_integer()} |
|
||||
{deadline, UnixTs :: pos_integer()} |
|
||||
unlimited.
|
||||
{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()]}}.
|
||||
}
|
||||
].
|
||||
|
||||
-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,8 +143,18 @@ 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,
|
||||
@ -162,10 +167,9 @@ construct_key(KID, JWK) ->
|
||||
%%
|
||||
|
||||
-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) ->
|
||||
@ -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,
|
||||
@ -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]).
|
||||
|
||||
%%
|
||||
|
||||
|
@ -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()]}}.
|
||||
}
|
||||
].
|
||||
|
||||
-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).
|
||||
|
||||
|
@ -33,8 +33,7 @@
|
||||
-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(#{
|
||||
@ -68,34 +66,30 @@ init_per_suite(Config) ->
|
||||
}),
|
||||
[{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().
|
||||
|
@ -31,14 +31,11 @@
|
||||
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(#{
|
||||
{ok, Token} = issue_token(
|
||||
#{
|
||||
Domain1 => uac_acl:from_list(ACL1),
|
||||
Domain2 => uac_acl:from_list(ACL2)
|
||||
}, unlimited),
|
||||
},
|
||||
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,10 +144,10 @@ 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 => #{},
|
||||
@ -170,6 +157,7 @@ unknown_resources_ok_test(_) ->
|
||||
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(#{
|
||||
{ok, Token} = issue_token(
|
||||
#{
|
||||
Domain1 => uac_acl:from_list(ACL),
|
||||
Domain2 => uac_acl:from_list(ACL)
|
||||
}, unlimited),
|
||||
},
|
||||
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}.
|
||||
|
Loading…
Reference in New Issue
Block a user