Skip to content

Commit 1c3f24e

Browse files
Support keycloak custom format via configuration
1 parent 211fc5b commit 1c3f24e

File tree

7 files changed

+291
-118
lines changed

7 files changed

+291
-118
lines changed

deps/rabbitmq_auth_backend_oauth2/app.bzl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ def all_beam_files(name = "all_beam_files"):
1313
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
1414
"src/rabbit_auth_backend_oauth2.erl",
1515
"src/rabbit_auth_backend_oauth2_app.erl",
16-
"src/rabbit_oauth2_keycloak.erl",
1716
"src/rabbit_oauth2_provider.erl",
1817
"src/rabbit_oauth2_rar.erl",
1918
"src/rabbit_oauth2_resource_server.erl",
@@ -51,7 +50,6 @@ def all_test_beam_files(name = "all_test_beam_files"):
5150
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
5251
"src/rabbit_auth_backend_oauth2.erl",
5352
"src/rabbit_auth_backend_oauth2_app.erl",
54-
"src/rabbit_oauth2_keycloak.erl",
5553
"src/rabbit_oauth2_provider.erl",
5654
"src/rabbit_oauth2_rar.erl",
5755
"src/rabbit_oauth2_resource_server.erl",
@@ -101,7 +99,6 @@ def all_srcs(name = "all_srcs"):
10199
"src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl",
102100
"src/rabbit_auth_backend_oauth2.erl",
103101
"src/rabbit_auth_backend_oauth2_app.erl",
104-
"src/rabbit_oauth2_keycloak.erl",
105102
"src/rabbit_oauth2_provider.erl",
106103
"src/rabbit_oauth2_rar.erl",
107104
"src/rabbit_oauth2_resource_server.erl",

deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl

Lines changed: 127 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
get_scope/1, set_scope/2,
2929
resolve_resource_server/1]).
3030

31-
-import(rabbit_oauth2_keycloak, [has_keycloak_scopes/1, extract_scopes_from_keycloak_format/1]).
32-
-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]).
31+
-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2]).
3332

3433
-import(rabbit_oauth2_scope, [filter_matching_scope_prefix_and_drop_it/2]).
3534

@@ -229,79 +228,142 @@ check_token(Token, {ResourceServer, InternalOAuthProvider}) ->
229228
{false, _} -> {refused, signature_invalid}
230229
end.
231230

231+
extract_scopes_from_scope_claim(Payload) ->
232+
case maps:find(?SCOPE_JWT_FIELD, Payload) of
233+
{ok, Bin} when is_binary(Bin) ->
234+
maps:put(?SCOPE_JWT_FIELD,
235+
binary:split(Bin, <<" ">>, [global, trim_all]),
236+
Payload);
237+
_ -> Payload
238+
end.
239+
232240
-spec normalize_token_scope(
233241
ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
234242
normalize_token_scope(ResourceServer, Payload) ->
235-
Payload0 = maps:map(fun(K, V) ->
236-
case K of
237-
?SCOPE_JWT_FIELD when is_binary(V) ->
238-
binary:split(V, <<" ">>, [global, trim_all]);
239-
_ -> V
240-
end
241-
end, Payload),
242-
243-
Payload1 = case has_additional_scopes_key(ResourceServer, Payload0) of
244-
true -> extract_scopes_from_additional_scopes_key(ResourceServer, Payload0);
245-
false -> Payload0
246-
end,
247-
248-
Payload2 = case has_keycloak_scopes(Payload1) of
249-
true -> extract_scopes_from_keycloak_format(Payload1);
250-
false -> Payload1
251-
end,
252-
253-
Payload3 = case ResourceServer#resource_server.scope_aliases of
254-
undefined -> Payload2;
255-
ScopeAliases -> extract_scopes_using_scope_aliases(ScopeAliases, Payload2)
256-
end,
257-
258-
Payload4 = case has_rich_auth_request_scopes(Payload3) of
259-
true -> extract_scopes_from_rich_auth_request(ResourceServer, Payload3);
260-
false -> Payload3
261-
end,
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)))),
262248

263249
FilteredScopes = filter_matching_scope_prefix_and_drop_it(
264-
get_scope(Payload4), ResourceServer#resource_server.scope_prefix),
265-
set_scope(FilteredScopes, Payload4).
266-
250+
get_scope(Payload1), ResourceServer#resource_server.scope_prefix),
251+
set_scope(FilteredScopes, Payload1).
267252

268253
-spec extract_scopes_using_scope_aliases(
269-
ScopeAliasMapping :: map(), Payload :: map()) -> map().
270-
extract_scopes_using_scope_aliases(ScopeAliasMapping, Payload) ->
271-
Scopes0 = get_scope(Payload),
272-
Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0),
273-
%% for all scopes, look them up in the scope alias map, and if they are
274-
%% present, add the alias to the final scope list. Note that we also preserve
275-
%% the original scopes, it should not hurt.
276-
ExpandedScopes =
277-
lists:foldl(fun(ScopeListItem, Acc) ->
278-
case maps:get(ScopeListItem, ScopeAliasMapping, undefined) of
279-
undefined ->
280-
Acc;
281-
MappedList when is_list(MappedList) ->
282-
Binaries = rabbit_data_coercion:to_list_of_binaries(MappedList),
283-
Acc ++ Binaries;
284-
Value ->
285-
Binaries = rabbit_data_coercion:to_list_of_binaries(Value),
286-
Acc ++ Binaries
287-
end
288-
end, Scopes, Scopes),
289-
set_scope(ExpandedScopes, Payload).
290-
291-
-spec has_additional_scopes_key(
292-
ResourceServer :: resource_server(), Payload :: map()) -> boolean().
293-
has_additional_scopes_key(ResourceServer, Payload) when is_map(Payload) ->
294-
case ResourceServer#resource_server.additional_scopes_key of
295-
undefined -> false;
296-
ScopeKey -> maps:is_key(ScopeKey, Payload)
297-
end.
254+
ResourceServer :: resource_server(), Payload :: map()) -> map().
255+
extract_scopes_using_scope_aliases(
256+
#resource_server{scope_aliases = ScopeAliasMapping}, Payload)
257+
when is_map(ScopeAliasMapping) ->
258+
Scopes0 = get_scope(Payload),
259+
Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0),
260+
%% for all scopes, look them up in the scope alias map, and if they are
261+
%% present, add the alias to the final scope list. Note that we also preserve
262+
%% the original scopes, it should not hurt.
263+
ExpandedScopes =
264+
lists:foldl(fun(ScopeListItem, Acc) ->
265+
case maps:get(ScopeListItem, ScopeAliasMapping, undefined) of
266+
undefined ->
267+
Acc;
268+
MappedList when is_list(MappedList) ->
269+
Binaries = rabbit_data_coercion:to_list_of_binaries(MappedList),
270+
Acc ++ Binaries;
271+
Value ->
272+
Binaries = rabbit_data_coercion:to_list_of_binaries(Value),
273+
Acc ++ Binaries
274+
end
275+
end, Scopes, Scopes),
276+
set_scope(ExpandedScopes, Payload);
277+
extract_scopes_using_scope_aliases(_, Payload) -> Payload.
278+
279+
%% Path is a binary expression which is a plain word like <<"roles">>
280+
%% or +1 word separated by . like <<"authorization.permissions.scopes">>
281+
%% The Payload is a map.
282+
%% Using the path <<"authorization.permissions.scopes">> as an example
283+
%% 1. lookup the key <<"authorization">> in the Payload
284+
%% 2. if it is found, the next map to use as payload is the value found from the key <<"authorization">>
285+
%% 3. lookup the key <<"permissions">> in the previous map
286+
%% 4. if it is found, it may be a map or a list of maps.
287+
%% 5. if it is a list of maps, iterate each element in the list
288+
%% 6. for each element in the list, which should be a map, find the key <<"scopes">>
289+
%% 7. because there are no more words/keys, return a list of all the values found
290+
%% associated to the word <<"scopes">>
291+
extract_token_value(R, Payload, Path, ValueMapperFun)
292+
when is_map(Payload), is_binary(Path), is_function(ValueMapperFun) ->
293+
extract_token_value_from_map(R, Payload, [], split_path(Path), ValueMapperFun);
294+
extract_token_value(_, _, _, _) ->
295+
[].
296+
297+
extract_scope_list_from_token_value(_R, List) when is_list(List) -> List;
298+
extract_scope_list_from_token_value(_R, Binary) when is_binary(Binary) ->
299+
binary:split(Binary, <<" ">>, [global, trim_all]);
300+
extract_scope_list_from_token_value(#resource_server{id = ResourceServerId}, Map) when is_map(Map) ->
301+
case maps:get(ResourceServerId, Map, undefined) of
302+
undefined -> [];
303+
Ks when is_list(Ks) ->
304+
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks];
305+
ClaimBin when is_binary(ClaimBin) ->
306+
UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]),
307+
[erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims];
308+
_ -> []
309+
end;
310+
extract_scope_list_from_token_value(_, _) -> [].
311+
312+
extract_token_value_from_map(_, _Map, Acc, [], _Mapper) ->
313+
Acc;
314+
extract_token_value_from_map(R, Map, Acc, [KeyStr], Mapper) when is_map(Map) ->
315+
case maps:find(KeyStr, Map) of
316+
{ok, Value} -> Acc ++ Mapper(R, Value);
317+
error -> Acc
318+
end;
319+
extract_token_value_from_map(R, Map, Acc, [KeyStr | Rest], Mapper) when is_map(Map) ->
320+
case maps:find(KeyStr, Map) of
321+
{ok, M} when is_map(M) -> extract_token_value_from_map(R, M, Acc, Rest, Mapper);
322+
{ok, L} when is_list(L) -> extract_token_value_from_list(R, L, Acc, Rest, Mapper);
323+
{ok, Value} when Rest =:= [] -> Acc ++ Mapper(R, Value);
324+
_ -> Acc
325+
end;
326+
extract_token_value_from_map(_, _, Acc, _, _Mapper) ->
327+
Acc.
328+
329+
extract_token_value_from_list(_, [], Acc, [], _Mapper) ->
330+
Acc;
331+
extract_token_value_from_list(_, [], Acc, [_KeyStr | _Rest], _Mapper) ->
332+
Acc;
333+
extract_token_value_from_list(R, [H | T], Acc, [KeyStr | Rest] = KeyList, Mapper) when is_map(H) ->
334+
NewAcc = case maps:find(KeyStr, H) of
335+
{ok, Map} when is_map(Map) -> extract_token_value_from_map(R, Map, Acc, Rest, Mapper);
336+
{ok, List} when is_list(List) -> extract_token_value_from_list(R, List, Acc, Rest, Mapper);
337+
{ok, Value} -> Acc++Mapper(R, Value);
338+
_ -> Acc
339+
end,
340+
extract_token_value_from_list(R, T, NewAcc, KeyList, Mapper);
341+
342+
extract_token_value_from_list(R, [E | T], Acc, [], Mapper) ->
343+
extract_token_value_from_list(R, T, Acc++Mapper(R, E), [], Mapper);
344+
extract_token_value_from_list(R, [E | _T] = L, Acc, KeyList, Mapper) when is_map(E) ->
345+
extract_token_value_from_list(R, L, Acc, KeyList, Mapper);
346+
extract_token_value_from_list(R, [_ | T], Acc, KeyList, Mapper) ->
347+
extract_token_value_from_list(R, T, Acc, KeyList, Mapper).
348+
349+
350+
split_path(Path) when is_binary(Path) ->
351+
binary:split(Path, <<".">>, [global, trim_all]).
352+
298353

299354
-spec extract_scopes_from_additional_scopes_key(
300355
ResourceServer :: resource_server(), Payload :: map()) -> map().
301-
extract_scopes_from_additional_scopes_key(ResourceServer, Payload) ->
302-
Claim = maps:get(ResourceServer#resource_server.additional_scopes_key, Payload),
303-
AdditionalScopes = extract_additional_scopes(ResourceServer, Claim),
304-
set_scope(AdditionalScopes ++ get_scope(Payload), Payload).
356+
extract_scopes_from_additional_scopes_key(
357+
#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,
363+
AdditionalScopes = [ extract_token_value(ResourceServer,
364+
Payload, Path, fun extract_scope_list_from_token_value/2) || Path <- Paths],
365+
set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload);
366+
extract_scopes_from_additional_scopes_key(_, Payload) -> Payload.
305367

306368
extract_additional_scopes(ResourceServer, ComplexClaim) ->
307369
ResourceServerId = ResourceServer#resource_server.id,

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl

Lines changed: 0 additions & 41 deletions
This file was deleted.

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
-include("oauth2.hrl").
1212
-import(uaa_jwt, [get_scope/1, set_scope/2]).
1313

14-
-export([extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]).
14+
-export([extract_scopes_from_rich_auth_request/2]).
1515

1616
-define(AUTHORIZATION_DETAILS_CLAIM, <<"authorization_details">>).
1717
-define(RAR_ACTIONS_FIELD, <<"actions">>).
@@ -44,15 +44,12 @@
4444
<<"management">>,
4545
<<"policymaker">> ]).
4646

47-
-spec has_rich_auth_request_scopes(Payload::map()) -> boolean().
48-
has_rich_auth_request_scopes(Payload) ->
49-
maps:is_key(?AUTHORIZATION_DETAILS_CLAIM, Payload).
50-
5147
-spec extract_scopes_from_rich_auth_request(ResourceServer :: resource_server(),
5248
Payload :: map()) -> map().
5349
%% https://oauth.net/2/rich-authorization-requests/
5450
extract_scopes_from_rich_auth_request(ResourceServer,
55-
#{?AUTHORIZATION_DETAILS_CLAIM := Permissions} = Payload) ->
51+
#{?AUTHORIZATION_DETAILS_CLAIM := Permissions} = Payload)
52+
when is_list(Permissions) ->
5653
ResourceServerType = ResourceServer#resource_server.resource_server_type,
5754

5855
FilteredPermissionsByType = lists:filter(fun(P) ->
@@ -61,7 +58,8 @@ extract_scopes_from_rich_auth_request(ResourceServer,
6158
ResourceServer#resource_server.id, FilteredPermissionsByType),
6259

6360
ExistingScopes = get_scope(Payload),
64-
set_scope(AdditionalScopes ++ ExistingScopes, Payload).
61+
set_scope(AdditionalScopes ++ ExistingScopes, Payload);
62+
extract_scopes_from_rich_auth_request(_, Payload) -> Payload.
6563

6664
put_location_attribute(Attribute, Map) ->
6765
put_attribute(binary:split(Attribute, <<":">>, [global, trim_all]), Map).

deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ parse_resource_pattern(Pattern, Permission) ->
9696
-spec filter_matching_scope_prefix_and_drop_it(list(), binary()|list()) -> list().
9797
filter_matching_scope_prefix_and_drop_it(Scopes, <<"">>) -> Scopes;
9898
filter_matching_scope_prefix_and_drop_it(Scopes, PrefixPattern) ->
99+
99100
PatternLength = byte_size(PrefixPattern),
100101
lists:filtermap(
101102
fun(ScopeEl) ->

deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,5 +316,15 @@
316316
}
317317
]}
318318
], []
319+
},
320+
{additional_scopes_key,
321+
"auth_oauth2.resource_server_id = new_resource_server_id
322+
auth_oauth2.additional_scopes_key = roles realm.roles",
323+
[
324+
{rabbitmq_auth_backend_oauth2, [
325+
{resource_server_id,<<"new_resource_server_id">>},
326+
{extra_scopes_source, <<"roles realm.roles">> }
327+
]}
328+
], []
319329
}
320330
].

0 commit comments

Comments
 (0)