Support in code the old keycloak format
That was not keycloak format it was an extension to the oauth spec introuduced a few years ago. To get a token from keycloak using this format, a.k.a. requesting party token, one has to specify a different claim type called urn:ietf:params:oauth:grant-type:uma-ticket
This commit is contained in:
parent
1179d3a3ec
commit
3041d6c253
|
@ -22,6 +22,14 @@
|
|||
|
||||
%% End of Key JWT fields
|
||||
|
||||
%% UMA claim-type returns a RPT which is a token
|
||||
%% where scopes are located under a map of list of objects which have
|
||||
%% the scopes in the "scopes" attribute
|
||||
%% Used by Keycloak, WSO2 and others.
|
||||
%% https://en.wikipedia.org/wiki/User-Managed_Access#cite_note-docs.wso2.com-19
|
||||
-define(SCOPES_LOCATION_IN_REQUESTING_PARTY_TOKEN, <<"authorization.permissions.scopes">>).
|
||||
|
||||
|
||||
-type raw_jwt_token() :: binary() | #{binary() => any()}.
|
||||
-type decoded_jwt_token() :: #{binary() => any()}.
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
|
||||
-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2]).
|
||||
|
||||
-import(rabbit_oauth2_scope, [filter_matching_scope_prefix_and_drop_it/2]).
|
||||
-import(rabbit_oauth2_scope, [
|
||||
filter_matching_scope_prefix/2,
|
||||
filter_matching_scope_prefix_and_drop_it/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
|
@ -241,14 +243,29 @@ extract_scopes_from_scope_claim(Payload) ->
|
|||
ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
|
||||
normalize_token_scope(ResourceServer, Payload) ->
|
||||
|
||||
Payload1 = extract_scopes_from_rich_auth_request(ResourceServer,
|
||||
filter_duplicates(
|
||||
filter_matching_scope_prefix(ResourceServer,
|
||||
extract_scopes_from_rich_auth_request(ResourceServer,
|
||||
extract_scopes_using_scope_aliases(ResourceServer,
|
||||
extract_scopes_from_additional_scopes_key(ResourceServer,
|
||||
extract_scopes_from_scope_claim(Payload)))),
|
||||
extract_scopes_from_requesting_party_token(ResourceServer,
|
||||
extract_scopes_from_scope_claim(Payload))))))).
|
||||
|
||||
FilteredScopes = filter_matching_scope_prefix_and_drop_it(
|
||||
get_scope(Payload1), ResourceServer#resource_server.scope_prefix),
|
||||
set_scope(FilteredScopes, Payload1).
|
||||
filter_duplicates(#{?SCOPE_JWT_FIELD := Scopes} = Payload) ->
|
||||
set_scope(lists:usort(Scopes), Payload);
|
||||
filter_duplicates(Payload) -> Payload.
|
||||
|
||||
-spec extract_scopes_from_requesting_party_token(
|
||||
ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
|
||||
extract_scopes_from_requesting_party_token(ResourceServer, Payload) ->
|
||||
Path = ?SCOPES_LOCATION_IN_REQUESTING_PARTY_TOKEN,
|
||||
case extract_token_value(ResourceServer, Payload, Path,
|
||||
fun extract_scope_list_from_token_value/2) of
|
||||
[] ->
|
||||
Payload;
|
||||
AdditionalScopes ->
|
||||
set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload)
|
||||
end.
|
||||
|
||||
-spec extract_scopes_using_scope_aliases(
|
||||
ResourceServer :: resource_server(), Payload :: map()) -> map().
|
||||
|
@ -322,9 +339,9 @@ extract_token_value_from_map(R, Map, Acc, [KeyStr | Rest], Mapper) when is_map(M
|
|||
{ok, L} when is_list(L) -> extract_token_value_from_list(R, L, Acc, Rest, Mapper);
|
||||
{ok, Value} when Rest =:= [] -> Acc ++ Mapper(R, Value);
|
||||
_ -> Acc
|
||||
end;
|
||||
extract_token_value_from_map(_, _, Acc, _, _Mapper) ->
|
||||
Acc.
|
||||
end.
|
||||
%extract_token_value_from_map(_, _, Acc, _, _Mapper) ->
|
||||
% Acc.
|
||||
|
||||
extract_token_value_from_list(_, [], Acc, [], _Mapper) ->
|
||||
Acc;
|
||||
|
@ -355,35 +372,13 @@ split_path(Path) when is_binary(Path) ->
|
|||
ResourceServer :: resource_server(), Payload :: map()) -> map().
|
||||
extract_scopes_from_additional_scopes_key(
|
||||
#resource_server{additional_scopes_key = Key} = ResourceServer, Payload)
|
||||
when is_list(Key) or is_binary(Key) ->
|
||||
Paths = case Key of
|
||||
B when is_binary(B) -> binary:split(B, <<" ">>, [global, trim_all]);
|
||||
L when is_list(L) -> L
|
||||
end,
|
||||
when is_binary(Key) ->
|
||||
Paths = binary:split(Key, <<" ">>, [global, trim_all]),
|
||||
AdditionalScopes = [ extract_token_value(ResourceServer,
|
||||
Payload, Path, fun extract_scope_list_from_token_value/2) || Path <- Paths],
|
||||
set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload);
|
||||
extract_scopes_from_additional_scopes_key(_, Payload) -> Payload.
|
||||
|
||||
extract_additional_scopes(ResourceServer, ComplexClaim) ->
|
||||
ResourceServerId = ResourceServer#resource_server.id,
|
||||
case ComplexClaim of
|
||||
L when is_list(L) -> L;
|
||||
M when is_map(M) ->
|
||||
case maps:get(ResourceServerId, M, undefined) of
|
||||
undefined -> [];
|
||||
Ks when is_list(Ks) ->
|
||||
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks];
|
||||
ClaimBin when is_binary(ClaimBin) ->
|
||||
UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]),
|
||||
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims];
|
||||
_ -> []
|
||||
end;
|
||||
Bin when is_binary(Bin) ->
|
||||
binary:split(Bin, <<" ">>, [global, trim_all]);
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
|
||||
%% A token may be present in the password credential or in the rabbit_auth_backend_oauth2
|
||||
%% credential. The former is the most common scenario for the first time authentication.
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
|
||||
-module(rabbit_oauth2_scope).
|
||||
|
||||
-include("oauth2.hrl").
|
||||
|
||||
-export([vhost_access/2,
|
||||
resource_access/3,
|
||||
topic_access/4,
|
||||
concat_scopes/2,
|
||||
filter_matching_scope_prefix/2,
|
||||
filter_matching_scope_prefix_and_drop_it/2]).
|
||||
|
||||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
|
@ -93,10 +96,18 @@ parse_resource_pattern(Pattern, Permission) ->
|
|||
_Other -> ignore
|
||||
end.
|
||||
|
||||
-spec filter_matching_scope_prefix_and_drop_it(list(), binary()|list()) -> list().
|
||||
filter_matching_scope_prefix_and_drop_it(Scopes, <<"">>) -> Scopes;
|
||||
filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) ->
|
||||
-spec filter_matching_scope_prefix(ResourceServer :: resource_server(),
|
||||
Payload :: map()) -> map().
|
||||
filter_matching_scope_prefix(
|
||||
#resource_server{scope_prefix = ScopePrefix},
|
||||
#{?SCOPE_JWT_FIELD := Scopes} = Payload) ->
|
||||
Payload#{?SCOPE_JWT_FIELD :=
|
||||
filter_matching_scope_prefix_and_drop_it(Scopes, ScopePrefix)};
|
||||
filter_matching_scope_prefix(_, Payload) -> Payload.
|
||||
|
||||
-spec filter_matching_scope_prefix_and_drop_it(list(), binary()|list()) -> list().
|
||||
filter_matching_scope_prefix_and_drop_it(Scopes, <<>>) -> Scopes;
|
||||
filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) ->
|
||||
PatternLength = byte_size(PrefixPattern),
|
||||
lists:filtermap(
|
||||
fun(ScopeEl) ->
|
||||
|
|
|
@ -43,7 +43,7 @@ all() ->
|
|||
test_invalid_signature,
|
||||
test_incorrect_kid,
|
||||
normalize_token_scope_using_multiple_scopes_key,
|
||||
normalize_token_scope_with_keycloak_scopes,
|
||||
normalize_token_scope_with_requesting_party_token_scopes,
|
||||
normalize_token_scope_with_rich_auth_request,
|
||||
normalize_token_scope_with_rich_auth_request_using_regular_expression_with_cluster,
|
||||
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field,
|
||||
|
@ -125,7 +125,7 @@ normalize_token_scope_using_multiple_scopes_key(_) ->
|
|||
Pairs = [
|
||||
%% common case
|
||||
{
|
||||
"keycloak format 1",
|
||||
"keycloak format 1, i.e. requesting party token",
|
||||
#{<<"authorization">> =>
|
||||
#{<<"permissions">> =>
|
||||
[#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
|
||||
|
@ -186,10 +186,10 @@ normalize_token_scope_using_multiple_scopes_key(_) ->
|
|||
additional_scopes_key = <<"authorization.permissions.scopes realm_access.roles resource_access.account.roles">>
|
||||
},
|
||||
Token = normalize_token_scope(ResourceServer, Token0),
|
||||
?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case)
|
||||
?assertEqual(lists:sort(ExpectedScope), lists:sort(uaa_jwt:get_scope(Token)), Case)
|
||||
end, Pairs).
|
||||
|
||||
normalize_token_scope_with_keycloak_scopes(_) ->
|
||||
normalize_token_scope_with_requesting_party_token_scopes(_) ->
|
||||
Pairs = [
|
||||
%% common case
|
||||
{
|
||||
|
@ -242,11 +242,8 @@ normalize_token_scope_with_keycloak_scopes(_) ->
|
|||
|
||||
lists:foreach(fun({Case, Authorization, ExpectedScope}) ->
|
||||
ResourceServer0 = new_resource_server(<<"rabbitmq-resource">>),
|
||||
ResourceServer = ResourceServer0#resource_server{
|
||||
additional_scopes_key = <<"authorization.permissions.scopes">>
|
||||
},
|
||||
Token0 = #{<<"authorization">> => Authorization},
|
||||
Token = normalize_token_scope(ResourceServer, Token0),
|
||||
Token = normalize_token_scope(ResourceServer0, Token0),
|
||||
?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case)
|
||||
end, Pairs).
|
||||
|
||||
|
@ -431,7 +428,7 @@ normalize_token_scope_with_rich_auth_request(_) ->
|
|||
}
|
||||
],
|
||||
[<<"tag:management">>, <<"tag:policymaker">>,
|
||||
<<"tag:management">>, <<"tag:monitoring">> ]
|
||||
<<"tag:monitoring">> ]
|
||||
},
|
||||
{ "should produce a scope for every user tag action but only for the clusters that match {resource_server_id}",
|
||||
[ #{<<"type">> => ?RESOURCE_SERVER_TYPE,
|
||||
|
|
Loading…
Reference in New Issue