parent
8cc9e4f628
commit
821f54c92a
|
|
@ -65,7 +65,9 @@ check_vhost_access(#auth_user{impl = DecodedToken},
|
|||
VHost, _Sock) ->
|
||||
with_decoded_token(DecodedToken,
|
||||
fun() ->
|
||||
Scopes = get_scopes(DecodedToken),
|
||||
Scopes = get_scopes(DecodedToken),
|
||||
ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","),
|
||||
rabbit_log:debug("Matching virtual host '~s' against the following scopes: ~s", [VHost, ScopeString]),
|
||||
rabbit_oauth2_scope:vhost_access(VHost, Scopes)
|
||||
end).
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
-module(rabbit_oauth2_scope).
|
||||
|
||||
-export([vhost_access/2, resource_access/3, topic_access/4]).
|
||||
-export([vhost_access/2, resource_access/3, topic_access/4, concat_scopes/2]).
|
||||
|
||||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
|
||||
|
|
@ -25,20 +25,22 @@
|
|||
%% API functions --------------------------------------------------------------
|
||||
-spec vhost_access(binary(), [binary()]) -> boolean().
|
||||
vhost_access(VHost, Scopes) ->
|
||||
PermissionScopes = get_scope_permissions(Scopes),
|
||||
lists:any(
|
||||
fun({ScopeVHost, _, _, _}) ->
|
||||
wildcard:match(VHost, ScopeVHost)
|
||||
fun({VHostPattern, _, _, _}) ->
|
||||
wildcard:match(VHost, VHostPattern)
|
||||
end,
|
||||
get_scope_permissions(Scopes)).
|
||||
PermissionScopes).
|
||||
|
||||
-spec resource_access(rabbit_types:r(atom()), permission(), [binary()]) -> boolean().
|
||||
resource_access(#resource{virtual_host = VHost, name = Name},
|
||||
Permission, Scopes) ->
|
||||
rabbit_log:info("VHost: '~s', scopes: ~p", [VHost, Scopes]),
|
||||
lists:any(
|
||||
fun({ScopeVHost, ScopeName, _, ScopePermission}) ->
|
||||
wildcard:match(VHost, ScopeVHost) andalso
|
||||
wildcard:match(Name, ScopeName) andalso
|
||||
Permission =:= ScopePermission
|
||||
fun({VHostPattern, NamePattern, _, ScopeGrantedPermission}) ->
|
||||
wildcard:match(VHost, VHostPattern) andalso
|
||||
wildcard:match(Name, NamePattern) andalso
|
||||
Permission =:= ScopeGrantedPermission
|
||||
end,
|
||||
get_scope_permissions(Scopes)).
|
||||
|
||||
|
|
@ -47,12 +49,12 @@ topic_access(#resource{virtual_host = VHost, name = ExchangeName},
|
|||
#{routing_key := RoutingKey},
|
||||
Scopes) ->
|
||||
lists:any(
|
||||
fun({ScopeVHost, ScopeExchangeName, ScopeRoutingKey, ScopePermission}) ->
|
||||
is_binary(ScopeRoutingKey) andalso
|
||||
wildcard:match(VHost, ScopeVHost) andalso
|
||||
wildcard:match(ExchangeName, ScopeExchangeName) andalso
|
||||
wildcard:match(RoutingKey, ScopeRoutingKey) andalso
|
||||
Permission =:= ScopePermission
|
||||
fun({VHostPattern, ExchangeNamePattern, RoutingKeyPattern, ScopeGrantedPermission}) ->
|
||||
is_binary(RoutingKeyPattern) andalso
|
||||
wildcard:match(VHost, VHostPattern) andalso
|
||||
wildcard:match(ExchangeName, ExchangeNamePattern) andalso
|
||||
wildcard:match(RoutingKey, RoutingKeyPattern) andalso
|
||||
Permission =:= ScopeGrantedPermission
|
||||
end,
|
||||
get_scope_permissions(Scopes)).
|
||||
|
||||
|
|
@ -69,6 +71,10 @@ get_scope_permissions(Scopes) when is_list(Scopes) ->
|
|||
end,
|
||||
Scopes).
|
||||
|
||||
-spec concat_scopes([binary()], string()) -> string().
|
||||
concat_scopes(Scopes, Separator) when is_list(Scopes) ->
|
||||
lists:concat(lists:join(Separator, lists:map(fun rabbit_data_coercion:to_list/1, Scopes))).
|
||||
|
||||
-spec parse_permission_pattern(binary()) -> {rabbit_types:r(pattern), permission()}.
|
||||
parse_permission_pattern(<<"read:", ResourcePatternBin/binary>>) ->
|
||||
Permission = read,
|
||||
|
|
@ -92,4 +98,3 @@ parse_resource_pattern(Pattern, Permission) ->
|
|||
{VhostPattern, NamePattern, RoutingKeyPattern, Permission};
|
||||
_Other -> ignore
|
||||
end.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,6 @@
|
|||
%% API
|
||||
%%
|
||||
|
||||
expirable_token() ->
|
||||
TokenPayload = fixture_token(),
|
||||
TokenPayload#{<<"exp">> := os:system_time(seconds) + timer:seconds(?EXPIRATION_TIME)}.
|
||||
|
||||
wait_for_token_to_expire() ->
|
||||
timer:sleep(?EXPIRATION_TIME).
|
||||
|
||||
sign_token_hs(Token, #{<<"kid">> := TokenKey} = Jwk) ->
|
||||
sign_token_hs(Token, Jwk, TokenKey).
|
||||
|
||||
|
|
@ -48,8 +41,29 @@ fixture_jwk() ->
|
|||
<<"use">> => <<"sig">>,
|
||||
<<"value">> => <<"tokenkey">>}.
|
||||
|
||||
full_permission_scopes() ->
|
||||
[<<"rabbitmq.configure:*/*">>,
|
||||
<<"rabbitmq.write:*/*">>,
|
||||
<<"rabbitmq.read:*/*">>].
|
||||
|
||||
expirable_token() ->
|
||||
TokenPayload = fixture_token(),
|
||||
TokenPayload#{<<"exp">> := os:system_time(seconds) + timer:seconds(?EXPIRATION_TIME)}.
|
||||
|
||||
wait_for_token_to_expire() ->
|
||||
timer:sleep(?EXPIRATION_TIME).
|
||||
|
||||
expired_token() ->
|
||||
expired_token_with_scopes(full_permission_scopes()).
|
||||
|
||||
expired_token_with_scopes(Scopes) ->
|
||||
token_with_scopes_and_expiration(Scopes, os:system_time(seconds) - timer:seconds(10)).
|
||||
|
||||
fixture_token_with_scopes(Scopes) ->
|
||||
#{<<"exp">> => os:system_time(seconds) + 3000,
|
||||
token_with_scopes_and_expiration(Scopes, os:system_time(seconds) + timer:seconds(10)).
|
||||
|
||||
token_with_scopes_and_expiration(Scopes, Expiration) ->
|
||||
#{<<"exp">> => Expiration,
|
||||
<<"kid">> => <<"token-key">>,
|
||||
<<"iss">> => <<"unit_test">>,
|
||||
<<"foo">> => <<"bar">>,
|
||||
|
|
@ -67,10 +81,5 @@ fixture_token(ExtraScopes) ->
|
|||
<<"rabbitmq.read:vhost/bar/%23%2Ffoo">>] ++ ExtraScopes,
|
||||
fixture_token_with_scopes(Scopes).
|
||||
|
||||
full_permission_scopes() ->
|
||||
[<<"rabbitmq.configure:*/*">>,
|
||||
<<"rabbitmq.write:*/*">>,
|
||||
<<"rabbitmq.read:*/*">>].
|
||||
|
||||
fixture_token_with_full_permissions() ->
|
||||
fixture_token_with_scopes(full_permission_scopes()).
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("amqp_client/include/amqp_client.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1,
|
||||
open_unmanaged_connection/4, open_unmanaged_connection/5,
|
||||
|
|
@ -28,7 +29,8 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, happy_path}
|
||||
{group, happy_path},
|
||||
{group, unhappy_path}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
|
|
@ -36,6 +38,10 @@ groups() ->
|
|||
{happy_path, [], [
|
||||
test_successful_connection_with_a_full_permission_token_and_all_defaults,
|
||||
test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost
|
||||
]},
|
||||
{unhappy_path, [], [
|
||||
test_failed_connection_with_expired_token,
|
||||
test_failed_connection_with_a_non_token
|
||||
]}
|
||||
].
|
||||
|
||||
|
|
@ -57,6 +63,37 @@ init_per_suite(Config) ->
|
|||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(Config, rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
|
||||
init_per_group(_Group, Config) ->
|
||||
%% The broker is managed by {init,end}_per_testcase().
|
||||
rabbit_ct_broker_helpers:add_vhost(Config, <<"avhost">>),
|
||||
Config.
|
||||
|
||||
end_per_group(_Group, Config) ->
|
||||
%% The broker is managed by {init,end}_per_testcase().
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"avhost">>),
|
||||
Config.
|
||||
|
||||
|
||||
init_per_testcase(test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost = Testcase, Config) ->
|
||||
rabbit_ct_broker_helpers:add_vhost(Config, <<"avhost">>),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config;
|
||||
|
||||
init_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config.
|
||||
|
||||
end_per_testcase(test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost = Testcase, Config) ->
|
||||
rabbit_ct_broker_helpers:add_vhost(Config, <<"avhost">>),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config;
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"avhost">>),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||
Config.
|
||||
|
||||
preconfigure_node(Config) ->
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||
[rabbit, auth_backends, [rabbit_auth_backend_uaa]]),
|
||||
|
|
@ -78,6 +115,16 @@ generate_valid_token(Config, Scopes) ->
|
|||
end,
|
||||
?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token_with_scopes(Scopes), Jwk).
|
||||
|
||||
generate_expired_token(Config) ->
|
||||
generate_expired_token(Config, ?UTIL_MOD:full_permission_scopes()).
|
||||
|
||||
generate_expired_token(Config, Scopes) ->
|
||||
Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of
|
||||
undefined -> ?UTIL_MOD:fixture_jwk();
|
||||
Value -> Value
|
||||
end,
|
||||
?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk).
|
||||
|
||||
preconfigure_token(Config) ->
|
||||
Token = generate_valid_token(Config),
|
||||
|
||||
|
|
@ -97,9 +144,24 @@ test_successful_connection_with_a_full_permission_token_and_all_defaults(Config)
|
|||
close_connection_and_channel(Conn, Ch).
|
||||
|
||||
test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) ->
|
||||
{_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt),
|
||||
Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token),
|
||||
{_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:avhost/*">>,
|
||||
<<"rabbitmq.write:avhost/*">>,
|
||||
<<"rabbitmq.read:avhost/*">>]),
|
||||
Conn = open_unmanaged_connection(Config, 0, <<"avhost">>, <<"username">>, Token),
|
||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||
#'queue.declare_ok'{queue = _} =
|
||||
amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
|
||||
close_connection_and_channel(Conn, Ch).
|
||||
|
||||
|
||||
|
||||
test_failed_connection_with_expired_token(Config) ->
|
||||
{_Algo, Token} = generate_expired_token(Config, [<<"rabbitmq.configure:avhost/*">>,
|
||||
<<"rabbitmq.write:avhost/*">>,
|
||||
<<"rabbitmq.read:avhost/*">>]),
|
||||
?assertEqual({error, not_allowed},
|
||||
open_unmanaged_connection(Config, 0, <<"avhost">>, <<"username">>, Token)).
|
||||
|
||||
test_failed_connection_with_a_non_token(Config) ->
|
||||
?assertMatch({error, {auth_failure, _}},
|
||||
open_unmanaged_connection(Config, 0, <<"avhost">>, <<"username">>, <<"a-non-token-value">>)).
|
||||
|
|
|
|||
Loading…
Reference in New Issue