Skip to content

Commit 46aadeb

Browse files
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
1 parent 90a49d0 commit 46aadeb

File tree

4 files changed

+60
-49
lines changed

4 files changed

+60
-49
lines changed

deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222

2323
%% End of Key JWT fields
2424

25+
%% UMA claim-type returns a RPT which is a token
26+
%% where scopes are located under a map of list of objects which have
27+
%% the scopes in the "scopes" attribute
28+
%% Used by Keycloak, WSO2 and others.
29+
%% https://en.wikipedia.org/wiki/User-Managed_Access#cite_note-docs.wso2.com-19
30+
-define(SCOPES_LOCATION_IN_REQUESTING_PARTY_TOKEN, <<"authorization.permissions.scopes">>).
31+
32+
2533
-type raw_jwt_token() :: binary() | #{binary() => any()}.
2634
-type decoded_jwt_token() :: #{binary() => any()}.
2735

deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030

3131
-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2]).
3232

33-
-import(rabbit_oauth2_scope, [filter_matching_scope_prefix_and_drop_it/2]).
33+
-import(rabbit_oauth2_scope, [
34+
filter_matching_scope_prefix/2,
35+
filter_matching_scope_prefix_and_drop_it/2]).
3436

3537
-ifdef(TEST).
3638
-compile(export_all).
@@ -240,15 +242,30 @@ extract_scopes_from_scope_claim(Payload) ->
240242
-spec normalize_token_scope(
241243
ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
242244
normalize_token_scope(ResourceServer, Payload) ->
243-
244-
Payload1 = extract_scopes_from_rich_auth_request(ResourceServer,
245-
extract_scopes_using_scope_aliases(ResourceServer,
246-
extract_scopes_from_additional_scopes_key(ResourceServer,
247-
extract_scopes_from_scope_claim(Payload)))),
248245

249-
FilteredScopes = filter_matching_scope_prefix_and_drop_it(
250-
get_scope(Payload1), ResourceServer#resource_server.scope_prefix),
251-
set_scope(FilteredScopes, Payload1).
246+
filter_duplicates(
247+
filter_matching_scope_prefix(ResourceServer,
248+
extract_scopes_from_rich_auth_request(ResourceServer,
249+
extract_scopes_using_scope_aliases(ResourceServer,
250+
extract_scopes_from_additional_scopes_key(ResourceServer,
251+
extract_scopes_from_requesting_party_token(ResourceServer,
252+
extract_scopes_from_scope_claim(Payload))))))).
253+
254+
filter_duplicates(#{?SCOPE_JWT_FIELD := Scopes} = Payload) ->
255+
set_scope(lists:usort(Scopes), Payload);
256+
filter_duplicates(Payload) -> Payload.
257+
258+
-spec extract_scopes_from_requesting_party_token(
259+
ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
260+
extract_scopes_from_requesting_party_token(ResourceServer, Payload) ->
261+
Path = ?SCOPES_LOCATION_IN_REQUESTING_PARTY_TOKEN,
262+
case extract_token_value(ResourceServer, Payload, Path,
263+
fun extract_scope_list_from_token_value/2) of
264+
[] ->
265+
Payload;
266+
AdditionalScopes ->
267+
set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload)
268+
end.
252269

253270
-spec extract_scopes_using_scope_aliases(
254271
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
322339
{ok, L} when is_list(L) -> extract_token_value_from_list(R, L, Acc, Rest, Mapper);
323340
{ok, Value} when Rest =:= [] -> Acc ++ Mapper(R, Value);
324341
_ -> Acc
325-
end;
326-
extract_token_value_from_map(_, _, Acc, _, _Mapper) ->
327-
Acc.
342+
end.
343+
%extract_token_value_from_map(_, _, Acc, _, _Mapper) ->
344+
% Acc.
328345

329346
extract_token_value_from_list(_, [], Acc, [], _Mapper) ->
330347
Acc;
@@ -355,35 +372,13 @@ split_path(Path) when is_binary(Path) ->
355372
ResourceServer :: resource_server(), Payload :: map()) -> map().
356373
extract_scopes_from_additional_scopes_key(
357374
#resource_server{additional_scopes_key = Key} = ResourceServer, Payload)
358-
when is_list(Key) or is_binary(Key) ->
359-
Paths = case Key of
360-
B when is_binary(B) -> binary:split(B, <<" ">>, [global, trim_all]);
361-
L when is_list(L) -> L
362-
end,
375+
when is_binary(Key) ->
376+
Paths = binary:split(Key, <<" ">>, [global, trim_all]),
363377
AdditionalScopes = [ extract_token_value(ResourceServer,
364378
Payload, Path, fun extract_scope_list_from_token_value/2) || Path <- Paths],
365379
set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload);
366380
extract_scopes_from_additional_scopes_key(_, Payload) -> Payload.
367381

368-
extract_additional_scopes(ResourceServer, ComplexClaim) ->
369-
ResourceServerId = ResourceServer#resource_server.id,
370-
case ComplexClaim of
371-
L when is_list(L) -> L;
372-
M when is_map(M) ->
373-
case maps:get(ResourceServerId, M, undefined) of
374-
undefined -> [];
375-
Ks when is_list(Ks) ->
376-
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks];
377-
ClaimBin when is_binary(ClaimBin) ->
378-
UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]),
379-
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims];
380-
_ -> []
381-
end;
382-
Bin when is_binary(Bin) ->
383-
binary:split(Bin, <<" ">>, [global, trim_all]);
384-
_ -> []
385-
end.
386-
387382

388383
%% A token may be present in the password credential or in the rabbit_auth_backend_oauth2
389384
%% credential. The former is the most common scenario for the first time authentication.

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
-module(rabbit_oauth2_scope).
99

10+
-include("oauth2.hrl").
11+
1012
-export([vhost_access/2,
1113
resource_access/3,
1214
topic_access/4,
1315
concat_scopes/2,
16+
filter_matching_scope_prefix/2,
1417
filter_matching_scope_prefix_and_drop_it/2]).
1518

1619
-include_lib("rabbit_common/include/rabbit.hrl").
@@ -93,10 +96,18 @@ parse_resource_pattern(Pattern, Permission) ->
9396
_Other -> ignore
9497
end.
9598

99+
-spec filter_matching_scope_prefix(ResourceServer :: resource_server(),
100+
Payload :: map()) -> map().
101+
filter_matching_scope_prefix(
102+
#resource_server{scope_prefix = ScopePrefix},
103+
#{?SCOPE_JWT_FIELD := Scopes} = Payload) ->
104+
Payload#{?SCOPE_JWT_FIELD :=
105+
filter_matching_scope_prefix_and_drop_it(Scopes, ScopePrefix)};
106+
filter_matching_scope_prefix(_, Payload) -> Payload.
107+
96108
-spec filter_matching_scope_prefix_and_drop_it(list(), binary()|list()) -> list().
97-
filter_matching_scope_prefix_and_drop_it(Scopes, <<"">>) -> Scopes;
98-
filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) ->
99-
109+
filter_matching_scope_prefix_and_drop_it(Scopes, <<>>) -> Scopes;
110+
filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) ->
100111
PatternLength = byte_size(PrefixPattern),
101112
lists:filtermap(
102113
fun(ScopeEl) ->

deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ all() ->
4343
test_invalid_signature,
4444
test_incorrect_kid,
4545
normalize_token_scope_using_multiple_scopes_key,
46-
normalize_token_scope_with_keycloak_scopes,
46+
normalize_token_scope_with_requesting_party_token_scopes,
4747
normalize_token_scope_with_rich_auth_request,
4848
normalize_token_scope_with_rich_auth_request_using_regular_expression_with_cluster,
4949
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(_) ->
125125
Pairs = [
126126
%% common case
127127
{
128-
"keycloak format 1",
128+
"keycloak format 1, i.e. requesting party token",
129129
#{<<"authorization">> =>
130130
#{<<"permissions">> =>
131131
[#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>,
@@ -186,10 +186,10 @@ normalize_token_scope_using_multiple_scopes_key(_) ->
186186
additional_scopes_key = <<"authorization.permissions.scopes realm_access.roles resource_access.account.roles">>
187187
},
188188
Token = normalize_token_scope(ResourceServer, Token0),
189-
?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case)
189+
?assertEqual(lists:sort(ExpectedScope), lists:sort(uaa_jwt:get_scope(Token)), Case)
190190
end, Pairs).
191191

192-
normalize_token_scope_with_keycloak_scopes(_) ->
192+
normalize_token_scope_with_requesting_party_token_scopes(_) ->
193193
Pairs = [
194194
%% common case
195195
{
@@ -241,12 +241,9 @@ normalize_token_scope_with_keycloak_scopes(_) ->
241241
],
242242

243243
lists:foreach(fun({Case, Authorization, ExpectedScope}) ->
244-
ResourceServer0 = new_resource_server(<<"rabbitmq-resource">>),
245-
ResourceServer = ResourceServer0#resource_server{
246-
additional_scopes_key = <<"authorization.permissions.scopes">>
247-
},
244+
ResourceServer0 = new_resource_server(<<"rabbitmq-resource">>),
248245
Token0 = #{<<"authorization">> => Authorization},
249-
Token = normalize_token_scope(ResourceServer, Token0),
246+
Token = normalize_token_scope(ResourceServer0, Token0),
250247
?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case)
251248
end, Pairs).
252249

@@ -431,7 +428,7 @@ normalize_token_scope_with_rich_auth_request(_) ->
431428
}
432429
],
433430
[<<"tag:management">>, <<"tag:policymaker">>,
434-
<<"tag:management">>, <<"tag:monitoring">> ]
431+
<<"tag:monitoring">> ]
435432
},
436433
{ "should produce a scope for every user tag action but only for the clusters that match {resource_server_id}",
437434
[ #{<<"type">> => ?RESOURCE_SERVER_TYPE,

0 commit comments

Comments
 (0)