OAuth 2: initial scope aliasing test
This commit is contained in:
parent
a2a54686e7
commit
0862199b9e
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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, #{})).
|
||||
|
|
|
|||
Loading…
Reference in New Issue