Merge pull request #4604 from rabbitmq/rabbitmq-server-4588
OAuth 2: support for scope aliases
This commit is contained in:
commit
2dccccfdb4
|
|
@ -25,12 +25,30 @@
|
|||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
-endif.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%%
|
||||
%% App environment
|
||||
%%
|
||||
|
||||
-type app_env() :: [{atom(), any()}].
|
||||
|
||||
-define(APP, rabbitmq_auth_backend_oauth2).
|
||||
-define(RESOURCE_SERVER_ID, resource_server_id).
|
||||
%% a term used by the IdentityServer community
|
||||
-define(COMPLEX_CLAIM, extra_scopes_source).
|
||||
-define(COMPLEX_CLAIM_APP_ENV_KEY, extra_scopes_source).
|
||||
%% scope aliases map "role names" to a set of scopes
|
||||
-define(SCOPE_MAPPINGS_APP_ENV_KEY, scope_aliases).
|
||||
|
||||
%%
|
||||
%% Key JWT fields
|
||||
%%
|
||||
|
||||
-define(AUD_JWT_FIELD, <<"aud">>).
|
||||
-define(SCOPE_JWT_FIELD, <<"scope">>).
|
||||
|
||||
%%
|
||||
%% API
|
||||
%%
|
||||
|
||||
description() ->
|
||||
[{name, <<"OAuth 2">>},
|
||||
|
|
@ -143,39 +161,125 @@ validate_token_expiry(#{}) -> ok.
|
|||
|
||||
-spec check_token(binary()) -> {ok, map()} | {error, term()}.
|
||||
check_token(Token) ->
|
||||
Settings = application:get_all_env(?APP),
|
||||
case uaa_jwt:decode_and_verify(Token) of
|
||||
{error, Reason} -> {refused, {error, Reason}};
|
||||
{true, Payload} -> validate_payload(post_process_payload(Payload));
|
||||
{true, Payload} -> validate_payload(post_process_payload(Payload, Settings));
|
||||
{false, _} -> {refused, signature_invalid}
|
||||
end.
|
||||
|
||||
post_process_payload(Payload) when is_map(Payload) ->
|
||||
post_process_payload(Payload, []).
|
||||
|
||||
post_process_payload(Payload, AppEnv) when is_map(Payload) ->
|
||||
Payload0 = maps:map(fun(K, V) ->
|
||||
case K of
|
||||
<<"aud">> when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
|
||||
<<"scope">> when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
|
||||
?AUD_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
|
||||
?SCOPE_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]);
|
||||
_ -> V
|
||||
end
|
||||
end,
|
||||
Payload
|
||||
),
|
||||
Payload1 = case does_include_complex_claim_field(Payload0) of
|
||||
true -> post_process_payload_complex_claim(Payload0);
|
||||
true -> post_process_payload_with_complex_claim(Payload0);
|
||||
false -> Payload0
|
||||
end,
|
||||
|
||||
Payload2 = case maps:is_key(<<"authorization">>, Payload1) of
|
||||
true -> post_process_payload_keycloak(Payload1);
|
||||
true -> post_process_payload_in_keycloak_format(Payload1);
|
||||
false -> Payload1
|
||||
end,
|
||||
|
||||
Payload3 = case has_configured_scope_aliases(AppEnv) of
|
||||
true -> post_process_payload_with_scope_aliases(Payload2, AppEnv);
|
||||
false -> Payload2
|
||||
end,
|
||||
|
||||
Payload3.
|
||||
|
||||
-spec has_configured_scope_aliases(AppEnv :: app_env()) -> boolean().
|
||||
has_configured_scope_aliases(AppEnv) ->
|
||||
Map = maps:from_list(AppEnv),
|
||||
maps:is_key(?SCOPE_MAPPINGS_APP_ENV_KEY, Map).
|
||||
|
||||
|
||||
-spec post_process_payload_with_scope_aliases(Payload :: map(), AppEnv :: app_env()) -> map().
|
||||
%% This is for those hopeless environments where the token structure is so out of
|
||||
%% messaging team's control that even the extra scopes field is no longer an option.
|
||||
%%
|
||||
%% This assumes that scopes can be random values that do not follow the RabbitMQ
|
||||
%% convention, or any other convention, in any way. They are just random client role IDs.
|
||||
%% See rabbitmq/rabbitmq-server#4588 for details.
|
||||
post_process_payload_with_scope_aliases(Payload, AppEnv) ->
|
||||
%% try JWT scope field value for alias
|
||||
Payload1 = post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv),
|
||||
%% try the configurable 'extra_scopes_source' field value for alias
|
||||
Payload2 = post_process_payload_with_scope_alias_in_extra_scopes_source(Payload1, AppEnv),
|
||||
Payload2.
|
||||
|
||||
does_include_complex_claim_field(Payload) when is_map(Payload) ->
|
||||
maps:is_key(application:get_env(?APP, ?COMPLEX_CLAIM, undefined), Payload).
|
||||
-spec post_process_payload_with_scope_alias_in_scope_field(Payload :: map(),
|
||||
AppEnv :: app_env()) -> map().
|
||||
%% First attempt: use the value in the 'scope' field for alias
|
||||
post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv) ->
|
||||
ScopeMappings = proplists:get_value(?SCOPE_MAPPINGS_APP_ENV_KEY, AppEnv, #{}),
|
||||
post_process_payload_with_scope_alias_field_named(Payload, ?SCOPE_JWT_FIELD, ScopeMappings).
|
||||
|
||||
post_process_payload_complex_claim(Payload) ->
|
||||
ComplexClaim = maps:get(application:get_env(?APP, ?COMPLEX_CLAIM, undefined), Payload),
|
||||
|
||||
-spec post_process_payload_with_scope_alias_in_extra_scopes_source(Payload :: map(),
|
||||
AppEnv :: app_env()) -> map().
|
||||
%% Second attempt: use the value in the configurable 'extra scopes source' field for alias
|
||||
post_process_payload_with_scope_alias_in_extra_scopes_source(Payload, AppEnv) ->
|
||||
ExtraScopesField = proplists:get_value(?COMPLEX_CLAIM_APP_ENV_KEY, AppEnv, undefined),
|
||||
case ExtraScopesField of
|
||||
%% nothing to inject
|
||||
undefined -> Payload;
|
||||
_ ->
|
||||
ScopeMappings = proplists:get_value(?SCOPE_MAPPINGS_APP_ENV_KEY, AppEnv, #{}),
|
||||
post_process_payload_with_scope_alias_field_named(Payload, ExtraScopesField, ScopeMappings)
|
||||
end.
|
||||
|
||||
|
||||
-spec post_process_payload_with_scope_alias_field_named(Payload :: map(),
|
||||
Field :: binary(),
|
||||
ScopeAliasMapping :: map()) -> map().
|
||||
post_process_payload_with_scope_alias_field_named(Payload, undefined, _ScopeAliasMapping) ->
|
||||
Payload;
|
||||
post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) ->
|
||||
ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []),
|
||||
AdditionalScopes = case FieldName of
|
||||
undefined -> [];
|
||||
[] -> [];
|
||||
_Value ->
|
||||
ScopeAlias = maps:get(FieldName, Payload, undefined),
|
||||
case ScopeAlias of
|
||||
undefined -> [];
|
||||
[] -> [];
|
||||
[Value1] ->
|
||||
rabbit_data_coercion:to_list(maps:get(Value1, ScopeAliasMapping, []));
|
||||
Value2 when is_binary(Value2) ->
|
||||
maps:get(Value2, ScopeAliasMapping, []);
|
||||
Value3 when is_list(Value3) ->
|
||||
maps:get(list_to_binary(Value3), ScopeAliasMapping, [])
|
||||
end
|
||||
end,
|
||||
|
||||
case AdditionalScopes of
|
||||
[] -> Payload;
|
||||
List when is_list(List) ->
|
||||
maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload);
|
||||
Bin when is_binary(Bin) ->
|
||||
maps:put(?SCOPE_JWT_FIELD, [Bin | ExistingScopes], Payload)
|
||||
end.
|
||||
|
||||
|
||||
-spec does_include_complex_claim_field(Payload :: map()) -> boolean().
|
||||
does_include_complex_claim_field(Payload) when is_map(Payload) ->
|
||||
maps:is_key(application:get_env(?APP, ?COMPLEX_CLAIM_APP_ENV_KEY, undefined), Payload).
|
||||
|
||||
-spec post_process_payload_with_complex_claim(Payload :: map()) -> map().
|
||||
post_process_payload_with_complex_claim(Payload) ->
|
||||
ComplexClaim = maps:get(application:get_env(?APP, ?COMPLEX_CLAIM_APP_ENV_KEY, undefined), Payload),
|
||||
ResourceServerId = rabbit_data_coercion:to_binary(application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>)),
|
||||
|
||||
AdditionalScopes =
|
||||
|
|
@ -199,18 +303,19 @@ post_process_payload_complex_claim(Payload) ->
|
|||
case AdditionalScopes of
|
||||
[] -> Payload;
|
||||
_ ->
|
||||
ExistingScopes = maps:get(<<"scope">>, Payload, []),
|
||||
maps:put(<<"scope">>, AdditionalScopes ++ ExistingScopes, Payload)
|
||||
ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []),
|
||||
maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload)
|
||||
end.
|
||||
|
||||
-spec post_process_payload_in_keycloak_format(Payload :: map()) -> map().
|
||||
%% keycloak token format: https://github.com/rabbitmq/rabbitmq-auth-backend-oauth2/issues/36
|
||||
post_process_payload_keycloak(#{<<"authorization">> := Authorization} = Payload) ->
|
||||
post_process_payload_in_keycloak_format(#{<<"authorization">> := Authorization} = Payload) ->
|
||||
AdditionalScopes = case maps:get(<<"permissions">>, Authorization, undefined) of
|
||||
undefined -> [];
|
||||
Permissions -> extract_scopes_from_keycloak_permissions([], Permissions)
|
||||
end,
|
||||
ExistingScopes = maps:get(<<"scope">>, Payload),
|
||||
maps:put(<<"scope">>, AdditionalScopes ++ ExistingScopes, Payload).
|
||||
ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload),
|
||||
maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload).
|
||||
|
||||
extract_scopes_from_keycloak_permissions(Acc, []) ->
|
||||
Acc;
|
||||
|
|
@ -225,14 +330,14 @@ extract_scopes_from_keycloak_permissions(Acc, [H | T]) when is_map(H) ->
|
|||
extract_scopes_from_keycloak_permissions(Acc, [_ | T]) ->
|
||||
extract_scopes_from_keycloak_permissions(Acc, T).
|
||||
|
||||
validate_payload(#{<<"scope">> := _Scope, <<"aud">> := _Aud} = DecodedToken) ->
|
||||
validate_payload(#{?SCOPE_JWT_FIELD := _Scope, ?AUD_JWT_FIELD := _Aud} = DecodedToken) ->
|
||||
ResourceServerEnv = application:get_env(?APP, ?RESOURCE_SERVER_ID, <<>>),
|
||||
ResourceServerId = rabbit_data_coercion:to_binary(ResourceServerEnv),
|
||||
validate_payload(DecodedToken, ResourceServerId).
|
||||
|
||||
validate_payload(#{<<"scope">> := Scope, <<"aud">> := Aud} = DecodedToken, ResourceServerId) ->
|
||||
validate_payload(#{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ResourceServerId) ->
|
||||
case check_aud(Aud, ResourceServerId) of
|
||||
ok -> {ok, DecodedToken#{<<"scope">> => filter_scopes(Scope, ResourceServerId)}};
|
||||
ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ResourceServerId)}};
|
||||
{error, Err} -> {refused, {invalid_aud, Err}}
|
||||
end.
|
||||
|
||||
|
|
@ -254,7 +359,7 @@ check_aud(Aud, ResourceServerId) ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
get_scopes(#{<<"scope">> := Scope}) -> Scope.
|
||||
get_scopes(#{?SCOPE_JWT_FIELD := Scope}) -> Scope.
|
||||
|
||||
-spec token_from_context(map()) -> binary() | undefined.
|
||||
token_from_context(AuthProps) ->
|
||||
|
|
@ -296,10 +401,12 @@ username_from(ClientProvidedUsername, DecodedToken) ->
|
|||
Value
|
||||
end.
|
||||
|
||||
-define(TAG_SCOPE_PREFIX, <<"tag:">>).
|
||||
|
||||
-spec tags_from(map()) -> list(atom()).
|
||||
tags_from(DecodedToken) ->
|
||||
Scopes = maps:get(<<"scope">>, DecodedToken, []),
|
||||
TagScopes = matching_scopes_without_prefix(Scopes, <<"tag:">>),
|
||||
Scopes = maps:get(?SCOPE_JWT_FIELD, DecodedToken, []),
|
||||
TagScopes = matching_scopes_without_prefix(Scopes, ?TAG_SCOPE_PREFIX),
|
||||
lists:usort(lists:map(fun rabbit_data_coercion:to_atom/1, TagScopes)).
|
||||
|
||||
matching_scopes_without_prefix(Scopes, PrefixPattern) ->
|
||||
|
|
|
|||
|
|
@ -70,10 +70,10 @@ expired_token() ->
|
|||
expired_token_with_scopes(full_permission_scopes()).
|
||||
|
||||
expired_token_with_scopes(Scopes) ->
|
||||
token_with_scopes_and_expiration(Scopes, os:system_time(seconds) - 10).
|
||||
token_with_scopes_and_expiration(Scopes, seconds_in_the_past(10)).
|
||||
|
||||
fixture_token_with_scopes(Scopes) ->
|
||||
token_with_scopes_and_expiration(Scopes, os:system_time(seconds) + 30).
|
||||
token_with_scopes_and_expiration(Scopes, default_expiration_moment()).
|
||||
|
||||
token_with_scopes_and_expiration(Scopes, Expiration) ->
|
||||
%% expiration is a timestamp with precision in seconds
|
||||
|
|
@ -97,3 +97,37 @@ fixture_token(ExtraScopes) ->
|
|||
|
||||
fixture_token_with_full_permissions() ->
|
||||
fixture_token_with_scopes(full_permission_scopes()).
|
||||
|
||||
token_with_scope_alias_in_scope_field(Alias) ->
|
||||
%% expiration is a timestamp with precision in seconds
|
||||
#{<<"exp">> => default_expiration_moment(),
|
||||
<<"kid">> => <<"token-key">>,
|
||||
<<"iss">> => <<"unit_test">>,
|
||||
<<"foo">> => <<"bar">>,
|
||||
<<"aud">> => [<<"rabbitmq">>],
|
||||
<<"scope">> => Alias}.
|
||||
|
||||
token_with_scope_alias_in_claim_field(Alias, Scopes) ->
|
||||
%% expiration is a timestamp with precision in seconds
|
||||
#{<<"exp">> => default_expiration_moment(),
|
||||
<<"kid">> => <<"token-key">>,
|
||||
<<"iss">> => <<"unit_test">>,
|
||||
<<"foo">> => <<"bar">>,
|
||||
<<"aud">> => [<<"rabbitmq">>],
|
||||
<<"scope">> => Scopes,
|
||||
<<"claims">> => Alias}.
|
||||
|
||||
seconds_in_the_future() ->
|
||||
seconds_in_the_future(30).
|
||||
|
||||
seconds_in_the_future(N) ->
|
||||
os:system_time(seconds) + N.
|
||||
|
||||
seconds_in_the_past() ->
|
||||
seconds_in_the_past(10).
|
||||
|
||||
seconds_in_the_past(N) ->
|
||||
os:system_time(seconds) - N.
|
||||
|
||||
default_expiration_moment() ->
|
||||
seconds_in_the_future(30).
|
||||
|
|
|
|||
|
|
@ -20,29 +20,46 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, happy_path},
|
||||
{group, unhappy_path}
|
||||
{group, basic_happy_path},
|
||||
{group, basic_unhappy_path},
|
||||
{group, token_refresh},
|
||||
{group, extra_scopes_source},
|
||||
{group, scope_aliases}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{happy_path, [], [
|
||||
{basic_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,
|
||||
test_successful_connection_with_simple_strings_for_aud_and_scope,
|
||||
test_successful_connection_with_complex_claim_as_a_map,
|
||||
test_successful_connection_with_complex_claim_as_a_list,
|
||||
test_successful_connection_with_complex_claim_as_a_binary,
|
||||
test_successful_connection_with_keycloak_token,
|
||||
test_successful_token_refresh
|
||||
]},
|
||||
{unhappy_path, [], [
|
||||
{basic_unhappy_path, [], [
|
||||
test_failed_connection_with_expired_token,
|
||||
test_failed_connection_with_a_non_token,
|
||||
test_failed_connection_with_a_token_with_insufficient_vhost_permission,
|
||||
test_failed_connection_with_a_token_with_insufficient_resource_permission,
|
||||
test_failed_connection_with_a_token_with_insufficient_resource_permission
|
||||
]},
|
||||
|
||||
{token_refresh, [], [
|
||||
test_failed_token_refresh_case1,
|
||||
test_failed_token_refresh_case2
|
||||
]},
|
||||
|
||||
{extra_scopes_source, [], [
|
||||
test_successful_connection_with_complex_claim_as_a_map,
|
||||
test_successful_connection_with_complex_claim_as_a_list,
|
||||
test_successful_connection_with_complex_claim_as_a_binary,
|
||||
test_successful_connection_with_keycloak_token
|
||||
]},
|
||||
|
||||
{scope_aliases, [], [
|
||||
test_successful_connection_with_with_scope_alias_in_extra_scopes_source,
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case1,
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case2,
|
||||
test_failed_connection_with_with_non_existent_scope_alias_in_extra_scopes_source,
|
||||
test_failed_connection_with_non_existent_scope_alias_in_scope_field
|
||||
]}
|
||||
].
|
||||
|
||||
|
|
@ -53,6 +70,9 @@ groups() ->
|
|||
-define(UTIL_MOD, rabbit_auth_backend_oauth2_test_util).
|
||||
-define(RESOURCE_SERVER_ID, <<"rabbitmq">>).
|
||||
-define(EXTRA_SCOPES_SOURCE, <<"additional_rabbitmq_scopes">>).
|
||||
-define(CLAIMS_FIELD, <<"claims">>).
|
||||
|
||||
-define(SCOPE_ALIAS_NAME, <<"role-1">>).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
|
|
@ -82,6 +102,9 @@ end_per_group(_Group, Config) ->
|
|||
[<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]),
|
||||
Config.
|
||||
|
||||
%%
|
||||
%% Per-case setup
|
||||
%%
|
||||
|
||||
init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse
|
||||
Testcase =:= test_successful_token_refresh ->
|
||||
|
|
@ -103,23 +126,74 @@ init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection
|
|||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config;
|
||||
|
||||
init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_with_scope_alias_in_extra_scopes_source ->
|
||||
rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost1">>),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||
[rabbitmq_auth_backend_oauth2, extra_scopes_source, ?CLAIMS_FIELD]),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||
[rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
?SCOPE_ALIAS_NAME => [
|
||||
<<"rabbitmq.configure:vhost1/*">>,
|
||||
<<"rabbitmq.write:vhost1/*">>,
|
||||
<<"rabbitmq.read:vhost1/*">>
|
||||
]}
|
||||
]),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config;
|
||||
|
||||
init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_scope_alias_in_scope_field_case1 orelse
|
||||
Testcase =:= test_successful_connection_with_scope_alias_in_scope_field_case2 ->
|
||||
rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost2">>),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||
[rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
?SCOPE_ALIAS_NAME => [
|
||||
<<"rabbitmq.configure:vhost2/*">>,
|
||||
<<"rabbitmq.write:vhost2/*">>,
|
||||
<<"rabbitmq.read:vhost2/*">>
|
||||
]}
|
||||
]),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config;
|
||||
|
||||
init_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config.
|
||||
|
||||
|
||||
%%
|
||||
%% Per-case Teardown
|
||||
%%
|
||||
|
||||
end_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse
|
||||
Testcase =:= test_failed_token_refresh_case2 ->
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost4">>),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||
Config;
|
||||
|
||||
end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse
|
||||
Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse
|
||||
Testcase =:= test_successful_connection_with_complex_claim_as_a_binary ->
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
|
||||
[rabbitmq_auth_backend_oauth2, extra_scopes_source, undefined]),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
|
||||
[rabbitmq_auth_backend_oauth2, extra_scopes_source]),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||
Config;
|
||||
|
||||
end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_with_scope_alias_in_extra_scopes_source ->
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
|
||||
[rabbitmq_auth_backend_oauth2, scope_aliases]),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
|
||||
[rabbitmq_auth_backend_oauth2, extra_scopes_source]),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||
Config;
|
||||
|
||||
end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_scope_alias_in_scope_field_case1 orelse
|
||||
Testcase =:= test_successful_connection_with_scope_alias_in_scope_field_case2 ->
|
||||
rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost2">>),
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env,
|
||||
[rabbitmq_auth_backend_oauth2, scope_aliases]),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase),
|
||||
Config;
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
|
|
@ -371,3 +445,42 @@ test_failed_token_refresh_case2(Config) ->
|
|||
amqp_connection:open_channel(Conn)),
|
||||
|
||||
close_connection(Conn).
|
||||
|
||||
|
||||
test_successful_connection_with_with_scope_alias_in_extra_scopes_source(Config) ->
|
||||
{_Algo, Token} = generate_valid_token_with_extra_fields(
|
||||
Config,
|
||||
#{<<"claims">> => ?SCOPE_ALIAS_NAME}
|
||||
),
|
||||
Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token),
|
||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||
#'queue.declare_ok'{} =
|
||||
amqp_channel:call(Ch, #'queue.declare'{queue = <<"one">>, exclusive = true}),
|
||||
close_connection_and_channel(Conn, Ch).
|
||||
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case1(Config) ->
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case(Config, ?SCOPE_ALIAS_NAME).
|
||||
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case2(Config) ->
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case(Config, [?SCOPE_ALIAS_NAME]).
|
||||
|
||||
test_successful_connection_with_scope_alias_in_scope_field_case(Config, Scopes) ->
|
||||
{_Algo, Token} = generate_valid_token(Config, Scopes),
|
||||
Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token),
|
||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||
#'queue.declare_ok'{} =
|
||||
amqp_channel:call(Ch, #'queue.declare'{queue = <<"one">>, exclusive = true}),
|
||||
close_connection_and_channel(Conn, Ch).
|
||||
|
||||
test_failed_connection_with_with_non_existent_scope_alias_in_extra_scopes_source(Config) ->
|
||||
{_Algo, Token} = generate_valid_token_with_extra_fields(
|
||||
Config,
|
||||
#{<<"claims">> => <<"non-existent alias 24823478374">>}
|
||||
),
|
||||
?assertMatch({error, not_allowed},
|
||||
open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)).
|
||||
|
||||
test_failed_connection_with_non_existent_scope_alias_in_scope_field(Config) ->
|
||||
{_Algo, Token} = generate_valid_token(Config, <<"non-existent alias a8798s7doaisd79">>),
|
||||
?assertMatch({error, not_allowed},
|
||||
open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token)).
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@ all() ->
|
|||
test_incorrect_kid,
|
||||
test_post_process_token_payload,
|
||||
test_post_process_token_payload_keycloak,
|
||||
test_post_process_token_payload_complex_claims
|
||||
test_post_process_token_payload_complex_claims,
|
||||
test_successful_access_with_a_token_that_uses_scope_alias_in_scope_field,
|
||||
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field,
|
||||
test_successful_access_with_a_token_that_uses_scope_alias_in_extra_scope_source_field,
|
||||
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
|
|
@ -235,6 +239,8 @@ test_successful_access_with_a_token(_) ->
|
|||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:fixture_token(), Jwk),
|
||||
|
||||
|
|
@ -244,36 +250,12 @@ test_successful_access_with_a_token(_) ->
|
|||
rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}),
|
||||
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)),
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = queue,
|
||||
name = <<"foo">>},
|
||||
configure,
|
||||
#{})),
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = exchange,
|
||||
name = <<"foo">>},
|
||||
write,
|
||||
#{})),
|
||||
assert_resource_access_granted(User, VHost, <<"foo">>, configure),
|
||||
assert_resource_access_granted(User, VHost, <<"foo">>, write),
|
||||
assert_resource_access_granted(User, VHost, <<"bar">>, read),
|
||||
assert_resource_access_granted(User, VHost, custom, <<"bar">>, read),
|
||||
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = custom,
|
||||
name = <<"bar">>},
|
||||
read,
|
||||
#{})),
|
||||
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_topic_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = topic,
|
||||
name = <<"bar">>},
|
||||
read,
|
||||
#{routing_key => <<"#/foo">>})).
|
||||
assert_topic_access_granted(User, VHost, <<"bar">>, read, #{routing_key => <<"#/foo">>}).
|
||||
|
||||
test_successful_access_with_a_token_that_has_tag_scopes(_) ->
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
|
|
@ -287,6 +269,155 @@ test_successful_access_with_a_token_that_has_tag_scopes(_) ->
|
|||
{ok, #auth_user{username = Username, tags = [management, policymaker]}} =
|
||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]).
|
||||
|
||||
test_successful_access_with_a_token_that_uses_scope_alias_in_scope_field(_) ->
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
Alias = <<"client-alias-1">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
Alias => [
|
||||
<<"rabbitmq.configure:vhost/one">>,
|
||||
<<"rabbitmq.write:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/one">>,
|
||||
<<"rabbitmq.read:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/two/abc">>,
|
||||
<<"rabbitmq.tag:management">>,
|
||||
<<"rabbitmq.tag:custom">>
|
||||
]
|
||||
}),
|
||||
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Jwk),
|
||||
|
||||
{ok, #auth_user{username = Username, tags = [custom, management]} = AuthUser} =
|
||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
assert_vhost_access_granted(AuthUser, VHost),
|
||||
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
|
||||
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"one">>, configure),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"one">>, read),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"two">>, read),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"two">>, write),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),
|
||||
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
|
||||
|
||||
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field(_) ->
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
Alias = <<"client-alias-33">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
<<"non-existent-alias-23948sdkfjsdof8">> => [
|
||||
<<"rabbitmq.configure:vhost/one">>,
|
||||
<<"rabbitmq.write:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/one">>,
|
||||
<<"rabbitmq.read:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/two/abc">>
|
||||
]
|
||||
}),
|
||||
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Jwk),
|
||||
|
||||
{ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
assert_vhost_access_denied(AuthUser, VHost),
|
||||
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
|
||||
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"one">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"one">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"two">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"two">>, write),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),
|
||||
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
|
||||
|
||||
test_successful_access_with_a_token_that_uses_scope_alias_in_extra_scope_source_field(_) ->
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
Alias = <<"client-alias-1">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
Alias => [
|
||||
<<"rabbitmq.configure:vhost/one">>,
|
||||
<<"rabbitmq.write:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/one">>,
|
||||
<<"rabbitmq.read:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/two/abc">>
|
||||
]
|
||||
}),
|
||||
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Jwk),
|
||||
|
||||
{ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
assert_vhost_access_granted(AuthUser, VHost),
|
||||
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
|
||||
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"one">>, configure),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"one">>, read),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"two">>, read),
|
||||
assert_resource_access_granted(AuthUser, VHost, <<"two">>, write),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),
|
||||
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
|
||||
|
||||
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field(_) ->
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"claims">>),
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
Alias = <<"client-alias-11">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{
|
||||
<<"non-existent-client-alias-9238923789">> => [
|
||||
<<"rabbitmq.configure:vhost/one">>,
|
||||
<<"rabbitmq.write:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/one">>,
|
||||
<<"rabbitmq.read:vhost/two">>,
|
||||
<<"rabbitmq.read:vhost/two/abc">>
|
||||
]
|
||||
}),
|
||||
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Jwk),
|
||||
|
||||
{ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
assert_vhost_access_denied(AuthUser, VHost),
|
||||
assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>),
|
||||
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"one">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"one">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"two">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"two">>, write),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, configure),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, read),
|
||||
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),
|
||||
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
|
||||
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
|
||||
|
||||
test_unsuccessful_access_with_a_bogus_token(_) ->
|
||||
Username = <<"username">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
|
|
@ -316,6 +447,7 @@ test_restricted_vhost_access_with_a_valid_token(_) ->
|
|||
?assertEqual(false, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"different vhost">>, none)).
|
||||
|
||||
test_insufficient_permissions_in_a_valid_token(_) ->
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
|
||||
|
||||
|
|
@ -328,29 +460,12 @@ test_insufficient_permissions_in_a_valid_token(_) ->
|
|||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
|
||||
%% access to these resources is not granted
|
||||
?assertEqual(false, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = queue,
|
||||
name = <<"foo1">>},
|
||||
configure,
|
||||
#{})),
|
||||
?assertEqual(false, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = custom,
|
||||
name = <<"bar">>},
|
||||
write,
|
||||
#{})),
|
||||
?assertEqual(false, rabbit_auth_backend_oauth2:check_topic_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = topic,
|
||||
name = <<"bar">>},
|
||||
read,
|
||||
#{routing_key => <<"foo/#">>})).
|
||||
assert_resource_access_denied(User, VHost, <<"foo1">>, configure),
|
||||
assert_resource_access_denied(User, VHost, <<"bar">>, write),
|
||||
assert_topic_access_refused(User, VHost, <<"bar">>, read, #{routing_key => <<"foo/#">>}).
|
||||
|
||||
test_token_expiration(_) ->
|
||||
VHost = <<"vhost">>,
|
||||
Username = <<"username">>,
|
||||
Jwk = ?UTIL_MOD:fixture_jwk(),
|
||||
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
|
||||
|
|
@ -361,32 +476,14 @@ test_token_expiration(_) ->
|
|||
Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk),
|
||||
{ok, #auth_user{username = Username} = User} =
|
||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]),
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = queue,
|
||||
name = <<"foo">>},
|
||||
configure,
|
||||
#{})),
|
||||
?assertEqual(true, rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = exchange,
|
||||
name = <<"foo">>},
|
||||
write,
|
||||
#{})),
|
||||
|
||||
assert_resource_access_granted(User, VHost, <<"foo">>, configure),
|
||||
assert_resource_access_granted(User, VHost, <<"foo">>, write),
|
||||
|
||||
?UTIL_MOD:wait_for_token_to_expire(),
|
||||
#{<<"exp">> := Exp} = TokenData,
|
||||
ExpectedError = "Provided JWT token has expired at timestamp " ++ integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")",
|
||||
?assertEqual({error, ExpectedError},
|
||||
rabbit_auth_backend_oauth2:check_resource_access(
|
||||
User,
|
||||
#resource{virtual_host = <<"vhost">>,
|
||||
kind = queue,
|
||||
name = <<"foo">>},
|
||||
configure,
|
||||
#{})),
|
||||
assert_resource_access_errors(ExpectedError, User, VHost, <<"foo">>, configure),
|
||||
|
||||
?assertMatch({refused, _, _},
|
||||
rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])).
|
||||
|
|
@ -549,3 +646,65 @@ test_validate_payload(_) ->
|
|||
?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID],
|
||||
<<"scope">> => [<<"bar">>, <<"other.third">>]}},
|
||||
rabbit_auth_backend_oauth2:validate_payload(KnownResourceServerId, ?RESOURCE_SERVER_ID)).
|
||||
|
||||
|
||||
%%
|
||||
%% Helpers
|
||||
%%
|
||||
|
||||
assert_vhost_access_granted(AuthUser, VHost) ->
|
||||
assert_vhost_access_response(true, AuthUser, VHost).
|
||||
|
||||
assert_vhost_access_denied(AuthUser, VHost) ->
|
||||
assert_vhost_access_response(false, AuthUser, VHost).
|
||||
|
||||
assert_vhost_access_response(ExpectedResult, AuthUser, VHost) ->
|
||||
?assertEqual(ExpectedResult,
|
||||
rabbit_auth_backend_oauth2:check_vhost_access(AuthUser, VHost, none)).
|
||||
|
||||
assert_resource_access_granted(AuthUser, VHost, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response(true, AuthUser, VHost, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_denied(AuthUser, VHost, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response(false, AuthUser, VHost, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind) ->
|
||||
?assertEqual(ExpectedResult,
|
||||
rabbit_auth_backend_oauth2:check_resource_access(
|
||||
AuthUser,
|
||||
rabbit_misc:r(VHost, queue, ResourceName),
|
||||
PermissionKind, #{})).
|
||||
|
||||
assert_resource_access_granted(AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response(true, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_denied(AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response(false, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) ->
|
||||
assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind).
|
||||
|
||||
assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) ->
|
||||
?assertEqual(ExpectedResult,
|
||||
rabbit_auth_backend_oauth2:check_resource_access(
|
||||
AuthUser,
|
||||
rabbit_misc:r(VHost, ResourceKind, ResourceName),
|
||||
PermissionKind, #{})).
|
||||
|
||||
assert_topic_access_granted(AuthUser, VHost, ResourceName, PermissionKind, AuthContext) ->
|
||||
assert_topic_access_response(true, AuthUser, VHost, ResourceName, PermissionKind, AuthContext).
|
||||
|
||||
assert_topic_access_refused(AuthUser, VHost, ResourceName, PermissionKind, AuthContext) ->
|
||||
assert_topic_access_response(false, AuthUser, VHost, ResourceName, PermissionKind, AuthContext).
|
||||
|
||||
assert_topic_access_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind, AuthContext) ->
|
||||
?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_topic_access(
|
||||
AuthUser,
|
||||
#resource{virtual_host = VHost,
|
||||
kind = topic,
|
||||
name = ResourceName},
|
||||
PermissionKind,
|
||||
AuthContext)).
|
||||
|
|
|
|||
|
|
@ -283,6 +283,16 @@ This release includes all applicable [bug fixes that shipped in `3.9.x` releases
|
|||
|
||||
#### Enhancements
|
||||
|
||||
* The plugin now supports scope aliases. In some environments, it's unrealistic to
|
||||
adopt JWTs that follow the `scope` convention assumed by the plugin. Instead,
|
||||
identity services fill `scope` or `claims` field with a "role name" or "role alias"
|
||||
that implicitly maps to a set of scopes/permissions.
|
||||
|
||||
With this feature, RabbitMQ operators can map those values to a set of
|
||||
scopes that can be translated to RabbitMQ permissions.
|
||||
|
||||
GitHub issue: [#4588](https://github.com/rabbitmq/rabbitmq-server/issues/4588)
|
||||
|
||||
* Improvements to JKW support and new HTTPS settings.
|
||||
|
||||
Contributed by @anhanhnguyen (Erlang Solutions).
|
||||
|
|
|
|||
Loading…
Reference in New Issue