OAuth 2: initial scope aliasing test

This commit is contained in:
Michael Klishin 2022-04-21 14:16:46 +04:00
parent a2a54686e7
commit 0862199b9e
No known key found for this signature in database
GPG Key ID: 8ADA141E1AD87C94
3 changed files with 130 additions and 19 deletions

View File

@ -37,7 +37,7 @@
%% a term used by the IdentityServer community
-define(COMPLEX_CLAIM_APP_ENV_KEY, extra_scopes_source).
%% scope aliases map "role names" to a set of scopes
-define(SCOPE_ALIASES_APP_ENV_KEY, scope_aliases).
-define(SCOPE_MAPPINGS_APP_ENV_KEY, scope_aliases).
%%
%% Key JWT fields
@ -201,7 +201,7 @@ post_process_payload(Payload, AppEnv) when is_map(Payload) ->
-spec has_configured_scope_aliases(AppEnv :: app_env()) -> boolean().
has_configured_scope_aliases(AppEnv) ->
Map = maps:from_list(AppEnv),
maps:is_key(scope_mappings, Map).
maps:is_key(?SCOPE_MAPPINGS_APP_ENV_KEY, Map).
-spec post_process_payload_with_scope_aliases(Payload :: map(), AppEnv :: app_env()) -> map().
@ -222,7 +222,7 @@ post_process_payload_with_scope_aliases(Payload, AppEnv) ->
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_ALIASES_APP_ENV_KEY, AppEnv, #{}),
ScopeMappings = proplists:get_value(?SCOPE_MAPPINGS_APP_ENV_KEY, AppEnv, #{}),
post_process_payload_with_scope_alias_field_named(Payload, ?SCOPE_JWT_FIELD, ScopeMappings).
@ -232,32 +232,44 @@ post_process_payload_with_scope_alias_in_scope_field(Payload, AppEnv) ->
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 ot inject
%% nothing to inject
undefined -> Payload;
_ ->
ScopeMappings = proplists:get_value(?SCOPE_ALIASES_APP_ENV_KEY, AppEnv, #{}),
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(),
Scopes :: map()) -> map().
post_process_payload_with_scope_alias_field_named(Payload, undefined, _AppEnv) ->
ScopeAliasMapping :: map()) -> map().
post_process_payload_with_scope_alias_field_named(Payload, undefined, _ScopeAliasMapping) ->
Payload;
post_process_payload_with_scope_alias_field_named(Payload, ScopeAlias, AppEnv) ->
AdditionalScopes = case ScopeAlias of
post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) ->
ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []),
AdditionalScopes = case FieldName of
undefined -> [];
_ ->
ScopeMappings = proplists:get_value(<<"scope_mappings">>, AppEnv, #{}),
maps:get(ScopeAlias, ScopeMappings, [])
[] -> [];
_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;
_ ->
ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []),
maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload)
[] -> 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.

View File

@ -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).

View File

@ -29,7 +29,8 @@ 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
].
init_per_suite(Config) ->
@ -287,6 +288,42 @@ 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">>
]
}),
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_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_bogus_token(_) ->
Username = <<"username">>,
application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>),
@ -549,3 +586,31 @@ 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_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind) ->
?assertEqual(ExpectedResult,
rabbit_auth_backend_oauth2:check_resource_access(
AuthUser,
rabbit_misc:r(VHost, queue, ResourceName),
PermissionKind, #{})).