|
28 | 28 | get_scope/1, set_scope/2,
|
29 | 29 | resolve_resource_server/1]).
|
30 | 30 |
|
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]). |
33 | 32 |
|
34 |
| --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]). |
35 | 36 |
|
36 | 37 | -ifdef(TEST).
|
37 | 38 | -compile(export_all).
|
@@ -229,98 +230,152 @@ check_token(Token, {ResourceServer, InternalOAuthProvider}) ->
|
229 | 230 | {false, _} -> {refused, signature_invalid}
|
230 | 231 | end.
|
231 | 232 |
|
| 233 | +extract_scopes_from_scope_claim(Payload) -> |
| 234 | + case maps:find(?SCOPE_JWT_FIELD, Payload) of |
| 235 | + {ok, Bin} when is_binary(Bin) -> |
| 236 | + maps:put(?SCOPE_JWT_FIELD, |
| 237 | + binary:split(Bin, <<" ">>, [global, trim_all]), |
| 238 | + Payload); |
| 239 | + _ -> Payload |
| 240 | + end. |
| 241 | + |
232 | 242 | -spec normalize_token_scope(
|
233 | 243 | ResourceServer :: resource_server(), DecodedToken :: decoded_jwt_token()) -> map().
|
234 | 244 | 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, |
262 |
| - |
263 |
| - FilteredScopes = filter_matching_scope_prefix_and_drop_it( |
264 |
| - get_scope(Payload4), ResourceServer#resource_server.scope_prefix), |
265 |
| - set_scope(FilteredScopes, Payload4). |
266 | 245 |
|
| 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. |
267 | 269 |
|
268 | 270 | -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) |
| 271 | + ResourceServer :: resource_server(), Payload :: map()) -> map(). |
| 272 | +extract_scopes_using_scope_aliases( |
| 273 | + #resource_server{scope_aliases = ScopeAliasMapping}, Payload) |
| 274 | + when is_map(ScopeAliasMapping) -> |
| 275 | + Scopes0 = get_scope(Payload), |
| 276 | + Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0), |
| 277 | + %% for all scopes, look them up in the scope alias map, and if they are |
| 278 | + %% present, add the alias to the final scope list. Note that we also preserve |
| 279 | + %% the original scopes, it should not hurt. |
| 280 | + ExpandedScopes = |
| 281 | + lists:foldl(fun(ScopeListItem, Acc) -> |
| 282 | + case maps:get(ScopeListItem, ScopeAliasMapping, undefined) of |
| 283 | + undefined -> |
| 284 | + Acc; |
| 285 | + MappedList when is_list(MappedList) -> |
| 286 | + Binaries = rabbit_data_coercion:to_list_of_binaries(MappedList), |
| 287 | + Acc ++ Binaries; |
| 288 | + Value -> |
| 289 | + Binaries = rabbit_data_coercion:to_list_of_binaries(Value), |
| 290 | + Acc ++ Binaries |
| 291 | + end |
| 292 | + end, Scopes, Scopes), |
| 293 | + set_scope(ExpandedScopes, Payload); |
| 294 | +extract_scopes_using_scope_aliases(_, Payload) -> Payload. |
| 295 | + |
| 296 | +%% Path is a binary expression which is a plain word like <<"roles">> |
| 297 | +%% or +1 word separated by . like <<"authorization.permissions.scopes">> |
| 298 | +%% The Payload is a map. |
| 299 | +%% Using the path <<"authorization.permissions.scopes">> as an example |
| 300 | +%% 1. lookup the key <<"authorization">> in the Payload |
| 301 | +%% 2. if it is found, the next map to use as payload is the value found from the key <<"authorization">> |
| 302 | +%% 3. lookup the key <<"permissions">> in the previous map |
| 303 | +%% 4. if it is found, it may be a map or a list of maps. |
| 304 | +%% 5. if it is a list of maps, iterate each element in the list |
| 305 | +%% 6. for each element in the list, which should be a map, find the key <<"scopes">> |
| 306 | +%% 7. because there are no more words/keys, return a list of all the values found |
| 307 | +%% associated to the word <<"scopes">> |
| 308 | +extract_token_value(R, Payload, Path, ValueMapperFun) |
| 309 | + when is_map(Payload), is_binary(Path), is_function(ValueMapperFun) -> |
| 310 | + extract_token_value_from_map(R, Payload, [], split_path(Path), ValueMapperFun); |
| 311 | +extract_token_value(_, _, _, _) -> |
| 312 | + []. |
| 313 | + |
| 314 | +extract_scope_list_from_token_value(_R, List) when is_list(List) -> List; |
| 315 | +extract_scope_list_from_token_value(_R, Binary) when is_binary(Binary) -> |
| 316 | + binary:split(Binary, <<" ">>, [global, trim_all]); |
| 317 | +extract_scope_list_from_token_value(#resource_server{id = ResourceServerId}, Map) when is_map(Map) -> |
| 318 | + case maps:get(ResourceServerId, Map, undefined) of |
| 319 | + undefined -> []; |
| 320 | + Ks when is_list(Ks) -> |
| 321 | + [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks]; |
| 322 | + ClaimBin when is_binary(ClaimBin) -> |
| 323 | + UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]), |
| 324 | + [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims]; |
| 325 | + _ -> [] |
| 326 | + end; |
| 327 | +extract_scope_list_from_token_value(_, _) -> []. |
| 328 | + |
| 329 | +extract_token_value_from_map(_, _Map, Acc, [], _Mapper) -> |
| 330 | + Acc; |
| 331 | +extract_token_value_from_map(R, Map, Acc, [KeyStr], Mapper) when is_map(Map) -> |
| 332 | + case maps:find(KeyStr, Map) of |
| 333 | + {ok, Value} -> Acc ++ Mapper(R, Value); |
| 334 | + error -> Acc |
| 335 | + end; |
| 336 | +extract_token_value_from_map(R, Map, Acc, [KeyStr | Rest], Mapper) when is_map(Map) -> |
| 337 | + case maps:find(KeyStr, Map) of |
| 338 | + {ok, M} when is_map(M) -> extract_token_value_from_map(R, M, Acc, Rest, Mapper); |
| 339 | + {ok, L} when is_list(L) -> extract_token_value_from_list(R, L, Acc, Rest, Mapper); |
| 340 | + {ok, Value} when Rest =:= [] -> Acc ++ Mapper(R, Value); |
| 341 | + _ -> Acc |
297 | 342 | end.
|
298 | 343 |
|
| 344 | +extract_token_value_from_list(_, [], Acc, [], _Mapper) -> |
| 345 | + Acc; |
| 346 | +extract_token_value_from_list(_, [], Acc, [_KeyStr | _Rest], _Mapper) -> |
| 347 | + Acc; |
| 348 | +extract_token_value_from_list(R, [H | T], Acc, [KeyStr | Rest] = KeyList, Mapper) when is_map(H) -> |
| 349 | + NewAcc = case maps:find(KeyStr, H) of |
| 350 | + {ok, Map} when is_map(Map) -> extract_token_value_from_map(R, Map, Acc, Rest, Mapper); |
| 351 | + {ok, List} when is_list(List) -> extract_token_value_from_list(R, List, Acc, Rest, Mapper); |
| 352 | + {ok, Value} -> Acc++Mapper(R, Value); |
| 353 | + _ -> Acc |
| 354 | + end, |
| 355 | + extract_token_value_from_list(R, T, NewAcc, KeyList, Mapper); |
| 356 | + |
| 357 | +extract_token_value_from_list(R, [E | T], Acc, [], Mapper) -> |
| 358 | + extract_token_value_from_list(R, T, Acc++Mapper(R, E), [], Mapper); |
| 359 | +extract_token_value_from_list(R, [E | _T] = L, Acc, KeyList, Mapper) when is_map(E) -> |
| 360 | + extract_token_value_from_list(R, L, Acc, KeyList, Mapper); |
| 361 | +extract_token_value_from_list(R, [_ | T], Acc, KeyList, Mapper) -> |
| 362 | + extract_token_value_from_list(R, T, Acc, KeyList, Mapper). |
| 363 | + |
| 364 | + |
| 365 | +split_path(Path) when is_binary(Path) -> |
| 366 | + binary:split(Path, <<".">>, [global, trim_all]). |
| 367 | + |
| 368 | + |
299 | 369 | -spec extract_scopes_from_additional_scopes_key(
|
300 | 370 | 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). |
305 |
| - |
306 |
| -extract_additional_scopes(ResourceServer, ComplexClaim) -> |
307 |
| - ResourceServerId = ResourceServer#resource_server.id, |
308 |
| - case ComplexClaim of |
309 |
| - L when is_list(L) -> L; |
310 |
| - M when is_map(M) -> |
311 |
| - case maps:get(ResourceServerId, M, undefined) of |
312 |
| - undefined -> []; |
313 |
| - Ks when is_list(Ks) -> |
314 |
| - [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- Ks]; |
315 |
| - ClaimBin when is_binary(ClaimBin) -> |
316 |
| - UnprefixedClaims = binary:split(ClaimBin, <<" ">>, [global, trim_all]), |
317 |
| - [erlang:iolist_to_binary([ResourceServerId, <<".">>, K]) || K <- UnprefixedClaims]; |
318 |
| - _ -> [] |
319 |
| - end; |
320 |
| - Bin when is_binary(Bin) -> |
321 |
| - binary:split(Bin, <<" ">>, [global, trim_all]); |
322 |
| - _ -> [] |
323 |
| - end. |
| 371 | +extract_scopes_from_additional_scopes_key( |
| 372 | + #resource_server{additional_scopes_key = Key} = ResourceServer, Payload) |
| 373 | + when is_binary(Key) -> |
| 374 | + Paths = binary:split(Key, <<" ">>, [global, trim_all]), |
| 375 | + AdditionalScopes = [ extract_token_value(ResourceServer, |
| 376 | + Payload, Path, fun extract_scope_list_from_token_value/2) || Path <- Paths], |
| 377 | + set_scope(lists:flatten(AdditionalScopes) ++ get_scope(Payload), Payload); |
| 378 | +extract_scopes_from_additional_scopes_key(_, Payload) -> Payload. |
324 | 379 |
|
325 | 380 |
|
326 | 381 | %% A token may be present in the password credential or in the rabbit_auth_backend_oauth2
|
|
0 commit comments