diff --git a/.github/workflows/test-authnz.yaml b/.github/workflows/test-authnz.yaml index 2b0342b03823..05f807179ecc 100644 --- a/.github/workflows/test-authnz.yaml +++ b/.github/workflows/test-authnz.yaml @@ -62,23 +62,10 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} - - name: Configure Bazel - run: | - if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then - cat << EOF >> user.bazelrc - build --remote_cache=https://storage.googleapis.com/${{ secrets.REMOTE_CACHE_BUCKET_NAME }} - build --google_default_credentials - - build --remote_download_toplevel - EOF - fi - cat << EOF >> user.bazelrc - build --color=yes - EOF - - name: Build & Load RabbitMQ OCI run: | - bazelisk run packaging/docker-image:rabbitmq-amd64 + make package-generic-unix + make docker-image - name: Configure Docker Network run: | @@ -91,7 +78,8 @@ jobs: - name: Run Suites run: | - RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging - name: Upload Test Artifacts diff --git a/.github/workflows/test-management-ui-for-pr.yaml b/.github/workflows/test-management-ui-for-pr.yaml index 98ec573b739d..358ff5571e5d 100644 --- a/.github/workflows/test-management-ui-for-pr.yaml +++ b/.github/workflows/test-management-ui-for-pr.yaml @@ -42,23 +42,10 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} - - name: Configure Bazel - run: | - if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then - cat << EOF >> user.bazelrc - build --remote_cache=https://storage.googleapis.com/${{ secrets.REMOTE_CACHE_BUCKET_NAME }} - build --google_default_credentials - - build --remote_download_toplevel - EOF - fi - cat << EOF >> user.bazelrc - build --color=yes - EOF - - name: Build & Load RabbitMQ OCI run: | - bazelisk run packaging/docker-image:rabbitmq-amd64 + make package-generic-unix + make docker-image - name: Configure Docker Network run: | @@ -71,8 +58,9 @@ jobs: - name: Run full ui suites on a standalone rabbitmq server run: | - RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ - ${SELENIUM_DIR}/run-suites.sh + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ + ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui mkdir -p /tmp/full-suite mv /tmp/selenium/* /tmp/full-suite mkdir -p /tmp/full-suite/logs diff --git a/.github/workflows/test-management-ui.yaml b/.github/workflows/test-management-ui.yaml index b05a80cb4e91..76fe452e10ed 100644 --- a/.github/workflows/test-management-ui.yaml +++ b/.github/workflows/test-management-ui.yaml @@ -56,23 +56,10 @@ jobs: with: credentials_json: ${{ secrets.REMOTE_CACHE_CREDENTIALS_JSON }} - - name: Configure Bazel - run: | - if [ -n "${{ secrets.REMOTE_CACHE_BUCKET_NAME }}" ]; then - cat << EOF >> user.bazelrc - build --remote_cache=https://storage.googleapis.com/${{ secrets.REMOTE_CACHE_BUCKET_NAME }} - build --google_default_credentials - - build --remote_download_toplevel - EOF - fi - cat << EOF >> user.bazelrc - build --color=yes - EOF - - name: Build & Load RabbitMQ OCI run: | - bazelisk run packaging/docker-image:rabbitmq-amd64 + make package-generic-unix + make docker-image - name: Configure Docker Network run: | @@ -84,9 +71,10 @@ jobs: docker build -t mocha-test --target test . - name: Run short ui suite on a 3-node rabbitmq cluster - run: | - RABBITMQ_DOCKER_IMAGE=bazel/packaging/docker-image:rabbitmq-amd64 \ - ADDON_PROFILES=cluster ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui + run: | + IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ + ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui mkdir -p /tmp/short-suite mv /tmp/selenium/* /tmp/short-suite mkdir -p /tmp/short-suite/logs diff --git a/MODULE.bazel b/MODULE.bazel index 5211632962cc..4721a077d58b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -277,8 +277,8 @@ erlang_package.hex_package( erlang_package.hex_package( name = "recon", build_file = "@rabbitmq-server//bazel:BUILD.recon", - sha256 = "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7", - version = "2.5.3", + sha256 = "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0", + version = "2.5.6", ) erlang_package.hex_package( diff --git a/bazel/BUILD.recon b/bazel/BUILD.recon index 9a2eb6cc8baa..35d78a04b4de 100644 --- a/bazel/BUILD.recon +++ b/bazel/BUILD.recon @@ -25,17 +25,9 @@ erlang_bytecode( "src/recon_rec.erl", "src/recon_trace.erl", ], - outs = [ - "ebin/recon.beam", - "ebin/recon_alloc.beam", - "ebin/recon_lib.beam", - "ebin/recon_map.beam", - "ebin/recon_rec.beam", - "ebin/recon_trace.beam", - ], - hdrs = [], + hdrs = [":public_and_private_hdrs"], app_name = "recon", - beam = [], + dest = "ebin", erlc_opts = "//:erlc_opts", ) @@ -57,20 +49,11 @@ filegroup( ], ) -filegroup( - name = "private_hdrs", - srcs = [], -) +filegroup(name = "private_hdrs") -filegroup( - name = "public_hdrs", - srcs = [], -) +filegroup(name = "public_hdrs") -filegroup( - name = "priv", - srcs = [], -) +filegroup(name = "priv") filegroup( name = "licenses", @@ -96,8 +79,12 @@ filegroup( erlang_app( name = "erlang_app", srcs = [":all_srcs"], + hdrs = [":public_hdrs"], app_name = "recon", beam_files = [":beam_files"], + extra_apps = ["syntax_tools"], + license_files = [":license_files"], + priv = [":priv"], ) alias( @@ -105,3 +92,10 @@ alias( actual = ":erlang_app", visibility = ["//visibility:public"], ) + +filegroup( + name = "license_files", + srcs = [ + "LICENSE", + ], +) diff --git a/deps/oauth2_client/app.bzl b/deps/oauth2_client/app.bzl index 6b4b31789a16..3ddba5d9a082 100644 --- a/deps/oauth2_client/app.bzl +++ b/deps/oauth2_client/app.bzl @@ -64,7 +64,7 @@ def all_srcs(name = "all_srcs"): ) filegroup( name = "public_hdrs", - srcs = ["include/oauth2_client.hrl"], + srcs = ["include/oauth2_client.hrl", "include/types.hrl"], ) filegroup( name = "license_files", @@ -88,7 +88,7 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/system_SUITE.erl"], outs = ["test/system_SUITE.beam"], - hdrs = ["include/oauth2_client.hrl"], + hdrs = ["include/oauth2_client.hrl", "include/types.hrl"], app_name = "oauth2_client", erlc_opts = "//:test_erlc_opts", ) @@ -97,7 +97,7 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/unit_SUITE.erl"], outs = ["test/unit_SUITE.beam"], - hdrs = ["include/oauth2_client.hrl"], + hdrs = ["include/oauth2_client.hrl", "include/types.hrl"], app_name = "oauth2_client", erlc_opts = "//:test_erlc_opts", ) diff --git a/deps/oauth2_client/include/oauth2_client.hrl b/deps/oauth2_client/include/oauth2_client.hrl index b7f93104f167..24534dc136f4 100644 --- a/deps/oauth2_client/include/oauth2_client.hrl +++ b/deps/oauth2_client/include/oauth2_client.hrl @@ -5,6 +5,7 @@ %% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. %% +-include("types.hrl"). % define access token request common constants @@ -44,66 +45,3 @@ -define(RESPONSE_END_SESSION_ENDPOINT, <<"end_session_endpoint">>). -define(RESPONSE_JWKS_URI, <<"jwks_uri">>). -define(RESPONSE_TLS_OPTIONS, <<"ssl_options">>). - -%% The closest we have to a type import in Erlang --type option(T) :: rabbit_types:option(T). - --type oauth_provider_id() :: root | binary(). - --record(openid_configuration, { - issuer :: option(uri_string:uri_string()), - token_endpoint :: option(uri_string:uri_string()), - authorization_endpoint :: option(uri_string:uri_string()), - end_session_endpoint :: option(uri_string:uri_string()), - jwks_uri :: option(uri_string:uri_string()) - }). --type openid_configuration() :: #openid_configuration{}. - --record(oauth_provider, { - id :: oauth_provider_id(), - issuer :: option(uri_string:uri_string()), - token_endpoint :: option(uri_string:uri_string()), - authorization_endpoint :: option(uri_string:uri_string()), - end_session_endpoint :: option(uri_string:uri_string()), - jwks_uri :: option(uri_string:uri_string()), - ssl_options :: option(list()) - }). - --type oauth_provider() :: #oauth_provider{}. - --record(access_token_request, { - client_id :: string() | binary(), - client_secret :: string() | binary(), - scope :: string() | binary() | undefined, - timeout :: option(integer()) - }). - --type access_token_request() :: #access_token_request{}. - --record(successful_access_token_response, { - access_token :: binary(), - token_type :: binary(), - refresh_token :: option(binary()), % A refresh token SHOULD NOT be included - % .. for client-credentials flow. - % https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3 - expires_in :: option(integer()) -}). - --type successful_access_token_response() :: #successful_access_token_response{}. - --record(unsuccessful_access_token_response, { - error :: integer(), - error_description :: binary() | string() | undefined -}). - --type unsuccessful_access_token_response() :: #unsuccessful_access_token_response{}. - --record(refresh_token_request, { - client_id :: string() | binary(), - client_secret :: string() | binary(), - scope :: string() | binary() | undefined, - refresh_token :: binary(), - timeout :: option(integer()) - }). - --type refresh_token_request() :: #refresh_token_request{}. diff --git a/deps/oauth2_client/include/types.hrl b/deps/oauth2_client/include/types.hrl new file mode 100644 index 000000000000..622cae22202c --- /dev/null +++ b/deps/oauth2_client/include/types.hrl @@ -0,0 +1,75 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. +%% + +%% Matches the option type in rabbit_types without introducing a dependency +%% on that module and RabbitMQ core (rabbit_common) +-type(option(T) :: T | 'none' | 'undefined'). + +-type oauth_provider_id() :: root | binary(). + +-record(openid_configuration, { + issuer :: option(uri_string:uri_string()), + token_endpoint :: option(uri_string:uri_string()), + authorization_endpoint :: option(uri_string:uri_string()), + end_session_endpoint :: option(uri_string:uri_string()), + jwks_uri :: option(uri_string:uri_string()) +}). +-type openid_configuration() :: #openid_configuration{}. + +-record(oauth_provider, { + id :: oauth_provider_id(), + issuer :: option(uri_string:uri_string()), + discovery_endpoint :: option(uri_string:uri_string()), + token_endpoint :: option(uri_string:uri_string()), + authorization_endpoint :: option(uri_string:uri_string()), + end_session_endpoint :: option(uri_string:uri_string()), + jwks_uri :: option(uri_string:uri_string()), + ssl_options :: option(list()) +}). + +-type query_list() :: [{unicode:chardata(), unicode:chardata() | true}]. + +-type oauth_provider() :: #oauth_provider{}. + +-record(access_token_request, { + client_id :: string() | binary(), + client_secret :: string() | binary(), + scope :: option(string() | binary()), + extra_parameters :: option(query_list()), + timeout :: option(integer()) +}). + +-type access_token_request() :: #access_token_request{}. + +-record(successful_access_token_response, { + access_token :: binary(), + token_type :: binary(), + %% Note: a refresh token SHOULD NOT be included + %% ... for client-credentials flow. + %% See https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3 + refresh_token :: option(binary()), + expires_in :: option(integer()) +}). + +-type successful_access_token_response() :: #successful_access_token_response{}. + +-record(unsuccessful_access_token_response, { + error :: integer(), + error_description :: binary() | string() | undefined +}). + +-type unsuccessful_access_token_response() :: #unsuccessful_access_token_response{}. + +-record(refresh_token_request, { + client_id :: string() | binary(), + client_secret :: string() | binary(), + scope :: string() | binary() | undefined, + refresh_token :: binary(), + timeout :: option(integer()) +}). + +-type refresh_token_request() :: #refresh_token_request{}. diff --git a/deps/oauth2_client/src/oauth2_client.erl b/deps/oauth2_client/src/oauth2_client.erl index 335bcfdfba1b..a6a75f0bbefa 100644 --- a/deps/oauth2_client/src/oauth2_client.erl +++ b/deps/oauth2_client/src/oauth2_client.erl @@ -8,15 +8,19 @@ -export([get_access_token/2, get_expiration_time/1, refresh_access_token/2, get_oauth_provider/1, get_oauth_provider/2, - get_openid_configuration/2, get_openid_configuration/3, + get_openid_configuration/2, + build_openid_discovery_endpoint/3, merge_openid_configuration/2, merge_oauth_provider/2, - extract_ssl_options_as_list/1 + extract_ssl_options_as_list/1, + format_ssl_options/1, format_oauth_provider/1, format_oauth_provider_id/1 ]). -include("oauth2_client.hrl"). + -spec get_access_token(oauth_provider(), access_token_request()) -> - {ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}. + {ok, successful_access_token_response()} | + {error, unsuccessful_access_token_response() | any()}. get_access_token(OAuthProvider, Request) -> rabbit_log:debug("get_access_token using OAuthProvider:~p and client_id:~p", [OAuthProvider, Request#access_token_request.client_id]), @@ -31,7 +35,8 @@ get_access_token(OAuthProvider, Request) -> parse_access_token_response(Response). -spec refresh_access_token(oauth_provider(), refresh_token_request()) -> - {ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}. + {ok, successful_access_token_response()} | + {error, unsuccessful_access_token_response() | any()}. refresh_access_token(OAuthProvider, Request) -> URL = OAuthProvider#oauth_provider.token_endpoint, Header = [], @@ -46,52 +51,76 @@ refresh_access_token(OAuthProvider, Request) -> append_paths(Path1, Path2) -> erlang:iolist_to_binary([Path1, Path2]). --spec get_openid_configuration(uri_string:uri_string(), erlang:iodata() | <<>>, +-spec build_openid_discovery_endpoint(Issuer :: uri_string:uri_string(), + OpenIdConfigurationPath :: uri_string:uri_string() | undefined, + Params :: query_list()) -> uri_string:uri_string() | undefined. + +build_openid_discovery_endpoint(undefined, _, _) -> undefined; +build_openid_discovery_endpoint(Issuer, undefined, Params) -> + build_openid_discovery_endpoint(Issuer, ?DEFAULT_OPENID_CONFIGURATION_PATH, + Params); +build_openid_discovery_endpoint(Issuer, OpenIdConfigurationPath, Params) -> + URLMap0 = uri_string:parse(Issuer), + OpenIdPath = ensure_leading_path_separator(OpenIdConfigurationPath), + URLMap1 = URLMap0#{ + path := case maps:get(path, URLMap0) of + [] -> OpenIdPath; + P -> append_paths(drop_trailing_path_separator(P), OpenIdPath) + end + }, + uri_string:recompose( + case {Params, maps:get(query, URLMap1, undefined)} of + {undefined, undefined} -> + URLMap1; + {_, undefined} -> + URLMap1#{query => uri_string:compose_query(Params)}; + {_, Q} -> + URLMap1#{query => uri_string:compose_query(Q ++ Params)} + end). +ensure_leading_path_separator(Path) when is_binary(Path) -> + ensure_leading_path_separator(binary:bin_to_list(Path)); +ensure_leading_path_separator(Path) when is_list(Path) -> + case string:slice(Path, 0, 1) of + "/" -> Path; + _ -> "/" ++ Path + end. +drop_trailing_path_separator(Path) when is_binary(Path) -> + drop_trailing_path_separator(binary:bin_to_list(Path)); +drop_trailing_path_separator("") -> ""; +drop_trailing_path_separator(Path) when is_list(Path) -> + case string:slice(Path, string:len(Path)-1, 1) of + "/" -> lists:droplast(Path); + _ -> Path + end. + +-spec get_openid_configuration(DiscoveryEndpoint :: uri_string:uri_string(), ssl:tls_option() | []) -> {ok, openid_configuration()} | {error, term()}. -get_openid_configuration(IssuerURI, OpenIdConfigurationPath, TLSOptions) -> - URLMap = uri_string:parse(IssuerURI), - Path = case maps:get(path, URLMap) of - "/" -> OpenIdConfigurationPath; - "" -> OpenIdConfigurationPath; - P -> append_paths(P, OpenIdConfigurationPath) - end, - URL = uri_string:resolve(Path, IssuerURI), - rabbit_log:debug("get_openid_configuration issuer URL ~p (~p)", [URL, +get_openid_configuration(DiscoverEndpoint, TLSOptions) -> + rabbit_log:debug("get_openid_configuration from ~p (~p)", [DiscoverEndpoint, format_ssl_options(TLSOptions)]), Options = [], - Response = httpc:request(get, {URL, []}, TLSOptions, Options), + Response = httpc:request(get, {DiscoverEndpoint, []}, TLSOptions, Options), parse_openid_configuration_response(Response). --spec get_openid_configuration(uri_string:uri_string(), ssl:tls_option() | []) -> - {ok, openid_configuration()} | {error, term()}. -get_openid_configuration(IssuerURI, TLSOptions) -> - get_openid_configuration(IssuerURI, ?DEFAULT_OPENID_CONFIGURATION_PATH, TLSOptions). -% Returns {ok, with_modidified_oauth_provider} or {ok} if oauth_provider was -% not modified -spec merge_openid_configuration(openid_configuration(), oauth_provider()) -> oauth_provider(). -merge_openid_configuration(OpendIdConfiguration, OAuthProvider) -> - OAuthProvider0 = case OpendIdConfiguration#openid_configuration.issuer of - undefined -> OAuthProvider; - Issuer -> - OAuthProvider#oauth_provider{issuer = Issuer} - end, - OAuthProvider1 = case OpendIdConfiguration#openid_configuration.token_endpoint of +merge_openid_configuration(OpenId, OAuthProvider0) -> + OAuthProvider1 = case OpenId#openid_configuration.token_endpoint of undefined -> OAuthProvider0; TokenEndpoint -> OAuthProvider0#oauth_provider{token_endpoint = TokenEndpoint} end, - OAuthProvider2 = case OpendIdConfiguration#openid_configuration.authorization_endpoint of + OAuthProvider2 = case OpenId#openid_configuration.authorization_endpoint of undefined -> OAuthProvider1; AuthorizationEndpoint -> OAuthProvider1#oauth_provider{authorization_endpoint = AuthorizationEndpoint} end, - OAuthProvider3 = case OpendIdConfiguration#openid_configuration.end_session_endpoint of + OAuthProvider3 = case OpenId#openid_configuration.end_session_endpoint of undefined -> OAuthProvider2; EndSessionEndpoint -> OAuthProvider2#oauth_provider{end_session_endpoint = EndSessionEndpoint} end, - case OpendIdConfiguration#openid_configuration.jwks_uri of + case OpenId#openid_configuration.jwks_uri of undefined -> OAuthProvider3; JwksUri -> OAuthProvider3#oauth_provider{jwks_uri = JwksUri} @@ -126,7 +155,8 @@ parse_openid_configuration_response({error, Reason}) -> parse_openid_configuration_response({ok,{{_,Code,Reason}, Headers, Body}}) -> map_response_to_openid_configuration(Code, Reason, Headers, Body). map_response_to_openid_configuration(Code, Reason, Headers, Body) -> - case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of + case decode_body(proplists:get_value("content-type", Headers, + ?CONTENT_JSON), Body) of {error, {error, InternalError}} -> {error, InternalError}; {error, _} = Error -> @@ -142,13 +172,16 @@ map_to_openid_configuration(Map) -> #openid_configuration{ issuer = maps:get(?RESPONSE_ISSUER, Map), token_endpoint = maps:get(?RESPONSE_TOKEN_ENDPOINT, Map, undefined), - authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, Map, undefined), - end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, Map, undefined), + authorization_endpoint = maps:get(?RESPONSE_AUTHORIZATION_ENDPOINT, + Map, undefined), + end_session_endpoint = maps:get(?RESPONSE_END_SESSION_ENDPOINT, + Map, undefined), jwks_uri = maps:get(?RESPONSE_JWKS_URI, Map, undefined) }. -spec get_expiration_time(successful_access_token_response()) -> - {ok, [{expires_in, integer() }| {exp, integer() }]} | {error, missing_exp_field}. + {ok, [{expires_in, integer() }| {exp, integer() }]} | + {error, missing_exp_field}. get_expiration_time(#successful_access_token_response{expires_in = ExpiresInSec, access_token = AccessToken}) -> case ExpiresInSec of @@ -168,53 +201,42 @@ update_oauth_provider_endpoints_configuration(OAuthProvider) -> unlock(LockId) end. -update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) -> - LockId = lock(), - try do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) of - V -> V - after - unlock(LockId) - end. - -do_update_oauth_provider_endpoints_configuration(OAuthProvider) -> +do_update_oauth_provider_endpoints_configuration(OAuthProvider) when + OAuthProvider#oauth_provider.id == root -> case OAuthProvider#oauth_provider.token_endpoint of - undefined -> - do_nothing; - TokenEndpoint -> - application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, TokenEndpoint) + undefined -> do_nothing; + TokenEndpoint -> set_env(token_endpoint, TokenEndpoint) end, case OAuthProvider#oauth_provider.authorization_endpoint of - undefined -> - do_nothing; - AuthzEndpoint -> - application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, AuthzEndpoint) + undefined -> do_nothing; + AuthzEndpoint -> set_env(authorization_endpoint, AuthzEndpoint) end, case OAuthProvider#oauth_provider.end_session_endpoint of - undefined -> - do_nothing; - EndSessionEndpoint -> - application:set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, EndSessionEndpoint) + undefined -> do_nothing; + EndSessionEndpoint -> set_env(end_session_endpoint, EndSessionEndpoint) end, - List = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), + List = get_env(key_config, []), ModifiedList = case OAuthProvider#oauth_provider.jwks_uri of undefined -> List; JwksEndPoint -> [{jwks_url, JwksEndPoint} | proplists:delete(jwks_url, List)] end, - application:set_env(rabbitmq_auth_backend_oauth2, key_config, ModifiedList), - rabbit_log:debug("Updated oauth_provider details: ~p ", [ format_oauth_provider(OAuthProvider)]), - OAuthProvider. + set_env(key_config, ModifiedList), + rabbit_log:debug("Updated oauth_provider details: ~p ", + [format_oauth_provider(OAuthProvider)]), + OAuthProvider; -do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), +do_update_oauth_provider_endpoints_configuration(OAuthProvider) -> + OAuthProviderId = OAuthProvider#oauth_provider.id, + OAuthProviders = get_env(oauth_providers, #{}), Proplist = maps:get(OAuthProviderId, OAuthProviders), ModifiedOAuthProviders = maps:put(OAuthProviderId, merge_oauth_provider(OAuthProvider, Proplist), OAuthProviders), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, ModifiedOAuthProviders), + set_env(oauth_providers, ModifiedOAuthProviders), rabbit_log:debug("Replaced oauth_providers "), OAuthProvider. use_global_locks_on_all_nodes() -> - case application:get_env(rabbitmq_auth_backend_oauth2, use_global_locks, true) of + case get_env(use_global_locks, true) of true -> {rabbit_nodes:list_running(), rabbit_nodes:lock_retries()}; _ -> {} end. @@ -227,7 +249,8 @@ lock() -> false -> undefined end; {Nodes, Retries} -> - case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, Nodes, Retries) of + case global:set_lock({oauth2_config_lock, rabbitmq_auth_backend_oauth2}, + Nodes, Retries) of true -> rabbitmq_auth_backend_oauth2; false -> undefined end @@ -238,62 +261,82 @@ unlock(LockId) -> undefined -> ok; Value -> case use_global_locks_on_all_nodes() of - {} -> global:del_lock({oauth2_config_lock, Value}); - {Nodes, _Retries} -> global:del_lock({oauth2_config_lock, Value}, Nodes) + {} -> + global:del_lock({oauth2_config_lock, Value}); + {Nodes, _Retries} -> + global:del_lock({oauth2_config_lock, Value}, Nodes) end end. -spec get_oauth_provider(list()) -> {ok, oauth_provider()} | {error, any()}. get_oauth_provider(ListOfRequiredAttributes) -> - case application:get_env(rabbitmq_auth_backend_oauth2, default_oauth_provider) of + case get_env(default_oauth_provider) of undefined -> get_oauth_provider_from_keyconfig(ListOfRequiredAttributes); - {ok, DefaultOauthProviderId} -> - rabbit_log:debug("Using default_oauth_provider ~p", [DefaultOauthProviderId]), + DefaultOauthProviderId -> + rabbit_log:debug("Using default_oauth_provider ~p", + [DefaultOauthProviderId]), get_oauth_provider(DefaultOauthProviderId, ListOfRequiredAttributes) end. +-spec download_oauth_provider(oauth_provider()) -> {ok, oauth_provider()} | + {error, any()}. +download_oauth_provider(OAuthProvider) -> + case OAuthProvider#oauth_provider.discovery_endpoint of + undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; + URL -> + rabbit_log:debug("Downloading oauth_provider using ~p ", [URL]), + case get_openid_configuration(URL, get_ssl_options_if_any(OAuthProvider)) of + {ok, OpenIdConfiguration} -> + {ok, update_oauth_provider_endpoints_configuration( + merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; + {error, _} = Error2 -> Error2 + end + end. + +ensure_oauth_provider_has_attributes(OAuthProvider, ListOfRequiredAttributes) -> + case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of + [] -> + rabbit_log:debug("Resolved oauth_provider ~p", + [format_oauth_provider(OAuthProvider)]), + {ok, OAuthProvider}; + _ = Attrs -> + {error, {missing_oauth_provider_attributes, Attrs}} + end. + get_oauth_provider_from_keyconfig(ListOfRequiredAttributes) -> OAuthProvider = lookup_oauth_provider_from_keyconfig(), - rabbit_log:debug("Using oauth_provider ~s from keyconfig", [format_oauth_provider(OAuthProvider)]), + rabbit_log:debug("Using oauth_provider ~p from keyconfig", + [format_oauth_provider(OAuthProvider)]), case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of [] -> {ok, OAuthProvider}; _ = MissingAttributes -> - rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), - Result2 = case OAuthProvider#oauth_provider.issuer of - undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; - Issuer -> - rabbit_log:debug("Downloading oauth_provider using issuer ~p", [Issuer]), - case get_openid_configuration(Issuer, get_ssl_options_if_any(OAuthProvider)) of - {ok, OpenIdConfiguration} -> - {ok, update_oauth_provider_endpoints_configuration( - merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; - {error, _} = Error2 -> Error2 - end - end, - case Result2 of - {ok, OAuthProvider2} -> - case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of - [] -> - rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]), - {ok, OAuthProvider2}; - _ = Attrs-> - {error, {missing_oauth_provider_attributes, Attrs}} - end; - {error, _} = Error3 -> Error3 + rabbit_log:debug("Looking up missing attributes ~p ...", + [MissingAttributes]), + case download_oauth_provider(OAuthProvider) of + {ok, OAuthProvider2} -> + ensure_oauth_provider_has_attributes(OAuthProvider2, + ListOfRequiredAttributes); + {error, _} = Error3 -> + Error3 end end. --spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | {error, any()}. +-spec get_oauth_provider(oauth_provider_id(), list()) -> {ok, oauth_provider()} | + {error, any()}. get_oauth_provider(root, ListOfRequiredAttributes) -> get_oauth_provider(ListOfRequiredAttributes); -get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) when is_list(OAuth2ProviderId) -> - get_oauth_provider(list_to_binary(OAuth2ProviderId), ListOfRequiredAttributes); +get_oauth_provider(OAuth2ProviderId, ListOfRequiredAttributes) + when is_list(OAuth2ProviderId) -> + get_oauth_provider(list_to_binary(OAuth2ProviderId), + ListOfRequiredAttributes); -get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAuthProviderId) -> - rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", [OAuthProviderId, ListOfRequiredAttributes]), +get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) + when is_binary(OAuthProviderId) -> + rabbit_log:debug("get_oauth_provider ~p with at least these attributes: ~p", + [OAuthProviderId, ListOfRequiredAttributes]), case lookup_oauth_provider_config(OAuthProviderId) of {error, _} = Error0 -> rabbit_log:debug("Failed to find oauth_provider ~p configuration due to ~p", @@ -308,28 +351,12 @@ get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes) when is_binary(OAu {ok, OAuthProvider}; _ = MissingAttributes -> rabbit_log:debug("OauthProvider has following missing attributes ~p", [MissingAttributes]), - Result2 = case OAuthProvider#oauth_provider.issuer of - undefined -> {error, {missing_oauth_provider_attributes, [issuer]}}; - Issuer -> - rabbit_log:debug("Downloading oauth_provider ~p using issuer ~p", - [OAuthProviderId, Issuer]), - case get_openid_configuration(Issuer, get_ssl_options_if_any(OAuthProvider)) of - {ok, OpenIdConfiguration} -> - {ok, update_oauth_provider_endpoints_configuration(OAuthProviderId, - merge_openid_configuration(OpenIdConfiguration, OAuthProvider))}; - {error, _} = Error2 -> Error2 - end - end, - case Result2 of + case download_oauth_provider(OAuthProvider) of {ok, OAuthProvider2} -> - case find_missing_attributes(OAuthProvider2, ListOfRequiredAttributes) of - [] -> - rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]), - {ok, OAuthProvider2}; - _ = Attrs-> - {error, {missing_oauth_provider_attributes, Attrs}} - end; - {error, _} = Error3 -> Error3 + ensure_oauth_provider_has_attributes(OAuthProvider2, + ListOfRequiredAttributes); + {error, _} = Error3 -> + Error3 end end end. @@ -358,18 +385,18 @@ find_missing_attributes(#oauth_provider{} = OAuthProvider, RequiredAttributes) - intersection(Filtered, RequiredAttributes). lookup_oauth_provider_from_keyconfig() -> - Issuer = application:get_env(rabbitmq_auth_backend_oauth2, issuer, undefined), - TokenEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, token_endpoint, undefined), - AuthorizationEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, undefined), - EndSessionEndpoint = application:get_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, undefined), - Map = maps:from_list(application:get_env(rabbitmq_auth_backend_oauth2, key_config, [])), + Map = maps:from_list(get_env(key_config, [])), + Issuer = get_env(issuer), + DiscoverEndpoint = build_openid_discovery_endpoint(Issuer, + get_env(discovery_endpoint_path), get_env(discovery_endpoint_params)), #oauth_provider{ id = root, issuer = Issuer, + discovery_endpoint = DiscoverEndpoint, jwks_uri = maps:get(jwks_url, Map, undefined), %% jwks_url not uri . _url is the legacy name - token_endpoint = TokenEndpoint, - authorization_endpoint = AuthorizationEndpoint, - end_session_endpoint = EndSessionEndpoint, + token_endpoint = get_env(token_endpoint), + authorization_endpoint = get_env(authorization_endpoint), + end_session_endpoint = get_env(end_session_endpoint), ssl_options = extract_ssl_options_as_list(Map) }. @@ -430,9 +457,9 @@ get_verify_or_peer_verification(Ssl_options, Default) -> end. lookup_oauth_provider_config(OAuth2ProviderId) -> - case application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers) of + case get_env(oauth_providers) of undefined -> {error, oauth_providers_not_found}; - {ok, MapOfProviders} when is_map(MapOfProviders) -> + MapOfProviders when is_map(MapOfProviders) -> case maps:get(OAuth2ProviderId, MapOfProviders, undefined) of undefined -> {error, {oauth_provider_not_found, OAuth2ProviderId}}; @@ -448,33 +475,50 @@ ensure_oauth_provider_has_id_property(OAuth2ProviderId, OAuth2Provider) -> end. build_access_token_request_body(Request) -> - uri_string:compose_query([ - grant_type_request_parameter(?CLIENT_CREDENTIALS_GRANT_TYPE), - client_id_request_parameter(Request#access_token_request.client_id), - client_secret_request_parameter(Request#access_token_request.client_secret)] - ++ scope_request_parameter_or_default(Request#access_token_request.scope, [])). + uri_string:compose_query( + append_extra_parameters(Request, + append_scope_request_parameter(Request#access_token_request.scope, [ + grant_type_request_parameter(?CLIENT_CREDENTIALS_GRANT_TYPE), + client_id_request_parameter( + Request#access_token_request.client_id), + client_secret_request_parameter( + Request#access_token_request.client_secret)]))). build_refresh_token_request_body(Request) -> - uri_string:compose_query([ - grant_type_request_parameter(?REFRESH_TOKEN_GRANT_TYPE), - refresh_token_request_parameter(Request#refresh_token_request.refresh_token), - client_id_request_parameter(Request#refresh_token_request.client_id), - client_secret_request_parameter(Request#refresh_token_request.client_secret)] - ++ scope_request_parameter_or_default(Request#refresh_token_request.scope, [])). + uri_string:compose_query( + append_scope_request_parameter(Request#refresh_token_request.scope, [ + grant_type_request_parameter(?REFRESH_TOKEN_GRANT_TYPE), + refresh_token_request_parameter(Request), + client_id_request_parameter(Request#refresh_token_request.client_id), + client_secret_request_parameter( + Request#refresh_token_request.client_secret)])). grant_type_request_parameter(Type) -> {?REQUEST_GRANT_TYPE, Type}. -client_id_request_parameter(Client_id) -> - {?REQUEST_CLIENT_ID, binary_to_list(Client_id)}. -client_secret_request_parameter(Client_secret) -> - {?REQUEST_CLIENT_SECRET, binary_to_list(Client_secret)}. -refresh_token_request_parameter(RefreshToken) -> - {?REQUEST_REFRESH_TOKEN, RefreshToken}. -scope_request_parameter_or_default(Scope, Default) -> + +client_id_request_parameter(ClientId) -> + {?REQUEST_CLIENT_ID, + binary_to_list(ClientId)}. + +client_secret_request_parameter(ClientSecret) -> + {?REQUEST_CLIENT_SECRET, + binary_to_list(ClientSecret)}. + +refresh_token_request_parameter(Request) -> + {?REQUEST_REFRESH_TOKEN, Request#refresh_token_request.refresh_token}. + +append_scope_request_parameter(Scope, QueryList) -> case Scope of - undefined -> Default; - <<>> -> Default; - Scope -> [{?REQUEST_SCOPE, Scope}] + undefined -> QueryList; + <<>> -> QueryList; + Scope -> [{?REQUEST_SCOPE, Scope} | QueryList] + end. + +append_extra_parameters(Request, QueryList) -> + case Request#access_token_request.extra_parameters of + undefined -> QueryList; + [] -> QueryList; + Params -> Params ++ QueryList end. get_ssl_options_if_any(OAuthProvider) -> @@ -521,14 +565,28 @@ map_to_unsuccessful_access_token_response(Map) -> error_description = maps:get(?RESPONSE_ERROR_DESCRIPTION, Map, undefined) }. map_to_oauth_provider(PropList) when is_list(PropList) -> + Issuer = proplists:get_value(issuer, PropList), + DiscoveryEndpoint = build_openid_discovery_endpoint(Issuer, + proplists:get_value(discovery_endpoint_path, PropList), + proplists:get_value(discovery_endpoint_params, PropList)), #oauth_provider{ - id = proplists:get_value(id, PropList), - issuer = proplists:get_value(issuer, PropList), - token_endpoint = proplists:get_value(token_endpoint, PropList), - authorization_endpoint = proplists:get_value(authorization_endpoint, PropList, undefined), - end_session_endpoint = proplists:get_value(end_session_endpoint, PropList, undefined), - jwks_uri = proplists:get_value(jwks_uri, PropList, undefined), - ssl_options = extract_ssl_options_as_list(maps:from_list(proplists:get_value(https, PropList, []))) + id = + proplists:get_value(id, PropList), + issuer = + Issuer, + discovery_endpoint = + DiscoveryEndpoint, + token_endpoint = + proplists:get_value(token_endpoint, PropList), + authorization_endpoint = + proplists:get_value(authorization_endpoint, PropList, undefined), + end_session_endpoint = + proplists:get_value(end_session_endpoint, PropList, undefined), + jwks_uri = + proplists:get_value(jwks_uri, PropList, undefined), + ssl_options = + extract_ssl_options_as_list(maps:from_list( + proplists:get_value(https, PropList, []))) }. map_to_access_token_response(Code, Reason, Headers, Body) -> case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of @@ -557,27 +615,35 @@ format_ssl_options(TlsOptions) -> [] -> 0; Certs -> length(Certs) end, - io_lib:format("{verify: ~p, fail_if_no_peer_cert: ~p, crl_check: ~p, " ++ - "depth: ~p, cacertfile: ~p, cacerts(count): ~p }", [ + lists:flatten(io_lib:format("{verify: ~p, fail_if_no_peer_cert: ~p, crl_check: ~p, depth: ~p, cacertfile: ~p, cacerts(count): ~p }", [ proplists:get_value(verify, TlsOptions), proplists:get_value(fail_if_no_peer_cert, TlsOptions), proplists:get_value(crl_check, TlsOptions), proplists:get_value(depth, TlsOptions), proplists:get_value(cacertfile, TlsOptions), - CaCertsCount]). + CaCertsCount])). format_oauth_provider_id(root) -> ""; format_oauth_provider_id(Id) -> binary_to_list(Id). -spec format_oauth_provider(oauth_provider()) -> string(). format_oauth_provider(OAuthProvider) -> - io_lib:format("{id: ~p, issuer: ~p, token_endpoint: ~p, " ++ + lists:flatten(io_lib:format("{id: ~p, issuer: ~p, discovery_endpoint: ~p, " ++ + " token_endpoint: ~p, " ++ "authorization_endpoint: ~p, end_session_endpoint: ~p, " ++ - "jwks_uri: ~p, ssl_options: ~s }", [ + "jwks_uri: ~p, ssl_options: ~p }", [ format_oauth_provider_id(OAuthProvider#oauth_provider.id), OAuthProvider#oauth_provider.issuer, + OAuthProvider#oauth_provider.discovery_endpoint, OAuthProvider#oauth_provider.token_endpoint, OAuthProvider#oauth_provider.authorization_endpoint, OAuthProvider#oauth_provider.end_session_endpoint, OAuthProvider#oauth_provider.jwks_uri, - format_ssl_options(OAuthProvider#oauth_provider.ssl_options)]). + format_ssl_options(OAuthProvider#oauth_provider.ssl_options)])). + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). diff --git a/deps/oauth2_client/test/system_SUITE.erl b/deps/oauth2_client/test/system_SUITE.erl index a0be9dd3976d..97ae8a4a5e5a 100644 --- a/deps/oauth2_client/test/system_SUITE.erl +++ b/deps/oauth2_client/test/system_SUITE.erl @@ -11,6 +11,8 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("oauth2_client.hrl"). +-import(oauth2_client, [ + build_openid_discovery_endpoint/3]). -compile(export_all). @@ -31,6 +33,7 @@ all() -> groups() -> [ + {with_all_oauth_provider_settings, [], [ {group, verify_get_oauth_provider} ]}, @@ -147,7 +150,6 @@ init_per_group(_, Config) -> get_http_oauth_server_expectations(TestCase, Config) -> case ?config(TestCase, Config) of undefined -> - ct:log("get_openid_configuration_http_expectation : ~p", [get_openid_configuration_http_expectation(TestCase)]), [ {token_endpoint, build_http_mock_behaviour(build_http_access_token_request(), build_http_200_access_token_response())}, {get_openid_configuration, get_openid_configuration_http_expectation(TestCase)} @@ -244,7 +246,6 @@ init_per_testcase(TestCase, Config) -> case ?config(group, Config) of https -> - ct:log("Start https with expectations ~p", [ListOfExpectations]), start_https_oauth_server(?AUTH_PORT, ?config(rmq_certsdir, Config), ListOfExpectations); _ -> @@ -277,11 +278,17 @@ end_per_group(with_default_oauth_provider, Config) -> end_per_group(_, Config) -> Config. +build_openid_discovery_endpoint(Issuer) -> + build_openid_discovery_endpoint(Issuer, undefined, undefined). + +build_openid_discovery_endpoint(Issuer, Path) -> + build_openid_discovery_endpoint(Issuer, Path, undefined). + get_openid_configuration(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, ActualOpenId} = oauth2_client:get_openid_configuration( - build_issuer("https"), + build_openid_discovery_endpoint(build_issuer("https")), SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, ActualOpenId). @@ -303,7 +310,7 @@ get_openid_configuration_returns_partial_payload(Config) -> SslOptions = [{ssl, ExpectedOAuthProvider0#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( - build_issuer("https"), + build_openid_discovery_endpoint(build_issuer("https")), SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). @@ -312,7 +319,7 @@ get_openid_configuration_using_path(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( - build_issuer("https", ?ISSUER_PATH), + build_openid_discovery_endpoint(build_issuer("https", ?ISSUER_PATH)), SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId,Actual). @@ -320,18 +327,16 @@ get_openid_configuration_using_path_and_custom_endpoint(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( - build_issuer("https", ?ISSUER_PATH), - ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, - SslOptions), + build_openid_discovery_endpoint(build_issuer("https", ?ISSUER_PATH), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT), SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). get_openid_configuration_using_custom_endpoint(Config) -> ExpectedOAuthProvider = ?config(oauth_provider, Config), SslOptions = [{ssl, ExpectedOAuthProvider#oauth_provider.ssl_options}], {ok, Actual} = oauth2_client:get_openid_configuration( - build_issuer("https"), - ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT, - SslOptions), + build_openid_discovery_endpoint(build_issuer("https"), + ?CUSTOM_OPENID_CONFIGURATION_ENDPOINT), SslOptions), ExpectedOpenId = map_oauth_provider_to_openid_configuration(ExpectedOAuthProvider), assertOpenIdConfiguration(ExpectedOpenId, Actual). @@ -396,6 +401,23 @@ grants_access_token(Config) -> ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). +grants_access_token_optional_parameters(Config) -> + #{request := #{parameters := Parameters}, + response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } + = lookup_expectation(token_endpoint, Config), + + AccessTokenRequest0 = build_access_token_request(Parameters), + AccessTokenRequest = AccessTokenRequest0#access_token_request{ + scope = "some-scope", + extra_parameters = [{"param1", "value1"}] + }, + {ok, #successful_access_token_response{access_token = AccessToken, + token_type = TokenType} } = + oauth2_client:get_access_token(?config(oauth_provider, Config), + AccessTokenRequest), + ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), + ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). + grants_refresh_token(Config) -> #{request := #{parameters := Parameters}, response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } @@ -465,7 +487,6 @@ verify_get_oauth_provider_returns_default_oauth_provider(DefaultOAuthProviderId) {ok, OAuthProvider2} = oauth2_client:get_oauth_provider(DefaultOAuthProviderId, [issuer, token_endpoint, jwks_uri]), - ct:log("verify_get_oauth_provider_returns_default_oauth_provider ~p vs ~p", [OAuthProvider1, OAuthProvider2]), ?assertEqual(OAuthProvider1, OAuthProvider2). get_oauth_provider(Config) -> @@ -546,6 +567,7 @@ get_oauth_provider_given_oauth_provider_id(Config) -> %%% HELPERS + build_issuer(Scheme) -> build_issuer(Scheme, ""). build_issuer(Scheme, Path) -> @@ -618,8 +640,6 @@ start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations {'_', [{Path, oauth_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} ]), - ct:log("start_https_oauth_server with expectation list : ~p -> dispatch: ~p", - [Expectations, Dispatch]), {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, @@ -630,8 +650,6 @@ start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations start_https_oauth_server(Port, CertsDir, #{request := #{path := Path}} = Expected) -> Dispatch = cowboy_router:compile([{'_', [{Path, oauth_http_mock, Expected}]}]), - ct:log("start_https_oauth_server with expectation : ~p -> dispatch: ~p", - [Expected, Dispatch]), {ok, _} = cowboy:start_tls( mock_http_auth_listener, [{port, Port}, diff --git a/deps/oauth2_client/test/unit_SUITE.erl b/deps/oauth2_client/test/unit_SUITE.erl index ab632ceedc68..e4216036db20 100644 --- a/deps/oauth2_client/test/unit_SUITE.erl +++ b/deps/oauth2_client/test/unit_SUITE.erl @@ -15,13 +15,16 @@ -compile(export_all). +-import(oauth2_client, [build_openid_discovery_endpoint/3]). + -define(UTIL_MOD, oauth2_client_test_util). all() -> [ - {group, ssl_options}, - {group, merge}, - {group, get_expiration_time} + build_openid_discovery_endpoint, + {group, ssl_options}, + {group, merge}, + {group, get_expiration_time} ]. groups() -> @@ -45,8 +48,38 @@ groups() -> ]} ]. +build_openid_discovery_endpoint(_) -> + Issuer = "https://issuer", + ?assertEqual(Issuer ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(Issuer, undefined, undefined)), + + IssuerWithPath = "https://issuer/v2", + ?assertEqual(IssuerWithPath ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(IssuerWithPath, undefined, undefined)), + + IssuerWithPathAndExtraPathSeparator = "https://issuer/v2/", + ?assertEqual("https://issuer/v2" ++ ?DEFAULT_OPENID_CONFIGURATION_PATH, + build_openid_discovery_endpoint(IssuerWithPathAndExtraPathSeparator, + undefined, undefined)), + + IssuerWithPath = "https://issuer/v2", + CustomPath = "/.well-known/other", + ?assertEqual(IssuerWithPath ++ CustomPath, + build_openid_discovery_endpoint(IssuerWithPath, CustomPath, undefined)), + + IssuerWithPath = "https://issuer/v2", + CustomPath = "/.well-known/other", + WithParams = [{"param1", "v1"}, {"param2", "v2"}], + ?assertEqual("https://issuer/v2/.well-known/other?param1=v1¶m2=v2", + build_openid_discovery_endpoint(IssuerWithPath, CustomPath, WithParams)). + + merge_oauth_provider(_) -> - OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, + OAuthProvider = #oauth_provider{ + id = "some_id", + issuer = "https://issuer", + discovery_endpoint = "https://issuer/.well-known/openid_configuration", + ssl_options = [ {verify, verify_none} ]}, Proplist = [], Proplist1 = oauth2_client:merge_oauth_provider(OAuthProvider, Proplist), ?assertEqual([], Proplist), @@ -74,11 +107,25 @@ merge_oauth_provider(_) -> {end_session_endpoint, OAuthProvider4#oauth_provider.end_session_endpoint}, {authorization_endpoint, OAuthProvider4#oauth_provider.authorization_endpoint}, {token_endpoint, OAuthProvider4#oauth_provider.token_endpoint}], - Proplist5). + Proplist5), + + % ensure id, issuer, ssl_options and discovery_endpoint are not affected + ?assertEqual(OAuthProvider#oauth_provider.id, + OAuthProvider4#oauth_provider.id), + ?assertEqual(OAuthProvider#oauth_provider.issuer, + OAuthProvider4#oauth_provider.issuer), + ?assertEqual(OAuthProvider#oauth_provider.discovery_endpoint, + OAuthProvider4#oauth_provider.discovery_endpoint), + ?assertEqual(OAuthProvider#oauth_provider.ssl_options, + OAuthProvider4#oauth_provider.ssl_options). merge_openid_configuration(_) -> OpenIdConfiguration = #openid_configuration{}, - OAuthProvider = #oauth_provider{id = "some_id", ssl_options = [ {verify, verify_none} ]}, + OAuthProvider = #oauth_provider{ + id = "some_id", + issuer = "https://issuer", + discovery_endpoint = "https://issuer/.well-known/openid_configuration", + ssl_options = [ {verify, verify_none} ]}, OAuthProvider1 = oauth2_client:merge_openid_configuration( OpenIdConfiguration, OAuthProvider), ?assertEqual(OAuthProvider#oauth_provider.id, OAuthProvider1#oauth_provider.id), @@ -125,7 +172,17 @@ merge_openid_configuration(_) -> ?assertEqual(OpenIdConfiguration2#openid_configuration.end_session_endpoint, OAuthProvider5#oauth_provider.end_session_endpoint), ?assertEqual(OpenIdConfiguration1#openid_configuration.jwks_uri, - OAuthProvider5#oauth_provider.jwks_uri). + OAuthProvider5#oauth_provider.jwks_uri), + + % ensure id, issuer, ssl_options and discovery_endpoint are not affected + ?assertEqual(OAuthProvider#oauth_provider.id, + OAuthProvider5#oauth_provider.id), + ?assertEqual(OAuthProvider#oauth_provider.issuer, + OAuthProvider5#oauth_provider.issuer), + ?assertEqual(OAuthProvider#oauth_provider.discovery_endpoint, + OAuthProvider5#oauth_provider.discovery_endpoint), + ?assertEqual(OAuthProvider#oauth_provider.ssl_options, + OAuthProvider5#oauth_provider.ssl_options). no_ssl_options_triggers_verify_peer(_) -> diff --git a/deps/rabbit/BUILD.bazel b/deps/rabbit/BUILD.bazel index d9910dc90e14..8ce54e6f584b 100644 --- a/deps/rabbit/BUILD.bazel +++ b/deps/rabbit/BUILD.bazel @@ -1257,10 +1257,10 @@ rabbitmq_integration_suite( rabbitmq_integration_suite( name = "amqp_address_SUITE", - shard_count = 2, additional_beam = [ ":test_amqp_utils_beam", ], + shard_count = 2, runtime_deps = [ "//deps/rabbitmq_amqp_client:erlang_app", ], diff --git a/deps/rabbit/src/rabbit_msg_store.erl b/deps/rabbit/src/rabbit_msg_store.erl index b28506ab2ab8..efd8d53a0507 100644 --- a/deps/rabbit/src/rabbit_msg_store.erl +++ b/deps/rabbit/src/rabbit_msg_store.erl @@ -16,7 +16,7 @@ -export([compact_file/2, truncate_file/4, delete_file/2]). %% internal --export([scan_file_for_valid_messages/1]). %% salvage tool +-export([scan_file_for_valid_messages/1, scan_file_for_valid_messages/2]). %% salvage tool -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, prioritise_call/4, prioritise_cast/3, @@ -1472,31 +1472,28 @@ list_sorted_filenames(Dir, Ext) -> -define(SCAN_BLOCK_SIZE, 4194304). %% 4MB -scan_file_for_valid_messages(Dir, FileName) -> - scan_file_for_valid_messages(form_filename(Dir, FileName)). - +%% Exported as a salvage tool. Not as accurate as node recovery +%% because it doesn't have the queue index. scan_file_for_valid_messages(Path) -> + scan_file_for_valid_messages(Path, fun(Obj) -> {valid, Obj} end). + +scan_file_for_valid_messages(Path, Fun) -> case file:open(Path, [read, binary, raw]) of {ok, Fd} -> {ok, FileSize} = file:position(Fd, eof), {ok, _} = file:position(Fd, bof), - Messages = scan(<<>>, Fd, 0, FileSize, #{}, []), + Messages = scan(<<>>, Fd, Fun, 0, FileSize, #{}, []), ok = file:close(Fd), - case Messages of - [] -> - {ok, [], 0}; - [{_, TotalSize, Offset}|_] -> - {ok, Messages, Offset + TotalSize} - end; + {ok, Messages}; {error, enoent} -> - {ok, [], 0}; + {ok, []}; {error, Reason} -> {error, {unable_to_scan_file, filename:basename(Path), Reason}} end. -scan(Buffer, Fd, Offset, FileSize, MsgIdsFound, Acc) -> +scan(Buffer, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) -> case file:read(Fd, ?SCAN_BLOCK_SIZE) of eof -> Acc; @@ -1505,12 +1502,12 @@ scan(Buffer, Fd, Offset, FileSize, MsgIdsFound, Acc) -> <<>> -> Data0; _ -> <> end, - scan_data(Data, Fd, Offset, FileSize, MsgIdsFound, Acc) + scan_data(Data, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) end. %% Message might have been found. scan_data(<> = Data, - Fd, Offset, FileSize, MsgIdsFound, Acc) + Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) when Size >= 16 -> <> = MsgIdAndMsg, case MsgIdsFound of @@ -1519,26 +1516,37 @@ scan_data(<> = Data, %% simply be a coincidence. Try the next byte. #{MsgIdInt := true} -> <<_, Rest2/bits>> = Data, - scan_data(Rest2, Fd, Offset + 1, FileSize, MsgIdsFound, Acc); + scan_data(Rest2, Fd, Fun, Offset + 1, FileSize, MsgIdsFound, Acc); %% Data looks to be a message. _ -> %% Avoid sub-binary construction. MsgId = <>, TotalSize = Size + 9, - scan_data(Rest, Fd, Offset + TotalSize, FileSize, - MsgIdsFound#{MsgIdInt => true}, - [{MsgId, TotalSize, Offset}|Acc]) + case Fun({MsgId, TotalSize, Offset}) of + %% Confirmed to be a message by the provided fun. + {valid, Entry} -> + scan_data(Rest, Fd, Fun, Offset + TotalSize, FileSize, + MsgIdsFound#{MsgIdInt => true}, [Entry|Acc]); + %% Confirmed to be a message but we don't need it anymore. + previously_valid -> + scan_data(Rest, Fd, Fun, Offset + TotalSize, FileSize, + MsgIdsFound#{MsgIdInt => true}, Acc); + %% Not a message, try the next byte. + invalid -> + <<_, Rest2/bits>> = Data, + scan_data(Rest2, Fd, Fun, Offset + 1, FileSize, MsgIdsFound, Acc) + end end; %% This might be the start of a message. -scan_data(<> = Data, Fd, Offset, FileSize, MsgIdsFound, Acc) +scan_data(<> = Data, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) when byte_size(Rest) < Size + 1, Size < FileSize - Offset -> - scan(Data, Fd, Offset, FileSize, MsgIdsFound, Acc); -scan_data(Data, Fd, Offset, FileSize, MsgIdsFound, Acc) + scan(Data, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc); +scan_data(Data, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) when byte_size(Data) < 8 -> - scan(Data, Fd, Offset, FileSize, MsgIdsFound, Acc); + scan(Data, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc); %% This is definitely not a message. Try the next byte. -scan_data(<<_, Rest/bits>>, Fd, Offset, FileSize, MsgIdsFound, Acc) -> - scan_data(Rest, Fd, Offset + 1, FileSize, MsgIdsFound, Acc). +scan_data(<<_, Rest/bits>>, Fd, Fun, Offset, FileSize, MsgIdsFound, Acc) -> + scan_data(Rest, Fd, Fun, Offset + 1, FileSize, MsgIdsFound, Acc). %%---------------------------------------------------------------------------- %% Ets index @@ -1742,47 +1750,39 @@ build_index(false, {MsgRefDeltaGen, MsgRefDeltaGenInit}, build_index_worker(Gatherer, #msstate { index_ets = IndexEts, dir = Dir }, File, Files) -> - FileName = filenum_to_name(File), + Path = form_filename(Dir, filenum_to_name(File)), rabbit_log:debug("Rebuilding message location index from ~ts (~B file(s) remaining)", - [form_filename(Dir, FileName), length(Files)]), + [Path, length(Files)]), %% The scan function already dealt with duplicate messages - %% within the file. We then get messages in reverse order. - {ok, Messages, FileSize} = - scan_file_for_valid_messages(Dir, FileName), - %% Valid messages are in file order so the last message is - %% the last message from the list. - {ValidMessages, ValidTotalSize} = - lists:foldl( - fun (Obj = {MsgId, TotalSize, Offset}, {VMAcc, VTSAcc}) -> - %% Fan-out may result in the same message data in multiple - %% files so we have to guard against it. - case index_lookup(IndexEts, MsgId) of - #msg_location { file = undefined } = StoreEntry -> - ok = index_update(IndexEts, StoreEntry #msg_location { - file = File, offset = Offset, - total_size = TotalSize }), - {[Obj | VMAcc], VTSAcc + TotalSize}; - _ -> - {VMAcc, VTSAcc} - end - end, {[], 0}, Messages), - FileSize1 = - case Files of - %% if it's the last file, we'll truncate to remove any - %% rubbish above the last valid message. This affects the - %% file size. - [] -> case ValidMessages of - [] -> 0; - _ -> {_MsgId, TotalSize, Offset} = - lists:last(ValidMessages), - Offset + TotalSize - end; - [_|_] -> FileSize - end, + %% within the file, and only returns valid messages (we do + %% the index lookup in the fun). But we get messages in reverse order. + {ok, Messages} = scan_file_for_valid_messages(Path, + fun (Obj = {MsgId, TotalSize, Offset}) -> + %% Fan-out may result in the same message data in multiple + %% files so we have to guard against it. + case index_lookup(IndexEts, MsgId) of + #msg_location { file = undefined } = StoreEntry -> + ok = index_update(IndexEts, StoreEntry #msg_location { + file = File, offset = Offset, + total_size = TotalSize }), + {valid, Obj}; + _ -> + invalid + end + end), + ValidTotalSize = lists:foldl(fun({_, TotalSize, _}, Acc) -> Acc + TotalSize end, 0, Messages), + %% Any file may have rubbish at the end of it that we will want truncated. + %% Note that the last message in the file is the first in the list. + FileSize = case Messages of + [] -> + 0; + [{_, TotalSize, Offset}|_] -> + Offset + TotalSize + end, ok = gatherer:in(Gatherer, #file_summary { file = File, valid_total_size = ValidTotalSize, - file_size = FileSize1, + file_size = FileSize, locked = false }), ok = gatherer:finish(Gatherer). @@ -1933,7 +1933,7 @@ compact_file(File, State = #gc_state { index_ets = IndexEts, %% Load the messages. It's possible to get 0 messages here; %% that's OK. That means we have little to do as the file is %% about to be deleted. - {Messages, _} = scan_and_vacuum_message_file(File, State), + Messages = scan_and_vacuum_message_file(File, State), %% Blank holes. We must do this first otherwise the file is left %% with data that may confuse the code (for example data that looks %% like a message, isn't a message, but spans over a real message). @@ -2087,7 +2087,7 @@ delete_file(File, State = #gc_state { file_summary_ets = FileSummaryEts, _ -> [#file_summary{ valid_total_size = 0, file_size = FileSize }] = ets:lookup(FileSummaryEts, File), - {[], 0} = scan_and_vacuum_message_file(File, State), + [] = scan_and_vacuum_message_file(File, State), ok = file:delete(form_filename(Dir, filenum_to_name(File))), true = ets:delete(FileSummaryEts, File), rabbit_log:debug("Deleted empty file number ~tp; reclaimed ~tp bytes", [File, FileSize]), @@ -2096,28 +2096,31 @@ delete_file(File, State = #gc_state { file_summary_ets = FileSummaryEts, scan_and_vacuum_message_file(File, #gc_state{ index_ets = IndexEts, dir = Dir }) -> %% Messages here will be end-of-file at start-of-list - {ok, Messages, _FileSize} = - scan_file_for_valid_messages(Dir, filenum_to_name(File)), - %% foldl will reverse so will end up with msgs in ascending offset order - lists:foldl( - fun ({MsgId, TotalSize, Offset}, Acc = {List, Size}) -> - case index_lookup(IndexEts, MsgId) of - #msg_location { file = File, total_size = TotalSize, - offset = Offset, ref_count = 0 } = Entry -> - index_delete_object(IndexEts, Entry), - Acc; - #msg_location { file = File, total_size = TotalSize, - offset = Offset } = Entry -> - {[ Entry | List ], TotalSize + Size}; - %% Fan-out may remove the entry but also write a new - %% entry in a different file when it needs to write - %% a message and the existing reference is in a file - %% that's about to be deleted. So we explicitly accept - %% these cases and ignore this message. - #msg_location { file = OtherFile, total_size = TotalSize } - when File =/= OtherFile -> - Acc; - not_found -> - Acc - end - end, {[], 0}, Messages). + Path = form_filename(Dir, filenum_to_name(File)), + {ok, Messages} = scan_file_for_valid_messages(Path, + fun ({MsgId, TotalSize, Offset}) -> + case index_lookup(IndexEts, MsgId) of + #msg_location { file = File, total_size = TotalSize, + offset = Offset, ref_count = 0 } = Entry -> + index_delete_object(IndexEts, Entry), + %% The message was valid, but since we have now deleted + %% it due to having no ref_count, it becomes invalid. + %% We still want to let the scan function skip though. + previously_valid; + #msg_location { file = File, total_size = TotalSize, + offset = Offset } = Entry -> + {valid, Entry}; + %% Fan-out may remove the entry but also write a new + %% entry in a different file when it needs to write + %% a message and the existing reference is in a file + %% that's about to be deleted. So we explicitly accept + %% these cases and ignore this message. + #msg_location { file = OtherFile, total_size = TotalSize } + when File =/= OtherFile -> + invalid; + not_found -> + invalid + end + end), + %% @todo Do we really need to reverse messages? + lists:reverse(Messages). diff --git a/deps/rabbit/test/backing_queue_SUITE.erl b/deps/rabbit/test/backing_queue_SUITE.erl index 2b4ce444c991..845cdc17ef56 100644 --- a/deps/rabbit/test/backing_queue_SUITE.erl +++ b/deps/rabbit/test/backing_queue_SUITE.erl @@ -630,6 +630,22 @@ msg_store_file_scan1(Config) -> %% Messages with no content. ok = Scan([{bin, <<0:64, "deadbeefdeadbeef", 255>>}]), ok = Scan([{msg, gen_id(), <<>>}]), + %% Tricky messages. + %% + %% These only get properly detected when the index is populated. + %% In this test case we simulate the index with a fun. + TrickyScan = fun (Blocks, Expected, Fun) -> + Path = gen_msg_file(Config, Blocks), + Result = rabbit_msg_store:scan_file_for_valid_messages(Path, Fun), + case Result of + Expected -> ok; + _ -> {expected, Expected, got, Result} + end + end, + ok = TrickyScan( + [{bin, <<0, 0:48, 17, 17, "idididididididid", 255, 0:4352/unit:8, 255>>}], + {ok, [{<<"idididididididid">>, 4378, 1}]}, + fun(Obj = {<<"idididididididid">>, 4378, 1}) -> {valid, Obj}; (_) -> invalid end), %% All good!! passed. @@ -662,12 +678,7 @@ gen_msg_file(Config, Blocks) -> gen_result(Blocks) -> Messages = gen_result(Blocks, 0, []), - case Messages of - [] -> - {ok, [], 0}; - [{_, TotalSize, Offset}|_] -> - {ok, Messages, Offset + TotalSize} - end. + {ok, Messages}. gen_result([], _, Acc) -> Acc; diff --git a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel index 71c3d2e46289..741bd873135d 100644 --- a/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel +++ b/deps/rabbitmq_auth_backend_oauth2/BUILD.bazel @@ -113,7 +113,7 @@ rabbitmq_integration_suite( ) rabbitmq_integration_suite( - name = "rabbit_oauth2_config_SUITE", + name = "rabbit_oauth2_provider_SUITE", additional_beam = [ "test/oauth2_http_mock.beam", ], @@ -122,6 +122,10 @@ rabbitmq_integration_suite( ], ) +rabbitmq_integration_suite( + name = "rabbit_oauth2_resource_server_SUITE" +) + rabbitmq_integration_suite( name = "jwks_SUITE", additional_beam = [ diff --git a/deps/rabbitmq_auth_backend_oauth2/Makefile b/deps/rabbitmq_auth_backend_oauth2/Makefile index 1066e7be8271..ce2bdbd048ac 100644 --- a/deps/rabbitmq_auth_backend_oauth2/Makefile +++ b/deps/rabbitmq_auth_backend_oauth2/Makefile @@ -6,7 +6,7 @@ BUILD_WITHOUT_QUIC=1 export BUILD_WITHOUT_QUIC LOCAL_DEPS = inets public_key -BUILD_DEPS = rabbit_common +BUILD_DEPS = rabbit_common rabbitmq_cli DEPS = rabbit cowlib jose base64url oauth2_client TEST_DEPS = cowboy rabbitmq_web_dispatch rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client rabbitmq_web_mqtt emqtt rabbitmq_amqp_client diff --git a/deps/rabbitmq_auth_backend_oauth2/app.bzl b/deps/rabbitmq_auth_backend_oauth2/app.bzl index 003818ac74be..a74d5bfe38e1 100644 --- a/deps/rabbitmq_auth_backend_oauth2/app.bzl +++ b/deps/rabbitmq_auth_backend_oauth2/app.bzl @@ -13,7 +13,10 @@ def all_beam_files(name = "all_beam_files"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", - "src/rabbit_oauth2_config.erl", + "src/rabbit_oauth2_provider.erl", + "src/rabbit_oauth2_resource_server.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_keycloak.erl", "src/rabbit_oauth2_schema.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", @@ -48,8 +51,11 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", - "src/rabbit_oauth2_config.erl", + "src/rabbit_oauth2_resource_server.erl", + "src/rabbit_oauth2_provider.erl", "src/rabbit_oauth2_schema.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_keycloak.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", "src/uaa_jwt.erl", @@ -85,6 +91,7 @@ def all_srcs(name = "all_srcs"): ) filegroup( name = "public_hdrs", + srcs = ["include/oauth2.hrl"], ) filegroup( @@ -94,8 +101,11 @@ def all_srcs(name = "all_srcs"): "src/Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand.erl", "src/rabbit_auth_backend_oauth2.erl", "src/rabbit_auth_backend_oauth2_app.erl", - "src/rabbit_oauth2_config.erl", + "src/rabbit_oauth2_provider.erl", + "src/rabbit_oauth2_resource_server.erl", "src/rabbit_oauth2_schema.erl", + "src/rabbit_oauth2_rar.erl", + "src/rabbit_oauth2_keycloak.erl", "src/rabbit_oauth2_scope.erl", "src/uaa_jwks.erl", "src/uaa_jwt.erl", @@ -223,9 +233,11 @@ def test_suite_beam_files(name = "test_suite_beam_files"): testonly = True, srcs = ["test/unit_SUITE.erl"], outs = ["test/unit_SUITE.beam"], + hdrs = ["include/oauth2.hrl"], app_name = "rabbitmq_auth_backend_oauth2", erlc_opts = "//:test_erlc_opts", - deps = ["//deps/rabbit_common:erlang_app"], + deps = ["//deps/rabbit_common:erlang_app", + "//deps/oauth2_client:erlang_app"], ) erlang_bytecode( name = "wildcard_match_SUITE_beam_files", @@ -236,10 +248,21 @@ def test_suite_beam_files(name = "test_suite_beam_files"): erlc_opts = "//:test_erlc_opts", ) erlang_bytecode( - name = "rabbit_oauth2_config_SUITE_beam_files", + name = "rabbit_oauth2_provider_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_oauth2_provider_SUITE.erl"], + outs = ["test/rabbit_oauth2_provider_SUITE.beam"], + hdrs = ["include/oauth2.hrl"], + app_name = "rabbitmq_auth_backend_oauth2", + erlc_opts = "//:test_erlc_opts", + deps = ["//deps/oauth2_client:erlang_app"], + ) + erlang_bytecode( + name = "rabbit_oauth2_resource_server_SUITE_beam_files", testonly = True, - srcs = ["test/rabbit_oauth2_config_SUITE.erl"], - outs = ["test/rabbit_oauth2_config_SUITE.beam"], + srcs = ["test/rabbit_oauth2_resource_server_SUITE.erl"], + outs = ["test/rabbit_oauth2_resource_server_SUITE.beam"], + hdrs = ["include/oauth2.hrl"], app_name = "rabbitmq_auth_backend_oauth2", erlc_opts = "//:test_erlc_opts", deps = ["//deps/oauth2_client:erlang_app"], diff --git a/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl b/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl new file mode 100644 index 000000000000..f5d0e6559bd5 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/include/oauth2.hrl @@ -0,0 +1,45 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2020-2023 VMware, Inc. or its affiliates. All rights reserved. +%% + + +-include_lib("oauth2_client/include/oauth2_client.hrl"). + +-define(APP, rabbitmq_auth_backend_oauth2). +-define(DEFAULT_PREFERRED_USERNAME_CLAIMS, [<<"sub">>, <<"client_id">>]). +%% scope aliases map "role names" to a set of scopes + +%% +%% Key JWT fields +%% + +-define(AUD_JWT_FIELD, <<"aud">>). +-define(SCOPE_JWT_FIELD, <<"scope">>). +-define(TAG_SCOPE_PREFIX, <<"tag:">>). + +%% End of Key JWT fields + + +-record(internal_oauth_provider, { + id :: oauth_provider_id(), + default_key :: binary() | undefined, + algorithms :: list() | undefined +}). +-type internal_oauth_provider() :: #internal_oauth_provider{}. + +-record(resource_server, { + id :: resource_server_id(), + resource_server_type :: binary() | undefined, + verify_aud :: boolean(), + scope_prefix :: binary(), + additional_scopes_key :: binary() | undefined, + preferred_username_claims :: list(), + scope_aliases :: map() | undefined, + oauth_provider_id :: oauth_provider_id() + }). + +-type resource_server() :: #resource_server{}. +-type resource_server_id() :: binary() | list(). diff --git a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema index 399708ae2562..5379f87560de 100644 --- a/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema +++ b/deps/rabbitmq_auth_backend_oauth2/priv/schema/rabbitmq_auth_backend_oauth2.schema @@ -158,6 +158,41 @@ "rabbitmq_auth_backend_oauth2.authorization_endpoint", [{datatype, string}, {validators, ["uri", "https_uri"]}]}. +{mapping, + "auth_oauth2.discovery_endpoint_path", + "rabbitmq_auth_backend_oauth2.discovery_endpoint_path", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.discovery_endpoint_params.$param", + "rabbitmq_auth_backend_oauth2.discovery_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_auth_backend_oauth2.discovery_endpoint_params", + fun(Conf) -> + rabbit_oauth2_schema:translate_endpoint_params("discovery_endpoint_params", Conf) + end}. + +{mapping, + "auth_oauth2.oauth_providers.$name.discovery_endpoint_params.$param", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.discovery_endpoint_path", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.algorithms.$algorithm", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}]}. + +{translation, "rabbitmq_auth_backend_oauth2.oauth_providers", + fun(Conf) -> + rabbit_oauth2_schema:translate_oauth_providers(Conf) + end}. + {mapping, "auth_oauth2.https.peer_verification", "rabbitmq_auth_backend_oauth2.key_config.peer_verification", @@ -301,6 +336,7 @@ [{datatype, string}] }. + {mapping, "auth_oauth2.resource_servers.$name.scope_prefix", "rabbitmq_auth_backend_oauth2.resource_servers", @@ -333,5 +369,5 @@ {translation, "rabbitmq_auth_backend_oauth2.resource_servers", fun(Conf) -> - rabbit_oauth2_schema:translate_resource_servers(Conf) + rabbit_oauth2_schema:translate_resource_servers(Conf) end}. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl index a43212655b87..27874000b00a 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl @@ -8,6 +8,7 @@ -module(rabbit_auth_backend_oauth2). -include_lib("rabbit_common/include/rabbit.hrl"). +-include("oauth2.hrl"). -behaviour(rabbit_authn_backend). -behaviour(rabbit_authz_backend). @@ -15,13 +16,22 @@ -export([description/0]). -export([user_login_authentication/2, user_login_authorization/2, check_vhost_access/3, check_resource_access/4, - check_topic_access/4, check_token/1, update_state/2, + check_topic_access/4, update_state/2, expiry_timestamp/1]). -% for testing --export([post_process_payload/2, get_expanded_scopes/2]). +%% for testing +-export([normalize_token_scope/2, get_expanded_scopes/2]). -import(rabbit_data_coercion, [to_map/1]). +-import(uaa_jwt, [ + decode_and_verify/3, + get_scope/1, set_scope/2, + resolve_resource_server/1]). + +-import(rabbit_oauth2_keycloak, [has_keycloak_scopes/1, extract_scopes_from_keycloak_format/1]). +-import(rabbit_oauth2_rar, [extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]). + +-import(rabbit_oauth2_scope, [filter_matching_scope_prefix_and_drop_it/2]). -ifdef(TEST). -compile(export_all). @@ -32,19 +42,12 @@ %% --define(RESOURCE_SERVER_ID, resource_server_id). %% a term defined for Rich Authorization Request tokens to identify a RabbitMQ permission %% verify server_server_id aud field is on the aud field %% a term used by the IdentityServer community %% scope aliases map "role names" to a set of scopes -%% -%% Key JWT fields -%% - --define(AUD_JWT_FIELD, <<"aud">>). --define(SCOPE_JWT_FIELD, <<"scope">>). %% %% API %% @@ -75,7 +78,7 @@ check_vhost_access(#auth_user{impl = DecodedTokenFun}, with_decoded_token(DecodedTokenFun(), fun(_Token) -> DecodedToken = DecodedTokenFun(), - Scopes = get_scopes(DecodedToken), + Scopes = get_scope(DecodedToken), ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","), rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]), rabbit_oauth2_scope:vhost_access(VHost, Scopes) @@ -85,7 +88,7 @@ check_resource_access(#auth_user{impl = DecodedTokenFun}, Resource, Permission, _AuthzContext) -> with_decoded_token(DecodedTokenFun(), fun(Token) -> - Scopes = get_scopes(Token), + Scopes = get_scope(Token), rabbit_oauth2_scope:resource_access(Resource, Permission, Scopes) end). @@ -98,19 +101,21 @@ check_topic_access(#auth_user{impl = DecodedTokenFun}, end). update_state(AuthUser, NewToken) -> - case check_token(NewToken) of - %% avoid logging the token - {error, _} = E -> E; - {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> - {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid"}; - {refused, Err} -> - {refused, rabbit_misc:format("Authentication using an OAuth 2/JWT token failed: ~tp", [Err])}; - {ok, DecodedToken} -> - Tags = tags_from(DecodedToken), - - {ok, AuthUser#auth_user{tags = Tags, - impl = fun() -> DecodedToken end}} - end. + case resolve_resource_server(NewToken) of + {error, _} = Err0 -> Err0; + {_, _} = Tuple -> + case check_token(NewToken, Tuple) of + %% avoid logging the token + {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> + {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid"}; + {refused, Err} -> + {refused, rabbit_misc:format("Authentication using an OAuth 2/JWT token failed: ~tp", [Err])}; + {ok, DecodedToken} -> + Tags = tags_from(DecodedToken), + {ok, AuthUser#auth_user{tags = Tags, + impl = fun() -> DecodedToken end}} + end + end. expiry_timestamp(#auth_user{impl = DecodedTokenFun}) -> case DecodedTokenFun() of @@ -125,31 +130,33 @@ expiry_timestamp(#auth_user{impl = DecodedTokenFun}) -> authenticate(_, AuthProps0) -> AuthProps = to_map(AuthProps0), Token = token_from_context(AuthProps), - - case check_token(Token) of - %% avoid logging the token - {error, _} = E -> E; - {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> - {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid", []}; - {refused, Err} -> - {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]}; - {ok, DecodedToken} -> - Func = fun(Token0) -> - Username = username_from(rabbit_oauth2_config:get_preferred_username_claims(), Token0), - Tags = tags_from(Token0), - - {ok, #auth_user{username = Username, - tags = Tags, - impl = fun() -> Token0 end}} - end, - case with_decoded_token(DecodedToken, Func) of - {error, Err} -> + case resolve_resource_server(Token) of + {error, _} = Err0 -> + {refused, "Authentication using OAuth 2/JWT token failed: ~tp", [Err0]}; + {ResourceServer, _} = Tuple -> + case check_token(Token, Tuple) of + {refused, {error, {invalid_token, error, _Err, _Stacktrace}}} -> + {refused, "Authentication using an OAuth 2/JWT token failed: provided token is invalid", []}; + {refused, Err} -> {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]}; - Else -> - Else + {ok, DecodedToken} -> + Func = fun(Token0) -> + Username = username_from( + ResourceServer#resource_server.preferred_username_claims, + Token0), + Tags = tags_from(Token0), + {ok, #auth_user{username = Username, + tags = Tags, + impl = fun() -> Token0 end}} + end, + case with_decoded_token(DecodedToken, Func) of + {error, Err} -> + {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [Err]}; + Else -> + Else + end end end. - with_decoded_token(DecodedToken, Fun) -> case validate_token_expiry(DecodedToken) of ok -> Fun(DecodedToken); @@ -166,100 +173,63 @@ validate_token_expiry(#{<<"exp">> := Exp}) when is_integer(Exp) -> end; validate_token_expiry(#{}) -> ok. --spec check_token(binary() | map()) -> +-spec check_token(binary() | map(), {resource_server(), internal_oauth_provider()}) -> {'ok', map()} | {'error', term() }| - {'refused', - 'signature_invalid' | + {'refused', 'signature_invalid' | {'error', term()} | {'invalid_aud', term()}}. -check_token(DecodedToken) when is_map(DecodedToken) -> +check_token(DecodedToken, _) when is_map(DecodedToken) -> {ok, DecodedToken}; -check_token(Token) -> - case uaa_jwt:decode_and_verify(Token) of - {error, Reason} -> - {refused, {error, Reason}}; - {true, TargetResourceServerId, Payload} -> - Payload0 = post_process_payload(TargetResourceServerId, Payload), - validate_payload(TargetResourceServerId, Payload0); - {false, _, _} -> {refused, signature_invalid} +check_token(Token, {ResourceServer, InternalOAuthProvider}) -> + case decode_and_verify(Token, ResourceServer, InternalOAuthProvider) of + {error, Reason} -> {refused, {error, Reason}}; + {true, Payload} -> {ok, normalize_token_scope(ResourceServer, Payload)}; + {false, _} -> {refused, signature_invalid} end. -post_process_payload(ResourceServerId, Payload) when is_map(Payload) -> +-spec normalize_token_scope( + ResourceServer :: resource_server(), DecodedToken :: map()) -> map(). +normalize_token_scope(ResourceServer, Payload) -> Payload0 = maps:map(fun(K, V) -> - case K of - ?AUD_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]); - ?SCOPE_JWT_FIELD when is_binary(V) -> binary:split(V, <<" ">>, [global, trim_all]); - _ -> V - end - end, - Payload - ), - - Payload1 = case does_include_complex_claim_field(ResourceServerId, Payload0) of - true -> post_process_payload_with_complex_claim(ResourceServerId, Payload0); + case K of + ?SCOPE_JWT_FIELD when is_binary(V) -> + binary:split(V, <<" ">>, [global, trim_all]); + _ -> V + end + end, Payload), + + Payload1 = case has_additional_scopes_key(ResourceServer, Payload0) of + true -> extract_scopes_from_additional_scopes_key(ResourceServer, Payload0); false -> Payload0 end, - Payload2 = case maps:is_key(<<"authorization">>, Payload1) of - true -> post_process_payload_in_keycloak_format(Payload1); + Payload2 = case has_keycloak_scopes(Payload1) of + true -> extract_scopes_from_keycloak_format(Payload1); false -> Payload1 end, - Payload3 = case rabbit_oauth2_config:has_scope_aliases(ResourceServerId) of - true -> post_process_payload_with_scope_aliases(ResourceServerId, Payload2); - false -> Payload2 + Payload3 = case ResourceServer#resource_server.scope_aliases of + undefined -> Payload2; + ScopeAliases -> extract_scopes_using_scope_aliases(ScopeAliases, Payload2) end, - Payload4 = case maps:is_key(<<"authorization_details">>, Payload3) of - true -> post_process_payload_in_rich_auth_request_format(ResourceServerId, Payload3); + Payload4 = case has_rich_auth_request_scopes(Payload3) of + true -> extract_scopes_from_rich_auth_request(ResourceServer, Payload3); false -> Payload3 end, - Payload4. + FilteredScopes = filter_matching_scope_prefix_and_drop_it( + get_scope(Payload4), ResourceServer#resource_server.scope_prefix), + set_scope(FilteredScopes, Payload4). --spec post_process_payload_with_scope_aliases(ResourceServerId :: binary(), Payload :: map()) -> map(). -%% This is for those hopeless environments where the token structure is so out of -%% messaging team's control that even the extra scopes field is no longer an option. -%% -%% This assumes that scopes can be random values that do not follow the RabbitMQ -%% convention, or any other convention, in any way. They are just random client role IDs. -%% See rabbitmq/rabbitmq-server#4588 for details. -post_process_payload_with_scope_aliases(ResourceServerId, Payload) -> - %% try JWT scope field value for alias - Payload1 = post_process_payload_with_scope_alias_in_scope_field(ResourceServerId, Payload), - %% try the configurable 'extra_scopes_source' field value for alias - post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload1). - - --spec post_process_payload_with_scope_alias_in_scope_field(ResourceServerId :: binary(), Payload :: map()) -> map(). -%% First attempt: use the value in the 'scope' field for alias -post_process_payload_with_scope_alias_in_scope_field(ResourceServerId, Payload) -> - ScopeMappings = rabbit_oauth2_config:get_scope_aliases(ResourceServerId), - post_process_payload_with_scope_alias_field_named(Payload, ?SCOPE_JWT_FIELD, ScopeMappings). - - --spec post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId :: binary(), Payload :: map()) -> map(). -%% Second attempt: use the value in the configurable 'extra scopes source' field for alias -post_process_payload_with_scope_alias_in_extra_scopes_source(ResourceServerId, Payload) -> - ExtraScopesField = rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId), - case ExtraScopesField of - %% nothing to inject - {error, not_found} -> Payload; - {ok, ExtraScopes} -> - ScopeMappings = rabbit_oauth2_config:get_scope_aliases(ResourceServerId), - post_process_payload_with_scope_alias_field_named(Payload, ExtraScopes, ScopeMappings) - end. - - --spec post_process_payload_with_scope_alias_field_named(Payload :: map(), - Field :: binary(), - ScopeAliasMapping :: map()) -> map(). -post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAliasMapping) -> - Scopes0 = maps:get(FieldName, Payload, []), +-spec extract_scopes_using_scope_aliases( + ScopeAliasMapping :: map(), Payload :: map()) -> map(). +extract_scopes_using_scope_aliases(ScopeAliasMapping, Payload) -> + Scopes0 = get_scope(Payload), Scopes = rabbit_data_coercion:to_list_of_binaries(Scopes0), %% for all scopes, look them up in the scope alias map, and if they are %% present, add the alias to the final scope list. Note that we also preserve @@ -277,304 +247,42 @@ post_process_payload_with_scope_alias_field_named(Payload, FieldName, ScopeAlias Acc ++ Binaries end end, Scopes, Scopes), - maps:put(?SCOPE_JWT_FIELD, ExpandedScopes, Payload). - - --spec does_include_complex_claim_field(ResourceServerId :: binary(), Payload :: map()) -> boolean(). -does_include_complex_claim_field(ResourceServerId, Payload) when is_map(Payload) -> - case rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId) of - {ok, ScopeKey} -> maps:is_key(ScopeKey, Payload); - {error, not_found} -> false - end. - --spec post_process_payload_with_complex_claim(ResourceServerId :: binary(), Payload :: map()) -> map(). -post_process_payload_with_complex_claim(ResourceServerId, Payload) -> - case rabbit_oauth2_config:get_additional_scopes_key(ResourceServerId) of - {ok, ScopesKey} -> - ComplexClaim = maps:get(ScopesKey, Payload), - AdditionalScopes = - 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, + set_scope(ExpandedScopes, Payload). - case AdditionalScopes of - [] -> Payload; - _ -> - ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []), - maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload) - end; - {error, not_found} -> Payload +-spec has_additional_scopes_key( + ResourceServer :: resource_server(), Payload :: map()) -> boolean(). +has_additional_scopes_key(ResourceServer, Payload) when is_map(Payload) -> + case ResourceServer#resource_server.additional_scopes_key of + undefined -> false; + ScopeKey -> maps:is_key(ScopeKey, Payload) end. --spec post_process_payload_in_keycloak_format(Payload :: map()) -> map(). -%% keycloak token format: https://github.com/rabbitmq/rabbitmq-auth-backend-oauth2/issues/36 -post_process_payload_in_keycloak_format(#{<<"authorization">> := Authorization} = Payload) -> - AdditionalScopes = case maps:get(<<"permissions">>, Authorization, undefined) of - undefined -> []; - Permissions -> extract_scopes_from_keycloak_permissions([], Permissions) - end, - ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload), - maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload). - -extract_scopes_from_keycloak_permissions(Acc, []) -> - Acc; -extract_scopes_from_keycloak_permissions(Acc, [H | T]) when is_map(H) -> - Scopes = case maps:get(<<"scopes">>, H, []) of - ScopesAsList when is_list(ScopesAsList) -> - ScopesAsList; - ScopesAsBinary when is_binary(ScopesAsBinary) -> - [ScopesAsBinary] - end, - extract_scopes_from_keycloak_permissions(Acc ++ Scopes, T); -extract_scopes_from_keycloak_permissions(Acc, [_ | T]) -> - extract_scopes_from_keycloak_permissions(Acc, T). - - --define(ACTIONS_FIELD, <<"actions">>). --define(LOCATIONS_FIELD, <<"locations">>). --define(TYPE_FIELD, <<"type">>). - --define(CLUSTER_LOCATION_ATTRIBUTE, <<"cluster">>). --define(VHOST_LOCATION_ATTRIBUTE, <<"vhost">>). --define(QUEUE_LOCATION_ATTRIBUTE, <<"queue">>). --define(EXCHANGE_LOCATION_ATTRIBUTE, <<"exchange">>). --define(ROUTING_KEY_LOCATION_ATTRIBUTE, <<"routing-key">>). --define(LOCATION_ATTRIBUTES, [?CLUSTER_LOCATION_ATTRIBUTE, ?VHOST_LOCATION_ATTRIBUTE, - ?QUEUE_LOCATION_ATTRIBUTE, ?EXCHANGE_LOCATION_ATTRIBUTE, ?ROUTING_KEY_LOCATION_ATTRIBUTE]). - --define(ALLOWED_TAG_VALUES, [<<"monitoring">>, <<"administrator">>, <<"management">>, <<"policymaker">> ]). --define(ALLOWED_ACTION_VALUES, [<<"read">>, <<"write">>, <<"configure">>, <<"monitoring">>, - <<"administrator">>, <<"management">>, <<"policymaker">> ]). - - -put_location_attribute(Attribute, Map) -> - put_attribute(binary:split(Attribute, <<":">>, [global, trim_all]), Map). - -put_attribute([Key, Value | _], Map) -> - case lists:member(Key, ?LOCATION_ATTRIBUTES) of - true -> maps:put(Key, Value, Map); - false -> Map - end; -put_attribute([_|_], Map) -> Map. - - -% convert [ <<"cluster:A">>, <<"vhost:B" >>, <<"A">>, <<"unknown:C">> ] to #{ <<"cluster">> : <<"A">>, <<"vhost">> : <<"B">> } -% filtering out non-key-value-pairs and keys which are not part of LOCATION_ATTRIBUTES -convert_attribute_list_to_attribute_map(L) -> - convert_attribute_list_to_attribute_map(L, #{}). -convert_attribute_list_to_attribute_map([H|L],Map) when is_binary(H) -> - convert_attribute_list_to_attribute_map(L, put_location_attribute(H,Map)); -convert_attribute_list_to_attribute_map([], Map) -> Map. - -build_permission_resource_path(Map) -> - Vhost = maps:get(?VHOST_LOCATION_ATTRIBUTE, Map, <<"*">>), - Resource = maps:get(?QUEUE_LOCATION_ATTRIBUTE, Map, - maps:get(?EXCHANGE_LOCATION_ATTRIBUTE, Map, <<"*">>)), - RoutingKey = maps:get(?ROUTING_KEY_LOCATION_ATTRIBUTE, Map, <<"*">>), - - <>. - -map_locations_to_permission_resource_paths(ResourceServerId, L) -> - Locations = case L of - undefined -> []; - LocationsAsList when is_list(LocationsAsList) -> - lists:map(fun(Location) -> convert_attribute_list_to_attribute_map( - binary:split(Location,<<"/">>,[global,trim_all])) end, LocationsAsList); - LocationsAsBinary when is_binary(LocationsAsBinary) -> - [convert_attribute_list_to_attribute_map( - binary:split(LocationsAsBinary,<<"/">>,[global,trim_all]))] - end, - - FilteredLocations = lists:filtermap(fun(L2) -> - case cluster_matches_resource_server_id(L2, ResourceServerId) and - legal_queue_and_exchange_values(L2) of - true -> { true, build_permission_resource_path(L2) }; - false -> false - end end, Locations), - - FilteredLocations. - -cluster_matches_resource_server_id(#{?CLUSTER_LOCATION_ATTRIBUTE := Cluster}, - ResourceServerId) -> - wildcard:match(ResourceServerId, Cluster); - -cluster_matches_resource_server_id(_,_) -> - false. - -legal_queue_and_exchange_values(#{?QUEUE_LOCATION_ATTRIBUTE := Queue, - ?EXCHANGE_LOCATION_ATTRIBUTE := Exchange}) -> - case Queue of - <<>> -> - case Exchange of - <<>> -> true; - _ -> false +-spec extract_scopes_from_additional_scopes_key( + ResourceServer :: resource_server(), Payload :: map()) -> map(). +extract_scopes_from_additional_scopes_key(ResourceServer, Payload) -> + Claim = maps:get(ResourceServer#resource_server.additional_scopes_key, Payload), + AdditionalScopes = extract_additional_scopes(ResourceServer, Claim), + set_scope(AdditionalScopes ++ get_scope(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; - _ -> - case Exchange of - Queue -> true; - _ -> false - end - end; -legal_queue_and_exchange_values(_) -> true. - -map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions) -> - map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions, []). -map_rich_auth_permissions_to_scopes(_, [], Acc) -> Acc; -map_rich_auth_permissions_to_scopes(ResourceServerId, - [ #{?ACTIONS_FIELD := Actions, ?LOCATIONS_FIELD := Locations } | T ], Acc) -> - ResourcePaths = map_locations_to_permission_resource_paths(ResourceServerId, Locations), - case ResourcePaths of - [] -> map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc); - _ -> - Scopes = case Actions of - undefined -> []; - ActionsAsList when is_list(ActionsAsList) -> - build_scopes(ResourceServerId, - skip_unknown_actions(ActionsAsList), ResourcePaths); - ActionsAsBinary when is_binary(ActionsAsBinary) -> - build_scopes(ResourceServerId, - skip_unknown_actions([ActionsAsBinary]), ResourcePaths) - end, - map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc ++ Scopes) - end. - -skip_unknown_actions(Actions) -> - lists:filter(fun(A) -> lists:member(A, ?ALLOWED_ACTION_VALUES) end, Actions). - -produce_list_of_user_tag_or_action_on_resources(ResourceServerId, ActionOrUserTag, Locations) -> - case lists:member(ActionOrUserTag, ?ALLOWED_TAG_VALUES) of - true -> [<< ResourceServerId/binary, ".tag:", ActionOrUserTag/binary >>]; - _ -> build_scopes_for_action(ResourceServerId, ActionOrUserTag, Locations, []) - end. - -build_scopes_for_action(ResourceServerId, Action, [Location|Locations], Acc) -> - Scope = << ResourceServerId/binary, ".", Action/binary, ":", Location/binary >>, - build_scopes_for_action(ResourceServerId, Action, Locations, [ Scope | Acc ] ); -build_scopes_for_action(_, _, [], Acc) -> Acc. - -build_scopes(ResourceServerId, Actions, Locations) -> - lists:flatmap(fun(Action) -> - produce_list_of_user_tag_or_action_on_resources(ResourceServerId, - Action, Locations) end, Actions). - -is_recognized_permission(#{?ACTIONS_FIELD := _, ?LOCATIONS_FIELD:= _ , ?TYPE_FIELD := Type }, ResourceServerType) -> - case ResourceServerType of - <<>> -> false; - V when V == Type -> true; - _ -> false - end; -is_recognized_permission(_, _) -> false. - - --spec post_process_payload_in_rich_auth_request_format(ResourceServerId :: binary(), Payload :: map()) -> map(). -%% https://oauth.net/2/rich-authorization-requests/ -post_process_payload_in_rich_auth_request_format(ResourceServerId, #{<<"authorization_details">> := Permissions} = Payload) -> - ResourceServerType = rabbit_oauth2_config:get_resource_server_type(ResourceServerId), - - FilteredPermissionsByType = lists:filter(fun(P) -> - is_recognized_permission(P, ResourceServerType) end, Permissions), - AdditionalScopes = map_rich_auth_permissions_to_scopes(ResourceServerId, FilteredPermissionsByType), - - ExistingScopes = maps:get(?SCOPE_JWT_FIELD, Payload, []), - maps:put(?SCOPE_JWT_FIELD, AdditionalScopes ++ ExistingScopes, Payload). - -validate_payload(ResourceServerId, DecodedToken) -> - ScopePrefix = rabbit_oauth2_config:get_scope_prefix(ResourceServerId), - validate_payload(ResourceServerId, DecodedToken, ScopePrefix). - -validate_payload(ResourceServerId, #{?SCOPE_JWT_FIELD := Scope, ?AUD_JWT_FIELD := Aud} = DecodedToken, ScopePrefix) -> - case check_aud(Aud, ResourceServerId) of - ok -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}}; - {error, Err} -> {refused, {invalid_aud, Err}} - end; -validate_payload(ResourceServerId, #{?AUD_JWT_FIELD := Aud} = DecodedToken, _ScopePrefix) -> - case check_aud(Aud, ResourceServerId) of - ok -> {ok, DecodedToken}; - {error, Err} -> {refused, {invalid_aud, Err}} - end; -validate_payload(ResourceServerId, #{?SCOPE_JWT_FIELD := Scope} = DecodedToken, ScopePrefix) -> - case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of - true -> {error, {badarg, {aud_field_is_missing}}}; - false -> {ok, DecodedToken#{?SCOPE_JWT_FIELD => filter_scopes(Scope, ScopePrefix)}} - end. - -filter_scopes(Scopes, <<"">>) -> Scopes; -filter_scopes(Scopes, ScopePrefix) -> - matching_scopes_without_prefix(Scopes, ScopePrefix). - -check_aud(_, <<>>) -> ok; -check_aud(Aud, ResourceServerId) -> - case rabbit_oauth2_config:is_verify_aud(ResourceServerId) of - true -> - case Aud of - List when is_list(List) -> - case lists:member(ResourceServerId, Aud) of - true -> ok; - false -> {error, {resource_id_not_found_in_aud, ResourceServerId, Aud}} - end; - _ -> {error, {badarg, {aud_is_not_a_list, Aud}}} - end; - false -> ok - end. - -%%-------------------------------------------------------------------- - -get_scopes(#{?SCOPE_JWT_FIELD := Scope}) -> Scope; -get_scopes(#{}) -> []. - --spec get_expanded_scopes(map(), #resource{}) -> [binary()]. -get_expanded_scopes(Token, #resource{virtual_host = VHost}) -> - Context = #{ token => Token , vhost => VHost}, - case maps:get(?SCOPE_JWT_FIELD, Token, []) of - [] -> []; - Scopes -> lists:map(fun(Scope) -> list_to_binary(parse_scope(Scope, Context)) end, Scopes) - end. - -parse_scope(Scope, Context) -> - { Acc0, _} = lists:foldl(fun(Elem, { Acc, Stage }) -> parse_scope_part(Elem, Acc, Stage, Context) end, - { [], undefined }, re:split(Scope,"([\{.*\}])",[{return,list},trim])), - Acc0. - -parse_scope_part(Elem, Acc, Stage, Context) -> - case Stage of - error -> {Acc, error}; - undefined -> - case Elem of - "{" -> { Acc, fun capture_var_name/3}; - Value -> { Acc ++ Value, Stage} - end; - _ -> Stage(Elem, Acc, Context) + Bin when is_binary(Bin) -> + binary:split(Bin, <<" ">>, [global, trim_all]); + _ -> [] end. -capture_var_name(Elem, Acc, #{ token := Token, vhost := Vhost}) -> - { Acc ++ resolve_scope_var(Elem, Token, Vhost), fun expect_closing_var/3}. - -expect_closing_var("}" , Acc, _Context) -> { Acc , undefined }; -expect_closing_var(_ , _Acc, _Context) -> {"", error}. - -resolve_scope_var(Elem, Token, Vhost) -> - case Elem of - "vhost" -> binary_to_list(Vhost); - _ -> - ElemAsBinary = list_to_binary(Elem), - binary_to_list(case maps:get(ElemAsBinary, Token, ElemAsBinary) of - Value when is_binary(Value) -> Value; - _ -> ElemAsBinary - end) - 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. @@ -630,25 +338,50 @@ find_claim_in_token(Claim, Token) -> _ -> false end. --define(TAG_SCOPE_PREFIX, <<"tag:">>). +-spec get_expanded_scopes(map(), #resource{}) -> [binary()]. +get_expanded_scopes(Token, #resource{virtual_host = VHost}) -> + Context = #{ token => Token , vhost => VHost}, + case get_scope(Token) of + [] -> []; + Scopes -> lists:map(fun(Scope) -> list_to_binary(parse_scope(Scope, Context)) end, Scopes) + end. + + +parse_scope(Scope, Context) -> + { Acc0, _} = lists:foldl(fun(Elem, { Acc, Stage }) -> parse_scope_part(Elem, Acc, Stage, Context) end, + { [], undefined }, re:split(Scope,"([\{.*\}])",[{return,list},trim])), + Acc0. + +parse_scope_part(Elem, Acc, Stage, Context) -> + case Stage of + error -> {Acc, error}; + undefined -> + case Elem of + "{" -> { Acc, fun capture_var_name/3}; + Value -> { Acc ++ Value, Stage} + end; + _ -> Stage(Elem, Acc, Context) + end. + +capture_var_name(Elem, Acc, #{ token := Token, vhost := Vhost}) -> + { Acc ++ resolve_scope_var(Elem, Token, Vhost), fun expect_closing_var/3}. + +expect_closing_var("}" , Acc, _Context) -> { Acc , undefined }; +expect_closing_var(_ , _Acc, _Context) -> {"", error}. + +resolve_scope_var(Elem, Token, Vhost) -> + case Elem of + "vhost" -> binary_to_list(Vhost); + _ -> + ElemAsBinary = list_to_binary(Elem), + binary_to_list(case maps:get(ElemAsBinary, Token, ElemAsBinary) of + Value when is_binary(Value) -> Value; + _ -> ElemAsBinary + end) + end. -spec tags_from(map()) -> list(atom()). tags_from(DecodedToken) -> Scopes = maps:get(?SCOPE_JWT_FIELD, DecodedToken, []), - TagScopes = matching_scopes_without_prefix(Scopes, ?TAG_SCOPE_PREFIX), + TagScopes = filter_matching_scope_prefix_and_drop_it(Scopes, ?TAG_SCOPE_PREFIX), lists:usort(lists:map(fun rabbit_data_coercion:to_atom/1, TagScopes)). - -matching_scopes_without_prefix(Scopes, PrefixPattern) -> - PatternLength = byte_size(PrefixPattern), - lists:filtermap( - fun(ScopeEl) -> - case binary:match(ScopeEl, PrefixPattern) of - {0, PatternLength} -> - ElLength = byte_size(ScopeEl), - {true, - binary:part(ScopeEl, - {PatternLength, ElLength - PatternLength})}; - _ -> false - end - end, - Scopes). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl deleted file mode 100644 index f6219c06ad0f..000000000000 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_config.erl +++ /dev/null @@ -1,474 +0,0 @@ -%% This Source Code Form is subject to the terms of the Mozilla Public -%% License, v. 2.0. If a copy of the MPL was not distributed with this -%% file, You can obtain one at https://mozilla.org/MPL/2.0/. -%% -%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. -%% - --module(rabbit_oauth2_config). - --include_lib("oauth2_client/include/oauth2_client.hrl"). - --define(APP, rabbitmq_auth_backend_oauth2). --define(DEFAULT_PREFERRED_USERNAME_CLAIMS, [<<"sub">>, <<"client_id">>]). - --define(TOP_RESOURCE_SERVER_ID, application:get_env(?APP, resource_server_id)). -%% scope aliases map "role names" to a set of scopes - --export([ - add_signing_key/2, add_signing_key/3, replace_signing_keys/1, - replace_signing_keys/2, - get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2, - get_default_key/0, - get_default_resource_server_id/0, - get_resource_server_id_for_audience/1, - get_algorithms/0, get_algorithms/1, get_default_key/1, - get_oauth_provider_id_for_resource_server_id/1, - get_oauth_provider/2, - get_allowed_resource_server_ids/0, find_audience_in_resource_server_ids/1, - is_verify_aud/0, is_verify_aud/1, - get_additional_scopes_key/0, get_additional_scopes_key/1, - get_default_preferred_username_claims/0, get_preferred_username_claims/0, - get_preferred_username_claims/1, - get_scope_prefix/0, get_scope_prefix/1, - get_resource_server_type/0, get_resource_server_type/1, - has_scope_aliases/1, get_scope_aliases/1 - ]). - --spec get_default_preferred_username_claims() -> list(). -get_default_preferred_username_claims() -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS. - --spec get_preferred_username_claims() -> list(). -get_preferred_username_claims() -> - case application:get_env(?APP, preferred_username_claims) of - {ok, Value} -> - append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS); - _ -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS - end. --spec get_preferred_username_claims(binary() | list()) -> list(). -get_preferred_username_claims(ResourceServerId) -> - ResourceServers = application:get_env(?APP, resource_servers, #{}), - ResourceServer = maps:get(ResourceServerId, ResourceServers, []), - case proplists:get_value(preferred_username_claims, ResourceServer, undefined) of - undefined -> - get_preferred_username_claims(); - Value -> - append_or_return_default(Value, ?DEFAULT_PREFERRED_USERNAME_CLAIMS) - end. - --spec get_default_key() -> {ok, binary()} | {error, no_default_key_configured}. -get_default_key() -> - get_default_key(root). - --spec get_default_key(oauth_provider_id()) -> {ok, binary()} | {error, no_default_key_configured}. -get_default_key(root) -> - case application:get_env(?APP, key_config, undefined) of - undefined -> - {error, no_default_key_configured}; - KeyConfig -> - case proplists:get_value(default_key, KeyConfig, undefined) of - undefined -> {error, no_default_key_configured}; - V -> {ok, V} - end - end; -get_default_key(OauthProviderId) -> - OauthProviders = application:get_env(?APP, oauth_providers, #{}), - case maps:get(OauthProviderId, OauthProviders, []) of - [] -> - {error, no_default_key_configured}; - OauthProvider -> - case proplists:get_value(default_key, OauthProvider, undefined) of - undefined -> {error, no_default_key_configured}; - V -> {ok, V} - end - end. - -%% -%% Signing Key storage: -%% -%% * Static signing keys configured via config file are stored under signing_keys attribute -%% in their respective location (under key_config for the root oauth provider and -%% directly under each oauth provider) -%% * Dynamic signing keys loaded via rabbitmqctl or via JWKS endpoint are stored under -%% jwks attribute in their respective location. However, this attribute stores the -%% combination of static signing keys and dynamic signing keys. If the same kid is -%% found in both sets, the dynamic kid overrides the static kid. -%% - --type key_type() :: json | pem | map. --spec add_signing_key(binary(), {key_type(), binary()} ) -> map() | {error, term()}. -add_signing_key(KeyId, Key) -> - LockId = lock(), - try do_add_signing_key(KeyId, Key, root) of - V -> V - after - unlock(LockId) - end. - --spec add_signing_key(binary(), {key_type(), binary()}, oauth_provider_id()) -> - map() | {error, term()}. -add_signing_key(KeyId, Key, OAuthProviderId) -> - case lock() of - {error, _} = Error -> - Error; - LockId -> - try do_add_signing_key(KeyId, Key, OAuthProviderId) of - V -> V - after - unlock(LockId) - end - end. - -do_add_signing_key(KeyId, Key, OAuthProviderId) -> - do_replace_signing_keys(maps:put(KeyId, Key, - get_signing_keys_from_jwks(OAuthProviderId)), OAuthProviderId). - -get_signing_keys_from_jwks(root) -> - KeyConfig = application:get_env(?APP, key_config, []), - proplists:get_value(jwks, KeyConfig, #{}); -get_signing_keys_from_jwks(OAuthProviderId) -> - OAuthProviders0 = application:get_env(?APP, oauth_providers, #{}), - OAuthProvider0 = maps:get(OAuthProviderId, OAuthProviders0, []), - proplists:get_value(jwks, OAuthProvider0, #{}). - --spec replace_signing_keys(map()) -> map() | {error, term()}. -replace_signing_keys(SigningKeys) -> - replace_signing_keys(SigningKeys, root). - --spec replace_signing_keys(map(), oauth_provider_id()) -> map() | {error, term()}. -replace_signing_keys(SigningKeys, OAuthProviderId) -> - case lock() of - {error,_} = Error -> - Error; - LockId -> - try do_replace_signing_keys(SigningKeys, OAuthProviderId) of - V -> V - after - unlock(LockId) - end - end. - -do_replace_signing_keys(SigningKeys, root) -> - KeyConfig = application:get_env(?APP, key_config, []), - KeyConfig1 = proplists:delete(jwks, KeyConfig), - KeyConfig2 = [{jwks, maps:merge( - proplists:get_value(signing_keys, KeyConfig1, #{}), - SigningKeys)} | KeyConfig1], - application:set_env(?APP, key_config, KeyConfig2), - rabbit_log:debug("Replacing signing keys ~p", [ KeyConfig2]), - SigningKeys; - -do_replace_signing_keys(SigningKeys, OauthProviderId) -> - OauthProviders0 = application:get_env(?APP, oauth_providers, #{}), - OauthProvider0 = maps:get(OauthProviderId, OauthProviders0, []), - OauthProvider1 = proplists:delete(jwks, OauthProvider0), - OauthProvider = [{jwks, maps:merge( - proplists:get_value(signing_keys, OauthProvider1, #{}), - SigningKeys)} | OauthProvider1], - - OauthProviders = maps:put(OauthProviderId, OauthProvider, OauthProviders0), - application:set_env(?APP, oauth_providers, OauthProviders), - rabbit_log:debug("Replacing signing keys for ~p -> ~p", [OauthProviderId, OauthProvider]), - SigningKeys. - - --spec get_signing_keys() -> map(). -get_signing_keys() -> - get_signing_keys(root). - --spec get_signing_keys(oauth_provider_id()) -> map(). -get_signing_keys(root) -> - case application:get_env(?APP, key_config, undefined) of - undefined -> - #{}; - KeyConfig -> - case proplists:get_value(jwks, KeyConfig, undefined) of - undefined -> proplists:get_value(signing_keys, KeyConfig, #{}); - Jwks -> Jwks - end - end; -get_signing_keys(OauthProviderId) -> - OauthProviders = application:get_env(?APP, oauth_providers, #{}), - OauthProvider = maps:get(OauthProviderId, OauthProviders, []), - case proplists:get_value(jwks, OauthProvider, undefined) of - undefined -> - proplists:get_value(signing_keys, OauthProvider, #{}); - Jwks -> - Jwks - end. - --spec get_resource_server_id_for_audience(binary() | list() | none) -> binary() | {error, term()}. -get_resource_server_id_for_audience(none) -> - case is_verify_aud() of - true -> - {error, no_matching_aud_found}; - false -> - case get_default_resource_server_id() of - {error, missing_resource_server_id_in_config} -> - {error, mising_audience_in_token_and_resource_server_in_config}; - V -> V - end - end; -get_resource_server_id_for_audience(Audience) -> - case find_audience_in_resource_server_ids(Audience) of - {ok, ResourceServerId} -> - ResourceServerId; - {error, only_one_resource_server_as_audience_found_many} = Error -> - Error; - {error, no_matching_aud_found} -> - case is_verify_aud() of - true -> - {error, no_matching_aud_found}; - false -> - case get_default_resource_server_id() of - {error, missing_resource_server_id_in_config} -> - {error, mising_audience_in_token_and_resource_server_in_config}; - V -> V - end - end - end. - --spec get_oauth_provider_id_for_resource_server_id(binary()) -> oauth_provider_id(). - -get_oauth_provider_id_for_resource_server_id(ResourceServerId) -> - get_oauth_provider_id_for_resource_server_id(get_default_resource_server_id(), - ResourceServerId). -get_oauth_provider_id_for_resource_server_id(TopResourceServerId, - ResourceServerId) when ResourceServerId =:= TopResourceServerId -> - case application:get_env(?APP, default_oauth_provider) of - undefined -> root; - {ok, DefaultOauthProviderId} -> DefaultOauthProviderId - end; -get_oauth_provider_id_for_resource_server_id(TopResourceServerId, - ResourceServerId) when ResourceServerId =/= TopResourceServerId -> - case proplists:get_value(oauth_provider_id, get_resource_server_props(ResourceServerId)) of - undefined -> - case application:get_env(?APP, default_oauth_provider) of - undefined -> root; - {ok, DefaultOauthProviderId} -> DefaultOauthProviderId - end; - OauthProviderId -> OauthProviderId - end. - --spec get_oauth_provider(oauth_provider_id(), list()) -> - {ok, oauth_provider()} | {error, any()}. -get_oauth_provider(OAuthProviderId, RequiredAttributeList) -> - oauth2_client:get_oauth_provider(OAuthProviderId, RequiredAttributeList). - --spec get_algorithms() -> list() | undefined. -get_algorithms() -> - get_algorithms(root). - --spec get_algorithms(oauth_provider_id()) -> list() | undefined. -get_algorithms(root) -> - proplists:get_value(algorithms, application:get_env(?APP, key_config, []), - undefined); -get_algorithms(OAuthProviderId) -> - OAuthProviders = application:get_env(?APP, oauth_providers, #{}), - case maps:get(OAuthProviderId, OAuthProviders, undefined) of - undefined -> undefined; - V -> proplists:get_value(algorithms, V, undefined) - end. - -get_resource_server_props(ResourceServerId) -> - ResourceServers = application:get_env(?APP, resource_servers, #{}), - maps:get(ResourceServerId, ResourceServers, []). - -get_signing_key(KeyId) -> - maps:get(KeyId, get_signing_keys(root), undefined). -get_signing_key(KeyId, OAuthProviderId) -> - maps:get(KeyId, get_signing_keys(OAuthProviderId), undefined). - - -append_or_return_default(ListOrBinary, Default) -> - case ListOrBinary of - VarList when is_list(VarList) -> VarList ++ Default; - VarBinary when is_binary(VarBinary) -> [VarBinary] ++ Default; - _ -> Default - end. - --spec get_default_resource_server_id() -> binary() | {error, term()}. -get_default_resource_server_id() -> - case ?TOP_RESOURCE_SERVER_ID of - undefined -> {error, missing_resource_server_id_in_config }; - {ok, ResourceServerId} -> ResourceServerId - end. - --spec get_allowed_resource_server_ids() -> list(). -get_allowed_resource_server_ids() -> - ResourceServers = application:get_env(?APP, resource_servers, #{}), - rabbit_log:debug("ResourceServers: ~p", [ResourceServers]), - ResourceServerIds = maps:fold(fun(K, V, List) -> List ++ - [proplists:get_value(id, V, K)] end, [], ResourceServers), - rabbit_log:debug("ResourceServersIds: ~p", [ResourceServerIds]), - ResourceServerIds ++ case get_default_resource_server_id() of - {error, _} -> []; - ResourceServerId -> [ ResourceServerId ] - end. - --spec find_audience_in_resource_server_ids(binary() | list()) -> - {ok, binary()} | {error, term()}. -find_audience_in_resource_server_ids(Audience) when is_binary(Audience) -> - find_audience_in_resource_server_ids(binary:split(Audience, <<" ">>, [global, trim_all])); -find_audience_in_resource_server_ids(AudList) when is_list(AudList) -> - AllowedAudList = get_allowed_resource_server_ids(), - case intersection(AudList, AllowedAudList) of - [One] -> {ok, One}; - [_One|_Tail] -> {error, only_one_resource_server_as_audience_found_many}; - [] -> {error, no_matching_aud_found} - end. - --spec is_verify_aud() -> boolean(). -is_verify_aud() -> application:get_env(?APP, verify_aud, true). - --spec is_verify_aud(binary()) -> boolean(). -is_verify_aud(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - is_verify_aud(undefined, ResourceServerId); - V -> - is_verify_aud(V, ResourceServerId) - end. -is_verify_aud(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> is_verify_aud(); -is_verify_aud(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - proplists:get_value(verify_aud, maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}), []), is_verify_aud()). - --spec get_additional_scopes_key() -> {ok, binary()} | {error, not_found}. -get_additional_scopes_key() -> - case application:get_env(?APP, extra_scopes_source, undefined) of - undefined -> {error, not_found}; - ScopeKey -> {ok, ScopeKey} - end. --spec get_additional_scopes_key(binary()) -> {ok, binary()} | {error, not_found}. -get_additional_scopes_key(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - get_additional_scopes_key(undefined, ResourceServerId); - V -> - get_additional_scopes_key(V, ResourceServerId) - end. -get_additional_scopes_key(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> get_additional_scopes_key(); -get_additional_scopes_key(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServer = maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}), []), - case proplists:get_value(extra_scopes_source, ResourceServer) of - undefined -> get_additional_scopes_key(); - <<>> -> get_additional_scopes_key(); - ScopeKey -> {ok, ScopeKey} - end. - --spec get_scope_prefix() -> binary(). -get_scope_prefix() -> - DefaultScopePrefix = case get_default_resource_server_id() of - {error, _} -> <<"">>; - V -> erlang:iolist_to_binary([V, <<".">>]) - end, - application:get_env(?APP, scope_prefix, DefaultScopePrefix). - --spec get_scope_prefix(binary()) -> binary(). -get_scope_prefix(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - get_scope_prefix(undefined, ResourceServerId); - V -> - get_scope_prefix(V, ResourceServerId) - end. -get_scope_prefix(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> get_scope_prefix(); -get_scope_prefix(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServer = maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}), []), - case proplists:get_value(scope_prefix, ResourceServer) of - undefined -> - case application:get_env(?APP, scope_prefix) of - undefined -> <>; - {ok, Prefix} -> Prefix - end; - Prefix -> Prefix - end. - --spec get_resource_server_type() -> binary(). -get_resource_server_type() -> application:get_env(?APP, resource_server_type, <<>>). - --spec get_resource_server_type(binary()) -> binary(). -get_resource_server_type(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - get_resource_server_type(undefined, ResourceServerId); - V -> - get_resource_server_type(V, ResourceServerId) - end. -get_resource_server_type(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> get_resource_server_type(); -get_resource_server_type(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServer = maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}), []), - proplists:get_value(resource_server_type, ResourceServer, - get_resource_server_type()). - --spec has_scope_aliases(binary()) -> boolean(). -has_scope_aliases(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - has_scope_aliases(undefined, ResourceServerId); - V -> - has_scope_aliases(V, ResourceServerId) - end. -has_scope_aliases(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - case application:get_env(?APP, scope_aliases) of - undefined -> false; - _ -> true - end; -has_scope_aliases(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServerProps = maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}),[]), - case proplists:is_defined(scope_aliases, ResourceServerProps) of - true -> true; - false -> has_scope_aliases(TopResourceServerId) - end. - --spec get_scope_aliases(binary()) -> map(). -get_scope_aliases(ResourceServerId) -> - case get_default_resource_server_id() of - {error, _} -> - get_scope_aliases(undefined, ResourceServerId); - V -> - get_scope_aliases(V, ResourceServerId) - end. -get_scope_aliases(TopResourceServerId, ResourceServerId) - when ResourceServerId =:= TopResourceServerId -> - application:get_env(?APP, scope_aliases, #{}); -get_scope_aliases(TopResourceServerId, ResourceServerId) - when ResourceServerId =/= TopResourceServerId -> - ResourceServerProps = maps:get(ResourceServerId, - application:get_env(?APP, resource_servers, #{}),[]), - proplists:get_value(scope_aliases, ResourceServerProps, - get_scope_aliases(TopResourceServerId)). - - -intersection(List1, List2) -> - [I || I <- List1, lists:member(I, List2)]. - -lock() -> - Nodes = rabbit_nodes:list_running(), - Retries = rabbit_nodes:lock_retries(), - LockId = case global:set_lock({oauth2_config_lock, - rabbitmq_auth_backend_oauth2}, Nodes, Retries) of - true -> rabbitmq_auth_backend_oauth2; - false -> {error, unable_to_claim_lock} - end, - LockId. - -unlock(LockId) -> - Nodes = rabbit_nodes:list_running(), - global:del_lock({oauth2_config_lock, LockId}, Nodes), - ok. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl new file mode 100644 index 000000000000..79c056a808a8 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_keycloak.erl @@ -0,0 +1,41 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_keycloak). + +-include("oauth2.hrl"). + +-export([extract_scopes_from_keycloak_format/1, has_keycloak_scopes/1]). +-import(uaa_jwt, [get_scope/1, set_scope/2]). + +-define(AUTHORIZATION_CLAIM, <<"authorization">>). +-define(PERMISSIONS_CLAIM, <<"permissions">>). +-define(SCOPES_CLAIM, <<"scopes">>). + +-spec has_keycloak_scopes(Payload::map()) -> boolean(). +has_keycloak_scopes(Payload) -> + maps:is_key(?AUTHORIZATION_CLAIM, Payload). + +-spec extract_scopes_from_keycloak_format(Payload :: map()) -> map(). +%% keycloak token format: https://github.com/rabbitmq/rabbitmq-auth-backend-oauth2/issues/36 +extract_scopes_from_keycloak_format(#{?AUTHORIZATION_CLAIM := Authorization} = Payload) -> + AdditionalScopes = extract_scopes_from_keycloak_permissions([], + maps:get(?PERMISSIONS_CLAIM, Authorization, [])), + set_scope(AdditionalScopes ++ get_scope(Payload), Payload). + +extract_scopes_from_keycloak_permissions(Acc, []) -> + Acc; +extract_scopes_from_keycloak_permissions(Acc, [H | T]) when is_map(H) -> + Scopes = case maps:get(?SCOPES_CLAIM, H, []) of + ScopesAsList when is_list(ScopesAsList) -> + ScopesAsList; + ScopesAsBinary when is_binary(ScopesAsBinary) -> + [ScopesAsBinary] + end, + extract_scopes_from_keycloak_permissions(Acc ++ Scopes, T); +extract_scopes_from_keycloak_permissions(Acc, [_ | T]) -> + extract_scopes_from_keycloak_permissions(Acc, T). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl new file mode 100644 index 000000000000..2891af5a8b8d --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_provider.erl @@ -0,0 +1,197 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_provider). + +-include("oauth2.hrl"). + +-export([ + get_internal_oauth_provider/0, get_internal_oauth_provider/1, + add_signing_key/2, add_signing_key/3, replace_signing_keys/1, + replace_signing_keys/2, + get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2 +]). + +-spec get_internal_oauth_provider() -> internal_oauth_provider(). +get_internal_oauth_provider() -> + get_internal_oauth_provider(root). + +-spec get_internal_oauth_provider(oauth_provider_id()) -> internal_oauth_provider(). +get_internal_oauth_provider(OAuthProviderId) -> + #internal_oauth_provider{ + id = OAuthProviderId, + default_key = get_default_key(OAuthProviderId), + algorithms = get_algorithms(OAuthProviderId) + }. + + +%% +%% Signing Key storage: +%% +%% * Static signing keys configured via config file are stored under signing_keys attribute +%% in their respective location (under key_config for the root oauth provider and +%% directly under each oauth provider) +%% * Dynamic signing keys loaded via rabbitmqctl or via JWKS endpoint are stored under +%% jwks attribute in their respective location. However, this attribute stores the +%% combination of static signing keys and dynamic signing keys. If the same kid is +%% found in both sets, the dynamic kid overrides the static kid. +%% + +-type key_type() :: json | pem | map. +-spec add_signing_key(binary(), {key_type(), binary()} ) -> map() | {error, term()}. +add_signing_key(KeyId, Key) -> + LockId = lock(), + try do_add_signing_key(KeyId, Key, root) of + V -> V + after + unlock(LockId) + end. + +-spec add_signing_key(binary(), {key_type(), binary()}, oauth_provider_id()) -> + map() | {error, term()}. +add_signing_key(KeyId, Key, OAuthProviderId) -> + case lock() of + {error, _} = Error -> + Error; + LockId -> + try do_add_signing_key(KeyId, Key, OAuthProviderId) of + V -> V + after + unlock(LockId) + end + end. + +do_add_signing_key(KeyId, Key, OAuthProviderId) -> + do_replace_signing_keys(maps:put(KeyId, Key, + get_signing_keys_from_jwks(OAuthProviderId)), OAuthProviderId). + +get_signing_keys_from_jwks(root) -> + KeyConfig = get_env(key_config, []), + proplists:get_value(jwks, KeyConfig, #{}); +get_signing_keys_from_jwks(OAuthProviderId) -> + OAuthProviders0 = get_env(oauth_providers, #{}), + OAuthProvider0 = maps:get(OAuthProviderId, OAuthProviders0, []), + proplists:get_value(jwks, OAuthProvider0, #{}). + +-spec replace_signing_keys(map()) -> map() | {error, term()}. +replace_signing_keys(SigningKeys) -> + replace_signing_keys(SigningKeys, root). + +-spec replace_signing_keys(map(), oauth_provider_id()) -> map() | {error, term()}. +replace_signing_keys(SigningKeys, OAuthProviderId) -> + case lock() of + {error,_} = Error -> + Error; + LockId -> + try do_replace_signing_keys(SigningKeys, OAuthProviderId) of + V -> V + after + unlock(LockId) + end + end. + +do_replace_signing_keys(SigningKeys, root) -> + KeyConfig = get_env(key_config, []), + KeyConfig1 = proplists:delete(jwks, KeyConfig), + KeyConfig2 = [{jwks, maps:merge( + proplists:get_value(signing_keys, KeyConfig1, #{}), + SigningKeys)} | KeyConfig1], + set_env(key_config, KeyConfig2), + rabbit_log:debug("Replacing signing keys for key_config with ~p keys", + [maps:size(SigningKeys)]), + SigningKeys; + +do_replace_signing_keys(SigningKeys, OauthProviderId) -> + OauthProviders0 = get_env(oauth_providers, #{}), + OauthProvider0 = maps:get(OauthProviderId, OauthProviders0, []), + OauthProvider1 = proplists:delete(jwks, OauthProvider0), + OauthProvider = [{jwks, maps:merge( + proplists:get_value(signing_keys, OauthProvider1, #{}), + SigningKeys)} | OauthProvider1], + + OauthProviders = maps:put(OauthProviderId, OauthProvider, OauthProviders0), + set_env(oauth_providers, OauthProviders), + rabbit_log:debug("Replacing signing keys for ~p -> ~p with ~p keys", + [OauthProviderId, OauthProvider, maps:size(SigningKeys)]), + SigningKeys. + + +-spec get_signing_keys() -> map(). +get_signing_keys() -> + get_signing_keys(root). + +-spec get_signing_keys(oauth_provider_id()) -> map(). +get_signing_keys(root) -> + case get_env(key_config) of + undefined -> + #{}; + KeyConfig -> + case proplists:get_value(jwks, KeyConfig, undefined) of + undefined -> proplists:get_value(signing_keys, KeyConfig, #{}); + Jwks -> Jwks + end + end; +get_signing_keys(OauthProviderId) -> + OauthProviders = get_env(oauth_providers, #{}), + OauthProvider = maps:get(OauthProviderId, OauthProviders, []), + case proplists:get_value(jwks, OauthProvider, undefined) of + undefined -> + proplists:get_value(signing_keys, OauthProvider, #{}); + Jwks -> + Jwks + end. + +get_signing_key(KeyId) -> + maps:get(KeyId, get_signing_keys(root), undefined). +get_signing_key(KeyId, OAuthProviderId) -> + maps:get(KeyId, get_signing_keys(OAuthProviderId), undefined). + +-spec get_default_key(oauth_provider_id()) -> binary() | undefined. +get_default_key(root) -> + case application:get_env(?APP, key_config, undefined) of + undefined -> undefined; + KeyConfig -> proplists:get_value(default_key, KeyConfig, undefined) + end; +get_default_key(OauthProviderId) -> + OauthProviders = application:get_env(?APP, oauth_providers, #{}), + case maps:get(OauthProviderId, OauthProviders, []) of + [] -> undefined; + OauthProvider -> proplists:get_value(default_key, OauthProvider, undefined) + end. + +-spec get_algorithms(oauth_provider_id()) -> list() | undefined. +get_algorithms(root) -> + proplists:get_value(algorithms, get_env(key_config, []), undefined); +get_algorithms(OAuthProviderId) -> + OAuthProviders = get_env(oauth_providers, #{}), + case maps:get(OAuthProviderId, OAuthProviders, undefined) of + undefined -> undefined; + V -> proplists:get_value(algorithms, V, undefined) + end. + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Value) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Value). + + +lock() -> + Nodes = rabbit_nodes:list_running(), + Retries = rabbit_nodes:lock_retries(), + LockId = case global:set_lock({oauth2_config_lock, + rabbitmq_auth_backend_oauth2}, Nodes, Retries) of + true -> rabbitmq_auth_backend_oauth2; + false -> {error, unable_to_claim_lock} + end, + LockId. + +unlock(LockId) -> + Nodes = rabbit_nodes:list_running(), + global:del_lock({oauth2_config_lock, LockId}, Nodes), + ok. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl new file mode 100644 index 000000000000..d8a2c36f8325 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_rar.erl @@ -0,0 +1,183 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +% Rich Authorization Request +-module(rabbit_oauth2_rar). + +-include("oauth2.hrl"). +-import(uaa_jwt, [get_scope/1, set_scope/2]). + +-export([extract_scopes_from_rich_auth_request/2, has_rich_auth_request_scopes/1]). + +-define(AUTHORIZATION_DETAILS_CLAIM, <<"authorization_details">>). +-define(RAR_ACTIONS_FIELD, <<"actions">>). +-define(RAR_LOCATIONS_FIELD, <<"locations">>). +-define(RAR_TYPE_FIELD, <<"type">>). + +-define(RAR_CLUSTER_LOCATION_ATTRIBUTE, <<"cluster">>). +-define(RAR_VHOST_LOCATION_ATTRIBUTE, <<"vhost">>). +-define(RAR_QUEUE_LOCATION_ATTRIBUTE, <<"queue">>). +-define(RAR_EXCHANGE_LOCATION_ATTRIBUTE, <<"exchange">>). +-define(RAR_ROUTING_KEY_LOCATION_ATTRIBUTE, <<"routing-key">>). +-define(RAR_LOCATION_ATTRIBUTES, [ + ?RAR_CLUSTER_LOCATION_ATTRIBUTE, + ?RAR_VHOST_LOCATION_ATTRIBUTE, + ?RAR_QUEUE_LOCATION_ATTRIBUTE, + ?RAR_EXCHANGE_LOCATION_ATTRIBUTE, + ?RAR_ROUTING_KEY_LOCATION_ATTRIBUTE]). + +-define(RAR_ALLOWED_TAG_VALUES, [ + <<"monitoring">>, + <<"administrator">>, + <<"management">>, + <<"policymaker">> ]). +-define(RAR_ALLOWED_ACTION_VALUES, [ + <<"read">>, + <<"write">>, + <<"configure">>, + <<"monitoring">>, + <<"administrator">>, + <<"management">>, + <<"policymaker">> ]). + +-spec has_rich_auth_request_scopes(Payload::map()) -> boolean(). +has_rich_auth_request_scopes(Payload) -> + maps:is_key(?AUTHORIZATION_DETAILS_CLAIM, Payload). + +-spec extract_scopes_from_rich_auth_request(ResourceServer :: resource_server(), + Payload :: map()) -> map(). +%% https://oauth.net/2/rich-authorization-requests/ +extract_scopes_from_rich_auth_request(ResourceServer, + #{?AUTHORIZATION_DETAILS_CLAIM := Permissions} = Payload) -> + ResourceServerType = ResourceServer#resource_server.resource_server_type, + + FilteredPermissionsByType = lists:filter(fun(P) -> + is_recognized_permission(P, ResourceServerType) end, Permissions), + AdditionalScopes = map_rich_auth_permissions_to_scopes( + ResourceServer#resource_server.id, FilteredPermissionsByType), + + ExistingScopes = get_scope(Payload), + set_scope(AdditionalScopes ++ ExistingScopes, Payload). + +put_location_attribute(Attribute, Map) -> + put_attribute(binary:split(Attribute, <<":">>, [global, trim_all]), Map). + +put_attribute([Key, Value | _], Map) -> + case lists:member(Key, ?RAR_LOCATION_ATTRIBUTES) of + true -> maps:put(Key, Value, Map); + false -> Map + end; +put_attribute([_|_], Map) -> Map. + + +% convert [ <<"cluster:A">>, <<"vhost:B" >>, <<"A">>, <<"unknown:C">> ] to #{ <<"cluster">> : <<"A">>, <<"vhost">> : <<"B">> } +% filtering out non-key-value-pairs and keys which are not part of LOCATION_ATTRIBUTES +convert_attribute_list_to_attribute_map(L) -> + convert_attribute_list_to_attribute_map(L, #{}). +convert_attribute_list_to_attribute_map([H|L],Map) when is_binary(H) -> + convert_attribute_list_to_attribute_map(L, put_location_attribute(H,Map)); +convert_attribute_list_to_attribute_map([], Map) -> Map. + +build_permission_resource_path(Map) -> + Vhost = maps:get(?RAR_VHOST_LOCATION_ATTRIBUTE, Map, <<"*">>), + Resource = maps:get(?RAR_QUEUE_LOCATION_ATTRIBUTE, Map, + maps:get(?RAR_EXCHANGE_LOCATION_ATTRIBUTE, Map, <<"*">>)), + RoutingKey = maps:get(?RAR_ROUTING_KEY_LOCATION_ATTRIBUTE, Map, <<"*">>), + + <>. + +map_locations_to_permission_resource_paths(ResourceServerId, L) -> + Locations = case L of + undefined -> []; + LocationsAsList when is_list(LocationsAsList) -> + lists:map(fun(Location) -> convert_attribute_list_to_attribute_map( + binary:split(Location,<<"/">>,[global,trim_all])) end, LocationsAsList); + LocationsAsBinary when is_binary(LocationsAsBinary) -> + [convert_attribute_list_to_attribute_map( + binary:split(LocationsAsBinary,<<"/">>,[global,trim_all]))] + end, + + FilteredLocations = lists:filtermap(fun(L2) -> + case cluster_matches_resource_server_id(L2, ResourceServerId) and + legal_queue_and_exchange_values(L2) of + true -> { true, build_permission_resource_path(L2) }; + false -> false + end end, Locations), + + FilteredLocations. + +cluster_matches_resource_server_id(#{?RAR_CLUSTER_LOCATION_ATTRIBUTE := Cluster}, + ResourceServerId) -> + wildcard:match(ResourceServerId, Cluster); + +cluster_matches_resource_server_id(_,_) -> + false. + +legal_queue_and_exchange_values(#{?RAR_QUEUE_LOCATION_ATTRIBUTE := Queue, + ?RAR_EXCHANGE_LOCATION_ATTRIBUTE := Exchange}) -> + case Queue of + <<>> -> + case Exchange of + <<>> -> true; + _ -> false + end; + _ -> + case Exchange of + Queue -> true; + _ -> false + end + end; +legal_queue_and_exchange_values(_) -> true. + +map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions) -> + map_rich_auth_permissions_to_scopes(ResourceServerId, Permissions, []). +map_rich_auth_permissions_to_scopes(_, [], Acc) -> Acc; +map_rich_auth_permissions_to_scopes(ResourceServerId, + [ #{?RAR_ACTIONS_FIELD := Actions, ?RAR_LOCATIONS_FIELD := Locations } | T ], Acc) -> + ResourcePaths = map_locations_to_permission_resource_paths(ResourceServerId, Locations), + case ResourcePaths of + [] -> map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc); + _ -> + Scopes = case Actions of + undefined -> []; + ActionsAsList when is_list(ActionsAsList) -> + build_scopes(ResourceServerId, + skip_unknown_actions(ActionsAsList), ResourcePaths); + ActionsAsBinary when is_binary(ActionsAsBinary) -> + build_scopes(ResourceServerId, + skip_unknown_actions([ActionsAsBinary]), ResourcePaths) + end, + map_rich_auth_permissions_to_scopes(ResourceServerId, T, Acc ++ Scopes) + end. + +skip_unknown_actions(Actions) -> + lists:filter(fun(A) -> lists:member(A, ?RAR_ALLOWED_ACTION_VALUES) end, Actions). + +produce_list_of_user_tag_or_action_on_resources(ResourceServerId, ActionOrUserTag, Locations) -> + case lists:member(ActionOrUserTag, ?RAR_ALLOWED_TAG_VALUES) of + true -> [<< ResourceServerId/binary, ".tag:", ActionOrUserTag/binary >>]; + _ -> build_scopes_for_action(ResourceServerId, ActionOrUserTag, Locations, []) + end. + +build_scopes_for_action(ResourceServerId, Action, [Location|Locations], Acc) -> + Scope = << ResourceServerId/binary, ".", Action/binary, ":", Location/binary >>, + build_scopes_for_action(ResourceServerId, Action, Locations, [ Scope | Acc ] ); +build_scopes_for_action(_, _, [], Acc) -> Acc. + +build_scopes(ResourceServerId, Actions, Locations) -> + lists:flatmap(fun(Action) -> + produce_list_of_user_tag_or_action_on_resources(ResourceServerId, + Action, Locations) end, Actions). + +is_recognized_permission(#{?RAR_ACTIONS_FIELD := _, ?RAR_LOCATIONS_FIELD:= _ , + ?RAR_TYPE_FIELD := Type }, ResourceServerType) -> + case ResourceServerType of + <<>> -> false; + V when V == Type -> true; + _ -> false + end; +is_recognized_permission(_, _) -> false. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl new file mode 100644 index 000000000000..84675df7c96d --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_resource_server.erl @@ -0,0 +1,240 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_resource_server). + +-include("oauth2.hrl"). + +-export([ + resolve_resource_server_from_audience/1, + new_resource_server/1 +]). + +-spec new_resource_server(resource_server_id()) -> resource_server(). +new_resource_server(ResourceServerId) -> + #resource_server{ + id = ResourceServerId, + resource_server_type = undefined, + verify_aud = true, + scope_prefix = erlang:iolist_to_binary([ResourceServerId, <<".">>]), + additional_scopes_key = undefined, + preferred_username_claims = ?DEFAULT_PREFERRED_USERNAME_CLAIMS, + scope_aliases = undefined, + oauth_provider_id = root + }. + +-spec resolve_resource_server_from_audience(binary() | list() | none) -> + {ok, resource_server()} | + {error, aud_matched_many_resource_servers_only_one_allowed} | + {error, no_matching_aud_found} | + {error, no_aud_found} | + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers} | + {error, too_many_resources_with_verify_aud_false}. +resolve_resource_server_from_audience(none) -> + translate_error_if_any( + find_unique_resource_server_without_verify_aud(), false); + +resolve_resource_server_from_audience(Audience) -> + RootResourseServerId = get_root_resource_server_id(), + ResourceServers = get_env(resource_servers, #{}), + ResourceServerIds = maps:fold(fun(K, V, List) -> List ++ + [proplists:get_value(id, V, K)] end, [], ResourceServers), + AllowedResourceServerIds = append(ResourceServerIds, RootResourseServerId), + + case find_audience(Audience, AllowedResourceServerIds) of + {error, aud_matched_many_resource_servers_only_one_allowed} = Error -> + Error; + {error, no_matching_aud_found} -> + translate_error_if_any( + find_unique_resource_server_without_verify_aud(), + true); + {ok, ResourceServerId} -> + {ok, get_resource_server(ResourceServerId)} + end. + +-spec get_root_resource_server_id() -> resource_server_id(). +get_root_resource_server_id() -> + get_env(resource_server_id, <<>>). + +-spec get_root_resource_server() -> resource_server(). +get_root_resource_server() -> + ResourceServerId = + get_root_resource_server_id(), + ScopeAliases = + get_env(scope_aliases), + PreferredUsernameClaims = + case get_env(preferred_username_claims) of + undefined -> ?DEFAULT_PREFERRED_USERNAME_CLAIMS; + Value -> + Value + end, + ResourceServerType = + get_env(resource_server_type), + VerifyAud = + get_boolean_env(verify_aud, true), + AdditionalScopesKey = + get_env(extra_scopes_source), + DefaultScopePrefix = + case ResourceServerId of + <<>> -> undefined; + _ -> erlang:iolist_to_binary([ResourceServerId, <<".">>]) + end, + ScopePrefix = + get_env(scope_prefix, DefaultScopePrefix), + OAuthProviderId = + case get_env(default_oauth_provider) of + undefined -> root; + DefaultOauthProviderId -> DefaultOauthProviderId + end, + + #resource_server{ + id = ResourceServerId, + resource_server_type = ResourceServerType, + verify_aud = VerifyAud, + scope_prefix = ScopePrefix, + additional_scopes_key = AdditionalScopesKey, + preferred_username_claims = PreferredUsernameClaims, + scope_aliases = ScopeAliases, + oauth_provider_id = OAuthProviderId + }. + +-spec get_resource_server(resource_server_id()) -> resource_server() | undefined. +get_resource_server(ResourceServerId) -> + RootResourseServer = get_root_resource_server(), + RootResourseServerId = RootResourseServer#resource_server.id, + case ResourceServerId of + <<>> -> undefined; + RootResourseServerId -> RootResourseServer; + _ -> get_resource_server(ResourceServerId, RootResourseServer) + end. + +-spec get_resource_server(ResourceServerId :: resource_server_id(), + DefaultResourceServerSettings :: resource_server()) -> resource_server(). +get_resource_server(ResourceServerId, RootResourseServer) when + ResourceServerId == RootResourseServer#resource_server.id -> + RootResourseServer; +get_resource_server(ResourceServerId, RootResourseServer) when + ResourceServerId =/= RootResourseServer#resource_server.id -> + ResourceServerProps = + maps:get(ResourceServerId, get_env(resource_servers, #{}), []), + ScopeAliases = + proplists:get_value(scope_aliases, ResourceServerProps, + RootResourseServer#resource_server.scope_aliases), + PreferredUsernameClaims = + proplists:get_value(preferred_username_claims, ResourceServerProps, + RootResourseServer#resource_server.preferred_username_claims), + ResourceServerType = + proplists:get_value(resource_server_type, ResourceServerProps, + RootResourseServer#resource_server.resource_server_type), + VerifyAud = + proplists:get_value(verify_aud, ResourceServerProps, + RootResourseServer#resource_server.verify_aud), + AdditionalScopesKey = + proplists:get_value(extra_scopes_source, ResourceServerProps, + RootResourseServer#resource_server.additional_scopes_key), + RootScopePrefix = get_env(scope_prefix, undefined), + ScopePrefix = + proplists:get_value(scope_prefix, ResourceServerProps, + case RootScopePrefix of + undefined -> erlang:iolist_to_binary([ResourceServerId, <<".">>]); + Prefix -> Prefix + end), + OAuthProviderId = + proplists:get_value(oauth_provider_id, ResourceServerProps, + RootResourseServer#resource_server.oauth_provider_id), + + #resource_server{ + id = ResourceServerId, + resource_server_type = ResourceServerType, + verify_aud = VerifyAud, + scope_prefix = ScopePrefix, + additional_scopes_key = AdditionalScopesKey, + preferred_username_claims = PreferredUsernameClaims, + scope_aliases = ScopeAliases, + oauth_provider_id = OAuthProviderId + }. + +-spec find_audience(binary() | list(), list()) -> + {ok, resource_server_id()} | + {error, aud_matched_many_resource_servers_only_one_allowed} | + {error, no_matching_aud_found}. +find_audience(Audience, ResourceIdList) when is_binary(Audience) -> + AudList = binary:split(Audience, <<" ">>, [global, trim_all]), + find_audience(AudList, ResourceIdList); +find_audience(AudList, ResourceIdList) when is_list(AudList) -> + case intersection(AudList, ResourceIdList) of + [One] -> {ok, One}; + [_One|_Tail] -> {error, aud_matched_many_resource_servers_only_one_allowed}; + [] -> {error, no_matching_aud_found} + end. + +-spec translate_error_if_any( + {ok, resource_server()} | + {error, not_found} | + {error, found_many}, boolean()) -> + {ok, resource_server()} | + {error, no_aud_found} | + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers} | + {error, no_matching_aud_found} | + {error, too_many_resources_with_verify_aud_false}. +translate_error_if_any(ResourceServerOrError, HasAudience) -> + case {ResourceServerOrError, HasAudience} of + {{ok, _} = Ok, _} -> + Ok; + {{error, not_found}, false} -> + {error, no_aud_found}; + {{error, not_found}, _} -> + {error, no_matching_aud_found}; + {{error, found_many}, false} -> + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers}; + {{error, found_many}, _} -> + {error, too_many_resources_with_verify_aud_false} + end. +-spec find_unique_resource_server_without_verify_aud() -> + {ok, resource_server()} | + {error, not_found} | + {error, found_many}. +find_unique_resource_server_without_verify_aud() -> + Root = get_root_resource_server(), + Map0 = maps:filter(fun(_K,V) -> not get_boolean_value(verify_aud, V, + Root#resource_server.verify_aud) end, get_env(resource_servers, #{})), + Map = case {Root#resource_server.id, Root#resource_server.verify_aud} of + {<<>>, _} -> Map0; + {_, true} -> Map0; + {Id, false} -> maps:put(Id, Root, Map0) + end, + case maps:size(Map) of + 0 -> {error, not_found}; + 1 -> {ok, get_resource_server(lists:last(maps:keys(Map)), Root)}; + _ -> {error, found_many} + end. + +append(List, Value) -> + case Value of + <<>> -> List; + _ -> List ++ [Value] + end. +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +-spec get_boolean_env(atom(), boolean()) -> boolean(). +get_boolean_env(Par, Def) -> + case get_env(Par, Def) of + true -> true; + false -> false; + _ -> true + end. +-spec get_boolean_value(term(), list(), boolean()) -> boolean(). +get_boolean_value(Key, Proplist, Def) -> + case proplists:get_value(Key, Proplist, Def) of + true -> true; + false -> false; + _ -> true + end. +intersection(List1, List2) -> + [I || I <- List1, lists:member(I, List2)]. diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl index d79972509ba0..72642a43dc1e 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_schema.erl @@ -11,7 +11,8 @@ -export([ translate_oauth_providers/1, translate_resource_servers/1, - translate_signing_keys/1 + translate_signing_keys/1, + translate_endpoint_params/2 ]). extract_key_as_binary({Name,_}) -> list_to_binary(Name). @@ -19,7 +20,8 @@ extract_value({_Name,V}) -> V. -spec translate_resource_servers([{list(), binary()}]) -> map(). translate_resource_servers(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.resource_servers", Conf), + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.resource_servers", + Conf), Map = merge_list_of_maps([ extract_resource_server_properties(Settings), extract_resource_server_preferred_username_claims(Settings) @@ -30,23 +32,30 @@ translate_resource_servers(Conf) -> _ -> V end end, Map), ResourceServers = maps:values(Map0), - lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, + lists:foldl(fun(Elem,AccMap) -> + maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, ResourceServers). -spec translate_oauth_providers([{list(), binary()}]) -> map(). translate_oauth_providers(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.oauth_providers", Conf), + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.oauth_providers", + Conf), merge_list_of_maps([ extract_oauth_providers_properties(Settings), + extract_oauth_providers_endpoint_params(discovery_endpoint_params, + Settings), extract_oauth_providers_algorithm(Settings), extract_oauth_providers_https(Settings), - extract_oauth_providers_signing_keys(Settings)]). + extract_oauth_providers_signing_keys(Settings) + ]). -spec translate_signing_keys([{list(), binary()}]) -> map(). translate_signing_keys(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.signing_keys", Conf), - ListOfKidPath = lists:map(fun({Id, Path}) -> {list_to_binary(lists:last(Id)), Path} end, Settings), + Settings = cuttlefish_variable:filter_by_prefix("auth_oauth2.signing_keys", + Conf), + ListOfKidPath = lists:map(fun({Id, Path}) -> { + list_to_binary(lists:last(Id)), Path} end, Settings), translate_list_of_signing_keys(ListOfKidPath). -spec translate_list_of_signing_keys([{list(), list()}]) -> map(). @@ -58,10 +67,20 @@ translate_list_of_signing_keys(ListOfKidPath) -> string:trim(Bin, trailing, "\n"); _Error -> %% this throws and makes Cuttlefish treak the key as invalid - cuttlefish:invalid("file does not exist or cannot be read by the node") + cuttlefish:invalid("file does not exist or cannot be " ++ + "read by the node") end end, - maps:map(fun(_K, Path) -> {pem, TryReadingFileFun(Path)} end, maps:from_list(ListOfKidPath)). + maps:map(fun(_K, Path) -> {pem, TryReadingFileFun(Path)} end, + maps:from_list(ListOfKidPath)). + +-spec translate_endpoint_params(list(), [{list(), binary()}]) -> + [{binary(), binary()}]. +translate_endpoint_params(Variable, Conf) -> + Params0 = cuttlefish_variable:filter_by_prefix("auth_oauth2." ++ Variable, + Conf), + [{list_to_binary(Param), list_to_binary(V)} || {["auth_oauth2", _, Param], V} + <- Params0]. validator_file_exists(Attr, Filename) -> case file:read_file(Filename) of @@ -70,17 +89,30 @@ validator_file_exists(Attr, Filename) -> _Error -> %% this throws and makes Cuttlefish treak the key as invalid cuttlefish:invalid(io_lib:format( - "Invalid attribute (~p) value: file ~p does not exist or cannot be read by the node", [Attr, Filename])) + "Invalid attribute (~p) value: file ~p does not exist or " ++ + "cannot be read by the node", [Attr, Filename])) end. + +validator_uri(Attr, Uri) when is_binary(Uri) -> + validator_uri(Attr, binary_to_list(Uri)); +validator_uri(Attr, Uri) when is_list(Uri) -> + case uri_string:parse(Uri) of + {error, _, _} = Error -> + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: ~p (~p)", [Attr, Uri, Error])); + _ -> Uri + end. + validator_https_uri(Attr, Uri) when is_binary(Uri) -> - list_to_binary(validator_https_uri(Attr, binary_to_list(Uri))); + validator_https_uri(Attr, binary_to_list(Uri)); -validator_https_uri(Attr, Uri) -> +validator_https_uri(Attr, Uri) when is_list(Uri) -> case string:nth_lexeme(Uri, 1, "://") == "https" of true -> Uri; false -> cuttlefish:invalid(io_lib:format( - "Invalid attribute (~p) value: uri ~p must be a valid https uri", [Attr, Uri])) + "Invalid attribute (~p) value: uri ~p must be a valid https uri", + [Attr, Uri])) end. merge_list_of_maps(ListOfMaps) -> @@ -91,10 +123,12 @@ extract_oauth_providers_properties(Settings) -> KeyFun = fun extract_key_as_binary/1, ValueFun = fun extract_value/1, - OAuthProviders = [{Name, mapOauthProviderProperty({list_to_atom(Key), list_to_binary(V)})} - || {["auth_oauth2","oauth_providers", Name, Key], V} <- Settings ], + OAuthProviders = [ + {Name, mapOauthProviderProperty({list_to_atom(Key), list_to_binary(V)})} + || {["auth_oauth2", "oauth_providers", Name, Key], V} <- Settings], maps:groups_from_list(KeyFun, ValueFun, OAuthProviders). + extract_resource_server_properties(Settings) -> KeyFun = fun extract_key_as_binary/1, ValueFun = fun extract_value/1, @@ -110,6 +144,11 @@ mapOauthProviderProperty({Key, Value}) -> jwks_uri -> validator_https_uri(Key, Value); end_session_endpoint -> validator_https_uri(Key, Value); authorization_endpoint -> validator_https_uri(Key, Value); + discovery_endpoint_path -> validator_uri(Key, Value); + discovery_endpoint_params -> + cuttlefish:invalid(io_lib:format( + "Invalid attribute (~p) value: should be a map of Key,Value pairs", + [Key])); _ -> Value end}. @@ -120,7 +159,8 @@ extract_oauth_providers_https(Settings) -> {["auth_oauth2","oauth_providers", Name, "https", Key], V} <- Settings ], maps:map(fun(_K,V)-> [{https, V}] end, - maps:groups_from_list(ExtractProviderNameFun, fun({_, V}) -> V end, AttributesPerProvider)). + maps:groups_from_list(ExtractProviderNameFun, fun({_, V}) -> V end, + AttributesPerProvider)). mapHttpProperty({Key, Value}) -> {Key, case Key of @@ -132,8 +172,10 @@ extract_oauth_providers_algorithm(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedAlgorithms = [{Name, {Index, list_to_binary(V)}} || - {["auth_oauth2","oauth_providers", Name, "algorithms", Index], V} <- Settings ], - SortedAlgorithms = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedAlgorithms), + {["auth_oauth2","oauth_providers", Name, "algorithms", Index], V} + <- Settings ], + SortedAlgorithms = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, + IndexedAlgorithms), Algorithms = [{Name, V} || {Name, {_I, V}} <- SortedAlgorithms], maps:map(fun(_K,V)-> [{algorithms, V}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Algorithms)). @@ -142,16 +184,28 @@ extract_resource_server_preferred_username_claims(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedClaims = [{Name, {Index, list_to_binary(V)}} || - {["auth_oauth2","resource_servers", Name, "preferred_username_claims", Index], V} <- Settings ], - SortedClaims = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, IndexedClaims), + {["auth_oauth2","resource_servers", Name, "preferred_username_claims", + Index], V} <- Settings ], + SortedClaims = lists:sort(fun({_,{AI,_}},{_,{BI,_}}) -> AI < BI end, + IndexedClaims), Claims = [{Name, V} || {Name, {_I, V}} <- SortedClaims], maps:map(fun(_K,V)-> [{preferred_username_claims, V}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, Claims)). +extract_oauth_providers_endpoint_params(Variable, Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedParams = [{Name, {list_to_binary(ParamName), list_to_binary(V)}} || + {["auth_oauth2","oauth_providers", Name, EndpointVar, ParamName], V} + <- Settings, EndpointVar == atom_to_list(Variable) ], + maps:map(fun(_K,V)-> [{Variable, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedParams)). + extract_oauth_providers_signing_keys(Settings) -> KeyFun = fun extract_key_as_binary/1, IndexedSigningKeys = [{Name, {list_to_binary(Kid), list_to_binary(V)}} || - {["auth_oauth2","oauth_providers", Name, "signing_keys", Kid], V} <- Settings ], + {["auth_oauth2","oauth_providers", Name, "signing_keys", Kid], V} + <- Settings ], maps:map(fun(_K,V)-> [{signing_keys, translate_list_of_signing_keys(V)}] end, maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedSigningKeys)). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl index d81c7ded0c8f..e9cc75d37d19 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/rabbit_oauth2_scope.erl @@ -7,7 +7,11 @@ -module(rabbit_oauth2_scope). --export([vhost_access/2, resource_access/3, topic_access/4, concat_scopes/2]). +-export([vhost_access/2, + resource_access/3, + topic_access/4, + concat_scopes/2, + filter_matching_scope_prefix_and_drop_it/2]). -include_lib("rabbit_common/include/rabbit.hrl"). @@ -88,3 +92,20 @@ parse_resource_pattern(Pattern, Permission) -> {VhostPattern, NamePattern, RoutingKeyPattern, 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) -> + PatternLength = byte_size(PrefixPattern), + lists:filtermap( + fun(ScopeEl) -> + case binary:match(ScopeEl, PrefixPattern) of + {0, PatternLength} -> + ElLength = byte_size(ScopeEl), + {true, + binary:part(ScopeEl, + {PatternLength, ElLength - PatternLength})}; + _ -> false + end + end, + Scopes). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl index edd81902da15..fd6c0b1cfc24 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwks.erl @@ -1,7 +1,7 @@ -module(uaa_jwks). -export([get/2]). --spec get(string() | binary(), term()) -> {ok, term()} | {error, term()}. +-spec get(uri_string:uri_string(), list()) -> {ok, term()} | {error, term()}. get(JwksUrl, SslOptions) -> Options = [{timeout, 60000}] ++ [{ssl, SslOptions}], httpc:request(get, {JwksUrl, []}, Options, []). diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl index eafaa2122c74..46a46cd41176 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt.erl @@ -7,16 +7,28 @@ -module(uaa_jwt). -export([add_signing_key/3, - decode_and_verify/1, + decode_and_verify/3, get_jwk/2, - verify_signing_key/2]). + verify_signing_key/2, + resolve_resource_server/1]). --export([client_id/1, sub/1, client_id/2, sub/2]). +-export([client_id/1, sub/1, client_id/2, sub/2, get_scope/1, set_scope/2]). +-include("oauth2.hrl"). -include_lib("jose/include/jose_jwk.hrl"). --include_lib("oauth2_client/include/oauth2_client.hrl"). --define(APP, rabbitmq_auth_backend_oauth2). +-import(rabbit_data_coercion, [ + to_map/1]). +-import(oauth2_client, [ + format_ssl_options/1, + format_oauth_provider_id/1, + get_oauth_provider/2]). +-import(rabbit_oauth2_resource_server, [ + resolve_resource_server_from_audience/1]). +-import(rabbit_oauth2_provider, [ + add_signing_key/2, get_signing_key/2, + get_internal_oauth_provider/1, + replace_signing_keys/2]). -type key_type() :: json | pem | map. @@ -24,7 +36,7 @@ add_signing_key(KeyId, Type, Value) -> case verify_signing_key(Type, Value) of ok -> - {ok, rabbit_oauth2_config:add_signing_key(KeyId, {Type, Value})}; + {ok, add_signing_key(KeyId, {Type, Value})}; {error, _} = Err -> Err end. @@ -32,91 +44,101 @@ add_signing_key(KeyId, Type, Value) -> -spec update_jwks_signing_keys(oauth_provider()) -> ok | {error, term()}. update_jwks_signing_keys(#oauth_provider{id = Id, jwks_uri = JwksUrl, ssl_options = SslOptions}) -> - rabbit_log:debug("OAuth 2 JWT: downloading keys from ~tp (TLS options: ~p)", - [JwksUrl, SslOptions]), + rabbit_log:debug("Downloading signing keys from ~tp (TLS options: ~p)", + [JwksUrl, format_ssl_options(SslOptions)]), case uaa_jwks:get(JwksUrl, SslOptions) of {ok, {_, _, JwksBody}} -> KeyList = maps:get(<<"keys">>, jose:decode(erlang:iolist_to_binary(JwksBody)), []), Keys = maps:from_list(lists:map(fun(Key) -> {maps:get(<<"kid">>, Key, undefined), {json, Key}} end, KeyList)), - rabbit_log:debug("OAuth 2 JWT: downloaded keys ~tp", [Keys]), - case rabbit_oauth2_config:replace_signing_keys(Keys, Id) of + rabbit_log:debug("Downloaded ~p signing keys", [maps:size(Keys)]), + case replace_signing_keys(Keys, Id) of {error, _} = Err -> Err; _ -> ok end; {error, _} = Err -> - rabbit_log:error("OAuth 2 JWT: failed to download keys: ~tp", [Err]), + rabbit_log:error("Failed to download signing keys: ~tp", [Err]), Err end. --spec decode_and_verify(binary()) -> {boolean(), binary(), map()} | {error, term()}. -decode_and_verify(Token) -> - case resolve_resource_server_id(Token) of - {error, _} = Err -> - Err; - ResourceServerId -> - OAuthProviderId = - rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(ResourceServerId), - rabbit_log:debug("OAuth 2 JWT: resolved resource_server_id: ~p oauth_provider_id: ~p", - [ResourceServerId, OAuthProviderId]), - case uaa_jwt_jwt:get_key_id(rabbit_oauth2_config:get_default_key(OAuthProviderId), Token) of - {ok, KeyId} -> - rabbit_log:debug("OAuth 2 JWT: signing_key_id : '~tp'", [KeyId]), - case get_jwk(KeyId, OAuthProviderId) of - {ok, JWK} -> - case uaa_jwt_jwt:decode_and_verify( - OAuthProviderId, - JWK, - Token) of - {true, Payload} -> {true, ResourceServerId, Payload}; - {false, Payload} -> {false, ResourceServerId, Payload} - end; - {error, _} = Err -> - Err - end; - {error, _} = Err -> Err - end +-spec decode_and_verify(binary(), resource_server(), internal_oauth_provider()) + -> {boolean(), map()} | {error, term()}. +decode_and_verify(Token, ResourceServer, InternalOAuthProvider) -> + OAuthProviderId = InternalOAuthProvider#internal_oauth_provider.id, + rabbit_log:debug("Decoding token for resource_server: ~p using oauth_provider_id: ~p", + [ResourceServer#resource_server.id, + format_oauth_provider_id(OAuthProviderId)]), + Result = case uaa_jwt_jwt:get_key_id(Token) of + undefined -> InternalOAuthProvider#internal_oauth_provider.default_key; + {ok, KeyId0} -> KeyId0; + {error, _} = Err -> Err + end, + case Result of + {error, _} = Err2 -> + Err2; + KeyId -> + case get_jwk(KeyId, InternalOAuthProvider) of + {ok, JWK} -> + Algorithms = InternalOAuthProvider#internal_oauth_provider.algorithms, + rabbit_log:debug("Verifying signature using signing_key_id : '~tp' and algorithms: ~p", + [KeyId, Algorithms]), + uaa_jwt_jwt:decode_and_verify(Algorithms, JWK, Token); + {error, _} = Err3 -> + Err3 + end end. -resolve_resource_server_id(Token) -> +-spec resolve_resource_server(binary()|map()) -> {error, term()} | + {resource_server(), internal_oauth_provider()}. +resolve_resource_server(DecodedToken) when is_map(DecodedToken) -> + Aud = maps:get(?AUD_JWT_FIELD, DecodedToken, none), + resolve_resource_server_given_audience(Aud); +resolve_resource_server(Token) -> case uaa_jwt_jwt:get_aud(Token) of + {error, _} = Error -> Error; + {ok, Audience} -> resolve_resource_server_given_audience(Audience) + end. +resolve_resource_server_given_audience(Audience) -> + case resolve_resource_server_from_audience(Audience) of {error, _} = Error -> Error; - {ok, Audience} -> - rabbit_oauth2_config:get_resource_server_id_for_audience(Audience) + {ok, ResourceServer} -> + {ResourceServer, get_internal_oauth_provider( + ResourceServer#resource_server.oauth_provider_id)} end. --spec get_jwk(binary(), oauth_provider_id()) -> {ok, map()} | {error, term()}. -get_jwk(KeyId, OAuthProviderId) -> - get_jwk(KeyId, OAuthProviderId, true). +-spec get_jwk(binary(), internal_oauth_provider()) -> {ok, map()} | {error, term()}. +get_jwk(KeyId, InternalOAuthProvider) -> + get_jwk(KeyId, InternalOAuthProvider, true). -get_jwk(KeyId, OAuthProviderId, AllowUpdateJwks) -> - case rabbit_oauth2_config:get_signing_key(KeyId, OAuthProviderId) of +get_jwk(KeyId, InternalOAuthProvider, AllowUpdateJwks) -> + OAuthProviderId = InternalOAuthProvider#internal_oauth_provider.id, + case get_signing_key(KeyId, OAuthProviderId) of undefined -> - if - AllowUpdateJwks -> - rabbit_log:debug("OAuth 2 JWT: signing key '~tp' not found. Downloading it... ", [KeyId]), - case rabbit_oauth2_config:get_oauth_provider(OAuthProviderId, [jwks_uri]) of + case AllowUpdateJwks of + true -> + rabbit_log:debug("Signing key '~tp' not found. Downloading it... ", [KeyId]), + case get_oauth_provider(OAuthProviderId, [jwks_uri]) of {ok, OAuthProvider} -> case update_jwks_signing_keys(OAuthProvider) of ok -> - get_jwk(KeyId, OAuthProviderId, false); + get_jwk(KeyId, InternalOAuthProvider, false); {error, no_jwks_url} -> {error, key_not_found}; {error, _} = Err -> Err end; {error, _} = Error -> - rabbit_log:debug("OAuth 2 JWT: unable to download keys due to ~p", [Error]), + rabbit_log:debug("Unable to download signing keys due to ~p", [Error]), Error end; - true -> - rabbit_log:debug("OAuth 2 JWT: signing key '~tp' not found. Downloading is not allowed", [KeyId]), + false -> + rabbit_log:debug("Signing key '~tp' not found. Downloading is not allowed", [KeyId]), {error, key_not_found} end; {Type, Value} -> - rabbit_log:debug("OAuth 2 JWT: signing key found: '~tp', '~tp'", [Type, Value]), + rabbit_log:debug("Signing key ~p found", [KeyId]), case Type of json -> uaa_jwt_jwk:make_jwk(Value); pem -> uaa_jwt_jwk:from_pem(Value); @@ -143,6 +165,13 @@ verify_signing_key(Type, Value) -> Err -> Err end. +-spec get_scope(map()) -> binary() | list(). +get_scope(#{?SCOPE_JWT_FIELD := Scope}) -> Scope; +get_scope(#{}) -> []. + +-spec set_scope(list(), map()) -> map(). +set_scope(Scopes, DecodedToken) -> + DecodedToken#{?SCOPE_JWT_FIELD => Scopes}. -spec client_id(map()) -> binary() | undefined. client_id(DecodedToken) -> diff --git a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl index 7d8c37457028..bd2cd557d0cf 100644 --- a/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl +++ b/deps/rabbitmq_auth_backend_oauth2/src/uaa_jwt_jwt.erl @@ -6,29 +6,28 @@ %% -module(uaa_jwt_jwt). --export([decode_and_verify/3, get_key_id/2, get_aud/1]). +-export([decode_and_verify/3, get_key_id/1, get_aud/1]). -include_lib("jose/include/jose_jwt.hrl"). -include_lib("jose/include/jose_jws.hrl"). -decode_and_verify(OauthProviderId, Jwk, Token) -> - Verify = - case rabbit_oauth2_config:get_algorithms(OauthProviderId) of - undefined -> jose_jwt:verify(Jwk, Token); - Algs -> jose_jwt:verify_strict(Jwk, Algs, Token) - end, +-spec decode_and_verify(list() | undefined, map(), binary()) -> {boolean(), map()}. +decode_and_verify(Algs, Jwk, Token) -> + Verify = case Algs of + undefined -> jose_jwt:verify(Jwk, Token); + _ -> jose_jwt:verify_strict(Jwk, Algs, Token) + end, case Verify of {true, #jose_jwt{fields = Fields}, _} -> {true, Fields}; {false, #jose_jwt{fields = Fields}, _} -> {false, Fields} end. - -get_key_id(DefaultKey, Token) -> +get_key_id(Token) -> try case jose_jwt:peek_protected(Token) of #jose_jws{fields = #{<<"kid">> := Kid}} -> {ok, Kid}; - #jose_jws{} -> DefaultKey + #jose_jws{} -> undefined end catch Type:Err:Stacktrace -> {error, {invalid_token, Type, Err, Stacktrace}} diff --git a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets index a76c0cdf1a23..4638312ecb52 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets +++ b/deps/rabbitmq_auth_backend_oauth2/test/config_schema_SUITE_data/rabbitmq_auth_backend_oauth2.snippets @@ -18,6 +18,8 @@ auth_oauth2.https.depth = 5 auth_oauth2.https.fail_if_no_peer_cert = false auth_oauth2.https.hostname_verification = wildcard + auth_oauth2.discovery_endpoint_path = /.well-known/openid-configuration + auth_oauth2.discovery_endpoint_params.param1 = value1 auth_oauth2.https.crl_check = true auth_oauth2.algorithms.1 = HS256 auth_oauth2.algorithms.2 = RS256", @@ -30,6 +32,10 @@ {preferred_username_claims, [<<"user_name">>, <<"username">>, <<"email">>]}, {verify_aud, true}, {issuer, "https://my-jwt-issuer"}, + {discovery_endpoint_path, "/.well-known/openid-configuration"}, + {discovery_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, {key_config, [ {default_key, <<"id1">>}, {signing_keys, @@ -136,6 +142,8 @@ auth_oauth2.oauth_providers.keycloak.https.depth = 2 auth_oauth2.oauth_providers.keycloak.default_key = token-key auth_oauth2.oauth_providers.keycloak.signing_keys.id1 = test/config_schema_SUITE_data/certs/key.pem + auth_oauth2.oauth_providers.keycloak.discovery_endpoint_path = /.well-known/openid-configuration + auth_oauth2.oauth_providers.keycloak.discovery_endpoint_params.param1 = value1 auth_oauth2.oauth_providers.keycloak.algorithms.1 = HS256 auth_oauth2.oauth_providers.keycloak.algorithms.2 = RS256", [ @@ -160,14 +168,18 @@ {cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"} ]}, {algorithms, [<<"HS256">>, <<"RS256">>]}, + {discovery_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, + {discovery_endpoint_path, "/.well-known/openid-configuration"}, {default_key, <<"token-key">>}, - {end_session_endpoint, <<"https://keycloak/logout">>}, - {authorization_endpoint, <<"https://keycloak/authorize">>}, - {jwks_uri, <<"https://keycloak/keys">>}, - {token_endpoint, <<"https://keycloak/token">>} + {end_session_endpoint, "https://keycloak/logout"}, + {authorization_endpoint, "https://keycloak/authorize"}, + {jwks_uri, "https://keycloak/keys"}, + {token_endpoint, "https://keycloak/token"} ], <<"uaa">> => [ - {issuer, <<"https://uaa">>} + {issuer, "https://uaa"} ] } diff --git a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl index db4de4d8a677..c3f324063535 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/jwks_SUITE.erl @@ -13,9 +13,20 @@ -include_lib("amqp_client/include/amqp_client.hrl"). -include_lib("eunit/include/eunit.hrl"). --import(rabbit_ct_client_helpers, [close_connection/1, close_channel/1, - open_unmanaged_connection/4, open_unmanaged_connection/5, - close_connection_and_channel/2]). +-import(rabbit_ct_client_helpers, [ + close_connection/1, + close_channel/1, + open_unmanaged_connection/4, + open_unmanaged_connection/5, + close_connection_and_channel/2 +]). +-import(rabbit_ct_helpers, [ + set_config/2, + get_config/2, get_config/3 +]). +-import(rabbit_ct_broker_helpers, [ + rpc/5 +]). -import(rabbit_mgmt_test_util, [amqp_port/1]). all() -> @@ -159,21 +170,23 @@ end_per_suite(Config) -> ] ++ rabbit_ct_broker_helpers:teardown_steps()). init_per_group(no_peer_verification, Config) -> - KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(non_strict_jwks_url, Config)}, {peer_verification, verify_none}]), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), - rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); + KeyConfig = set_config(?config(key_config, Config), [ + {jwks_url, ?config(non_strict_jwks_url, Config)}, + {peer_verification, verify_none} + ]), + ok = rpc_set_env(Config,key_config, KeyConfig), + set_config(Config, {key_config, KeyConfig}); init_per_group(without_kid, Config) -> - rabbit_ct_helpers:set_config(Config, [{include_kid, false}]); + set_config(Config, [{include_kid, false}]); init_per_group(with_resource_servers_rabbitmq1_with_oauth_provider_A, Config) -> - ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), - Resource0 = maps:get(<<"rabbitmq1">>, ResourceServersConfig0, [{id, <<"rabbitmq1">>}]), - ResourceServersConfig1 = maps:put(<<"rabbitmq1">>, [{oauth_provider_id, <<"A">>} | Resource0], ResourceServersConfig0), - - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), + Resource0 = maps:get(<<"rabbitmq1">>, + ResourceServersConfig0, [{id, <<"rabbitmq1">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq1">>, + [{oauth_provider_id, <<"A">>} | Resource0], ResourceServersConfig0), + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1); init_per_group(with_oauth_providers_A_B_and_C, Config) -> OAuthProviders = #{ @@ -190,58 +203,50 @@ init_per_group(with_oauth_providers_A_B_and_C, Config) -> {https, [{verify, verify_none}]} ] }, - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders), Config; init_per_group(with_default_oauth_provider_B, Config) -> - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"B">>]); + ok = rpc_set_env(Config, default_oauth_provider, <<"B">>); + init_per_group(with_oauth_providers_A_with_default_key, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), OAuthProviders1 = maps:put(<<"A">>, [ - {default_key, ?UTIL_MOD:token_key(?config(fixture_jwksA, Config))} | OAuthProvider], - OAuthProviders0), + {default_key, ?UTIL_MOD:token_key(?config(fixture_jwksA, Config))} + | OAuthProvider], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), Config; init_per_group(with_oauth_provider_A_with_jwks_with_one_signing_key, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"A">>, OAuthProviders0, []), - OAuthProviders1 = maps:put(<<"A">>, [{jwks_uri, strict_jwks_url(Config, "/jwksA")} | OAuthProvider], + OAuthProviders1 = maps:put(<<"A">>, [ + {jwks_uri, strict_jwks_url(Config, "/jwksA")} | OAuthProvider], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), Config; init_per_group(with_resource_servers_rabbitmq2, Config) -> - ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), - Resource0 = maps:get(<<"rabbitmq2">>, ResourceServersConfig0, [{id, <<"rabbitmq2">>}]), - ResourceServersConfig1 = maps:put(<<"rabbitmq2">>, Resource0, ResourceServersConfig0), - - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), + Resource0 = maps:get(<<"rabbitmq2">>, ResourceServersConfig0, + [{id, <<"rabbitmq2">>}]), + ResourceServersConfig1 = maps:put(<<"rabbitmq2">>, Resource0, + ResourceServersConfig0), + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1); init_per_group(with_oauth_providers_B_with_default_key_static_key, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), OAuthProviders1 = maps:put(<<"B">>, [ {default_key, ?UTIL_MOD:token_key(?config(fixture_staticB, Config))} | proplists:delete(default_key, OAuthProvider)], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config,oauth_providers, OAuthProviders1), Config; init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), Jwks1 = ?config(fixture_staticC_1, Config), Jwks2 = ?config(fixture_staticC_2, Config), @@ -249,16 +254,14 @@ init_per_group(with_oauth_provider_C_with_two_static_keys, Config) -> ?UTIL_MOD:token_key(Jwks1) => {json, Jwks1}, ?UTIL_MOD:token_key(Jwks2) => {json, Jwks2} }, - OAuthProviders1 = maps:put(<<"C">>, [{signing_keys, SigningKeys} | OAuthProvider], - OAuthProviders0), + OAuthProviders1 = maps:put(<<"C">>, [ + {signing_keys, SigningKeys} | OAuthProvider], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), Config; init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, Config) -> - KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig = rpc_get_env(Config, key_config, []), Jwks1 = ?config(fixture_static_1, Config), Jwks2 = ?config(fixture_static_2, Config), SigningKeys = #{ @@ -267,28 +270,25 @@ init_per_group(with_root_oauth_provider_with_two_static_keys_and_one_jwks_key, C }, KeyConfig1 = [{signing_keys, SigningKeys}, {jwks_url, strict_jwks_url(Config, "/jwks")}| KeyConfig], - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), - + ok = rpc_set_env(Config, key_config, KeyConfig1), Config; init_per_group(with_root_oauth_provider_with_default_key_1, Config) -> - KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, key_config, []]), - KeyConfig1 = [{default_key, ?UTIL_MOD:token_key(?config(fixture_static_1, Config))} | KeyConfig], - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_static_1, Config))} + | KeyConfig], + ok = rpc_set_env(Config, key_config, KeyConfig1), Config; init_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> - KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, key_config, []]), - KeyConfig1 = [{default_key, ?UTIL_MOD:token_key(?config(fixture_jwk, Config))} | KeyConfig], - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + KeyConfig = rpc_get_env(Config, key_config, []), + KeyConfig1 = [ + {default_key, ?UTIL_MOD:token_key(?config(fixture_jwk, Config))} + | KeyConfig], + ok = rpc_set_env(Config, key_config, KeyConfig1), Config; init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signing_keys, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"B">>, OAuthProviders0, []), Jwks = ?config(fixture_staticB, Config), SigningKeys = #{ @@ -299,63 +299,55 @@ init_per_group(with_oauth_provider_B_with_one_static_key_and_jwks_with_two_signi {jwks_uri, strict_jwks_url(Config, "/jwksB")} | OAuthProvider], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), Config; init_per_group(with_resource_servers_rabbitmq3_with_oauth_provider_C, Config) -> - ResourceServersConfig0 = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, resource_servers, #{}]), + ResourceServersConfig0 = rpc_get_env(Config, resource_servers, #{}), Resource0 = maps:get(<<"rabbitmq3">>, ResourceServersConfig0, [ {id, <<"rabbitmq3">>},{oauth_provider_id, <<"C">>}]), - ResourceServersConfig1 = maps:put(<<"rabbitmq3">>, Resource0, ResourceServersConfig0), + ResourceServersConfig1 = maps:put(<<"rabbitmq3">>, Resource0, + ResourceServersConfig0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, resource_servers, ResourceServersConfig1]); + ok = rpc_set_env(Config, resource_servers, ResourceServersConfig1); init_per_group(with_oauth_providers_C_with_default_key_static_key_1, Config) -> - {ok, OAuthProviders0} = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, oauth_providers]), + {ok, OAuthProviders0} = rpc_get_env(Config, oauth_providers), OAuthProvider = maps:get(<<"C">>, OAuthProviders0, []), Jwks = ?config(fixture_staticC_1, Config), OAuthProviders1 = maps:put(<<"C">>, [ {default_key, ?UTIL_MOD:token_key(Jwks)} | OAuthProvider], OAuthProviders0), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders1]), + ok = rpc_set_env(Config, oauth_providers, OAuthProviders1), Config; init_per_group(_Group, Config) -> - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), + ok = rpc_set_env(Config, resource_server_id, ?RESOURCE_SERVER_ID), Config. end_per_group(without_kid, Config) -> rabbit_ct_helpers:delete_config(Config, include_kid); end_per_group(no_peer_verification, Config) -> - KeyConfig = rabbit_ct_helpers:set_config(?config(key_config, Config), [{jwks_url, ?config(strict_jwks_url, Config)}, {peer_verification, verify_peer}]), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), - rabbit_ct_helpers:set_config(Config, {key_config, KeyConfig}); + KeyConfig = set_config(?config(key_config, Config), [ + {jwks_url, ?config(strict_jwks_url, Config)}, + {peer_verification, verify_peer}]), + ok = rpc_set_env(Config, key_config, KeyConfig), + set_config(Config, {key_config, KeyConfig}); end_per_group(with_default_oauth_provider_B, Config) -> - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, - [rabbitmq_auth_backend_oauth2, default_oauth_provider]); + ok = rpc_unset_env(Config, default_oauth_provider); end_per_group(with_root_oauth_provider_with_default_key_1, Config) -> - KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig = rpc_get_env(Config, key_config, []), KeyConfig1 = proplists:delete(default_key, KeyConfig), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + ok = rpc_set_env(Config, key_config, KeyConfig1), Config; end_per_group(with_root_oauth_provider_with_default_jwks_key, Config) -> - KeyConfig = rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, key_config, []]), + KeyConfig = rpc_get_env(Config, key_config, []), KeyConfig1 = proplists:delete(default_key, KeyConfig), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig1]), + ok = rpc_set_env(Config, key_config, KeyConfig1), Config; end_per_group(_Group, Config) -> @@ -363,44 +355,50 @@ end_per_group(_Group, Config) -> add_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). - lists:foreach(fun(Value) -> rabbit_ct_broker_helpers:add_vhost(Config, Value) end, - [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). + lists:foreach(fun(Value) -> + rabbit_ct_broker_helpers:add_vhost(Config, Value) end, + [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). %rabbit_ct_helpers:set_config(Config, []). delete_vhosts(Config) -> %% The broker is managed by {init,end}_per_testcase(). - lists:foreach(fun(Value) -> rabbit_ct_broker_helpers:delete_vhost(Config, Value) end, - [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). + lists:foreach(fun(Value) -> + rabbit_ct_broker_helpers:delete_vhost(Config, Value) end, + [<<"vhost1">>, <<"vhost2">>, <<"vhost3">>, <<"vhost4">>]). -init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse - Testcase =:= test_successful_token_refresh -> +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse + Testcase =:= test_successful_token_refresh -> rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost1">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -init_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse - Testcase =:= test_failed_token_refresh_case2 -> +init_per_testcase(Testcase, Config) when + Testcase =:= test_failed_token_refresh_case1 orelse + Testcase =:= test_failed_token_refresh_case2 -> rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost4">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse - Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse - Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, extra_scopes_source, ?EXTRA_SCOPES_SOURCE]), +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> + ok = rpc_set_env(Config, extra_scopes_source, ?EXTRA_SCOPES_SOURCE), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_algorithm_restriction -> +init_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_algorithm_restriction -> KeyConfig = ?config(key_config, Config), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, [{algorithms, [<<"HS256">>]} | KeyConfig]]), + ok = rpc_set_env(Config, key_config, [{algorithms, [<<"HS256">>]} | KeyConfig]), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -init_per_testcase(Testcase, Config) when Testcase =:= test_failed_connection_with_algorithm_restriction -> +init_per_testcase(Testcase, Config) when + Testcase =:= test_failed_connection_with_algorithm_restriction -> KeyConfig = ?config(key_config, Config), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, [{algorithms, [<<"RS256">>]} | KeyConfig]]), + ok = rpc_set_env(Config, key_config, [{algorithms, [<<"RS256">>]} | KeyConfig]), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; @@ -408,25 +406,28 @@ init_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_started(Config, Testcase), Config. -end_per_testcase(Testcase, Config) when Testcase =:= test_failed_token_refresh_case1 orelse - Testcase =:= test_failed_token_refresh_case2 -> +end_per_testcase(Testcase, Config) when + Testcase =:= test_failed_token_refresh_case1 orelse + Testcase =:= test_failed_token_refresh_case2 -> rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost4">>), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse - Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse - Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> +end_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_complex_claim_as_a_map orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_list orelse + Testcase =:= test_successful_connection_with_complex_claim_as_a_binary -> rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>), ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, [rabbitmq_auth_backend_oauth2, extra_scopes_source]), rabbit_ct_helpers:testcase_started(Config, Testcase), Config; -end_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_algorithm_restriction orelse - Testcase =:= test_failed_connection_with_algorithm_restriction -> +end_per_testcase(Testcase, Config) when + Testcase =:= test_successful_connection_with_algorithm_restriction orelse + Testcase =:= test_failed_connection_with_algorithm_restriction -> rabbit_ct_broker_helpers:delete_vhost(Config, <<"vhost1">>), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, [rabbitmq_auth_backend_oauth2, key_config, ?config(key_config, Config)]), + ok = rpc_set_env(Config, key_config, ?config(key_config, Config)), rabbit_ct_helpers:testcase_finished(Config, Testcase), Config; @@ -436,10 +437,9 @@ end_per_testcase(Testcase, Config) -> Config. preconfigure_node(Config) -> - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]), - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, resource_server_id, ?RESOURCE_SERVER_ID]), + ok = rpc(Config, 0, application, set_env, + [rabbit, auth_backends, [rabbit_auth_backend_oauth2]]), + ok = rpc_set_env(Config, resource_server_id, ?RESOURCE_SERVER_ID), add_vhosts(Config), Config. @@ -477,25 +477,23 @@ start_jwks_server(Config0) -> KeyConfig = [{jwks_url, StrictJwksUrl}, {peer_verification, verify_peer}, {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}], - ok = rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, key_config, KeyConfig]), - rabbit_ct_helpers:set_config(Config, - [ - {non_strict_jwks_url, NonStrictJwksUrl}, - {strict_jwks_url, StrictJwksUrl}, - {key_config, KeyConfig}, - {fixture_static_1, Jwk7}, - {fixture_static_2, Jwk8}, - {fixture_staticB, Jwk4}, - {fixture_staticC_1, Jwk5}, - {fixture_staticC_2, Jwk6}, - {fixture_jwksB_1, Jwk1}, - {fixture_jwksB_2, Jwk3}, - {fixture_jwksA, Jwk}, - {fixture_jwk, Jwk}, - {fixture_jwks_1, [Jwk1, Jwk3]}, - {fixture_jwks_2, [Jwk2]} - ]). + ok = rpc_set_env(Config, key_config, KeyConfig), + set_config(Config, [ + {non_strict_jwks_url, NonStrictJwksUrl}, + {strict_jwks_url, StrictJwksUrl}, + {key_config, KeyConfig}, + {fixture_static_1, Jwk7}, + {fixture_static_2, Jwk8}, + {fixture_staticB, Jwk4}, + {fixture_staticC_1, Jwk5}, + {fixture_staticC_2, Jwk6}, + {fixture_jwksB_1, Jwk1}, + {fixture_jwksB_2, Jwk3}, + {fixture_jwksA, Jwk}, + {fixture_jwk, Jwk}, + {fixture_jwks_1, [Jwk1, Jwk3]}, + {fixture_jwks_2, [Jwk2]} + ]). strict_jwks_url(Config) -> strict_jwks_url(Config, "/jwks"). strict_jwks_url(Config, Path) -> @@ -517,54 +515,63 @@ generate_valid_token(Config, Scopes) -> generate_valid_token(Config, Scopes, undefined). generate_valid_token(Config, Scopes, Audience) -> - Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of + Jwk = + case get_config(Config, fixture_jwk) of undefined -> ?UTIL_MOD:fixture_jwk(); Value -> Value end, generate_valid_token(Config, Jwk, Scopes, Audience). generate_valid_token(Config, Jwk, Scopes, Audience) -> - Token = case Audience of - undefined -> ?UTIL_MOD:fixture_token_with_scopes(Scopes); - DefinedAudience -> maps:put(<<"aud">>, DefinedAudience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)) + Token = + case Audience of + undefined -> + ?UTIL_MOD:fixture_token_with_scopes(Scopes); + DefinedAudience -> + maps:put(<<"aud">>, DefinedAudience, + ?UTIL_MOD:fixture_token_with_scopes(Scopes)) end, IncludeKid = rabbit_ct_helpers:get_config(Config, include_kid, true), ?UTIL_MOD:sign_token_hs(Token, Jwk, IncludeKid). generate_valid_token_with_extra_fields(Config, ExtraFields) -> - Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of - undefined -> ?UTIL_MOD:fixture_jwk(); - Value -> Value - end, + Jwk = + case rabbit_ct_helpers:get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, Token = maps:merge(?UTIL_MOD:fixture_token_with_scopes([]), ExtraFields), - ?UTIL_MOD:sign_token_hs(Token, Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). + ?UTIL_MOD:sign_token_hs(Token, Jwk, + rabbit_ct_helpers:get_config(Config, include_kid, true)). generate_expired_token(Config) -> generate_expired_token(Config, ?UTIL_MOD:full_permission_scopes()). generate_expired_token(Config, Scopes) -> - Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of - undefined -> ?UTIL_MOD:fixture_jwk(); - Value -> Value - end, + Jwk = + case get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, ?UTIL_MOD:sign_token_hs(?UTIL_MOD:expired_token_with_scopes(Scopes), Jwk, - rabbit_ct_helpers:get_config(Config, include_kid, true)). + get_config(Config, include_kid, true)). generate_expirable_token(Config, Seconds) -> generate_expirable_token(Config, ?UTIL_MOD:full_permission_scopes(), Seconds). generate_expirable_token(Config, Scopes, Seconds) -> - Jwk = case rabbit_ct_helpers:get_config(Config, fixture_jwk) of - undefined -> ?UTIL_MOD:fixture_jwk(); - Value -> Value - end, + Jwk = + case get_config(Config, fixture_jwk) of + undefined -> ?UTIL_MOD:fixture_jwk(); + Value -> Value + end, Expiration = os:system_time(seconds) + Seconds, - ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration(Scopes, Expiration), - Jwk, rabbit_ct_helpers:get_config(Config, include_kid, true)). + ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_scopes_and_expiration( + Scopes, Expiration), Jwk, get_config(Config, include_kid, true)). preconfigure_token(Config) -> Token = generate_valid_token(Config), - rabbit_ct_helpers:set_config(Config, {fixture_jwt, Token}). + set_config(Config, {fixture_jwt, Token}). %% @@ -682,7 +689,7 @@ test_unsuccessful_connection_for_rabbitmq_audience_signed_by_root_oauth_provider ?assertMatch({error, {auth_failure, _}}, open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). test_successful_connection_with_a_full_permission_token_and_all_defaults(Config) -> - {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), + {_Algo, Token} = get_config(Config, fixture_jwt), verify_queue_declare_with_token(Config, Token). verify_queue_declare_with_token(Config, Token) -> @@ -734,10 +741,12 @@ test_successful_queue_declaration_using_multiple_keys_and_audiences(Config) -> test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost(Config) -> - {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>, - <<"rabbitmq.write:vhost1/*">>, - <<"rabbitmq.read:vhost1/*">>]), - Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token), + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, + Token), {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), @@ -758,7 +767,13 @@ test_successful_connection_with_simple_strings_for_aud_and_scope(Config) -> test_successful_connection_with_complex_claim_as_a_map(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, - #{<<"additional_rabbitmq_scopes">> => #{<<"rabbitmq">> => [<<"configure:*/*">>, <<"read:*/*">>, <<"write:*/*">>]}} + #{<<"additional_rabbitmq_scopes">> => #{ + <<"rabbitmq">> => [ + <<"configure:*/*">>, + <<"read:*/*">>, + <<"write:*/*">> + ]} + } ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -769,7 +784,11 @@ test_successful_connection_with_complex_claim_as_a_map(Config) -> test_successful_connection_with_complex_claim_as_a_list(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, - #{<<"additional_rabbitmq_scopes">> => [<<"rabbitmq.configure:*/*">>, <<"rabbitmq.read:*/*">>, <<"rabbitmq.write:*/*">>]} + #{<<"additional_rabbitmq_scopes">> => [ + <<"rabbitmq.configure:*/*">>, + <<"rabbitmq.read:*/*">>, + <<"rabbitmq.write:*/*">> + ]} ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -780,7 +799,8 @@ test_successful_connection_with_complex_claim_as_a_list(Config) -> test_successful_connection_with_complex_claim_as_a_binary(Config) -> {_Algo, Token} = generate_valid_token_with_extra_fields( Config, - #{<<"additional_rabbitmq_scopes">> => <<"rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/*">>} + #{<<"additional_rabbitmq_scopes">> => + <<"rabbitmq.configure:*/* rabbitmq.read:*/* rabbitmq.write:*/*">>} ), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), @@ -815,79 +835,94 @@ test_successful_connection_with_keycloak_token(Config) -> test_successful_token_refresh(Config) -> Duration = 5, - {_Algo, Token} = generate_expirable_token(Config, [<<"rabbitmq.configure:vhost1/*">>, - <<"rabbitmq.write:vhost1/*">>, - <<"rabbitmq.read:vhost1/*">>], - Duration), - Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token), + {_Algo, Token} = generate_expirable_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">> + ], Duration), + Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), - {_Algo2, Token2} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost1/*">>, - <<"rabbitmq.write:vhost1/*">>, - <<"rabbitmq.read:vhost1/*">>]), + {_Algo2, Token2} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), ?UTIL_MOD:wait_for_token_to_expire(timer:seconds(Duration)), - ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, <<"token refresh">>)), - + ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, + <<"token refresh">>)), {ok, Ch2} = amqp_connection:open_channel(Conn), - #'queue.declare_ok'{queue = _} = - amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), - #'queue.declare_ok'{queue = _} = - amqp_channel:call(Ch2, #'queue.declare'{exclusive = true}), + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, + #'queue.declare'{exclusive = true}), + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch2, + #'queue.declare'{exclusive = true}), amqp_channel:close(Ch2), close_connection_and_channel(Conn, Ch). test_successful_connection_with_algorithm_restriction(Config) -> - {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), + {_Algo, Token} = get_config(Config, fixture_jwt), Conn = open_unmanaged_connection(Config, 0, <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), - #'queue.declare_ok'{queue = _} = - amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), + #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, + #'queue.declare'{exclusive = true}), close_connection_and_channel(Conn, Ch). test_failed_connection_with_expired_token(Config) -> - {_Algo, Token} = generate_expired_token(Config, [<<"rabbitmq.configure:vhost1/*">>, - <<"rabbitmq.write:vhost1/*">>, - <<"rabbitmq.read:vhost1/*">>]), + {_Algo, Token} = generate_expired_token(Config, [ + <<"rabbitmq.configure:vhost1/*">>, + <<"rabbitmq.write:vhost1/*">>, + <<"rabbitmq.read:vhost1/*">>]), ?assertMatch({error, {auth_failure, _}}, - open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, Token)). + open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, Token)). test_failed_connection_with_a_non_token(Config) -> ?assertMatch({error, {auth_failure, _}}, - open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"username">>, <<"a-non-token-value">>)). + open_unmanaged_connection(Config, 0, <<"vhost1">>, + <<"username">>, <<"a-non-token-value">>)). test_failed_connection_with_a_token_with_insufficient_vhost_permission(Config) -> - {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:alt-vhost/*">>, - <<"rabbitmq.write:alt-vhost/*">>, - <<"rabbitmq.read:alt-vhost/*">>]), + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:alt-vhost/*">>, + <<"rabbitmq.write:alt-vhost/*">>, + <<"rabbitmq.read:alt-vhost/*">>]), ?assertEqual({error, not_allowed}, - open_unmanaged_connection(Config, 0, <<"off-limits-vhost">>, <<"username">>, Token)). + open_unmanaged_connection(Config, 0, <<"off-limits-vhost">>, + <<"username">>, Token)). test_failed_connection_with_a_token_with_insufficient_resource_permission(Config) -> - {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost2/jwt*">>, - <<"rabbitmq.write:vhost2/jwt*">>, - <<"rabbitmq.read:vhost2/jwt*">>]), - Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token), + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost2/jwt*">>, + <<"rabbitmq.write:vhost2/jwt*">>, + <<"rabbitmq.read:vhost2/jwt*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, + Token), {ok, Ch} = amqp_connection:open_channel(Conn), ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _}, - amqp_channel:call(Ch, #'queue.declare'{queue = <<"alt-prefix.eq.1">>, exclusive = true})), + amqp_channel:call(Ch, #'queue.declare'{queue = <<"alt-prefix.eq.1">>, + exclusive = true})), close_connection(Conn). test_failed_token_refresh_case1(Config) -> - {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost4/*">>, - <<"rabbitmq.write:vhost4/*">>, - <<"rabbitmq.read:vhost4/*">>]), - Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token), + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, + Token), {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), - {_Algo2, Token2} = generate_expired_token(Config, [<<"rabbitmq.configure:vhost4/*">>, - <<"rabbitmq.write:vhost4/*">>, - <<"rabbitmq.read:vhost4/*">>]), + {_Algo2, Token2} = generate_expired_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), %% the error is communicated asynchronously via a connection-level error - ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, <<"token refresh">>)), + ?assertEqual(ok, amqp_connection:update_secret(Conn, Token2, + <<"token refresh">>)), {ok, Ch2} = amqp_connection:open_channel(Conn), ?assertExit({{shutdown, {server_initiated_close, 403, _}}, _}, @@ -896,16 +931,19 @@ test_failed_token_refresh_case1(Config) -> close_connection(Conn). test_failed_token_refresh_case2(Config) -> - {_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost4/*">>, - <<"rabbitmq.write:vhost4/*">>, - <<"rabbitmq.read:vhost4/*">>]), - Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, <<"username">>, Token), + {_Algo, Token} = generate_valid_token(Config, [ + <<"rabbitmq.configure:vhost4/*">>, + <<"rabbitmq.write:vhost4/*">>, + <<"rabbitmq.read:vhost4/*">>]), + Conn = open_unmanaged_connection(Config, 0, <<"vhost4">>, + <<"username">>, Token), {ok, Ch} = amqp_connection:open_channel(Conn), #'queue.declare_ok'{queue = _} = amqp_channel:call(Ch, #'queue.declare'{exclusive = true}), %% the error is communicated asynchronously via a connection-level error - ?assertEqual(ok, amqp_connection:update_secret(Conn, <<"not-a-token-^^^^5%">>, <<"token refresh">>)), + ?assertEqual(ok, amqp_connection:update_secret(Conn, <<"not-a-token-^^^^5%">>, + <<"token refresh">>)), ?assertExit({{shutdown, {connection_closing, {server_initiated_close, 530, _}}}, _}, amqp_connection:open_channel(Conn)), @@ -913,6 +951,20 @@ test_failed_token_refresh_case2(Config) -> close_connection(Conn). test_failed_connection_with_algorithm_restriction(Config) -> - {_Algo, Token} = rabbit_ct_helpers:get_config(Config, fixture_jwt), + {_Algo, Token} = get_config(Config, fixture_jwt), ?assertMatch({error, {auth_failure, _}}, open_unmanaged_connection(Config, 0, <<"username">>, Token)). + +%%% HELPERS +rpc_unset_env(Config, Par) -> + rpc(Config, 0, application, unset_env, + [rabbitmq_auth_backend_oauth2, Par]). +rpc_set_env(Config, Par, Val) -> + rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, Par, Val]). +rpc_get_env(Config, Par) -> + rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par]). +rpc_get_env(Config, Par, Default) -> + rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par, Default]). \ No newline at end of file diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl deleted file mode 100644 index 1d3736bd414a..000000000000 --- a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_config_SUITE.erl +++ /dev/null @@ -1,1016 +0,0 @@ -%% This Source Code Form is subject to the terms of the Mozilla Public -%% License, v. 2.0. If a copy of the MPL was not distributed with this -%% file, You can obtain one at https://mozilla.org/MPL/2.0/. -%% -%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. -%% - --module(rabbit_oauth2_config_SUITE). - --compile(export_all). --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("oauth2_client/include/oauth2_client.hrl"). - --define(RABBITMQ,<<"rabbitmq">>). --define(RABBITMQ_RESOURCE_ONE,<<"rabbitmq1">>). --define(RABBITMQ_RESOURCE_TWO,<<"rabbitmq2">>). --define(AUTH_PORT, 8000). - - -all() -> [ - {group, with_rabbitmq_node}, - {group, with_resource_server_id}, - {group, without_resource_server_id}, - {group, with_resource_servers}, - {group, with_resource_servers_and_resource_server_id}, - {group, inheritance_group} -]. -groups() -> [ - {with_rabbitmq_node, [], [ - add_signing_keys_for_specific_oauth_provider, - add_signing_keys_for_root_oauth_provider, - - replace_signing_keys_for_root_oauth_provider, - replace_signing_keys_for_specific_oauth_provider, - {with_root_static_signing_keys, [], [ - replace_merge_root_static_keys_with_newly_added_keys, - replace_override_root_static_keys_with_newly_added_keys - ]}, - {with_static_signing_keys_for_specific_oauth_provider, [], [ - replace_merge_static_keys_with_newly_added_keys, - replace_override_static_keys_with_newly_added_keys - ]} - ]}, - {with_resource_server_id, [], [ - get_default_resource_server_id, - get_allowed_resource_server_ids_returns_resource_server_id, - get_resource_server_id_for_rabbit_audience_returns_rabbit, - get_resource_server_id_for_none_audience_should_fail, - get_resource_server_id_for_unknown_audience_should_fail, - {with_verify_aud_false, [], [ - get_resource_server_id_for_rabbit_audience_returns_rabbit, - get_resource_server_id_for_none_audience_returns_rabbit, - get_resource_server_id_for_unknown_audience_returns_rabbit - ]}, - find_audience_in_resource_server_ids_found_resource_server_id, - get_oauth_provider_root_with_jwks_uri_should_fail, - get_default_key_should_fail, - {with_default_key, [], [ - get_default_key - ]}, - {with_static_signing_keys, [], [ - get_signing_keys - ]}, - {with_static_signing_keys_for_oauth_provider_A, [], [ - get_signing_keys_for_oauth_provider_A - ]}, - get_algorithms_should_return_undefined, - {with_algorithms, [], [ - get_algorithms - ]}, - {with_jwks_url, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_oauth_providers_A_with_jwks_uri, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri - ]} - ]}, - {with_oauth_providers_A_B_with_jwks_uri, [], [ - get_default_key_for_provider_A_should_fail, - {with_default_key, [], [ - get_default_key_for_provider_A_should_fail - ]}, - {with_default_key_for_provider_A, [], [ - get_default_key_for_provider_A - ]}, - get_algorithms_for_provider_A_should_return_undefined, - {with_algorithms_for_provider_A, [], [ - get_algorithms_for_provider_A - ]}, - get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri, - {with_default_oauth_provider_B, [], [ - get_oauth_provider_should_return_oauth_provider_B_with_jwks_uri - ]} - ]} - ]}, - {with_oauth_providers_A_with_jwks_uri, [], [ - get_oauth_provider_root_with_jwks_uri_should_fail, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri - ]} - ]}, - {with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_oauth_providers_A_with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_default_oauth_provider_A, [], [ - get_oauth_provider_should_return_oauth_provider_A_with_all_discovered_endpoints - ]} - ]}, - {with_oauth_providers_A_B_with_issuer, [], [ - get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints, - {with_default_oauth_provider_B, [], [ - get_oauth_provider_should_return_oauth_provider_B_with_all_discovered_endpoints - ]} - ]} - ]} - ]}, - {without_resource_server_id, [], [ - get_default_resource_server_id_returns_error, - get_allowed_resource_server_ids_returns_empty_list - ]}, - {with_resource_servers, [], [ - get_allowed_resource_server_ids_returns_resource_servers_ids, - find_audience_in_resource_server_ids_found_one_resource_servers, - index_resource_servers_by_id_else_by_key, - is_verify_aud_for_resource_two_returns_true, - {with_verify_aud_false_for_resource_two, [], [ - is_verify_aud_for_resource_one_returns_true, - is_verify_aud_for_resource_two_returns_false - ]}, - get_scope_prefix_for_resource_one_returns_default_scope_prefix, - {with_root_scope_prefix, [], [ - get_scope_prefix_for_resource_one_returns_root_scope_prefix, - {with_empty_scope_prefix_for_resource_one, [], [ - get_scope_prefix_for_resource_one_returns_empty_scope_prefix, - get_scope_prefix_for_resource_two_returns_root_scope_prefix - ]} - ]}, - {with_jwks_url, [], [ - get_oauth_provider_for_both_resources_should_return_root_oauth_provider, - {with_oauth_providers_A_with_jwks_uri, [], [ - {with_default_oauth_provider_A, [], [ - get_oauth_provider_for_both_resources_should_return_oauth_provider_A - ]} - ]}, - {with_different_oauth_provider_for_each_resource, [], [ - {with_oauth_providers_A_B_with_jwks_uri, [], [ - get_oauth_provider_for_resource_one_should_return_oauth_provider_A, - get_oauth_provider_for_resource_two_should_return_oauth_provider_B - ]} - ]} - ]} - ]}, - {with_resource_servers_and_resource_server_id, [], [ - get_allowed_resource_server_ids_returns_all_resource_servers_ids, - find_audience_in_resource_server_ids_found_resource_server_id, - find_audience_in_resource_server_ids_found_one_resource_servers, - find_audience_in_resource_server_ids_using_binary_audience - ]}, - - {inheritance_group, [], [ - get_additional_scopes_key, - get_additional_scopes_key_when_not_defined, - is_verify_aud, - is_verify_aud_when_is_false, - get_default_preferred_username_claims, - get_preferred_username_claims, - get_scope_prefix, - get_empty_scope_prefix, - get_scope_prefix_when_not_defined, - get_resource_server_type, - get_resource_server_type_when_not_defined, - has_scope_aliases, - has_scope_aliases_when_not_defined, - get_scope_aliases - ]} -]. - -init_per_suite(Config) -> - rabbit_ct_helpers:log_environment(), - rabbit_ct_helpers:run_setup_steps(Config). - -end_per_suite(Config) -> - rabbit_ct_helpers:run_teardown_steps(Config). - -init_per_group(with_rabbitmq_node, Config) -> - Config1 = rabbit_ct_helpers:set_config(Config, [ - {rmq_nodename_suffix, with_rabbitmq_node}, - {rmq_nodes_count, 1} - ]), - rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps()); -init_per_group(with_default_key, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, - proplists:delete(default_key, KeyConfig) ++ [{default_key,<<"default-key">>}]), - Config; -init_per_group(with_root_static_signing_keys, Config) -> - KeyConfig = call_get_env(Config, key_config, []), - SigningKeys = #{ - <<"mykey-root-1">> => <<"some key root-1">>, - <<"mykey-root-2">> => <<"some key root-2">> - }, - call_set_env(Config, key_config, - proplists:delete(default_key, KeyConfig) ++ [{signing_keys,SigningKeys}]), - Config; -init_per_group(with_static_signing_keys_for_specific_oauth_provider, Config) -> - OAuthProviders = call_get_env(Config, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), - SigningKeys = #{ - <<"mykey-root-1">> => <<"some key root-1">>, - <<"mykey-root-2">> => <<"some key root-2">> - }, - OAuthProvider1 = proplists:delete(signing_keys, OAuthProvider) ++ [{signing_keys, SigningKeys}], - - call_set_env(Config, oauth_providers, maps:put(<<"A">>, OAuthProvider1, OAuthProviders)), - Config; - -init_per_group(with_default_key_for_provider_A, Config) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, maps:put(<<"A">>, - proplists:delete(default_key, OAuthProvider) ++ [{default_key,<<"A-default-key">>}], - OAuthProviders)), - Config; -init_per_group(with_static_signing_keys, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - SigningKeys = #{<<"mykey-1-1">> => <<"some key 1-1">>, - <<"mykey-1-2">> => <<"some key 1-2">>}, - application:set_env(rabbitmq_auth_backend_oauth2, key_config, - proplists:delete(signing_keys, KeyConfig) ++ [{signing_keys, SigningKeys}]), - Config; -init_per_group(with_static_signing_keys_for_oauth_provider_A, Config) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), - SigningKeys = #{<<"A-mykey-1-1">> => <<"A-some key 1-1">>, - <<"A-mykey-1-2">> => <<"A-some key 1-2">>}, - - OAuthProvider0 = proplists:delete(signing_keys, OAuthProvider) ++ - [{signing_keys, SigningKeys}], - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - maps:put(<<"A">>, OAuthProvider0, OAuthProviders)), - Config; - -init_per_group(with_jwks_url, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig - ++ [{jwks_url,build_url_to_oauth_provider(<<"/keys">>)}]), - [{key_config_before_group_with_jwks_url, KeyConfig} | Config]; -init_per_group(with_issuer, Config) -> - {ok, _} = application:ensure_all_started(inets), - {ok, _} = application:ensure_all_started(ssl), - application:ensure_all_started(cowboy), - CertsDir = ?config(rmq_certsdir, Config), - CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), - SslOptions = ssl_options(verify_peer, false, CaCertFile), - - HttpOauthServerExpectations = get_openid_configuration_expectations(), - ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), - - start_https_oauth_server(?AUTH_PORT, CertsDir, ListOfExpectations), - application:set_env(rabbitmq_auth_backend_oauth2, use_global_locks, false), - application:set_env(rabbitmq_auth_backend_oauth2, issuer, build_url_to_oauth_provider(<<"/">>)), - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig ++ SslOptions), - - [{key_config_before_group_with_issuer, KeyConfig}, {ssl_options, SslOptions} | Config]; - -init_per_group(with_oauth_providers_A_with_jwks_uri, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - #{<<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>) } - ] } ), - Config; -init_per_group(with_oauth_providers_A_with_issuer, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - #{<<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {https, ?config(ssl_options, Config)} - ] } ), - Config; -init_per_group(with_oauth_providers_A_B_with_jwks_uri, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - #{ <<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/A/keys">>)} - ], - <<"B">> => [ - {issuer,build_url_to_oauth_provider(<<"/B">>) }, - {jwks_uri,build_url_to_oauth_provider(<<"/B/keys">>)} - ] }), - Config; -init_per_group(with_oauth_providers_A_B_with_issuer, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - #{ <<"A">> => [ - {issuer,build_url_to_oauth_provider(<<"/A">>) }, - {https, ?config(ssl_options, Config)} - ], - <<"B">> => [ - {issuer,build_url_to_oauth_provider(<<"/B">>) }, - {https, ?config(ssl_options, Config)} - ] }), - Config; - -init_per_group(with_default_oauth_provider_A, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"A">>), - Config; - -init_per_group(with_default_oauth_provider_B, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, <<"B">>), - Config; - - - -init_per_group(with_resource_server_id, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - Config; - -init_per_group(with_root_scope_prefix, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"some-prefix:">>), - Config; -init_per_group(with_empty_scope_prefix_for_resource_one, Config) -> - ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), - Proplist = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers, []), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - maps:put(?RABBITMQ_RESOURCE_ONE, [{scope_prefix, <<"">>} | proplists:delete(scope_prefix, Proplist)], ResourceServers)), - Config; - -init_per_group(with_verify_aud_false, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), - Config; -init_per_group(with_verify_aud_false_for_resource_two, Config) -> - ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), - Proplist = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers, []), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - maps:put(?RABBITMQ_RESOURCE_TWO, [{verify_aud, false} | proplists:delete(verify_aud, Proplist)], ResourceServers)), - Config; -init_per_group(with_algorithms, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig ++ - [{algorithms, [<<"HS256">>, <<"RS256">>]}]), - [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; -init_per_group(with_algorithms_for_provider_A, Config) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - maps:put(<<"A">>, [{algorithms, [<<"HS256">>, <<"RS256">>]} | OAuthProvider], OAuthProviders)), - [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; - -init_per_group(with_resource_servers_and_resource_server_id, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, [{jwks_url,<<"https://oauth-for-rabbitmq">> }]), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ - { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq1">> } - ]} - - ], - ?RABBITMQ_RESOURCE_TWO => [ - { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq2">> } - ]} - ] - }), - Config; -init_per_group(with_different_oauth_provider_for_each_resource, Config) -> - {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), - Rabbit1 = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers) ++ [ {oauth_provider_id, <<"A">>} ], - Rabbit2 = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers) ++ [ {oauth_provider_id, <<"B">>} ], - ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), - Config; - -init_per_group(with_resource_servers, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ - { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq1">> } - ]} - ], - ?RABBITMQ_RESOURCE_TWO => [ - { key_config, [ - {jwks_url,<<"https://oauth-for-rabbitmq2">> } - ]} - ], - <<"0">> => [ {id, <<"rabbitmq-0">> } ], - <<"1">> => [ {id, <<"rabbitmq-1">> } ] - - }), - Config; - -init_per_group(inheritance_group, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?RABBITMQ), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>), - application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"some-prefix-">>), - application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"roles">>), - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{}), - - application:set_env(rabbitmq_auth_backend_oauth2, key_config, [ {jwks_url,<<"https://oauth-for-rabbitmq">> } ]), - - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - #{?RABBITMQ_RESOURCE_ONE => [ - { extra_scopes_source, <<"extra-scope-1">>}, - { verify_aud, false}, - { preferred_username_claims, [<<"email-address">>] }, - { scope_prefix, <<"my-prefix:">> }, - { resource_server_type, <<"my-type">> }, - { scope_aliases, #{} } - ], - ?RABBITMQ_RESOURCE_TWO => [ {id, ?RABBITMQ_RESOURCE_TWO } ] - } - ), - Config; - -init_per_group(_any, Config) -> - Config. - -end_per_group(with_rabbitmq_node, Config) -> - rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); -end_per_group(with_root_static_signing_keys, Config) -> - KeyConfig = call_get_env(Config, key_config, []), - call_set_env(Config, key_config, KeyConfig), - Config; -end_per_group(get_empty_scope_prefix, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - Config; -end_per_group(with_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; -end_per_group(with_verify_aud_false, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, verify_aud), - Config; -end_per_group(with_verify_aud_false_for_resource_two, Config) -> - ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), - Proplist = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers, []), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - maps:put(?RABBITMQ_RESOURCE_TWO, proplists:delete(verify_aud, Proplist), ResourceServers)), - Config; -end_per_group(with_empty_scope_prefix_for_resource_one, Config) -> - ResourceServers = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers, #{}), - Proplist = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers, []), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, - maps:put(?RABBITMQ_RESOURCE_ONE, proplists:delete(scope_prefix, Proplist), ResourceServers)), - Config; - -end_per_group(with_default_key, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, - proplists:delete(default_key, KeyConfig)), - Config; -end_per_group(with_algorithms, Config) -> - KeyConfig = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, - proplists:delete(algorithms, KeyConfig)), - Config; -end_per_group(with_algorithms_for_provider_A, Config) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - maps:put(<<"A">>, proplists:delete(algorithms, OAuthProvider),OAuthProviders)), - Config; -end_per_group(with_static_signing_keys_for_oauth_provider_A, Config) -> - OAuthProviders = application:get_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{}), - OAuthProvider = maps:get(<<"A">>, OAuthProviders), - OAuthProvider0 = proplists:delete(signing_keys, OAuthProvider), - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, - maps:put(<<"A">>, OAuthProvider0, OAuthProviders)), - Config; -end_per_group(with_jwks_url, Config) -> - KeyConfig = ?config(key_config_before_group_with_jwks_url, Config), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig), - Config; -end_per_group(with_issuer, Config) -> - KeyConfig = ?config(key_config_before_group_with_issuer, Config), - application:unset_env(rabbitmq_auth_backend_oauth2, issuer), - application:set_env(rabbitmq_auth_backend_oauth2, key_config, KeyConfig), - stop_http_auth_server(), - Config; -end_per_group(with_oauth_providers_A_with_jwks_uri, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; -end_per_group(with_oauth_providers_A_with_issuer, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; -end_per_group(with_oauth_providers_A_B_with_jwks_uri, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; -end_per_group(with_oauth_providers_A_B_with_issuer, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; - -end_per_group(with_oauth_providers_A, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; -end_per_group(with_oauth_providers_A_B, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), - Config; -end_per_group(with_default_oauth_provider_B, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), - Config; -end_per_group(with_default_oauth_provider_A, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), - Config; - -end_per_group(get_oauth_provider_for_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; - -end_per_group(with_resource_servers_and_resource_server_id, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - Config; - -end_per_group(with_resource_servers, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), - Config; - -end_per_group(with_different_oauth_provider_for_each_resource, Config) -> - {ok, ResourceServers} = application:get_env(rabbitmq_auth_backend_oauth2, resource_servers), - Rabbit1 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers)), - Rabbit2 = proplists:delete(oauth_provider_id, maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers)), - ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), - application:set_env(rabbitmq_auth_backend_oauth2, resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, ResourceServers1)), - Config; - -end_per_group(inheritance_group, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), - - application:unset_env(rabbitmq_auth_backend_oauth2, key_config), - - application:unset_env(rabbitmq_auth_backend_oauth2, resource_servers), - Config; - -end_per_group(with_root_scope_prefix, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - Config; - -end_per_group(_any, Config) -> - Config. - -init_per_testcase(get_preferred_username_claims, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, preferred_username_claims, [<<"username">>]), - Config; - -init_per_testcase(get_additional_scopes_key_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, extra_scopes_source), - Config; -init_per_testcase(is_verify_aud_when_is_false, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), - Config; -init_per_testcase(get_empty_scope_prefix, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, scope_prefix, <<"">>), - Config; -init_per_testcase(get_scope_prefix_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix), - Config; -init_per_testcase(get_resource_server_type_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_type), - Config; -init_per_testcase(has_scope_aliases_when_not_defined, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases), - Config; - -init_per_testcase(_TestCase, Config) -> - Config. - -end_per_testcase(get_preferred_username_claims, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, preferred_username_claims), - Config; - - -end_per_testcase(_Testcase, Config) -> - Config. - -%% ----- - -call_set_env(Config, Par, Value) -> - rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, - [rabbitmq_auth_backend_oauth2, Par, Value]). - -call_get_env(Config, Par, Def) -> - rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, - [rabbitmq_auth_backend_oauth2, Par, Def]). - -call_add_signing_key(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_key, Args). - -call_get_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_keys, Args). -call_get_signing_keys(Config) -> - call_get_signing_keys(Config, []). - -call_get_signing_key(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, get_signing_key, Args). - -call_add_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, add_signing_keys, Args). - -call_replace_signing_keys(Config, Args) -> - rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_config, replace_signing_keys, Args). - - -add_signing_keys_for_root_oauth_provider(Config) -> - #{<<"mykey-1">> := <<"some key 1">>} = - call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - #{<<"mykey-1">> := <<"some key 1">>} = - call_get_signing_keys(Config), - - #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = - call_add_signing_key(Config, [<<"mykey-2">>, <<"some key 2">>]), - #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = - call_get_signing_keys(Config), - - ?assertEqual(<<"some key 1">>, - call_get_signing_key(Config, [<<"mykey-1">>])). - -add_signing_keys_for_specific_oauth_provider(Config) -> - #{<<"mykey-3-1">> := <<"some key 3-1">>} = - call_add_signing_key(Config, - [<<"mykey-3-1">>, <<"some key 3-1">>, <<"my-oauth-provider-3">>]), - #{<<"mykey-4-1">> := <<"some key 4-1">>} = - call_add_signing_key(Config, - [<<"mykey-4-1">>, <<"some key 4-1">>, <<"my-oauth-provider-4">>]), - #{<<"mykey-3-1">> := <<"some key 3-1">>} = - call_get_signing_keys(Config, [<<"my-oauth-provider-3">>]), - #{<<"mykey-4-1">> := <<"some key 4-1">>} = - call_get_signing_keys(Config, [<<"my-oauth-provider-4">>]), - - #{<<"mykey-3-1">> := <<"some key 3-1">>, - <<"mykey-3-2">> := <<"some key 3-2">>} = - call_add_signing_key(Config, [ - <<"mykey-3-2">>, <<"some key 3-2">>, <<"my-oauth-provider-3">>]), - - #{<<"mykey-1">> := <<"some key 1">>} = - call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - #{<<"mykey-1">> := <<"some key 1">>} = - call_get_signing_keys(Config, []), - - ?assertEqual(<<"some key 3-1">>, - call_get_signing_key(Config, [<<"mykey-3-1">> , <<"my-oauth-provider-3">>])). - -replace_merge_root_static_keys_with_newly_added_keys(Config) -> - NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys]), - #{ <<"mykey-root-1">> := <<"some key root-1">>, - <<"mykey-root-2">> := <<"some key root-2">>, - <<"key-2">> := <<"some key 2">>, - <<"key-3">> := <<"some key 3">> - } = call_get_signing_keys(Config). -replace_merge_static_keys_with_newly_added_keys(Config) -> - NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys, <<"A">>]), - #{ <<"mykey-root-1">> := <<"some key root-1">>, - <<"mykey-root-2">> := <<"some key root-2">>, - <<"key-2">> := <<"some key 2">>, - <<"key-3">> := <<"some key 3">> - } = call_get_signing_keys(Config, [<<"A">>]). -replace_override_root_static_keys_with_newly_added_keys(Config) -> - NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys]), - #{ <<"mykey-root-1">> := <<"new key root-1">>, - <<"mykey-root-2">> := <<"some key root-2">>, - <<"key-3">> := <<"some key 3">> - } = call_get_signing_keys(Config). -replace_override_static_keys_with_newly_added_keys(Config) -> - NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys, <<"A">>]), - #{ <<"mykey-root-1">> := <<"new key root-1">>, - <<"mykey-root-2">> := <<"some key root-2">>, - <<"key-3">> := <<"some key 3">> - } = call_get_signing_keys(Config, [<<"A">>]). - -replace_signing_keys_for_root_oauth_provider(Config) -> - call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), - NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys]), - #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = - call_get_signing_keys(Config). - -replace_signing_keys_for_specific_oauth_provider(Config) -> - OAuthProviderId = <<"my-oauth-provider-3">>, - #{<<"mykey-3-1">> := <<"some key 3-1">>} = - call_add_signing_key(Config, - [<<"mykey-3-1">>, <<"some key 3-1">>, OAuthProviderId]), - NewKeys = #{<<"key-2">> => <<"some key 2">>, - <<"key-3">> => <<"some key 3">>}, - call_replace_signing_keys(Config, [NewKeys, OAuthProviderId]), - #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = - call_get_signing_keys(Config, [OAuthProviderId]). - - -get_default_resource_server_id_returns_error(_Config) -> - {error, _} = rabbit_oauth2_config:get_default_resource_server_id(). - -get_resource_server_id_for_rabbit_audience_returns_rabbit(_Config) -> - ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(?RABBITMQ)). -get_resource_server_id_for_none_audience_returns_rabbit(_Config) -> - ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(none)). -get_resource_server_id_for_unknown_audience_returns_rabbit(_Config) -> - ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_resource_server_id_for_audience(<<"unknown">>)). - -get_resource_server_id_for_none_audience_should_fail(_Config) -> - ?assertEqual({error, no_matching_aud_found}, rabbit_oauth2_config:get_resource_server_id_for_audience(none)). -get_resource_server_id_for_unknown_audience_should_fail(_Config) -> - ?assertEqual({error, no_matching_aud_found}, rabbit_oauth2_config:get_resource_server_id_for_audience(<<"unknown">>)). - -get_default_resource_server_id(_Config) -> - ?assertEqual(?RABBITMQ, rabbit_oauth2_config:get_default_resource_server_id()). - -get_allowed_resource_server_ids_returns_empty_list(_Config) -> - [] = rabbit_oauth2_config:get_allowed_resource_server_ids(). - -get_allowed_resource_server_ids_returns_resource_server_id(_Config) -> - [?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). - -get_allowed_resource_server_ids_returns_all_resource_servers_ids(_Config) -> - [ <<"rabbitmq1">>, <<"rabbitmq2">>, ?RABBITMQ] = rabbit_oauth2_config:get_allowed_resource_server_ids(). - -get_allowed_resource_server_ids_returns_resource_servers_ids(_Config) -> - [<<"rabbitmq-0">>, <<"rabbitmq-1">>, <<"rabbitmq1">>, <<"rabbitmq2">> ] = - lists:sort(rabbit_oauth2_config:get_allowed_resource_server_ids()). - -index_resource_servers_by_id_else_by_key(_Config) -> - {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"0">>), - {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq-0">>]), - {ok, <<"rabbitmq-0">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq-0">>). - -find_audience_in_resource_server_ids_returns_key_not_found(_Config) -> - {error, no_matching_aud_found} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ). - -find_audience_in_resource_server_ids_returns_found_too_many(_Config) -> - {error, only_one_resource_server_as_audience_found_many} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"rabbitmq1">>]). - -find_audience_in_resource_server_ids_found_one_resource_servers(_Config) -> - {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq1">>), - {ok, <<"rabbitmq1">>} = rabbit_oauth2_config:find_audience_in_resource_server_ids([<<"rabbitmq1">>, <<"other">>]). - -find_audience_in_resource_server_ids_found_resource_server_id(_Config) -> - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(?RABBITMQ), - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids([?RABBITMQ, <<"other">>]). - -find_audience_in_resource_server_ids_using_binary_audience(_Config) -> - {ok, ?RABBITMQ} = rabbit_oauth2_config:find_audience_in_resource_server_ids(<<"rabbitmq other">>). - -get_additional_scopes_key(_Config) -> - ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key()), - ?assertEqual({ok, <<"extra-scope-1">>}, rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq1">> )), - ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)), - ?assertEqual({ok, <<"roles">>}, rabbit_oauth2_config:get_additional_scopes_key(?RABBITMQ)). - -get_additional_scopes_key_when_not_defined(_Config) -> - ?assertEqual({error, not_found}, rabbit_oauth2_config:get_additional_scopes_key()), - ?assertEqual(rabbit_oauth2_config:get_additional_scopes_key(), rabbit_oauth2_config:get_additional_scopes_key(<<"rabbitmq2">>)). - -is_verify_aud(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(?RABBITMQ), rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). -is_verify_aud_for_resource_one_returns_false(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_ONE)). - -is_verify_aud_for_resource_two_returns_true(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_TWO)). - -is_verify_aud_when_is_false(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud()), - ?assertEqual(rabbit_oauth2_config:is_verify_aud(), rabbit_oauth2_config:is_verify_aud(<<"rabbitmq2">>)). - -is_verify_aud_for_resource_one_returns_true(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_ONE)). -is_verify_aud_for_resource_two_returns_false(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:is_verify_aud(?RABBITMQ_RESOURCE_TWO)). - -get_default_preferred_username_claims(_Config) -> - ?assertEqual(rabbit_oauth2_config:get_default_preferred_username_claims(), rabbit_oauth2_config:get_preferred_username_claims()). - -get_preferred_username_claims(_Config) -> - ?assertEqual([<<"username">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims()), - ?assertEqual([<<"email-address">>] ++ rabbit_oauth2_config:get_default_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_preferred_username_claims(), - rabbit_oauth2_config:get_preferred_username_claims(<<"rabbitmq2">>)). - -get_scope_prefix_when_not_defined(_Config) -> - ?assertEqual(<<"rabbitmq.">>, rabbit_oauth2_config:get_scope_prefix()), - ?assertEqual(<<"rabbitmq2.">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). - -get_empty_scope_prefix(_Config) -> - ?assertEqual(<<"">>, rabbit_oauth2_config:get_scope_prefix()), - ?assertEqual(<<"">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). - -get_scope_prefix(_Config) -> - ?assertEqual(<<"some-prefix-">>, rabbit_oauth2_config:get_scope_prefix()), - ?assertEqual(<<"my-prefix:">>, rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_scope_prefix(), rabbit_oauth2_config:get_scope_prefix(<<"rabbitmq2">>)). - -get_scope_prefix_for_resource_one_returns_default_scope_prefix(_Config) -> - ?assertEqual(undefined, application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix)), - ?assertEqual(append_paths(?RABBITMQ_RESOURCE_ONE, <<".">>), - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)). -get_scope_prefix_for_resource_one_returns_root_scope_prefix(_Config) -> - {ok, Prefix} = application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix), - ?assertEqual(rabbit_oauth2_config:get_scope_prefix(), - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)), - ?assertEqual(Prefix, - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)). -get_scope_prefix_for_resource_one_returns_empty_scope_prefix(_Config) -> - ?assertEqual(<<"">>, - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_ONE)). -get_scope_prefix_for_resource_two_returns_root_scope_prefix(_Config) -> - {ok, Prefix} = application:get_env(rabbitmq_auth_backend_oauth2, scope_prefix), - ?assertEqual(rabbit_oauth2_config:get_scope_prefix(), - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_TWO)), - ?assertEqual(Prefix, - rabbit_oauth2_config:get_scope_prefix(?RABBITMQ_RESOURCE_TWO)). - -get_resource_server_type_when_not_defined(_Config) -> - ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type()), - ?assertEqual(<<>>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). - -get_resource_server_type(_Config) -> - ?assertEqual(<<"rabbitmq-type">>, rabbit_oauth2_config:get_resource_server_type()), - ?assertEqual(<<"my-type">>, rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_resource_server_type(), rabbit_oauth2_config:get_resource_server_type(<<"rabbitmq2">>)). - -has_scope_aliases_when_not_defined(_Config) -> - ?assertEqual(false, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). - -has_scope_aliases(_Config) -> - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(?RABBITMQ)), - ?assertEqual(true, rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:has_scope_aliases(?RABBITMQ), rabbit_oauth2_config:has_scope_aliases(<<"rabbitmq2">>)). - -get_scope_aliases(_Config) -> - ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(?RABBITMQ)), - ?assertEqual(#{}, rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq1">>)), - ?assertEqual(rabbit_oauth2_config:get_scope_aliases(?RABBITMQ), rabbit_oauth2_config:get_scope_aliases(<<"rabbitmq2">>)). - -get_default_key_should_fail(_Config) -> - {error, no_default_key_configured} = rabbit_oauth2_config:get_default_key(). -get_default_key(_Config) -> - {ok, <<"default-key">>} = rabbit_oauth2_config:get_default_key(). -get_default_key_for_provider_A_should_fail(_Config) -> - {error, no_default_key_configured} = rabbit_oauth2_config:get_default_key(<<"A">>). -get_default_key_for_provider_A(_Config) -> - {ok, <<"A-default-key">>} = rabbit_oauth2_config:get_default_key(<<"A">>). - -get_signing_keys(_Config) -> - #{<<"mykey-1-1">> := <<"some key 1-1">>, - <<"mykey-1-2">> := <<"some key 1-2">>} = rabbit_oauth2_config:get_signing_keys(), - <<"some key 1-1">> = rabbit_oauth2_config:get_signing_key(<<"mykey-1-1">>), - undefined = rabbit_oauth2_config:get_signing_key(<<"unknown">>). -get_signing_keys_for_oauth_provider_A(_Config) -> - #{<<"A-mykey-1-1">> := <<"A-some key 1-1">>, - <<"A-mykey-1-2">> := <<"A-some key 1-2">>} = rabbit_oauth2_config:get_signing_keys(<<"A">>), - <<"A-some key 1-1">> = rabbit_oauth2_config:get_signing_key(<<"A-mykey-1-1">>, <<"A">>), - undefined = rabbit_oauth2_config:get_signing_key(<<"unknown">>, <<"A">>). - -get_algorithms_should_return_undefined(_Config) -> - undefined = rabbit_oauth2_config:get_algorithms(). -get_algorithms(Config) -> - ?assertEqual(?config(algorithms, Config), rabbit_oauth2_config:get_algorithms()). -get_algorithms_for_provider_A_should_return_undefined(_Config) -> - undefined = rabbit_oauth2_config:get_algorithms(<<"A">>). -get_algorithms_for_provider_A(Config) -> - ?assertEqual(?config(algorithms, Config), rabbit_oauth2_config:get_algorithms(<<"A">>)). - -get_oauth_provider_root_with_jwks_uri_should_fail(_Config) -> - root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {error, _Message} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]). -get_oauth_provider_A_with_jwks_uri_should_fail(_Config) -> - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {error, _Message} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]). -get_oauth_provider_should_return_root_oauth_provider_with_jwks_uri(_Config) -> - root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri). -get_oauth_provider_for_both_resources_should_return_root_oauth_provider(_Config) -> - root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), - root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO). -get_oauth_provider_for_resource_one_should_return_oauth_provider_A(_Config) -> - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). -get_oauth_provider_for_both_resources_should_return_oauth_provider_A(_Config) -> - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_ONE), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO). -get_oauth_provider_for_resource_two_should_return_oauth_provider_B(_Config) -> - <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ_RESOURCE_TWO), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). - -get_oauth_provider_should_return_root_oauth_provider_with_all_discovered_endpoints(_Config) -> - root = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(root, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/">>), OAuthProvider#oauth_provider.issuer). -append_paths(Path1, Path2) -> - erlang:iolist_to_binary([Path1, Path2]). - -get_oauth_provider_should_return_oauth_provider_B_with_jwks_uri(_Config) -> - <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri). - -get_oauth_provider_should_return_oauth_provider_B_with_all_discovered_endpoints(_Config) -> - <<"B">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"B">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/B/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/B">>), OAuthProvider#oauth_provider.issuer). - -get_oauth_provider_should_return_oauth_provider_A_with_jwks_uri(_Config) -> - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri). - -get_oauth_provider_should_return_oauth_provider_A_with_all_discovered_endpoints(_Config) -> - <<"A">> = rabbit_oauth2_config:get_oauth_provider_id_for_resource_server_id(?RABBITMQ), - {ok, OAuthProvider} = rabbit_oauth2_config:get_oauth_provider(<<"A">>, [jwks_uri]), - ?assertEqual(build_url_to_oauth_provider(<<"/A/keys">>), OAuthProvider#oauth_provider.jwks_uri), - ?assertEqual(build_url_to_oauth_provider(<<"/A">>), OAuthProvider#oauth_provider.issuer). - -get_openid_configuration_expectations() -> - [ {get_root_openid_configuration, - - #{request => #{ - method => <<"GET">>, - path => <<"/.well-known/openid-configuration">> - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {issuer, build_url_to_oauth_provider(<<"/">>) }, - {jwks_uri, build_url_to_oauth_provider(<<"/keys">>)} - ]} - ] - } - }, - {get_A_openid_configuration, - - #{request => #{ - method => <<"GET">>, - path => <<"/A/.well-known/openid-configuration">> - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {issuer, build_url_to_oauth_provider(<<"/A">>) }, - {jwks_uri, build_url_to_oauth_provider(<<"/A/keys">>)} - ]} - ] - } - }, - {get_B_openid_configuration, - - #{request => #{ - method => <<"GET">>, - path => <<"/B/.well-known/openid-configuration">> - }, - response => [ - {code, 200}, - {content_type, ?CONTENT_JSON}, - {payload, [ - {issuer, build_url_to_oauth_provider(<<"/B">>) }, - {jwks_uri, build_url_to_oauth_provider(<<"/B/keys">>)} - ]} - ] - } - } - ]. - -start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations) -> - Dispatch = cowboy_router:compile([ - {'_', [{Path, oauth2_http_mock, Expected} || #{request := #{path := Path}} = Expected <- Expectations ]} - ]), - ct:log("start_https_oauth_server (port:~p) with expectation list : ~p -> dispatch: ~p", [Port, Expectations, Dispatch]), - {ok, Pid} = cowboy:start_tls( - mock_http_auth_listener, - [{port, Port}, - {certfile, filename:join([CertsDir, "server", "cert.pem"])}, - {keyfile, filename:join([CertsDir, "server", "key.pem"])} - ], - #{env => #{dispatch => Dispatch}}), - ct:log("Started on Port ~p and pid ~p", [ranch:get_port(mock_http_auth_listener), Pid]). - -build_url_to_oauth_provider(Path) -> - uri_string:recompose(#{scheme => "https", - host => "localhost", - port => rabbit_data_coercion:to_integer(?AUTH_PORT), - path => Path}). - -stop_http_auth_server() -> - cowboy:stop_listener(mock_http_auth_listener). - --spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list(). -ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) -> - [{verify, PeerVerification}, - {depth, 10}, - {fail_if_no_peer_cert, FailIfNoPeerCert}, - {crl_check, false}, - {crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}, - {cacertfile, CaCertFile}]. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl new file mode 100644 index 000000000000..9f830585aa18 --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_provider_SUITE.erl @@ -0,0 +1,523 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_provider_SUITE). + +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("oauth2.hrl"). + +-define(RABBITMQ,<<"rabbitmq">>). +-define(RABBITMQ_RESOURCE_ONE,<<"rabbitmq1">>). +-define(RABBITMQ_RESOURCE_TWO,<<"rabbitmq2">>). +-define(AUTH_PORT, 8000). + +-import(rabbit_oauth2_provider, [ + get_internal_oauth_provider/0,get_internal_oauth_provider/1, + add_signing_key/2, add_signing_key/3, replace_signing_keys/1, + replace_signing_keys/2, + get_signing_keys/0, get_signing_keys/1, get_signing_key/1, get_signing_key/2 +]). +-import(oauth2_client, [get_oauth_provider/2]). + +all() -> [ + {group, with_rabbitmq_node}, + {group, verify_oauth_provider_A}, + {group, verify_oauth_provider_root} +]. +groups() -> [ + {with_rabbitmq_node, [], [ + add_signing_keys_for_specific_oauth_provider, + add_signing_keys_for_root_oauth_provider, + + replace_signing_keys_for_root_oauth_provider, + replace_signing_keys_for_specific_oauth_provider, + {with_root_static_signing_keys, [], [ + replace_merge_root_static_keys_with_newly_added_keys, + replace_override_root_static_keys_with_newly_added_keys + ]}, + {with_static_signing_keys_for_specific_oauth_provider, [], [ + replace_merge_static_keys_with_newly_added_keys, + replace_override_static_keys_with_newly_added_keys + ]} + ]}, + {verify_oauth_provider_A, [], verify_provider()}, + {verify_oauth_provider_root, [], verify_provider()} +]. + +verify_provider() -> [ + internal_oauth_provider_has_no_default_key, + {oauth_provider_with_default_key, [], [ + internal_oauth_provider_has_default_key + ]}, + internal_oauth_provider_has_no_algorithms, + {oauth_provider_with_algorithms, [], [ + internal_oauth_provider_has_algorithms + ]}, + get_oauth_provider_with_jwks_uri_returns_error, + {oauth_provider_with_jwks_uri, [], [ + get_oauth_provider_has_jwks_uri + ]}, + {oauth_provider_with_issuer, [], [ + get_oauth_provider_has_jwks_uri + ]} +]. + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_group(with_rabbitmq_node, Config) -> + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, with_rabbitmq_node}, + {rmq_nodes_count, 1} + ]), + rabbit_ct_helpers:run_steps(Config1, rabbit_ct_broker_helpers:setup_steps()); + +init_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + call_set_env(Config, key_config, + proplists:delete(default_key, KeyConfig) ++ [{signing_keys,SigningKeys}]), + Config; + +init_per_group(with_static_signing_keys_for_specific_oauth_provider, Config) -> + OAuthProviders = call_get_env(Config, oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + SigningKeys = #{ + <<"mykey-root-1">> => <<"some key root-1">>, + <<"mykey-root-2">> => <<"some key root-2">> + }, + OAuthProvider1 = proplists:delete(signing_keys, OAuthProvider) ++ + [{signing_keys, SigningKeys}], + + call_set_env(Config, oauth_providers, maps:put(<<"A">>, OAuthProvider1, + OAuthProviders)), + Config; + +init_per_group(oauth_provider_with_jwks_uri, Config) -> + URL = case ?config(oauth_provider_id, Config) of + root -> + RootUrl = build_url_to_oauth_provider(<<"/keys">>), + set_env(key_config, [{jwks_url, RootUrl}]), + RootUrl; + <<"A">> -> + AUrl = build_url_to_oauth_provider(<<"/A/keys">>), + set_oauth_provider_properties(<<"A">>, [{jwks_uri, AUrl}]), + AUrl + end, + [{jwks_uri, URL} | Config]; + +init_per_group(oauth_provider_with_issuer, Config) -> + {ok, _} = application:ensure_all_started(inets), + {ok, _} = application:ensure_all_started(ssl), + application:ensure_all_started(cowboy), + CertsDir = ?config(rmq_certsdir, Config), + CaCertFile = filename:join([CertsDir, "testca", "cacert.pem"]), + SslOptions = ssl_options(verify_peer, false, CaCertFile), + + HttpOauthServerExpectations = get_openid_configuration_expectations(), + ListOfExpectations = maps:values(proplists:to_map(HttpOauthServerExpectations)), + + start_https_oauth_server(?AUTH_PORT, CertsDir, ListOfExpectations), + set_env(use_global_locks, false), + {Issuer, JwksUri} = case ?config(oauth_provider_id, Config) of + root -> + Url = build_url_to_oauth_provider(<<"/">>), + set_env(issuer, Url), + set_env(key_config, SslOptions), + {Url, build_url_to_oauth_provider(<<"/keys">>)}; + <<"A">> -> + Url = build_url_to_oauth_provider(<<"/A">>), + set_oauth_provider_properties(<<"A">>, [{issuer, Url}, {https, SslOptions}]), + {Url, build_url_to_oauth_provider(<<"/A/keys">>)} + end, + [{issuer, Issuer}, {jwks_uri, JwksUri}] ++ Config; + +init_per_group(with_resource_server_id, Config) -> + set_env(resource_server_id, ?RABBITMQ), + Config; + +init_per_group(with_algorithms, Config) -> + KeyConfig = get_env(key_config, []), + set_env(key_config, KeyConfig ++ [{algorithms, [<<"HS256">>, <<"RS256">>]}]), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; + +init_per_group(with_algorithms_for_provider_A, Config) -> + OAuthProviders = get_env(oauth_providers, #{}), + OAuthProvider = maps:get(<<"A">>, OAuthProviders, []), + set_env(oauth_providers, maps:put(<<"A">>, + [{algorithms, [<<"HS256">>, <<"RS256">>]} | OAuthProvider], OAuthProviders)), + [{algorithms, [<<"HS256">>, <<"RS256">>]} | Config]; + +init_per_group(with_different_oauth_provider_for_each_resource, Config) -> + {ok, ResourceServers} = get_env(resource_servers), + Rabbit1 = maps:get(?RABBITMQ_RESOURCE_ONE, ResourceServers) ++ + [ {oauth_provider_id, <<"A">>} ], + Rabbit2 = maps:get(?RABBITMQ_RESOURCE_TWO, ResourceServers) ++ + [ {oauth_provider_id, <<"B">>} ], + ResourceServers1 = maps:update(?RABBITMQ_RESOURCE_ONE, Rabbit1, ResourceServers), + set_env(resource_servers, maps:update(?RABBITMQ_RESOURCE_TWO, Rabbit2, + ResourceServers1)), + Config; + + +init_per_group(verify_oauth_provider_A, Config) -> + set_env(oauth_providers, + #{ <<"A">> => [ + {id, <<"A">>} + ] + }), + [{oauth_provider_id, <<"A">>} |Config]; + +init_per_group(verify_oauth_provider_root, Config) -> + [{oauth_provider_id, root} |Config]; + +init_per_group(_any, Config) -> + Config. + +end_per_group(with_rabbitmq_node, Config) -> + rabbit_ct_helpers:run_steps(Config, rabbit_ct_broker_helpers:teardown_steps()); + +end_per_group(with_root_static_signing_keys, Config) -> + KeyConfig = call_get_env(Config, key_config, []), + call_set_env(Config, key_config, KeyConfig), + Config; + +end_per_group(with_resource_server_id, Config) -> + unset_env(resource_server_id), + Config; + +end_per_group(oauth_provider_with_issuer, Config) -> + case ?config(oauth_provider_id, Config) of + root -> + unset_env(issuer), + unset_env(https); + Id -> + unset_oauth_provider_properties(Id, [issuer, https]) + end, + stop_http_auth_server(), + Config; +end_per_group(oauth_provider_with_jwks_uri, Config) -> + case ?config(oauth_provider_id, Config) of + root -> unset_env(jwks_url); + Id -> unset_oauth_provider_properties(Id, [jwks_uri]) + end, + Config; + +end_per_group(oauth_provider_with_default_key, Config) -> +case ?config(oauth_provider_id, Config) of + root -> unset_env(default_key); + Id -> unset_oauth_provider_properties(Id, [default_key]) + end, + Config; + +end_per_group(_any, Config) -> + Config. + +%% ----- Utility functions + +call_set_env(Config, Par, Value) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, + [rabbitmq_auth_backend_oauth2, Par, Value]). + +call_get_env(Config, Par, Def) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, get_env, + [rabbitmq_auth_backend_oauth2, Par, Def]). + +call_add_signing_key(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + add_signing_key, Args). + +call_get_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + get_signing_keys, Args). + +call_get_signing_keys(Config) -> + call_get_signing_keys(Config, []). + +call_get_signing_key(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + get_signing_key, Args). + +call_add_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + add_signing_keys, Args). + +call_replace_signing_keys(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_oauth2_provider, + replace_signing_keys, Args). + +%% ----- Test cases + +add_signing_keys_for_root_oauth_provider(Config) -> + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config), + + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_add_signing_key(Config, [<<"mykey-2">>, <<"some key 2">>]), + #{<<"mykey-1">> := <<"some key 1">>, <<"mykey-2">> := <<"some key 2">>} = + call_get_signing_keys(Config), + + ?assertEqual(<<"some key 1">>, + call_get_signing_key(Config, [<<"mykey-1">>])). + +add_signing_keys_for_specific_oauth_provider(Config) -> + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, <<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_add_signing_key(Config, + [<<"mykey-4-1">>, <<"some key 4-1">>, <<"my-oauth-provider-4">>]), + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-3">>]), + #{<<"mykey-4-1">> := <<"some key 4-1">>} = + call_get_signing_keys(Config, [<<"my-oauth-provider-4">>]), + + #{<<"mykey-3-1">> := <<"some key 3-1">>, + <<"mykey-3-2">> := <<"some key 3-2">>} = + call_add_signing_key(Config, [ + <<"mykey-3-2">>, <<"some key 3-2">>, <<"my-oauth-provider-3">>]), + + #{<<"mykey-1">> := <<"some key 1">>} = + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + #{<<"mykey-1">> := <<"some key 1">>} = + call_get_signing_keys(Config, []), + + ?assertEqual(<<"some key 3-1">>, + call_get_signing_key(Config, [<<"mykey-3-1">> , <<"my-oauth-provider-3">>])). + +replace_merge_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). + +replace_merge_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"some key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-2">> := <<"some key 2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). + +replace_override_root_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config). +replace_override_static_keys_with_newly_added_keys(Config) -> + NewKeys = #{<<"mykey-root-1">> => <<"new key root-1">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, <<"A">>]), + #{ <<"mykey-root-1">> := <<"new key root-1">>, + <<"mykey-root-2">> := <<"some key root-2">>, + <<"key-3">> := <<"some key 3">> + } = call_get_signing_keys(Config, [<<"A">>]). + +replace_signing_keys_for_root_oauth_provider(Config) -> + call_add_signing_key(Config, [<<"mykey-1">>, <<"some key 1">>]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config). + +replace_signing_keys_for_specific_oauth_provider(Config) -> + OAuthProviderId = <<"my-oauth-provider-3">>, + #{<<"mykey-3-1">> := <<"some key 3-1">>} = + call_add_signing_key(Config, + [<<"mykey-3-1">>, <<"some key 3-1">>, OAuthProviderId]), + NewKeys = #{<<"key-2">> => <<"some key 2">>, + <<"key-3">> => <<"some key 3">>}, + call_replace_signing_keys(Config, [NewKeys, OAuthProviderId]), + #{<<"key-2">> := <<"some key 2">>, <<"key-3">> := <<"some key 3">>} = + call_get_signing_keys(Config, [OAuthProviderId]). + + +get_algorithms_should_return_undefined(_Config) -> + OAuthProvider = get_internal_oauth_provider(), + undefined = OAuthProvider#internal_oauth_provider.algorithms. + +get_algorithms(Config) -> + OAuthProvider = get_internal_oauth_provider(), + Algorithms = OAuthProvider#internal_oauth_provider.algorithms, + ?assertEqual(?config(algorithms, Config), Algorithms). + +get_algorithms_for_provider_A_should_return_undefined(_Config) -> + OAuthProvider = get_internal_oauth_provider(<<"A">>), + undefined = OAuthProvider#internal_oauth_provider.algorithms. + +get_algorithms_for_provider_A(Config) -> + OAuthProvider = get_internal_oauth_provider(<<"A">>), + Algorithms = OAuthProvider#internal_oauth_provider.algorithms, + ?assertEqual(?config(algorithms, Config), Algorithms). + +append_paths(Path1, Path2) -> + erlang:iolist_to_binary([Path1, Path2]). + + + +internal_oauth_provider_has_no_default_key(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(undefined, + InternalOAuthProvider#internal_oauth_provider.default_key). + +internal_oauth_provider_has_default_key(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(?config(default_key, Config), + InternalOAuthProvider#internal_oauth_provider.default_key). + +internal_oauth_provider_has_no_algorithms(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(undefined, + InternalOAuthProvider#internal_oauth_provider.algorithms). + +internal_oauth_provider_has_algorithms(Config) -> + InternalOAuthProvider = get_internal_oauth_provider( + ?config(oauth_provider_id, Config)), + ?assertEqual(?config(algorithms, Config), + InternalOAuthProvider#internal_oauth_provider.algorithms). + +get_oauth_provider_with_jwks_uri_returns_error(Config) -> + {error, _} = get_oauth_provider( + ?config(oauth_provider_id, Config), [jwks_uri]). + +get_oauth_provider_has_jwks_uri(Config) -> + {ok, OAuthProvider} = get_oauth_provider( + ?config(oauth_provider_id, Config), [jwks_uri]), + ct:log("OAuthProvider: ~p", [OAuthProvider]), + ?assertEqual(?config(jwks_uri, Config), OAuthProvider#oauth_provider.jwks_uri). + + +%% ---- Utility functions + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). +unset_env(Par) -> + application:unset_env(rabbitmq_auth_backend_oauth2, Par). + +get_openid_configuration_expectations() -> + [ {get_root_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/keys">>)} + ]} + ] + } + }, + {get_A_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/A/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/A">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/A/keys">>)} + ]} + ] + } + }, + {get_B_openid_configuration, + + #{request => #{ + method => <<"GET">>, + path => <<"/B/.well-known/openid-configuration">> + }, + response => [ + {code, 200}, + {content_type, ?CONTENT_JSON}, + {payload, [ + {issuer, build_url_to_oauth_provider(<<"/B">>) }, + {jwks_uri, build_url_to_oauth_provider(<<"/B/keys">>)} + ]} + ] + } + } + ]. + +start_https_oauth_server(Port, CertsDir, Expectations) when is_list(Expectations) -> + Dispatch = cowboy_router:compile([ + {'_', [{Path, oauth2_http_mock, Expected} || + #{request := #{path := Path}} = Expected <- Expectations ]} + ]), + {ok, Pid} = cowboy:start_tls( + mock_http_auth_listener, + [{port, Port}, + {certfile, filename:join([CertsDir, "server", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "server", "key.pem"])} + ], + #{env => #{dispatch => Dispatch}}). + +build_url_to_oauth_provider(Path) -> + uri_string:recompose(#{scheme => "https", + host => "localhost", + port => rabbit_data_coercion:to_integer(?AUTH_PORT), + path => Path}). + +stop_http_auth_server() -> + cowboy:stop_listener(mock_http_auth_listener). + +set_oauth_provider_properties(OAuthProviderId, Proplist) -> + OAuthProviders = get_env(oauth_providers, #{}), + CurProplist = maps:get(OAuthProviderId, OAuthProviders), + CurMap = proplists:to_map(CurProplist), + Map = proplists:to_map(Proplist), + set_env(oauth_providers, maps:put(OAuthProviderId, + maps:to_list(maps:merge(CurMap, Map)), OAuthProviders)). + +unset_oauth_provider_properties(OAuthProviderId, PropertyNameList) -> + OAuthProviders = get_env(oauth_providers, #{}), + CurProplist = maps:get(OAuthProviderId, OAuthProviders), + CurMap = proplists:to_map(CurProplist), + set_env(oauth_providers, maps:put(OAuthProviderId, + maps:to_list(maps:filter(fun(K,_V) -> + not proplists:is_defined(K, PropertyNameList) end, CurMap)), + OAuthProviders)). + +-spec ssl_options(ssl:verify_type(), boolean(), file:filename()) -> list(). +ssl_options(PeerVerification, FailIfNoPeerCert, CaCertFile) -> + [{verify, PeerVerification}, + {depth, 10}, + {fail_if_no_peer_cert, FailIfNoPeerCert}, + {crl_check, false}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 10000}]}}}, + {cacertfile, CaCertFile}]. diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl new file mode 100644 index 000000000000..3e1fb745b6ec --- /dev/null +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_resource_server_SUITE.erl @@ -0,0 +1,452 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_oauth2_resource_server_SUITE). + +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("oauth2.hrl"). + +-define(RABBITMQ,<<"rabbitmq">>). +-define(RABBITMQ_RESOURCE_ONE,<<"rabbitmq1">>). +-define(RABBITMQ_RESOURCE_TWO,<<"rabbitmq2">>). +-define(OAUTH_PROVIDER_A,<<"A">>). +-define(OAUTH_PROVIDER_B,<<"B">>). + +-import(oauth2_client, [get_oauth_provider/2]). +-import(rabbit_oauth2_resource_server, [resolve_resource_server_from_audience/1]). + + +all() -> [ + {group, without_resource_server_id}, + {group, with_rabbitmq_as_resource_server_id}, + {group, with_two_resource_servers} + %{group, with_two_resource_servers_and_rabbitmq_as_resource_server_id} +]. +groups() -> [ + {with_rabbitmq_as_resource_server_id, [], [ + resolve_resource_server_for_rabbitmq_audience, + resolve_resource_server_for_rabbitmq_plus_unknown_audience, + resolve_resource_server_for_none_audience_returns_no_aud_found, + resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found, + {with_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_rabbitmq, + resolve_resource_server_for_unknown_audience_returns_rabbitmq + ]}, + {verify_get_rabbitmq_server_configuration, [], + verify_get_rabbitmq_server_configuration()} + ]}, + {without_resource_server_id, [], [ + resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found + ]}, + + {with_two_resource_servers, [], [ + resolve_resource_server_id_for_rabbitmq1, + resolve_resource_server_id_for_rabbitmq2, + resolve_resource_server_id_for_both_resources_returns_error, + resolve_resource_server_for_none_audience_returns_no_aud_found, + resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found, + {with_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_rabbitmq2, + resolve_resource_server_for_unknown_audience_returns_rabbitmq2, + {with_rabbitmq1_verify_aud_false, [], [ + resolve_resource_server_for_none_audience_returns_error + ]} + ]}, + verify_rabbitmq1_server_configuration, + {verify_configuration_inheritance_with_rabbitmq2, [], + verify_configuration_inheritance_with_rabbitmq2()}, + {with_rabbitmq_as_resource_server_id, [], [ + resolve_resource_server_for_rabbitmq_audience, + resolve_resource_server_id_for_rabbitmq1, + resolve_resource_server_id_for_rabbitmq2 + ]} + ]} +]. + +verify_get_rabbitmq_server_configuration() -> [ + rabbitmq_verify_aud_is_true, + {with_verify_aud_false, [], [ + rabbitmq_verify_aud_is_false + ]}, + rabbitmq_has_default_scope_prefix, + {with_scope_prefix, [], [ + rabbitmq_has_scope_prefix + ]}, + {with_empty_scope_prefix, [], [ + rabbitmq_has_empty_scope_prefix + ]}, + rabbitmq_oauth_provider_id_is_root, + {with_default_oauth_provider_A, [], [ + rabbitmq_oauth_provider_id_is_A + ]}, + rabbitmq_has_no_additional_scopes_key, + {with_additional_scopes_key, [], [ + rabbitmq_has_additional_scopes_key + ]}, + rabbitmq_has_no_preferred_username_claims_but_gets_default, + {with_preferred_username_claims, [], [ + rabbitmq_has_preferred_username_claims + ]}, + rabbitmq_has_no_scope_aliases, + {with_scope_aliases, [], [ + rabbitmq_has_scope_aliases + ]} +]. + +verify_configuration_inheritance_with_rabbitmq2() -> [ + rabbitmq2_verify_aud_is_true, + {with_verify_aud_false, [], [ + rabbitmq2_verify_aud_is_false + ]}, + rabbitmq2_has_default_scope_prefix, + {with_scope_prefix, [], [ + rabbitmq2_has_scope_prefix + ]}, + rabbitmq2_oauth_provider_id_is_root, + {with_default_oauth_provider_A, [], [ + rabbitmq2_oauth_provider_id_is_A + ]}, + rabbitmq2_has_no_additional_scopes_key, + {with_additional_scopes_key, [], [ + rabbitmq2_has_additional_scopes_key + ]}, + rabbitmq2_has_no_preferred_username_claims_but_gets_default, + {with_preferred_username_claims, [], [ + rabbitmq2_has_preferred_username_claims_plus_default + ]}, + rabbitmq2_has_no_scope_aliases, + {with_scope_aliases, [], [ + rabbitmq2_has_scope_aliases + ]} +]. + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_group(with_default_oauth_provider_A, Config) -> + set_env(default_oauth_provider, ?OAUTH_PROVIDER_A), + Config; + +init_per_group(with_default_oauth_provider_B, Config) -> + set_env(default_oauth_provider, ?OAUTH_PROVIDER_B), + Config; + +init_per_group(with_rabbitmq_as_resource_server_id, Config) -> + set_env(resource_server_id, ?RABBITMQ), + Config; + +init_per_group(with_scope_prefix, Config) -> + Prefix = <<"some-prefix:">>, + set_env(scope_prefix, Prefix), + [{scope_prefix, Prefix} | Config]; + +init_per_group(with_empty_scope_prefix, Config) -> + Prefix = <<"">>, + set_env(scope_prefix, Prefix), + Config; + +init_per_group(with_additional_scopes_key, Config) -> + Key = <<"roles">>, + set_env(extra_scopes_source, Key), + [{additional_scopes_key, Key} | Config]; + +init_per_group(with_preferred_username_claims, Config) -> + Claims = [<<"new-user">>, <<"new-email">>], + set_env(preferred_username_claims, Claims), + [{preferred_username_claims, Claims} | Config]; + +init_per_group(with_scope_aliases, Config) -> + Aliases = #{ + <<"admin">> => [<<"rabbitmq.tag:administrator">>] + }, + set_env(scope_aliases, Aliases), + [{scope_aliases, Aliases} | Config]; + +init_per_group(with_verify_aud_false, Config) -> + set_env(verify_aud, false), + Config; + +init_per_group(with_rabbitmq1_verify_aud_false, Config) -> + RabbitMQServers = get_env(resource_servers, #{}), + Resource0 = maps:get(?RABBITMQ_RESOURCE_ONE, RabbitMQServers, []), + Resource = [{verify_aud, false} | Resource0], + set_env(resource_servers, maps:put(?RABBITMQ_RESOURCE_ONE, Resource, + RabbitMQServers)), + Config; + +init_per_group(with_two_resource_servers, Config) -> + RabbitMQ1 = [ + {id, ?RABBITMQ_RESOURCE_ONE}, + {resource_server_type, <<"some-type">>}, + {verify_aud, true}, + {scope_prefix, <<"some-prefix">>}, + {additional_scopes_key, <<"roles">>}, + {preferred_username_claims, [<<"x-username">>, <<"x-email">>]}, + {scope_aliases, #{ <<"admin">> => [<<"rabbitmq.tag:administrator">>]}}, + {oauth_provider_id, ?OAUTH_PROVIDER_A} + ], + RabbitMQ2 = [ + {id, ?RABBITMQ_RESOURCE_TWO} + ], + set_env(resource_servers, #{ + ?RABBITMQ_RESOURCE_ONE => RabbitMQ1, + ?RABBITMQ_RESOURCE_TWO => RabbitMQ2 + }), + [{?RABBITMQ_RESOURCE_ONE, RabbitMQ1}, {?RABBITMQ_RESOURCE_TWO, RabbitMQ2}] + ++ Config; + +init_per_group(_any, Config) -> + Config. + +end_per_group(with_default_oauth_provider_A, Config) -> + unset_env(default_oauth_provider), + Config; + +end_per_group(with_default_oauth_provider_B, Config) -> + unset_env(default_oauth_provider), + Config; + +end_per_group(with_rabbitmq_as_resource_server_id, Config) -> + unset_env(resource_server_id), + Config; + +end_per_group(with_empty_scope_prefix, Config) -> + unset_env(scope_prefix), + Config; + +end_per_group(with_verify_aud_false, Config) -> + unset_env(verify_aud), + Config; + +end_per_group(with_two_resource_servers, Config) -> + unset_env(resource_servers), + Config; + +end_per_group(with_scope_prefix, Config) -> + unset_env(scope_prefix), + Config; + +end_per_group(with_rabbitmq1_verify_aud_false, Config) -> + RabbitMQServers = get_env(resource_servers, #{}), + Resource = maps:get(?RABBITMQ_RESOURCE_ONE, RabbitMQServers, []), + set_env(resource_servers, maps:put(?RABBITMQ_RESOURCE_ONE, + proplists:delete(verify_aud, Resource), + RabbitMQServers)), + Config; + +end_per_group(with_additional_scopes_key, Config) -> + unset_env(extra_scopes_source), + Config; + +end_per_group(with_preferred_username_claims, Config) -> + unset_env(preferred_username_claims), + Config; + +end_per_group(with_scope_aliases, Config) -> + unset_env(scope_aliases), + Config; + +end_per_group(_any, Config) -> + Config. + + +%% --- Test cases + +resolve_resource_server_for_rabbitmq_audience(_) -> + assert_resource_server_id(?RABBITMQ, ?RABBITMQ). + +resolve_resource_server_for_rabbitmq_plus_unknown_audience(_) -> + assert_resource_server_id(?RABBITMQ, [?RABBITMQ, <<"unknown">>]). + +resolve_resource_server_for_none_audience_returns_no_aud_found(_) -> + assert_resource_server_id({error, no_aud_found}, none). + +resolve_resource_server_for_none_audience_returns_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, none). + +resolve_resource_server_for_unknown_audience_returns_no_matching_aud_found(_) -> + assert_resource_server_id({error, no_matching_aud_found}, <<"unknown">>). + +resolve_resource_server_for_none_audience_returns_rabbitmq(_) -> + assert_resource_server_id(?RABBITMQ, none). + +resolve_resource_server_for_unknown_audience_returns_rabbitmq(_) -> + assert_resource_server_id(?RABBITMQ, <<"unknown">>). + +resolve_resource_server_for_unknown_audience_returns_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, <<"unknown">>). + +resolve_resource_server_for_none_audience_returns_error(_) -> + assert_resource_server_id( + {error, no_aud_found_cannot_pick_one_from_too_many_resource_servers}, + none). +resolve_resource_server_id_for_any_audience_returns_no_matching_aud_found(_) -> + assert_resource_server_id({error, no_matching_aud_found}, ?RABBITMQ), + assert_resource_server_id({error, no_matching_aud_found}, <<"unknown">>). + +resolve_resource_server_id_for_rabbitmq1(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_ONE, ?RABBITMQ_RESOURCE_ONE). + +resolve_resource_server_id_for_rabbitmq2(_) -> + assert_resource_server_id(?RABBITMQ_RESOURCE_TWO, ?RABBITMQ_RESOURCE_TWO). + +resolve_resource_server_id_for_both_resources_returns_error(_) -> + assert_resource_server_id({error, aud_matched_many_resource_servers_only_one_allowed}, + [?RABBITMQ_RESOURCE_TWO, ?RABBITMQ_RESOURCE_ONE]). + +rabbitmq_verify_aud_is_true(_) -> + assert_verify_aud(true, ?RABBITMQ). + +rabbitmq_verify_aud_is_false(_) -> + assert_verify_aud(false, ?RABBITMQ). + +rabbitmq2_verify_aud_is_true(_) -> + assert_verify_aud(true, ?RABBITMQ_RESOURCE_TWO). + +both_resources_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_ONE), + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_verify_aud_is_false(_) -> + assert_verify_aud(false, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_default_scope_prefix(_) -> + assert_scope_prefix(erlang:iolist_to_binary([?RABBITMQ_RESOURCE_TWO, <<".">>]), + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_scope_prefix(Config) -> + assert_scope_prefix(?config(scope_prefix, Config), ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_oauth_provider_id_is_A(_) -> + assert_oauth_provider_id(?OAUTH_PROVIDER_A, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_additional_scopes_key(_) -> + assert_additional_scopes_key(undefined, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_additional_scopes_key(Config) -> + assert_additional_scopes_key(?config(additional_scopes_key, Config), + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_preferred_username_claims_but_gets_default(_) -> + assert_preferred_username_claims(?DEFAULT_PREFERRED_USERNAME_CLAIMS, + ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_preferred_username_claims_plus_default(Config) -> + assert_preferred_username_claims(?config(preferred_username_claims, Config) + , ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_no_scope_aliases(_) -> + assert_scope_aliases(undefined, ?RABBITMQ_RESOURCE_TWO). + +rabbitmq2_has_scope_aliases(Config) -> + assert_scope_aliases(?config(scope_aliases, Config), ?RABBITMQ_RESOURCE_TWO). + +rabbitmq_oauth_provider_id_is_root(_) -> + assert_oauth_provider_id(root, ?RABBITMQ). + +rabbitmq_oauth_provider_id_is_A(_) -> + assert_oauth_provider_id(?OAUTH_PROVIDER_A, ?RABBITMQ). + +rabbitmq_has_default_scope_prefix(_) -> + assert_scope_prefix(erlang:iolist_to_binary([?RABBITMQ, <<".">>]), ?RABBITMQ). + +rabbitmq_has_scope_prefix(Config) -> + assert_scope_prefix(?config(scope_prefix, Config), ?RABBITMQ). + +rabbitmq_has_empty_scope_prefix(_) -> + assert_scope_prefix(<<"">>, ?RABBITMQ). + +rabbitmq_has_no_additional_scopes_key(_) -> + assert_additional_scopes_key(undefined, ?RABBITMQ). + +rabbitmq_has_additional_scopes_key(Config) -> + assert_additional_scopes_key(?config(additional_scopes_key, Config), + ?RABBITMQ). + +rabbitmq_has_no_preferred_username_claims_but_gets_default(_) -> + assert_preferred_username_claims(?DEFAULT_PREFERRED_USERNAME_CLAIMS, ?RABBITMQ). + +rabbitmq_has_preferred_username_claims(Config) -> + assert_preferred_username_claims(?config(preferred_username_claims, Config), + ?RABBITMQ). + +rabbitmq_has_no_scope_aliases(_) -> + assert_scope_aliases(undefined, ?RABBITMQ). + +rabbitmq_has_scope_aliases(Config) -> + assert_scope_aliases(?config(scope_aliases, Config), ?RABBITMQ). + +verify_rabbitmq1_server_configuration(Config) -> + ConfigRabbitMQ = ?config(?RABBITMQ_RESOURCE_ONE, Config), + {ok, ActualRabbitMQ} = resolve_resource_server_from_audience(?RABBITMQ_RESOURCE_ONE), + ?assertEqual(proplists:get_value(id, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.id), + ?assertEqual(proplists:get_value(resource_server_type, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.resource_server_type), + ?assertEqual(proplists:get_value(verify_aud, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.verify_aud), + ?assertEqual(proplists:get_value(scope_prefix, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.scope_prefix), + ?assertEqual(proplists:get_value(extract_scopes_source, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.additional_scopes_key), + ?assertEqual(proplists:get_value(preferred_username_claims, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.preferred_username_claims), + ?assertEqual(proplists:get_value(scope_aliases, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.scope_aliases), + ?assertEqual(proplists:get_value(oauth_provider_id, ConfigRabbitMQ), + ActualRabbitMQ#resource_server.oauth_provider_id). + +%% ----- + +assert_resource_server_id({error, ExpectedError}, Audience) -> + {error, ExpectedError} = resolve_resource_server_from_audience(Audience); +assert_resource_server_id(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.id). + +assert_verify_aud(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.verify_aud). + +assert_oauth_provider_id(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ct:log("Actual:~p", [Actual]), + ?assertEqual(Expected, Actual#resource_server.oauth_provider_id). + +assert_scope_prefix(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.scope_prefix). + +assert_additional_scopes_key(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.additional_scopes_key). + +assert_preferred_username_claims(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.preferred_username_claims). + +assert_scope_aliases(Expected, Audience) -> + {ok, Actual} = resolve_resource_server_from_audience(Audience), + ?assertEqual(Expected, Actual#resource_server.scope_aliases). + +get_env(Par) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par). +get_env(Par, Def) -> + application:get_env(rabbitmq_auth_backend_oauth2, Par, Def). +set_env(Par, Val) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Val). +unset_env(Par) -> + application:unset_env(rabbitmq_auth_backend_oauth2, Par). diff --git a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl index 58e69c334d83..ccf1b3a0f6ac 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/rabbit_oauth2_schema_SUITE.erl @@ -12,6 +12,11 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). +-import(rabbit_oauth2_schema, [ + translate_endpoint_params/2, + translate_oauth_providers/1, + translate_resource_servers/1 +]). all() -> [ @@ -24,120 +29,210 @@ all() -> test_oauth_providers_https, test_oauth_providers_https_with_missing_cacertfile, test_oauth_providers_signing_keys, + test_without_endpoint_params, + test_with_endpoint_params, + test_with_invalid_endpoint_params, test_without_resource_servers, test_with_one_resource_server, test_with_many_resource_servers, - test_resource_servers_attributes + test_resource_servers_attributes, + test_invalid_oauth_providers_endpoint_params, + test_without_oauth_providers_with_endpoint_params ]. test_without_oauth_providers(_) -> - #{} = rabbit_oauth2_schema:translate_oauth_providers([]). + #{} = translate_oauth_providers([]). test_without_resource_servers(_) -> - #{} = rabbit_oauth2_schema:translate_resource_servers([]). + #{} = translate_resource_servers([]). + +test_without_endpoint_params(_) -> + [] = translate_endpoint_params("oauth_discovery_endpoint_params", []). + +test_with_invalid_endpoint_params(_) -> + try translate_endpoint_params("discovery_endpoint_params", [ + {["auth_oauth2","discovery_endpoint_params"], "some-value1"}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_with_endpoint_params(_) -> + Conf = [ + {["auth_oauth2","discovery_endpoint_params","param1"], "some-value1"}, + {["auth_oauth2","discovery_endpoint_params","param2"], "some-value2"} + ], + [ {<<"param1">>, <<"some-value1">>}, {<<"param2">>, <<"some-value2">>} ] = + translate_endpoint_params("discovery_endpoint_params", Conf). + +test_invalid_oauth_providers_endpoint_params(_) -> + try translate_oauth_providers([ + {["auth_oauth2","oauth_providers", "X", "discovery_endpoint_params"], ""}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_without_oauth_providers_with_endpoint_params(_) -> + Conf = [ + {["auth_oauth2","oauth_providers", "A", "discovery_endpoint_params","param1"], + "some-value1"}, + {["auth_oauth2","oauth_providers", "A", "discovery_endpoint_params","param2"], + "some-value2"}, + {["auth_oauth2","oauth_providers", "B", "discovery_endpoint_params","param3"], + "some-value3"} + ], + #{ + <<"A">> := [{discovery_endpoint_params, [ + {<<"param1">>, <<"some-value1">>}, + {<<"param2">>, <<"some-value2">>} + ]}], + <<"B">> := [{discovery_endpoint_params, [ + {<<"param3">>, <<"some-value3">>} + ]}] + + } = translate_oauth_providers(Conf). test_with_one_oauth_provider(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://rabbit"} - ], - #{<<"keycloak">> := [{issuer, <<"https://rabbit">>}] - } = rabbit_oauth2_schema:translate_oauth_providers(Conf). + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"],"https://rabbit"} + ], + #{<<"keycloak">> := [{issuer, "https://rabbit"}] + } = translate_oauth_providers(Conf). test_with_one_resource_server(_) -> - Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"} - ], + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"} + ], #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>}] - } = rabbit_oauth2_schema:translate_resource_servers(Conf). + } = translate_resource_servers(Conf). test_with_many_oauth_providers(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","uaa","issuer"],"https://uaa"} - ], - #{<<"keycloak">> := [{issuer, <<"https://keycloak">>} + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","uaa","issuer"], + "https://uaa"}, + {["auth_oauth2","oauth_providers","uaa","discovery_endpoint_path"], + "/some-path"} + ], + #{<<"keycloak">> := [{issuer, "https://keycloak"} ], - <<"uaa">> := [{issuer, <<"https://uaa">>} + <<"uaa">> := [{issuer, "https://uaa"}, + {discovery_endpoint_path, "/some-path"} ] - } = rabbit_oauth2_schema:translate_oauth_providers(Conf). + } = translate_oauth_providers(Conf). test_with_many_resource_servers(_) -> - Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1"}, - {["auth_oauth2","resource_servers","rabbitmq2","id"],"rabbitmq2"} - ], + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"], "rabbitmq1"}, + {["auth_oauth2","resource_servers","rabbitmq2","id"], "rabbitmq2"} + ], #{<<"rabbitmq1">> := [{id, <<"rabbitmq1">>} ], <<"rabbitmq2">> := [{id, <<"rabbitmq2">>} ] - } = rabbit_oauth2_schema:translate_resource_servers(Conf). + } = translate_resource_servers(Conf). test_oauth_providers_attributes(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","default_key"],"token-key"} - ], + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"], + "token-key"} + ], #{<<"keycloak">> := [{default_key, <<"token-key">>}, - {issuer, <<"https://keycloak">>} + {issuer, "https://keycloak"} ] - } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)). + } = sort_settings(translate_oauth_providers(Conf)). test_resource_servers_attributes(_) -> - Conf = [{["auth_oauth2","resource_servers","rabbitmq1","id"],"rabbitmq1xxx"}, - {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"],"somescope."}, - {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"],"roles"}, - {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"],"userid"}, - {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"],"groupid"} - ], + Conf = [ + {["auth_oauth2","resource_servers","rabbitmq1","id"], + "rabbitmq1xxx"}, + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"], + "somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"], + "roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"], + "userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"], + "groupid"} + ], #{<<"rabbitmq1xxx">> := [{additional_scopes_key, <<"roles">>}, {id, <<"rabbitmq1xxx">>}, {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, {scope_prefix, <<"somescope.">>} ] - } = sort_settings(rabbit_oauth2_schema:translate_resource_servers(Conf)), + } = sort_settings(translate_resource_servers(Conf)), Conf2 = [ - {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"],"somescope."}, - {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"],"roles"}, - {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"],"userid"}, - {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"],"groupid"} - ], + {["auth_oauth2","resource_servers","rabbitmq1","scope_prefix"], + "somescope."}, + {["auth_oauth2","resource_servers","rabbitmq1","additional_scopes_key"], + "roles"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","1"], + "userid"}, + {["auth_oauth2","resource_servers","rabbitmq1","preferred_username_claims","2"], + "groupid"} + ], #{<<"rabbitmq1">> := [{additional_scopes_key, <<"roles">>}, {id, <<"rabbitmq1">>}, {preferred_username_claims, [<<"userid">>, <<"groupid">>]}, {scope_prefix, <<"somescope.">>} ] - } = sort_settings(rabbit_oauth2_schema:translate_resource_servers(Conf2)). + } = sort_settings(translate_resource_servers(Conf2)). test_oauth_providers_attributes_with_invalid_uri(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"http://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","default_key"],"token-key"} - ], - try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "http://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","default_key"], + "token-key"} + ], + try sort_settings(translate_oauth_providers(Conf)) of _ -> {throw, should_have_failed} catch _ -> ok end. test_oauth_providers_algorithms(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","algorithms","2"],"HS256"}, - {["auth_oauth2","oauth_providers","keycloak","algorithms","1"],"RS256"} - ], + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","2"], + "HS256"}, + {["auth_oauth2","oauth_providers","keycloak","algorithms","1"], + "RS256"} + ], #{<<"keycloak">> := [{algorithms, [<<"RS256">>, <<"HS256">>]}, - {issuer, <<"https://keycloak">>} + {issuer, "https://keycloak"} ] - } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)). + } = sort_settings(translate_oauth_providers(Conf)). test_oauth_providers_https(Conf) -> - CuttlefishConf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","https","verify"],verify_none}, - {["auth_oauth2","oauth_providers","keycloak","https","peer_verification"],verify_peer}, - {["auth_oauth2","oauth_providers","keycloak","https","depth"],2}, - {["auth_oauth2","oauth_providers","keycloak","https","hostname_verification"],wildcard}, - {["auth_oauth2","oauth_providers","keycloak","https","crl_check"],false}, - {["auth_oauth2","oauth_providers","keycloak","https","fail_if_no_peer_cert"],true}, - {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],cert_filename(Conf)} - ], + CuttlefishConf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","verify"], + verify_none}, + {["auth_oauth2","oauth_providers","keycloak","https","peer_verification"], + verify_peer}, + {["auth_oauth2","oauth_providers","keycloak","https","depth"], + 2}, + {["auth_oauth2","oauth_providers","keycloak","https","hostname_verification"], + wildcard}, + {["auth_oauth2","oauth_providers","keycloak","https","crl_check"], + false}, + {["auth_oauth2","oauth_providers","keycloak","https","fail_if_no_peer_cert"], + true}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"], + cert_filename(Conf)} + ], #{<<"keycloak">> := [{https, [{verify, verify_none}, {peer_verification, verify_peer}, {depth, 2}, @@ -146,38 +241,46 @@ test_oauth_providers_https(Conf) -> {fail_if_no_peer_cert, true}, {cacertfile, _CaCertFile} ]}, - {issuer, <<"https://keycloak">>} + {issuer, "https://keycloak"} ] - } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(CuttlefishConf)). + } = sort_settings(translate_oauth_providers(CuttlefishConf)). test_oauth_providers_https_with_missing_cacertfile(_) -> - Conf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"],"/non-existent.pem"} - ], - try sort_settings(rabbit_oauth2_schema:translate_oauth_providers(Conf)) of + Conf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","https","cacertfile"], + "/non-existent.pem"} + ], + try sort_settings(translate_oauth_providers(Conf)) of _ -> {throw, should_have_failed} catch _ -> ok end. test_oauth_providers_signing_keys(Conf) -> - CuttlefishConf = [{["auth_oauth2","oauth_providers","keycloak","issuer"],"https://keycloak"}, - {["auth_oauth2","oauth_providers","keycloak","signing_keys","2"], cert_filename(Conf)}, - {["auth_oauth2","oauth_providers","keycloak","signing_keys","1"], cert_filename(Conf)} - ], - #{<<"keycloak">> := [{issuer, <<"https://keycloak">>}, + CuttlefishConf = [ + {["auth_oauth2","oauth_providers","keycloak","issuer"], + "https://keycloak"}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","2"], + cert_filename(Conf)}, + {["auth_oauth2","oauth_providers","keycloak","signing_keys","1"], + cert_filename(Conf)} + ], + #{<<"keycloak">> := [{issuer, "https://keycloak"}, {signing_keys, SigningKeys} ] - } = sort_settings(rabbit_oauth2_schema:translate_oauth_providers(CuttlefishConf)), + } = sort_settings(translate_oauth_providers(CuttlefishConf)), ct:log("SigningKey: ~p", [SigningKeys]), #{<<"1">> := {pem, <<"I'm not a certificate">>}, <<"2">> := {pem, <<"I'm not a certificate">>} - } = SigningKeys. + } = SigningKeys. cert_filename(Conf) -> string:concat(?config(data_dir, Conf), "certs/cert.pem"). sort_settings(MapOfListOfSettings) -> maps:map(fun(_K,List) -> - lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, MapOfListOfSettings). + lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, + MapOfListOfSettings). diff --git a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl index c8b3f296e213..aaeb0b929601 100644 --- a/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl +++ b/deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl @@ -11,19 +11,27 @@ -include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include("oauth2.hrl"). +-import(rabbit_auth_backend_oauth2, [ + user_login_authentication/2, + user_login_authorization/2, + normalize_token_scope/2, + check_vhost_access/3]). +-import(rabbit_oauth2_resource_server, [ + new_resource_server/1 +]). all() -> [ - test_own_scope, - test_validate_payload_with_scope_prefix, - test_validate_payload, - test_validate_payload_without_scope, - test_validate_payload_when_verify_aud_false, - - test_unsuccessful_access_without_scopes, - test_successful_access_with_a_token_with_variables_in_scopes, - test_successful_access_with_a_parsed_token, + filter_matching_scope_prefix_and_drop_it, + normalize_token_scopes_with_scope_prefix, + normalize_token_scope_from_space_separated_list_in_scope_claim, + normalize_token_scope_without_scope_claim, + + unsuccessful_access_without_scopes, + successful_access_with_a_token_with_variables_in_scopes, + successful_access_with_a_parsed_token, test_successful_access_with_a_token_that_has_tag_scopes, test_unsuccessful_access_with_a_bogus_token, test_restricted_vhost_access_with_a_valid_token, @@ -31,10 +39,9 @@ all() -> test_token_expiration, test_invalid_signature, test_incorrect_kid, - test_post_process_token_payload, - test_post_process_token_payload_keycloak, - test_post_process_payload_rich_auth_request, - test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster, + normalize_token_scope_with_keycloak_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, test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field, test_username_from, @@ -57,7 +64,7 @@ groups() -> test_successful_authentication_without_scopes, test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field, test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field, - test_post_process_token_payload_complex_claims, + normalize_token_scope_with_additional_scopes_complex_claims, test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix ]} @@ -73,7 +80,7 @@ end_per_suite(Config) -> Env = ?config(env, Config), lists:foreach( fun({K, V}) -> - application:set_env(rabbitmq_auth_backend_oauth2, K, V) + set_env(K, V) end, Env), rabbit_ct_helpers:run_teardown_steps(Config). @@ -91,7 +98,7 @@ init_per_group(with_rabbitmq_node, Config) -> rabbit_ct_helpers:run_steps(Config2, rabbit_ct_broker_helpers:setup_steps()); init_per_group(with_resource_server_id, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(resource_server_id, <<"rabbitmq">>), Config; init_per_group(_, Config) -> @@ -104,36 +111,6 @@ end_per_group(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), Config. -init_per_testcase(test_post_process_token_payload_complex_claims, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, <<"additional_rabbitmq_scopes">>), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq-resource">>), - Config; - -init_per_testcase(test_validate_payload_when_verify_aud_false, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, verify_aud, false), - Config; - - - -init_per_testcase(test_post_process_payload_rich_auth_request, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>), - Config; - -init_per_testcase(test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_type, <<"rabbitmq-type">>), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq-test">>), - Config; - -init_per_testcase(_, Config) -> - Config. - -end_per_testcase(test_post_process_token_payload_complex_claims, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, extra_scopes_source, undefined), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, undefined), - Config; - -end_per_testcase(_, Config) -> - Config. %% @@ -145,100 +122,68 @@ end_per_testcase(_, Config) -> -define(RESOURCE_SERVER_TYPE, <<"rabbitmq-type">>). -define(DEFAULT_SCOPE_PREFIX, <<"rabbitmq.">>). -test_post_process_token_payload(_) -> - ArgumentsExpections = [ - {{[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}, - {[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}}, - {{<<"rabbitmq hare">>, <<"read write configure">>}, - {[<<"rabbitmq">>, <<"hare">>], [<<"read">>, <<"write">>, <<"configure">>]}}, - {{<<"rabbitmq">>, <<"read">>}, - {[<<"rabbitmq">>], [<<"read">>]}} - ], - lists:foreach( - fun({{Aud, Scope}, {ExpectedAud, ExpectedScope}}) -> - Payload = post_process_token_payload(Aud, Scope), - ?assertEqual(ExpectedAud, maps:get(<<"aud">>, Payload)), - ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload)) - end, ArgumentsExpections). -post_process_token_payload(Audience, Scopes) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - Token = maps:put(<<"aud">>, Audience, ?UTIL_MOD:fixture_token_with_scopes(Scopes)), - {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk), - case rabbit_oauth2_config:find_audience_in_resource_server_ids(Audience) of - {ok, TargetResourceServerId} -> - {true, Payload} = uaa_jwt_jwt:decode_and_verify(TargetResourceServerId, Jwk, EncodedToken), - rabbit_auth_backend_oauth2:post_process_payload(TargetResourceServerId, Payload); - {error, _} = Error -> Error - end. - -test_post_process_token_payload_keycloak(_) -> +normalize_token_scope_with_keycloak_scopes(_) -> Pairs = [ %% common case - { - #{<<"permissions">> => - [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, - <<"rsname">> => <<"allvhost">>, - <<"scopes">> => [<<"rabbitmq-resource.read:*/*">>]}, - #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, - <<"rsname">> => <<"vhost1">>, - <<"scopes">> => [<<"rabbitmq-resource-read">>]}, - #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, - <<"rsname">> => <<"Default Resource">>}]}, - [<<"rabbitmq-resource.read:*/*">>, <<"rabbitmq-resource-read">>] + { + "common case", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>, + <<"scopes">> => [<<"rabbitmq-resource.read:*/*">>]}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>, + <<"scopes">> => [<<"rabbitmq-resource.write:vhost1/*">>]}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>, + <<"scopes">> => [<<"unknown-resource.write:vhost1/*">>]} + ] }, - - %% one scopes field with a string instead of an array - { - #{<<"permissions">> => - [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, - <<"rsname">> => <<"allvhost">>, - <<"scopes">> => <<"rabbitmq-resource.read:*/*">>}, - #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, - <<"rsname">> => <<"vhost1">>, - <<"scopes">> => [<<"rabbitmq-resource-read">>]}, - #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, - <<"rsname">> => <<"Default Resource">>}]}, - [<<"rabbitmq-resource.read:*/*">>, <<"rabbitmq-resource-read">>] - }, - - %% no scopes field in permissions - { - #{<<"permissions">> => - [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, - <<"rsname">> => <<"allvhost">>}, - #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, - <<"rsname">> => <<"vhost1">>}, - #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, - <<"rsname">> => <<"Default Resource">>}]}, - [] - }, - - %% no permissions - { - #{<<"permissions">> => []}, + [<<"read:*/*">>, <<"write:vhost1/*">>] + }, + { + "one scopes field with a string instead of an array", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>, + <<"scopes">> => <<"rabbitmq-resource.read:*/*">>}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>, + <<"scopes">> => [<<"unknown-resource-read">>]}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>}]}, + [<<"read:*/*">>] + }, + { + "no scopes field in permissions", + #{<<"permissions">> => + [#{<<"rsid">> => <<"2c390fe4-02ad-41c7-98a2-cebb8c60ccf1">>, + <<"rsname">> => <<"allvhost">>}, + #{<<"rsid">> => <<"e7f12e94-4c34-43d8-b2b1-c516af644cee">>, + <<"rsname">> => <<"vhost1">>}, + #{<<"rsid">> => <<"12ac3d1c-28c2-4521-8e33-0952eff10bd9">>, + <<"rsname">> => <<"Default Resource">>}]}, + [] + }, + { + "no permissions", + #{<<"permissions">> => []}, [] - }, - %% missing permissions key - {#{}, []} + }, + {"missing permissions key", #{}, []} ], - lists:foreach( - fun({Authorization, ExpectedScope}) -> - Payload = post_process_payload_with_keycloak_authorization(Authorization), - ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload)) - end, Pairs). -post_process_payload_with_keycloak_authorization(Authorization) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - Token = maps:put(<<"authorization">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])), - {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk), - {true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken), - rabbit_auth_backend_oauth2:post_process_payload(<<"rabbitmq">>, Payload). + lists:foreach(fun({Case, Authorization, ExpectedScope}) -> + ResourceServer = new_resource_server(<<"rabbitmq-resource">>), + Token0 = #{<<"authorization">> => Authorization}, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(ExpectedScope, uaa_jwt:get_scope(Token), Case) + end, Pairs). -test_post_process_payload_rich_auth_request_using_regular_expression_with_cluster(_) -> +normalize_token_scope_with_rich_auth_request_using_regular_expression_with_cluster(_) -> Pairs = [ - { "should filter out those permisions whose locations do not refer to cluster : {resource_server_id}", [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, <<"locations">> => [<<"cluster:rabbitmq-test">>], @@ -249,7 +194,7 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste <<"actions">> => [<<"read">>] } ], - [<<"rabbitmq-test.read:*/*/*">> ] + [<<"read:*/*/*">> ] }, { "can use regular expression on any location's attribute ", @@ -258,7 +203,7 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste <<"actions">> => [<<"read">>] } ], - [<<"rabbitmq-test.read:^finance-*/*/*">> ] + [<<"read:^finance-*/*/*">> ] }, { "should filter out any location which does not match the cluster's pattern ", @@ -274,406 +219,424 @@ test_post_process_payload_rich_auth_request_using_regular_expression_with_cluste lists:foreach( fun({Case, Permissions, ExpectedScope}) -> - Payload = post_process_payload_with_rich_auth_request(<<"rabbitmq-test">>, Permissions), - ?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case) + ResourceServer0 = new_resource_server(<<"rabbitmq-test">>), + ResourceServer = ResourceServer0#resource_server{ + resource_server_type = ?RESOURCE_SERVER_TYPE + }, + Token0 = #{<<"authorization_details">> => Permissions}, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(lists:sort(ExpectedScope), + lists:sort(uaa_jwt:get_scope(Token)), Case) end, Pairs). -test_post_process_payload_rich_auth_request(_) -> +normalize_token_scope_with_rich_auth_request(_) -> - Pairs = [ - { "should merge all permissions for the current cluster", - [ - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:finance/vhost:primary-*">>], - <<"actions">> => [<<"configure">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"management">> ] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"administrator">> ] - } - ], - [ <<"rabbitmq.tag:management">>, <<"rabbitmq.tag:administrator">> ] - }, - { "should filter out those permisions whose type does not match ", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"read">>] - }, - #{<<"type">> => <<"unknown">>, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/*">> ] - }, - { "should filter out those permisions whose type is the empty string", - [ - #{<<"type">> => <<>>, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"read">>] - } - ], - [ ] - }, - { "should filter out those permisions with empty string action", - [ - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => <<>> - } - ], - [ ] - }, - { "should filter out those permisions whose locations do not refer to cluster : {resource_server_id}", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"read">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq-other">>], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/*">> ] - }, - { "should filter out those permisions whose locations' regexpr do not match the cluster : {resource_server_id} ", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbit*">>], - <<"actions">> => [<<"read">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:*">>], - <<"actions">> => [<<"write">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq-other">>], - <<"actions">> => [<<"configure">>] - } - ], - [<<"rabbitmq.read:*/*/*">>, <<"rabbitmq.write:*/*/*">> ] - }, - - { "should ignore permissions without actions", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbit*">>], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/*">>] - }, - { "should ignore permissions without locations", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"actions">> => [<<"read">>] - } - ] - ,[] - }, - { "should ignore unknown actions", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>], - <<"actions">> => [<<"read2">>, <<"read">>] - } - ] - ,[<<"rabbitmq.read:*/*/*">> ] - }, - { "should filter out locations with permissions not meant for {resource_server_id}", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq">>, <<"cluster:unknown">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/*">> ] - }, - { "should produce a scope for every (action, location) permutation for all locations meant for {resource_server_id}", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:a/*/*">>, <<"rabbitmq.read:b/*/*">> ] - }, - { "should support all known user tags ", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, <<"cluster:other">> ], - <<"actions">> => [<<"management">>, <<"policymaker">>, <<"management">>, <<"monitoring">>] - } - ], - [<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>, <<"rabbitmq.tag:management">>, <<"rabbitmq.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, - <<"locations">> => [<<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, <<"cluster:other">> ], - <<"actions">> => [<<"management">>, <<"policymaker">>] - } - ], - [<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">> ] - }, - - { "should produce as scope for every location meant for {resource_server_id} multiplied by actions", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">> ], - <<"actions">> => [<<"read">>, <<"write">>] - } - ], - [<<"rabbitmq.read:a/*/*">>, <<"rabbitmq.read:b/*/*">>, <<"rabbitmq.write:a/*/*">>, <<"rabbitmq.write:b/*/*">> ] - }, - { "should accept single value locations", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => <<"cluster:rabbitmq">>, - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/*">> ] - }, - { "should accept single value actions", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => <<"cluster:rabbitmq">>, - <<"actions">> => <<"read">> - } - ], - [<<"rabbitmq.read:*/*/*">> ] - }, - { "should merge all scopes produced by each permission", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:a">> ], - <<"actions">> => [<<"read">>] - }, - #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:b">> ], - <<"actions">> => [<<"write">>] - } - ], - [<<"rabbitmq.read:a/*/*">>, <<"rabbitmq.write:b/*/*">> ] - }, - { "can grant permission to a queue in any virtual host", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/queue:b">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/b/*">> ] - }, - { "can grant permission to an exchange in any virtual host", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/exchange:b">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/b/*">> ] - }, - { "cannot specify both exchange and queue unless they have the same value", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/queue:b/exchange:c">> ], - <<"actions">> => [<<"read">>] - } - ], - [] - }, - { "can specify exchange and queue when have same value", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/queue:*/exchange:*">> ], - <<"actions">> => [<<"read">>] - } - ], - [ <<"rabbitmq.read:*/*/*">> ] - }, - { "can specify routing-key only -> on any vhost and on any queue if that makes sense ", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/routing-key:b">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/b">> ] - }, - { "can specify vhost, queue or exchange and routing-key that combine fixed values and wildcards", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:finance-*/*-invoice/r-*">> ] - }, - { "should ignore any location's attribute other than the supported ones", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/unknown:finance-*/queue:*-invoice/routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*-invoice/r-*">> ] - }, - { "should not matter the location's attributes order", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/queue:invoices/vhost:finance/routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:finance/invoices/r-*">> ] - }, - { "should ignore locations like //", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq//routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/r-*">> ] - }, - { "should default to wildcard those attributes with empty value", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/queue:/vhost:/routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:*/*/r-*">> ] - }, - { "should ignore any location path element which is not compliant with : format", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"some-prefix-value/cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:finance-*/*-invoice/r-*">> ] - }, - { "can use regular expression on any location's attribute", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => [<<"cluster:rabbitmq/vhost:^finance-*">> ], - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:^finance-*/*/*">> ] - }, - { "can use single string value for location", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, - <<"actions">> => [<<"read">>] - } - ], - [<<"rabbitmq.read:^finance-*/*/*">> ] - }, - { "can use single string value for action", - [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, - <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, - <<"actions">> => <<"read">> - } + Pairs = [ + { "should merge all permissions for the current cluster", + [ + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:finance/vhost:primary-*">>], + <<"actions">> => [<<"configure">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"management">> ] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"administrator">> ] + } + ], + [ <<"tag:management">>, <<"tag:administrator">> ] + }, + { "should filter out those permisions whose type does not match ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => <<"unknown">>, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out those permisions whose type is the empty string", + [ + #{<<"type">> => <<>>, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + } + ], + [ ] + }, + { "should filter out those permisions with empty string action", + [ + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => <<>> + } + ], + [ ] + }, + { "should filter out those permisions whose locations do not refer to cluster : {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq-other">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out those permisions whose locations' regexpr do not match the cluster : {resource_server_id} ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbit*">>], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:*">>], + <<"actions">> => [<<"write">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq-other">>], + <<"actions">> => [<<"configure">>] + } + ], + [<<"read:*/*/*">>, <<"write:*/*/*">> ] + }, + { "should ignore permissions without actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbit*">>], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">>] + }, + { "should ignore permissions without locations", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"actions">> => [<<"read">>] + } + ], + [] + }, + { "should ignore unknown actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>], + <<"actions">> => [<<"read2">>, <<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should filter out locations with permissions not meant for {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq">>, <<"cluster:unknown">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should produce a scope for every (action, location) permutation for all locations meant for {resource_server_id}", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, + <<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:a/*/*">>, <<"read:b/*/*">> ] + }, + { "should support all known user tags ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, + <<"cluster:other">> ], + <<"actions">> => [ + <<"management">>, <<"policymaker">>, <<"management">>, + <<"monitoring">>] + } + ], + [<<"tag:management">>, <<"tag:policymaker">>, + <<"tag:management">>, <<"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, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">>, + <<"cluster:other">> ], + <<"actions">> => [<<"management">>, <<"policymaker">>] + } + ], + [<<"tag:management">>, <<"tag:policymaker">> ] + }, + { "should produce as scope for every location meant for {resource_server_id} multiplied by actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:a">>, <<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"read">>, <<"write">>] + } + ], + [<<"read:a/*/*">>, <<"read:b/*/*">>, <<"write:a/*/*">>, <<"write:b/*/*">> ] + }, + { "should accept single value locations", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq">>, + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/*">> ] + }, + { "should accept single value actions", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq">>, + <<"actions">> => <<"read">> + } + ], + [<<"read:*/*/*">> ] + }, + { "should merge all scopes produced by each permission", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:a">> ], + <<"actions">> => [<<"read">>] + }, + #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:b">> ], + <<"actions">> => [<<"write">>] + } + ], + [<<"read:a/*/*">>, <<"write:b/*/*">> ] + }, + { "can grant permission to a queue in any virtual host", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/b/*">> ] + }, + { "can grant permission to an exchange in any virtual host", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/exchange:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/b/*">> ] + }, + { "cannot specify both exchange and queue unless they have the same value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:b/exchange:c">> ], + <<"actions">> => [<<"read">>] + } + ], + [] + }, + { "can specify exchange and queue when have same value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:*/exchange:*">> ], + <<"actions">> => [<<"read">>] + } + ], + [ <<"read:*/*/*">> ] + }, + { "can specify routing-key only -> on any vhost and on any queue if that makes sense ", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/routing-key:b">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/b">> ] + }, + { "can specify vhost, queue or exchange and routing-key that combine fixed values and wildcards", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance-*/*-invoice/r-*">> ] + }, + { "should ignore any location's attribute other than the supported ones", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/unknown:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*-invoice/r-*">> ] + }, + { "should not matter the location's attributes order", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/queue:invoices/vhost:finance/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance/invoices/r-*">> ] + }, + { "should ignore locations like //", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq//routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/r-*">> ] + }, + { "should default to wildcard those attributes with empty value", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"cluster:rabbitmq/queue:/vhost:/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:*/*/r-*">> ] + }, + { "should ignore any location path element which is not compliant with : format", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [ + <<"some-prefix-value/cluster:rabbitmq/vhost:finance-*/queue:*-invoice/routing-key:r-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:finance-*/*-invoice/r-*">> ] + }, + { "can use regular expression on any location's attribute", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => [<<"cluster:rabbitmq/vhost:^finance-*">> ], + <<"actions">> => [<<"read">>] + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "can use single string value for location", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, + <<"actions">> => [<<"read">>] + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "can use single string value for action", + [ #{<<"type">> => ?RESOURCE_SERVER_TYPE, + <<"locations">> => <<"cluster:rabbitmq/vhost:^finance-*">>, + <<"actions">> => <<"read">> + } + ], + [<<"read:^finance-*/*/*">> ] + }, + { "should ignore empty permission lists", + [], + [] + } ], - [<<"rabbitmq.read:^finance-*/*/*">> ] - }, - { "should ignore empty permission lists", - [], - [] - } - ], - lists:foreach( - fun({Case, Permissions, ExpectedScope}) -> - Payload = post_process_payload_with_rich_auth_request(<<"rabbitmq">>, Permissions), - ?assertEqual(lists:sort(ExpectedScope), lists:sort(maps:get(<<"scope">>, Payload)), Case) + lists:foreach(fun({Case, Permissions, ExpectedScope0}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server{ + resource_server_type = ?RESOURCE_SERVER_TYPE + }, + Token0 = #{<<"authorization_details">> => Permissions}, + Token = normalize_token_scope(ResourceServer, Token0), + ExpectedScopes = lists:sort(ExpectedScope0), + ActualScopes = lists:sort(uaa_jwt:get_scope(Token)), + ?assertEqual(ExpectedScopes, ActualScopes, Case) end, Pairs). -post_process_payload_with_rich_auth_request(ResourceServerId, Permissions) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - Token = maps:put(<<"authorization_details">>, Permissions, ?UTIL_MOD:plain_token_without_scopes_and_aud()), - {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk), - {true, Payload} = uaa_jwt_jwt:decode_and_verify(<<"rabbitmq">>, Jwk, EncodedToken), - rabbit_auth_backend_oauth2:post_process_payload(ResourceServerId, Payload). - -test_post_process_token_payload_complex_claims(_) -> +normalize_token_scope_with_additional_scopes_complex_claims(_) -> Pairs = [ - %% claims in form of binary - { - <<"rabbitmq.rabbitmq-resource.read:*/* rabbitmq.rabbitmq-resource-read">>, - [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] - }, - %% claims in form of binary - empty result - {<<>>, []}, - %% claims in form of list - { - [<<"rabbitmq.rabbitmq-resource.read:*/*">>, + { + "claims in form of binary", + <<"rabbitmq.rabbitmq-resource.read:*/* rabbitmq.rabbitmq-resource-read">>, + [<<"read:*/*">>] + }, + {"claims in form of binary - empty result", <<>>, []}, + { + "claims in form of list", + [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq2.rabbitmq-resource-read">>], - [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq2.rabbitmq-resource-read">>] + [<<"read:*/*">>] + }, + {"claims in form of list - empty result", [], []}, + { + "claims are map with list content", + #{<<"rabbitmq">> => + [<<"rabbitmq-resource.read:*/*">>, + <<"rabbitmq-resource-read">>], + <<"rabbitmq3">> => + [<<"rabbitmq-resource.write:*/*">>, + <<"rabbitmq-resource-write">>]}, + [<<"read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] + }, + { + "claims are map with list content - empty result", + #{<<"rabbitmq2">> => + [<<"rabbitmq-resource.read:*/*">>, + <<"rabbitmq-resource-read">>]}, + [] + }, + { + "claims are map with binary content", + #{ <<"rabbitmq">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>, + <<"rabbitmq3">> => <<"rabbitmq-resource.write:*/* rabbitmq-resource-write">>}, + [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] + }, + { + "claims are map with binary content - empty result", + #{<<"rabbitmq2">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>}, [] + }, + { + "claims are map with empty binary content - empty result", + #{<<"rabbitmq">> => <<>>}, [] + }, + { + "claims are map with empty list content - empty result", + #{<<"rabbitmq">> => []}, [] + }, + { + "no extra claims provided", + [], [] + }, + { + "no extra claims provided", #{}, [] + }], + lists:foreach(fun({Case, Authorization, ExpectedScope0}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server{ + scope_prefix = <<"rabbitmq.rabbitmq-resource.">>, + additional_scopes_key = <<"custom-key">> }, - %% claims in form of list - empty result - {[], []}, - %% claims are map with list content - { - #{<<"rabbitmq">> => - [<<"rabbitmq-resource.read:*/*">>, - <<"rabbitmq-resource-read">>], - <<"rabbitmq3">> => - [<<"rabbitmq-resource.write:*/*">>, - <<"rabbitmq-resource-write">>]}, - [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] - }, - %% claims are map with list content - empty result - { - #{<<"rabbitmq2">> => - [<<"rabbitmq-resource.read:*/*">>, - <<"rabbitmq-resource-read">>]}, - [] - }, - %% claims are map with binary content - { - #{<<"rabbitmq">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>, - <<"rabbitmq3">> => <<"rabbitmq-resource.write:*/* rabbitmq-resource-write">>}, - [<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>] - }, - %% claims are map with binary content - empty result - { - #{<<"rabbitmq2">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>}, [] - }, - %% claims are map with empty binary content - empty result - { - #{<<"rabbitmq">> => <<>>}, [] - }, - %% claims are map with empty list content - empty result - { - #{<<"rabbitmq">> => []}, [] - }, - %% no extra claims provided - {[], []}, - %% no extra claims provided - {#{}, []} - ], - lists:foreach( - fun({Authorization, ExpectedScope}) -> - Payload = post_process_payload_with_complex_claim_authorization(<<"rabbitmq-resource">>, Authorization), - ?assertEqual(ExpectedScope, maps:get(<<"scope">>, Payload)) - end, Pairs). - -post_process_payload_with_complex_claim_authorization(ResourceServerId, Authorization) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - Token = maps:put(<<"additional_rabbitmq_scopes">>, Authorization, ?UTIL_MOD:fixture_token_with_scopes([])), - {_, EncodedToken} = ?UTIL_MOD:sign_token_hs(Token, Jwk), - {true, Payload} = uaa_jwt_jwt:decode_and_verify(Jwk, EncodedToken), - rabbit_auth_backend_oauth2:post_process_payload(ResourceServerId, Payload). + Token0 = #{<<"custom-key">> => Authorization}, + Token = normalize_token_scope(ResourceServer, Token0), + ExpectedScopes = lists:sort(ExpectedScope0), + ActualScopes = lists:sort(uaa_jwt:get_scope(Token)), + ?assertEqual(ExpectedScopes, ActualScopes, Case) + end, Pairs). test_successful_authentication_without_scopes(_) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + Jwk = ?UTIL_MOD:fixture_jwk(), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), - Username = <<"username">>, - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), - {ok, #auth_user{username = Username} } = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]). + {ok, #auth_user{username = Username} } = + user_login_authentication(Username, [{password, Token}]). test_successful_authorization_without_scopes(_) -> - Jwk = ?UTIL_MOD:fixture_jwk(), - UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + Jwk = ?UTIL_MOD:fixture_jwk(), + UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], + set_env(key_config, UaaEnv), - Username = <<"username">>, - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Username = <<"username">>, + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), - {ok, _ } = - rabbit_auth_backend_oauth2:user_login_authorization(Username, [{password, Token}]). + {ok, _ } = user_login_authorization(Username, [{password, Token}]). test_successful_access_with_a_token(_) -> %% Generate a token with JOSE @@ -681,74 +644,79 @@ test_successful_access_with_a_token(_) -> %% Check user access granted by token Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), VHost = <<"vhost">>, Username = <<"username">>, - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), {ok, #auth_user{username = Username} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), -% {ok, #auth_user{username = Username} = User} = -% rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}), + user_login_authentication(Username, [{password, Token}]), - ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, none)), + ?assertEqual(true, check_vhost_access(User, <<"vhost">>, none)), assert_resource_access_granted(User, VHost, <<"foo">>, configure), assert_resource_access_granted(User, VHost, <<"foo">>, write), assert_resource_access_granted(User, VHost, <<"bar">>, read), assert_resource_access_granted(User, VHost, custom, <<"bar">>, read), - assert_topic_access_granted(User, VHost, <<"bar">>, read, #{routing_key => <<"#/foo">>}). + assert_topic_access_granted(User, VHost, <<"bar">>, read, + #{routing_key => <<"#/foo">>}). -test_successful_access_with_a_token_with_variables_in_scopes(_) -> +successful_access_with_a_token_with_variables_in_scopes(_) -> %% Generate a token with JOSE %% Check authorization with the token %% Check user access granted by token Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), VHost = <<"my-vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs( - ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token([<<"rabbitmq.read:{vhost}/*/{sub}">>]), Username), + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( + [<<"rabbitmq.read:{vhost}/*/{sub}">>]), Username), Jwk), {ok, #auth_user{username = Username} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}), + user_login_authentication(Username, #{password => Token}), - assert_topic_access_granted(User, VHost, <<"bar">>, read, #{routing_key => Username}). + assert_topic_access_granted(User, VHost, <<"bar">>, read, + #{routing_key => Username}). -test_successful_access_with_a_parsed_token(_) -> +successful_access_with_a_parsed_token(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), Username = <<"username">>, - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), {ok, #auth_user{impl = Impl} } = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), {ok, _ } = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{rabbit_auth_backend_oauth2, Impl}]). + user_login_authentication(Username, [{rabbit_auth_backend_oauth2, Impl}]). test_successful_access_with_a_token_that_has_tag_scopes(_) -> Jwk = ?UTIL_MOD:fixture_jwk(), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), Username = <<"username">>, - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( - [<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>]), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token( + [<<"rabbitmq.tag:management">>, <<"rabbitmq.tag:policymaker">>]), + Username), Jwk), {ok, #auth_user{username = Username, tags = [management, policymaker]}} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]). + user_login_authentication(Username, [{password, Token}]). test_successful_access_with_a_token_that_uses_single_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), + set_env(key_config, UaaEnv), Alias = <<"client-alias-1">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ Alias => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -766,7 +734,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field( ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -786,10 +754,10 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field( test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix(_) -> 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, scope_prefix, <<>>), + set_env(key_config, UaaEnv), + set_env(scope_prefix, <<>>), Alias = <<"client-alias-1">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ Alias => [ <<"configure:vhost/one">>, <<"write:vhost/two">>, @@ -807,7 +775,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_ ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -827,11 +795,11 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_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), + set_env(key_config, UaaEnv), Role1 = <<"client-aliases-1">>, Role2 = <<"client-aliases-2">>, Role3 = <<"client-aliases-3">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ Role1 => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.tag:management">> @@ -850,10 +818,11 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( - ?UTIL_MOD:token_with_scope_alias_in_scope_field([Role1, Role2, Role3]), Username), Jwk), + ?UTIL_MOD:token_with_scope_alias_in_scope_field([Role1, Role2, Role3]), + Username), Jwk), {ok, #auth_user{username = Username} = AuthUser} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -872,10 +841,10 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi test_unsuccessful_access_with_a_token_that_uses_missing_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">>), + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), Alias = <<"client-alias-33">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ <<"non-existent-alias-23948sdkfjsdof8">> => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -890,7 +859,7 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_fie Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( ?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), Username), Jwk), - {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), assert_vhost_access_denied(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -909,10 +878,10 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_fie test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_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, extra_scopes_source, <<"claims">>), + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), Alias = <<"client-alias-1">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ Alias => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -925,9 +894,10 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_ VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( - ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Username), Jwk), + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), + Username), Jwk), - {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -946,12 +916,12 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_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, extra_scopes_source, <<"claims">>), + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), Role1 = <<"client-aliases-1">>, Role2 = <<"client-aliases-2">>, Role3 = <<"client-aliases-3">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ Role1 => [ <<"rabbitmq.configure:vhost/one">> ], @@ -969,9 +939,10 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc Username = <<"username">>, Claims = [Role1, Role2, Role3], Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( - ?UTIL_MOD:token_with_scope_alias_in_claim_field(Claims, [<<"unrelated">>]), Username), Jwk), + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Claims, [<<"unrelated">>]), + Username), Jwk), - {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), assert_vhost_access_granted(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -990,11 +961,11 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_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, extra_scopes_source, <<"claims">>), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(key_config, UaaEnv), + set_env(extra_scopes_source, <<"claims">>), + set_env(resource_server_id, <<"rabbitmq">>), Alias = <<"client-alias-11">>, - application:set_env(rabbitmq_auth_backend_oauth2, scope_aliases, #{ + set_env(scope_aliases, #{ <<"non-existent-client-alias-9238923789">> => [ <<"rabbitmq.configure:vhost/one">>, <<"rabbitmq.write:vhost/two">>, @@ -1007,9 +978,10 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_sco VHost = <<"vhost">>, Username = <<"username">>, Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( - ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), Username), Jwk), + ?UTIL_MOD:token_with_scope_alias_in_claim_field(Alias, [<<"unrelated">>]), + Username), Jwk), - {ok, AuthUser} = rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + {ok, AuthUser} = user_login_authentication(Username, [{password, Token}]), assert_vhost_access_denied(AuthUser, VHost), assert_vhost_access_denied(AuthUser, <<"some-other-vhost">>), @@ -1027,87 +999,91 @@ test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_sco test_unsuccessful_access_with_a_bogus_token(_) -> Username = <<"username">>, - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(resource_server_id, <<"rabbitmq">>), Jwk0 = ?UTIL_MOD:fixture_jwk(), Jwk = Jwk0#{<<"k">> => <<"bm90b2tlbmtleQ">>}, UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), - ?assertMatch({refused, _, _}, - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, <<"not a token">>}])). + ?assertMatch({refused, _, _}, user_login_authentication(Username, + [{password, <<"not a token">>}])). -test_unsuccessful_access_without_scopes(_) -> +unsuccessful_access_without_scopes(_) -> Username = <<"username">>, - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(resource_server_id, <<"rabbitmq">>), Jwk = ?UTIL_MOD:fixture_jwk(), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:token_without_scopes(), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:token_without_scopes(), Username), Jwk), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), - {ok, #auth_user{username = Username, tags = [], impl = _CredentialsFun } = AuthUser} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + {ok, #auth_user{username = Username, tags = [], impl = _CredentialsFun } + = AuthUser} = user_login_authentication(Username, [{password, Token}]), assert_vhost_access_denied(AuthUser, <<"vhost">>). test_restricted_vhost_access_with_a_valid_token(_) -> Username = <<"username">>, - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(resource_server_id, <<"rabbitmq">>), Jwk = ?UTIL_MOD:fixture_jwk(), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), %% this user can authenticate successfully and access certain vhosts {ok, #auth_user{username = Username, tags = []} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), %% access to a different vhost - ?assertEqual(false, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"different vhost">>, none)). + ?assertEqual(false, check_vhost_access(User, <<"different vhost">>, none)). test_insufficient_permissions_in_a_valid_token(_) -> VHost = <<"vhost">>, Username = <<"username">>, - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(resource_server_id, <<"rabbitmq">>), Jwk = ?UTIL_MOD:fixture_jwk(), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), + set_env(key_config, UaaEnv), {ok, #auth_user{username = Username} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), %% access to these resources is not granted assert_resource_access_denied(User, VHost, <<"foo1">>, configure), assert_resource_access_denied(User, VHost, <<"bar">>, write), - assert_topic_access_refused(User, VHost, <<"bar">>, read, #{routing_key => <<"foo/#">>}). + assert_topic_access_refused(User, VHost, <<"bar">>, read, + #{routing_key => <<"foo/#">>}). test_invalid_signature(_) -> Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), WrongJwk = ?UTIL_MOD:fixture_jwk("wrong", <<"GawgguFyGrWKav7AX4VKUg">>), UaaEnv = [{signing_keys, #{<<"token-key">> => {map, WrongJwk}}}], - application:set_env(rabbitmq_auth_backend_oauth2, key_config, UaaEnv), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), ?assertMatch({refused, _, [signature_invalid]}, - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])). + user_login_authentication(Username, [{password, Token}])). test_token_expiration(_) -> VHost = <<"vhost">>, Username = <<"username">>, 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">>), + set_env(key_config, UaaEnv), + set_env(resource_server_id, <<"rabbitmq">>), TokenData = ?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username), Token = ?UTIL_MOD:sign_token_hs(TokenData, Jwk), {ok, #auth_user{username = Username} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}]), + user_login_authentication(Username, [{password, Token}]), assert_resource_access_granted(User, VHost, <<"foo">>, configure), assert_resource_access_granted(User, VHost, <<"foo">>, write), @@ -1118,26 +1094,32 @@ test_token_expiration(_) -> ?UTIL_MOD:wait_for_token_to_expire(), #{<<"exp">> := Exp} = TokenData, - ExpectedError = "Provided JWT token has expired at timestamp " ++ integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")", + ExpectedError = "Provided JWT token has expired at timestamp " ++ + integer_to_list(Exp) ++ " (validated at " ++ integer_to_list(Exp) ++ ")", assert_resource_access_errors(ExpectedError, User, VHost, <<"foo">>, configure), ?assertMatch({refused, _, _}, - rabbit_auth_backend_oauth2:user_login_authentication(Username, [{password, Token}])). + user_login_authentication(Username, [{password, Token}])). test_incorrect_kid(_) -> AltKid = <<"other-token-key">>, Username = <<"username">>, Jwk = ?UTIL_MOD:fixture_jwk(), - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, <<"rabbitmq">>), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, AltKid, true), - ?assertMatch({refused, "Authentication using an OAuth 2/JWT token failed: ~tp", [{error,{missing_oauth_provider_attributes, [issuer]}}]}, - rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token})). + set_env(resource_server_id, + <<"rabbitmq">>), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, + AltKid, true), + ?assertMatch( + {refused, "Authentication using an OAuth 2/JWT token failed: ~tp", + [{error,{missing_oauth_provider_attributes, [issuer]}}]}, + user_login_authentication(Username, #{password => Token})). login_and_check_vhost_access(Username, Token, Vhost) -> {ok, #auth_user{username = Username} = User} = - rabbit_auth_backend_oauth2:user_login_authentication(Username, #{password => Token}), + user_login_authentication(Username, #{password => Token}), - ?assertEqual(true, rabbit_auth_backend_oauth2:check_vhost_access(User, <<"vhost">>, Vhost)). + ?assertEqual(true, check_vhost_access(User, <<"vhost">>, Vhost)). test_command_json(Config) -> Username = <<"username">>, @@ -1146,9 +1128,12 @@ test_command_json(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], - #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), json => Json}), - Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), - rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). + #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + json => Json}), + Token = ?UTIL_MOD:sign_token_hs( + ?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). test_username_from(_) -> Pairs = [ @@ -1185,7 +1170,8 @@ test_username_from(_) -> lists:foreach( fun( {Comment, PreferredUsernameClaims, Token, ExpectedUsername}) -> - ActualUsername = rabbit_auth_backend_oauth2:username_from(PreferredUsernameClaims, Token), + ActualUsername = rabbit_auth_backend_oauth2:username_from( + PreferredUsernameClaims, Token), ?assertEqual(ExpectedUsername, ActualUsername, Comment) end, Pairs). @@ -1202,10 +1188,13 @@ test_command_pem_file(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], - #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem_file => PublicKeyFile}), + #{node => rabbit_ct_broker_helpers:get_node_config( + Config, 0, nodename), pem_file => PublicKeyFile}), - Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), Jwk, <<"token-key">>), - rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). + Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:fixture_token(), + Jwk, <<"token-key">>), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). test_command_pem(Config) -> @@ -1218,10 +1207,13 @@ test_command_pem(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], - #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}), + #{node => rabbit_ct_broker_helpers:get_node_config( + Config, 0, nodename), pem => Pem}), - Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk, <<"token-key">>), - rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). + Token = ?UTIL_MOD:sign_token_rsa(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk, <<"token-key">>), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). test_command_pem_no_kid(Config) -> Username = <<"username">>, @@ -1233,13 +1225,16 @@ test_command_pem_no_kid(Config) -> 'Elixir.RabbitMQ.CLI.Ctl.Commands.AddUaaKeyCommand':run( [<<"token-key">>], - #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), pem => Pem}), + #{node => rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + pem => Pem}), - Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username), Jwk), - rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, login_and_check_vhost_access, [Username, Token, none]). + Token = ?UTIL_MOD:sign_token_no_kid(?UTIL_MOD:token_with_sub( + ?UTIL_MOD:fixture_token(), Username), Jwk), + rabbit_ct_broker_helpers:rpc(Config, 0, unit_SUITE, + login_and_check_vhost_access, [Username, Token, none]). -test_own_scope(_) -> +filter_matching_scope_prefix_and_drop_it(_) -> Examples = [ {<<"foo.">>, [<<"foo">>, <<"foo.bar">>, <<"bar.foo">>, <<"one.two">>, <<"foobar">>, <<"foo.other.third">>], @@ -1250,82 +1245,60 @@ test_own_scope(_) -> ], lists:map( fun({ScopePrefix, Src, Dest}) -> - Dest = rabbit_auth_backend_oauth2:filter_scopes(Src, ScopePrefix) + Dest = rabbit_oauth2_scope:filter_matching_scope_prefix_and_drop_it( + Src, ScopePrefix) end, Examples). -test_validate_payload_resource_server_id_mismatch(_) -> - NoKnownResourceServerId = #{<<"aud">> => [<<"foo">>, <<"bar">>], - <<"scope">> => [<<"foo">>, <<"foo.bar">>, - <<"bar.foo">>, <<"one.two">>, - <<"foobar">>, <<"foo.other.third">>]}, - EmptyAud = #{<<"aud">> => [], - <<"scope">> => [<<"foo.bar">>, <<"bar.foo">>]}, - - ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, - [<<"foo">>,<<"bar">>]}}}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, NoKnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)), - - ?assertEqual({refused, {invalid_aud, {resource_id_not_found_in_aud, ?RESOURCE_SERVER_ID, []}}}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, EmptyAud, ?DEFAULT_SCOPE_PREFIX)). - -test_validate_payload_with_scope_prefix(_) -> - Scenarios = [ { <<"">>, - #{<<"aud">> => [?RESOURCE_SERVER_ID], - <<"scope">> => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ]}, - [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] - }, - { <<"some-prefix::">>, - #{<<"aud">> => [?RESOURCE_SERVER_ID], - <<"scope">> => [<<"some-prefix::foo">>, <<"foo.bar">>, <<"some-prefix::other.third">> ]}, - [<<"foo">>, <<"other.third">>] - } - - ], - - lists:map(fun({ ScopePrefix, Token, ExpectedScopes}) -> - ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], <<"scope">> => ExpectedScopes } }, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, Token, ScopePrefix)) - end - , Scenarios). - -test_validate_payload(_) -> - KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID], - <<"scope">> => [<<"foo">>, <<"rabbitmq.bar">>, - <<"bar.foo">>, <<"one.two">>, - <<"foobar">>, <<"rabbitmq.other.third">>]}, - ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID], - <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, KnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)). - -test_validate_payload_without_scope(_) -> - KnownResourceServerId = #{<<"aud">> => [?RESOURCE_SERVER_ID] - }, - ?assertEqual({ok, #{<<"aud">> => [?RESOURCE_SERVER_ID] }}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, KnownResourceServerId, ?DEFAULT_SCOPE_PREFIX)). - -test_validate_payload_when_verify_aud_false(_) -> - WithoutAud = #{ - <<"scope">> => [<<"foo">>, <<"rabbitmq.bar">>, - <<"bar.foo">>, <<"one.two">>, - <<"foobar">>, <<"rabbitmq.other.third">>]}, - ?assertEqual({ok, #{ - <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, WithoutAud, ?DEFAULT_SCOPE_PREFIX)), - - WithAudWithUnknownResourceId = #{ - <<"aud">> => [<<"unknown">>], - <<"scope">> => [<<"foo">>, <<"rabbitmq.bar">>, - <<"bar.foo">>, <<"one.two">>, - <<"foobar">>, <<"rabbitmq.other.third">>]}, - ?assertEqual({ok, #{<<"aud">> => [<<"unknown">>], - <<"scope">> => [<<"bar">>, <<"other.third">>]}}, - rabbit_auth_backend_oauth2:validate_payload(?RESOURCE_SERVER_ID, WithAudWithUnknownResourceId, ?DEFAULT_SCOPE_PREFIX)). +normalize_token_scopes_with_scope_prefix(_) -> + Scenarios = [ + { + <<"">>, + #{ + ?SCOPE_JWT_FIELD => [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] + }, + [<<"foo">>, <<"foo.bar">>, <<"foo.other.third">> ] + }, + { + <<"some-prefix::">>, + #{ + ?SCOPE_JWT_FIELD => [ + <<"some-prefix::foo">>, <<"foo.bar">>, + <<"some-prefix::other.third">> ] + }, + [<<"foo">>, <<"other.third">>] + } + ], + + lists:map(fun({ ScopePrefix, Token0, ExpectedScopes}) -> + ResourceServer0 = new_resource_server(?RESOURCE_SERVER_ID), + ResourceServer = ResourceServer0#resource_server { + scope_prefix = ScopePrefix + }, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual(ExpectedScopes, uaa_jwt:get_scope(Token)) + end, Scenarios). + +normalize_token_scope_from_space_separated_list_in_scope_claim(_) -> + ResourceServer = new_resource_server(?RESOURCE_SERVER_ID), + Token0 = #{ + ?SCOPE_JWT_FIELD => <<"foo rabbitmq.bar bar.foo one.two foobar rabbitmq.other.third">> + }, + Token = normalize_token_scope(ResourceServer, Token0), + ?assertEqual([<<"bar">>, <<"other.third">>], uaa_jwt:get_scope(Token)). + +normalize_token_scope_without_scope_claim(_) -> + ResourceServer = new_resource_server(?RESOURCE_SERVER_ID), + Token0 = #{ }, + ?assertEqual([], uaa_jwt:get_scope(normalize_token_scope(ResourceServer, Token0))). %% %% Helpers %% +set_env(Par, Var) -> + application:set_env(rabbitmq_auth_backend_oauth2, Par, Var). + assert_vhost_access_granted(AuthUser, VHost) -> assert_vhost_access_response(true, AuthUser, VHost). @@ -1334,48 +1307,66 @@ assert_vhost_access_denied(AuthUser, VHost) -> assert_vhost_access_response(ExpectedResult, AuthUser, VHost) -> ?assertEqual(ExpectedResult, - rabbit_auth_backend_oauth2:check_vhost_access(AuthUser, VHost, none)). + 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_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(false, AuthUser, VHost, ResourceName, + PermissionKind). -assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceName, PermissionKind) -> - assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceName, PermissionKind). +assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceName, + PermissionKind) -> + assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, + ResourceName, PermissionKind). -assert_resource_access_response(ExpectedResult, 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, #{})). -assert_resource_access_granted(AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> - assert_resource_access_response(true, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind). +assert_resource_access_granted(AuthUser, VHost, ResourceKind, ResourceName, + PermissionKind) -> + assert_resource_access_response(true, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind). -assert_resource_access_denied(AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> - assert_resource_access_response(false, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind). +assert_resource_access_denied(AuthUser, VHost, ResourceKind, ResourceName, + PermissionKind) -> + assert_resource_access_response(false, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind). -assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> - assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind). +assert_resource_access_errors(ExpectedError, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind) -> + assert_resource_access_response({error, ExpectedError}, AuthUser, VHost, + ResourceKind, ResourceName, PermissionKind). -assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceKind, ResourceName, PermissionKind) -> +assert_resource_access_response(ExpectedResult, AuthUser, VHost, ResourceKind, + ResourceName, PermissionKind) -> ?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_resource_access( AuthUser, rabbit_misc:r(VHost, ResourceKind, ResourceName), PermissionKind, #{})). -assert_topic_access_granted(AuthUser, VHost, ResourceName, PermissionKind, AuthContext) -> - assert_topic_access_response(true, AuthUser, VHost, ResourceName, PermissionKind, AuthContext). +assert_topic_access_granted(AuthUser, VHost, ResourceName, PermissionKind, + AuthContext) -> + assert_topic_access_response(true, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext). -assert_topic_access_refused(AuthUser, VHost, ResourceName, PermissionKind, AuthContext) -> - assert_topic_access_response(false, AuthUser, VHost, ResourceName, PermissionKind, AuthContext). +assert_topic_access_refused(AuthUser, VHost, ResourceName, PermissionKind, + AuthContext) -> + assert_topic_access_response(false, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext). -assert_topic_access_response(ExpectedResult, AuthUser, VHost, ResourceName, PermissionKind, AuthContext) -> - ?assertEqual(ExpectedResult, rabbit_auth_backend_oauth2:check_topic_access( +assert_topic_access_response(ExpectedResult, AuthUser, VHost, ResourceName, + PermissionKind, AuthContext) -> + ?assertEqual(ExpectedResult, + rabbit_auth_backend_oauth2:check_topic_access( AuthUser, #resource{virtual_host = VHost, kind = topic, diff --git a/deps/rabbitmq_ct_helpers/app.bzl b/deps/rabbitmq_ct_helpers/app.bzl index 7f56b8dfcbab..a2f85973d675 100644 --- a/deps/rabbitmq_ct_helpers/app.bzl +++ b/deps/rabbitmq_ct_helpers/app.bzl @@ -11,7 +11,9 @@ def all_beam_files(name = "all_beam_files"): name = "other_beam", testonly = True, srcs = [ + "src/ct_master_fork.erl", "src/cth_log_redirect_any_domains.erl", + "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", @@ -37,7 +39,9 @@ def all_test_beam_files(name = "all_test_beam_files"): name = "test_other_beam", testonly = True, srcs = [ + "src/ct_master_fork.erl", "src/cth_log_redirect_any_domains.erl", + "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", @@ -99,7 +103,9 @@ def all_srcs(name = "all_srcs"): name = "srcs", testonly = True, srcs = [ + "src/ct_master_fork.erl", "src/cth_log_redirect_any_domains.erl", + "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", diff --git a/deps/rabbitmq_management/.gitignore b/deps/rabbitmq_management/.gitignore index 96463fa9b670..e44f8b646fac 100644 --- a/deps/rabbitmq_management/.gitignore +++ b/deps/rabbitmq_management/.gitignore @@ -2,12 +2,5 @@ test/config_schema_SUITE_data/schema/ -selenium/node_modules -selenium/package-lock.json -selenium/screens/*/* -selenium/logs -selenium/suites/logs/* -selenium/suites/screens/* -selenium/test/oauth/*/h2/*.trace.db -selenium/test/oauth/*/h2/*.lock.db -selenium/*/target/* +test/js/node_modules +test/js/package-lock.json \ No newline at end of file diff --git a/deps/rabbitmq_management/BUILD.bazel b/deps/rabbitmq_management/BUILD.bazel index 6b560bb7059e..2d0677b21fac 100644 --- a/deps/rabbitmq_management/BUILD.bazel +++ b/deps/rabbitmq_management/BUILD.bazel @@ -130,6 +130,11 @@ rabbitmq_suite( ], ) +rabbitmq_suite( + name = "rabbit_mgmt_schema_SUITE", + size = "small" +) + rabbitmq_integration_suite( name = "clustering_prop_SUITE", size = "large", diff --git a/deps/rabbitmq_management/app.bzl b/deps/rabbitmq_management/app.bzl index 7fd01cd065c8..4e197d13f2b9 100644 --- a/deps/rabbitmq_management/app.bzl +++ b/deps/rabbitmq_management/app.bzl @@ -30,6 +30,7 @@ def all_beam_files(name = "all_beam_files"): "src/rabbit_mgmt_load_definitions.erl", "src/rabbit_mgmt_login.erl", "src/rabbit_mgmt_nodes.erl", + "src/rabbit_mgmt_schema.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", "src/rabbit_mgmt_stats.erl", @@ -163,6 +164,7 @@ def all_test_beam_files(name = "all_test_beam_files"): "src/rabbit_mgmt_load_definitions.erl", "src/rabbit_mgmt_login.erl", "src/rabbit_mgmt_nodes.erl", + "src/rabbit_mgmt_schema.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", "src/rabbit_mgmt_stats.erl", @@ -387,6 +389,7 @@ def all_srcs(name = "all_srcs"): "src/rabbit_mgmt_load_definitions.erl", "src/rabbit_mgmt_login.erl", "src/rabbit_mgmt_nodes.erl", + "src/rabbit_mgmt_schema.erl", "src/rabbit_mgmt_oauth_bootstrap.erl", "src/rabbit_mgmt_reset_handler.erl", "src/rabbit_mgmt_stats.erl", @@ -495,6 +498,15 @@ def all_srcs(name = "all_srcs"): ) def test_suite_beam_files(name = "test_suite_beam_files"): + erlang_bytecode( + name = "rabbit_mgmt_schema_SUITE_beam_files", + testonly = True, + srcs = ["test/rabbit_mgmt_schema_SUITE.erl"], + outs = ["test/rabbit_mgmt_schema_SUITE.beam"], + app_name = "rabbitmq_management", + erlc_opts = "//:test_erlc_opts", + deps = ["@proper//:erlang_app"], + ) erlang_bytecode( name = "cache_SUITE_beam_files", testonly = True, diff --git a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema index 83c32b3022ac..244a46261465 100644 --- a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema +++ b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema @@ -472,6 +472,26 @@ end}. {mapping, "management.oauth_response_type", "rabbitmq_management.oauth_response_type", [{datatype, string}]}. +%% Configure OAuth2 authorization_endpoint additional request parameters +{mapping, "management.oauth_authorization_endpoint_params.$name", + "rabbitmq_management.oauth_authorization_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_management.oauth_authorization_endpoint_params", + fun(Conf) -> + rabbit_mgmt_schema:translate_endpoint_params("oauth_authorization_endpoint_params", Conf) + end}. + +%% Configure OAuth2 token_endpoint additional request parameters +{mapping, "management.oauth_token_endpoint_params.$name", + "rabbitmq_management.oauth_token_endpoint_params", + [{datatype, string}]}. + +{translation, "rabbitmq_management.oauth_token_endpoint_params", + fun(Conf) -> + rabbit_mgmt_schema:translate_endpoint_params("oauth_token_endpoint_params", Conf) + end}. + %% The scopes RabbitMq should claim during the authorization flow. Defaults to "openid profile" {mapping, "management.oauth_scopes", "rabbitmq_management.oauth_scopes", [{datatype, string}]}. @@ -488,7 +508,6 @@ end}. {mapping, "management.oauth_initiated_logon_type", "rabbitmq_management.oauth_initiated_logon_type", [{datatype, {enum, [sp_initiated, idp_initiated]}}]}. - {mapping, "management.oauth_resource_servers.$name.id", "rabbitmq_management.oauth_resource_servers", @@ -514,8 +533,6 @@ end}. [{datatype, string}] }. - - {mapping, "management.oauth_resource_servers.$name.oauth_client_id", "rabbitmq_management.oauth_resource_servers", @@ -534,7 +551,6 @@ end}. [{datatype, string}] }. - {mapping, "management.oauth_resource_servers.$name.oauth_scopes", "rabbitmq_management.oauth_resource_servers", @@ -552,36 +568,17 @@ end}. "rabbitmq_management.oauth_resource_servers", [{datatype, {enum, [sp_initiated, idp_initiated]}}]}. +{mapping, "management.oauth_resource_servers.$name.oauth_authorization_endpoint_params.$name", + "rabbitmq_management.oauth_resource_servers", + [{datatype, string}]}. + +{mapping, "management.oauth_resource_servers.$name.oauth_token_endpoint_params.$name", + "rabbitmq_management.oauth_resource_servers", + [{datatype, string}]}. + {translation, "rabbitmq_management.oauth_resource_servers", fun(Conf) -> - Settings = cuttlefish_variable:filter_by_prefix("management.oauth_resource_servers", Conf), - ResourceServers = [{Name, {list_to_atom(Key), V}} || {["management","oauth_resource_servers", Name, Key], V} <- Settings ], - KeyFun = fun({Name,_}) -> list_to_binary(Name) end, - ValueFun = fun({_,V}) -> V end, - NewGroup = maps:groups_from_list(KeyFun, ValueFun, ResourceServers), - ListOrSingleFun = fun(K, List) -> - case K of - key_config -> proplists:get_all_values(K, List); - _ -> - case proplists:lookup_all(K, List) of - [One] -> proplists:get_value(K, List); - [One|_] = V -> V - end - end - end, - GroupKeyConfigFun = fun(K, List) -> - ListKeys = proplists:get_keys(List), - [ {K,ListOrSingleFun(K,List)} || K <- ListKeys ] - end, - NewGroupTwo = maps:map(GroupKeyConfigFun, NewGroup), - IndexByIdOrElseNameFun = fun(K, V, NewMap) -> - case proplists:get_value(id, V) of - undefined -> maps:put(K, V, NewMap); - ID when is_binary(ID) -> maps:put(ID, V, NewMap); - ID -> maps:put(list_to_binary(ID), V, NewMap) - end - end, - maps:fold(IndexByIdOrElseNameFun,#{}, NewGroupTwo) + rabbit_mgmt_schema:translate_oauth_resource_servers(Conf) end}. %% =========================================================================== diff --git a/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js b/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js index 6ebc53a6ed01..12c8c99a002f 100644 --- a/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js +++ b/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js @@ -46,15 +46,9 @@ function auth_settings_apply_defaults(authSettings) { } if (!resource_server.oauth_response_type) { resource_server.oauth_response_type = authSettings.oauth_response_type - if (!resource_server.oauth_response_type) { - resource_server.oauth_response_type = "code" - } } if (!resource_server.oauth_scopes) { resource_server.oauth_scopes = authSettings.oauth_scopes - if (!resource_server.oauth_scopes) { - resource_server.oauth_scopes = "openid profile" - } } if (!resource_server.oauth_client_id) { resource_server.oauth_client_id = authSettings.oauth_client_id @@ -78,6 +72,14 @@ function auth_settings_apply_defaults(authSettings) { if (!resource_server.oauth_metadata_url) { resource_server.oauth_metadata_url = authSettings.metadata_url } + if (!resource_server.oauth_authorization_endpoint_params) { + resource_server.oauth_authorization_endpoint_params = + authSettings.oauth_authorization_endpoint_params + } + if (!resource_server.oauth_token_endpoint_params) { + resource_server.oauth_token_endpoint_params = + authSettings.oauth_token_endpoint_params + } resource_server.id = resource_server_id authSettings.resource_servers.push(resource_server) } @@ -98,21 +100,21 @@ function get_oauth_settings() { export function oauth_initialize_if_required(state = "index") { let oauth = oauth_initialize(get_oauth_settings()) if (!oauth.enabled) return oauth; - switch (state) { - case 'login-callback': - oauth_completeLogin(); break; - case 'logout-callback': - oauth_completeLogout(); break; - default: + switch (state) { + case 'login-callback': + oauth_completeLogin(); break; + case 'logout-callback': + oauth_completeLogout(); break; + default: oauth = oauth_initiate(oauth); } - return oauth; + return oauth; } export function oauth_initiate(oauth) { if (oauth.enabled) { if (!oauth.sp_initiated) { - oauth.logged_in = has_auth_credentials(); + oauth.logged_in = has_auth_credentials(); } else { oauth_is_logged_in().then( status => { if (status.loggedIn && !has_auth_credentials()) { @@ -122,7 +124,7 @@ export function oauth_initiate(oauth) { if (!status.loggedIn) { clear_auth(); } else { - oauth.logged_in = true; + oauth.logged_in = true; oauth.expiryDate = new Date(status.user.expires_at * 1000); // it is epoch in seconds let current = new Date(); _management_logger.debug('token expires in ', (oauth.expiryDate-current)/1000, @@ -139,40 +141,41 @@ export function oauth_initiate(oauth) { } return oauth; } -function oauth_initialize_user_manager(resource_server) { - let oidcSettings = { - userStore: new oidc.WebStorageStateStore({ store: window.localStorage }), - authority: resource_server.oauth_provider_url, - client_id: resource_server.oauth_client_id, - response_type: resource_server.oauth_response_type, - scope: resource_server.oauth_scopes, - resource: resource_server.id, - redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html", - post_logout_redirect_uri: rabbit_base_uri() + "/", - - automaticSilentRenew: true, - revokeAccessTokenOnSignout: true, - extraQueryParams: { - audience: resource_server.id, // required by oauth0 - }, - }; - if (resource_server.end_session_endpoint != "") { - oidcSettings.metadataSeed = { - end_session_endpoint: resource_server.end_session_endpoint - } - } - if (resource_server.oauth_client_secret != "") { - oidcSettings.client_secret = resource_server.oauth_client_secret; - } - if (resource_server.oauth_metadata_url != "") { - oidcSettings.metadataUrl = resource_server.oauth_metadata_url; +export function oidc_settings_from(resource_server) { + let oidcSettings = { + userStore: new oidc.WebStorageStateStore({ store: window.localStorage }), + authority: resource_server.oauth_provider_url, + metadataUrl: resource_server.oauth_metadata_url, + client_id: resource_server.oauth_client_id, + response_type: resource_server.oauth_response_type, + scope: resource_server.oauth_scopes, + redirect_uri: rabbit_base_uri() + "/js/oidc-oauth/login-callback.html", + post_logout_redirect_uri: rabbit_base_uri() + "/", + automaticSilentRenew: true, + revokeAccessTokenOnSignout: true + } + if (resource_server.end_session_endpoint != "") { + oidcSettings.metadataSeed = { + end_session_endpoint: resource_server.end_session_endpoint } + } + if (resource_server.oauth_client_secret != "") { + oidcSettings.client_secret = resource_server.oauth_client_secret + } + if (resource_server.oauth_authorization_endpoint_params) { + oidcSettings.extraQueryParams = resource_server.oauth_authorization_endpoint_params + } + if (resource_server.oauth_token_endpoint_params) { + oidcSettings.extraTokenParams = resource_server.oauth_token_endpoint_params + } + return oidcSettings +} +function oauth_initialize_user_manager(resource_server) { oidc.Log.setLevel(oidc.Log.DEBUG); oidc.Log.setLogger(console); - mgr = new oidc.UserManager(oidcSettings); -// oauth.readiness_url = mgr.settings.metadataUrl; + mgr = new oidc.UserManager(oidc_settings_from(resource_server)) _management_logger = new oidc.Logger("Management"); @@ -218,20 +221,6 @@ export function oauth_initialize(authSettings) { return oauth; } -function log() { - message = "" - Array.prototype.forEach.call(arguments, function(msg) { - if (msg instanceof Error) { - msg = "Error: " + msg.message; - } - else if (typeof msg !== "string") { - msg = JSON.stringify(msg, null, 2); - } - message += msg - }); - _management_logger.info(message) -} - function oauth_is_logged_in() { return mgr.getUser().then(user => { if (!user) { diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl new file mode 100644 index 000000000000..19e973a47748 --- /dev/null +++ b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl @@ -0,0 +1,67 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_mgmt_schema). + + +-export([ + translate_oauth_resource_servers/1, + translate_endpoint_params/2 +]). + +extract_key_as_binary({Name,_}) -> list_to_binary(Name). +extract_value({_Name,V}) -> V. + +-spec translate_oauth_resource_servers([{list(), binary()}]) -> map(). +translate_oauth_resource_servers(Conf) -> + Settings = cuttlefish_variable:filter_by_prefix( + "management.oauth_resource_servers", Conf), + Map = merge_list_of_maps([ + extract_resource_server_properties(Settings), + extract_resource_server_endpoint_params(oauth_authorization_endpoint_params, Settings), + extract_resource_server_endpoint_params(oauth_token_endpoint_params, Settings) + ]), + Map0 = maps:map(fun(K,V) -> + case proplists:get_value(id, V) of + undefined -> V ++ [{id, K}]; + _ -> V + end end, Map), + ResourceServers = maps:values(Map0), + lists:foldl(fun(Elem,AccMap)-> maps:put(proplists:get_value(id, Elem), Elem, AccMap) end, #{}, + ResourceServers). + +-spec translate_endpoint_params(list(), [{list(), binary()}]) -> [{binary(), binary()}]. +translate_endpoint_params(Variable, Conf) -> + Params0 = cuttlefish_variable:filter_by_prefix("management." ++ Variable, Conf), + [{list_to_binary(Param), list_to_binary(V)} || {["management", _, Param], V} <- Params0]. + +merge_list_of_maps(ListOfMaps) -> + lists:foldl(fun(Elem, AccIn) -> maps:merge_with(fun(_K,V1,V2) -> V1 ++ V2 end, + Elem, AccIn) end, #{}, ListOfMaps). + +convert_list_to_binary(V) when is_list(V) -> + list_to_binary(V); +convert_list_to_binary(V) -> + V. + +extract_resource_server_properties(Settings) -> + KeyFun = fun extract_key_as_binary/1, + ValueFun = fun extract_value/1, + + OAuthResourceServers = [{Name, {list_to_atom(Key), convert_list_to_binary(V)}} + || {["management","oauth_resource_servers", Name, Key], V} <- Settings ], + maps:groups_from_list(KeyFun, ValueFun, OAuthResourceServers). + + +extract_resource_server_endpoint_params(Variable, Settings) -> + KeyFun = fun extract_key_as_binary/1, + + IndexedParams = [{Name, {list_to_binary(ParamName), list_to_binary(V)}} || + {["management","oauth_resource_servers", Name, EndpointVar, ParamName], V} + <- Settings, EndpointVar == atom_to_list(Variable) ], + maps:map(fun(_K,V)-> [{Variable, V}] end, + maps:groups_from_list(KeyFun, fun({_, V}) -> V end, IndexedParams)). diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl index cc3f0b3f486f..c8db33e1d778 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl @@ -25,6 +25,18 @@ variances(Req, Context) -> content_types_provided(ReqData, Context) -> {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. +merge_property(Key, List, MapIn) -> + case proplists:get_value(Key, List) of + undefined -> MapIn; + V0 -> MapIn#{Key => V0} + end. + +extract_oauth_provider_info_props_as_map(ManagementProps) -> + lists:foldl(fun(K, Acc) -> + merge_property(K, ManagementProps, Acc) end, #{}, [oauth_provider_url, + oauth_metadata_url, oauth_authorization_endpoint_params, + oauth_token_endpoint_params]). + merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, ManagementProps) -> OAuthProviderResult = case proplists:get_value(oauth_provider_id, OAuthResourceServer) of undefined -> oauth2_client:get_oauth_provider([issuer]); @@ -34,17 +46,19 @@ merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, ManagementProp {ok, OAuthProvider} -> oauth_provider_to_map(OAuthProvider); {error, _} -> #{} end, - OAuthProviderInfo1 = maps:merge(OAuthProviderInfo0, - case proplists:get_value(oauth_provider_url, ManagementProps) of - undefined -> #{}; - V1 -> #{oauth_provider_url => V1} - end), + OAuthProviderInfo1 = maps:merge(OAuthProviderInfo0, + extract_oauth_provider_info_props_as_map(ManagementProps)), maps:merge(OAuthProviderInfo1, proplists:to_map(MgtResourceServer)). oauth_provider_to_map(OAuthProvider) -> % only include issuer and end_session_endpoint for now. The other endpoints are resolved by oidc-client library - Map0 = #{ oauth_provider_url => OAuthProvider#oauth_provider.issuer }, - case OAuthProvider#oauth_provider.end_session_endpoint of + Map0 = case OAuthProvider#oauth_provider.issuer of + undefined -> #{}; + Issuer -> #{ oauth_provider_url => Issuer, + oauth_metadata_url => OAuthProvider#oauth_provider.discovery_endpoint + } + end, + case OAuthProvider#oauth_provider.end_session_endpoint of undefined -> Map0; V -> maps:put(end_session_endpoint, V, Map0) end. @@ -66,7 +80,7 @@ extract_oauth2_and_mgt_resources(OAuth2BackendProps, ManagementProps) -> MgtResources = maps:map( fun(K,V) -> merge_oauth_provider_info(maps:get(K, OAuth2Resources, #{}), V, ManagementProps) end, skip_disabled_mgt_resource_servers(MgtResources1)), - case maps:size(MgtResources) of + case maps:size(MgtResources) of 0 -> {}; _ -> {MgtResources} end. @@ -75,8 +89,22 @@ getAllDeclaredOauth2Resources(OAuth2BackendProps) -> OAuth2Resources = proplists:get_value(resource_servers, OAuth2BackendProps, #{}), case proplists:get_value(resource_server_id, OAuth2BackendProps) of undefined -> OAuth2Resources; - Id -> maps:put(Id, [{id, Id}], OAuth2Resources) + Id -> maps:put(Id, buildRootResourceServerIfAny(Id, OAuth2BackendProps), + OAuth2Resources) end. +buildRootResourceServerIfAny(Id, Props) -> + [ {id, Id}, + {oauth_client_id, + proplists:get_value(oauth_client_id, Props)}, + {oauth_client_secret, + proplists:get_value(oauth_client_secret, Props)}, + {oauth_response_type, + proplists:get_value(oauth_response_type, Props)}, + {oauth_authorization_endpoint_params, + proplists:get_value(oauth_authorization_endpoint_params, Props)}, + {oauth_token_endpoint_params, + proplists:get_value(oauth_token_endpoint_params, Props)} + ]. authSettings() -> ManagementProps = application:get_all_env(rabbitmq_management), @@ -109,10 +137,19 @@ filter_mgt_resource_servers_without_oauth_client_id_for_sp_initiated(MgtResource end. filter_mgt_resource_servers_without_oauth_provider_url(MgtResourceServers) -> - maps:filter(fun(_K1,V1) -> maps:is_key(oauth_provider_url, V1) end, MgtResourceServers). + maps:filter(fun(_K1,V1) -> maps:is_key(oauth_provider_url, V1) end, MgtResourceServers). + +ensure_oauth_resource_server_properties_are_binaries(Key, Value) -> + case Key of + oauth_authorization_endpoint_params -> Value; + oauth_token_endpoint_params -> Value; + _ -> to_binary(Value) + end. produce_auth_settings(MgtResourceServers, ManagementProps) -> - ConvertValuesToBinary = fun(_K,V) -> [ {K1, to_binary(V1)} || {K1,V1} <- maps:to_list(V) ] end, + ConvertValuesToBinary = fun(_K,V) -> [ + {K1, ensure_oauth_resource_server_properties_are_binaries(K1, V1)} || {K1,V1} + <- maps:to_list(V)] end, FilteredMgtResourceServers = filter_mgt_resource_servers_without_oauth_provider_url( filter_mgt_resource_servers_without_oauth_client_id_for_sp_initiated(MgtResourceServers, ManagementProps)), @@ -122,14 +159,16 @@ produce_auth_settings(MgtResourceServers, ManagementProps) -> filter_empty_properties([ {oauth_enabled, true}, {oauth_resource_servers, maps:map(ConvertValuesToBinary, FilteredMgtResourceServers)}, - to_tuple(oauth_disable_basic_auth, ManagementProps, true), + to_tuple(oauth_disable_basic_auth, ManagementProps, fun to_binary/1, true), to_tuple(oauth_client_id, ManagementProps), to_tuple(oauth_client_secret, ManagementProps), to_tuple(oauth_scopes, ManagementProps), case proplists:get_value(oauth_initiated_logon_type, ManagementProps, sp_initiated) of sp_initiated -> {}; idp_initiated -> {oauth_initiated_logon_type, <<"idp_initiated">>} - end + end, + to_tuple(oauth_authorization_endpoint_params, ManagementProps, undefined, undefined), + to_tuple(oauth_token_endpoint_params, ManagementProps, undefined, undefined) ]) end. @@ -141,6 +180,7 @@ filter_empty_properties(ListOfProperties) -> end end, ListOfProperties). +to_binary(Value) when is_boolean(Value)-> Value; to_binary(Value) -> rabbit_data_coercion:to_binary(Value). to_json(ReqData, Context) -> @@ -158,9 +198,19 @@ is_invalid(List) -> end end, List). to_tuple(Key, Proplist) -> - case proplists:is_defined(Key, Proplist) of - true -> {Key, rabbit_data_coercion:to_binary(proplists:get_value(Key, Proplist))}; - false -> {} - end. -to_tuple(Key, Proplist, DefaultValue) -> - {Key, proplists:get_value(Key, Proplist, DefaultValue)}. + to_tuple(Key, Proplist, fun to_binary/1, undefined). + +to_tuple(Key, Proplist, ConvertFun, DefaultValue) -> + case proplists:is_defined(Key, Proplist) of + true -> + {Key, case ConvertFun of + undefined -> proplists:get_value(Key, Proplist); + _ -> ConvertFun(proplists:get_value(Key, Proplist)) + end + }; + false -> + case DefaultValue of + undefined -> {}; + _ -> {Key, proplists:get_value(Key, Proplist, DefaultValue)} + end + end. diff --git a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets index d26639620bb8..1208f4ddad0f 100644 --- a/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets +++ b/deps/rabbitmq_management/test/config_schema_SUITE_data/rabbitmq_management.snippets @@ -621,15 +621,23 @@ management.oauth_client_id = rabbitmq_client_code management.oauth_client_secret = rabbitmq_client_secret management.oauth_scopes = openid profile rabbitmq.* + management.oauth_authorization_endpoint_params.param1 = value1 + management.oauth_token_endpoint_params.param2 = value2 management.oauth_initiated_logon_type = idp_initiated", [ {rabbitmq_management, [ + {oauth_authorization_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, {oauth_enabled, true}, {oauth_provider_url, "http://localhost:8080"}, {oauth_client_id, "rabbitmq_client_code"}, {oauth_client_secret, "rabbitmq_client_secret"}, {oauth_scopes, "openid profile rabbitmq.*"}, - {oauth_initiated_logon_type, idp_initiated} + {oauth_initiated_logon_type, idp_initiated}, + {oauth_token_endpoint_params, [ + {<<"param2">>, <<"value2">>} + ]} ]} ], [rabbitmq_management] }, @@ -640,7 +648,9 @@ management.oauth_resource_servers.1.label = One management.oauth_resource_servers.1.oauth_client_id = one management.oauth_resource_servers.1.oauth_scopes = openid profile rabbitmq.* + management.oauth_resource_servers.1.oauth_token_endpoint_params.param2 = value2 management.oauth_resource_servers.2.oauth_provider_url = http://two + management.oauth_resource_servers.2.oauth_authorization_endpoint_params.param1 = value1 management.oauth_resource_servers.2.id = resource-two management.oauth_resource_servers.2.oauth_client_id = two management.oauth_resource_servers.3.oauth_initiated_logon_type = idp_initiated @@ -650,21 +660,28 @@ {oauth_enabled, true}, {oauth_resource_servers, #{ - <<"resource-one">> => [ - {oauth_scopes, "openid profile rabbitmq.*"}, - {oauth_client_id, "one"}, - {id, "resource-one"}, - {label, "One"}, - {oauth_provider_url, "http://one:8080"} + <<"3">> => [ + {oauth_provider_url, <<"http://three">>}, + {oauth_initiated_logon_type, idp_initiated}, + {id, <<"3">>} + ], + <<"resource-one">> => [ + {oauth_token_endpoint_params, [ + {<<"param2">>, <<"value2">>} + ]}, + {oauth_scopes, <<"openid profile rabbitmq.*">>}, + {oauth_client_id, <<"one">>}, + {label, <<"One">>}, + {id, <<"resource-one">>}, + {oauth_provider_url, <<"http://one:8080">>} ], <<"resource-two">> => [ - {oauth_client_id, "two"}, - {id, "resource-two"}, - {oauth_provider_url, "http://two"} - ], - <<"3">> => [ - {oauth_initiated_logon_type, idp_initiated}, - {oauth_provider_url, "http://three"} + {oauth_authorization_endpoint_params, [ + {<<"param1">>, <<"value1">>} + ]}, + {oauth_client_id, <<"two">>}, + {id, <<"resource-two">>}, + {oauth_provider_url, <<"http://two">>} ] } } diff --git a/deps/rabbitmq_management/test/js/.babelrc b/deps/rabbitmq_management/test/js/.babelrc new file mode 100644 index 000000000000..1320b9a3272a --- /dev/null +++ b/deps/rabbitmq_management/test/js/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} diff --git a/deps/rabbitmq_management/test/js/package.json b/deps/rabbitmq_management/test/js/package.json new file mode 100644 index 000000000000..0748d98ba9c0 --- /dev/null +++ b/deps/rabbitmq_management/test/js/package.json @@ -0,0 +1,35 @@ +{ + "type":"module", + "dependencies": { + + + "json": "^11.0.0", + + + "mocha": "^10.7.3" + + }, + + "scripts": { + + + "test": "mocha --recursive --trace-warnings --require @babel/register" + + }, + + "devDependencies": { + + + "@babel/cli": "^7.25.6", + + + "@babel/core": "^7.25.2", + + + "@babel/preset-env": "^7.25.4", + + + "@babel/register": "^7.24.6" + + } +} diff --git a/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js b/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js new file mode 100644 index 000000000000..88431a0c9498 --- /dev/null +++ b/deps/rabbitmq_management/test/js/test/oidc-oauth/helper.test.js @@ -0,0 +1,22 @@ +const assert = require('assert') +import oidc_settings_from from '../../../../priv/www/js/oidc-oauth/helper.js' + +describe('oidc_settings_from', function () { + describe('single root resource', function () { + + describe('with minimum required settings', function () { + var resource = { + oauth_client_id : "some-client", + oauth_provider_url : "https://someurl", + oauth_metadata_url : "https://someurl/extra" + } + var oidc_settings = oidc_settings_from(resource) + + it('oidc_settings should have client_id ', function () { + assert.equal(resource.oauth_provider_url, oidc_settings.authority) + assert.equal(resource.oauth_metadata_url, oidc_settings.metadataUrl) + assert.equal(resource.oauth_client_id, oidc_settings.client_id) + }) + }) + }) +}) \ No newline at end of file diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl new file mode 100644 index 000000000000..47c369978cb9 --- /dev/null +++ b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl @@ -0,0 +1,76 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% +-module(rabbit_mgmt_schema_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-import(rabbit_mgmt_schema, [translate_endpoint_params/2, translate_oauth_resource_servers/1]). + +all() -> + [ + test_empty_endpoint_params, + test_invalid_endpoint_params, + test_translate_endpoint_params, + test_with_one_resource_server, + test_with_many_resource_servers + ]. + + +test_empty_endpoint_params(_) -> + [] = translate_endpoint_params("oauth_authorization_endpoint_params", []), + [] = translate_endpoint_params("oauth_token_endpoint_params", []). + +test_invalid_endpoint_params(_) -> + try translate_endpoint_params("oauth_authorization_endpoint_params", [ + {["param1","param2"], "some-value1"}]) of + _ -> {throw, should_have_failed} + catch + _ -> ok + end. + +test_translate_endpoint_params(_) -> + [ {<<"param1">>, <<"some-value1">>} ] = + translate_endpoint_params("oauth_authorization_endpoint_params", [ + {["management","oauth_authorization_endpoint_params","param1"], "some-value1"} + ]). + +test_with_one_resource_server(_) -> + Conf = [ + {["management","oauth_resource_servers","rabbitmq1","id"],"rabbitmq1"} + ], + #{ + <<"rabbitmq1">> := [ + {id, <<"rabbitmq1">>} + ] + } = translate_oauth_resource_servers(Conf). + +test_with_many_resource_servers(_) -> + Conf = [ + {["management","oauth_resource_servers","keycloak","label"],"Keycloak"}, + {["management","oauth_resource_servers","uaa","label"],"Uaa"} + ], + #{ + <<"keycloak">> := [ + {label, <<"Keycloak">>}, + {id, <<"keycloak">>} + ], + <<"uaa">> := [ + {label, <<"Uaa">>}, + {id, <<"uaa">>} + ] + } = translate_oauth_resource_servers(Conf). + + +cert_filename(Conf) -> + string:concat(?config(data_dir, Conf), "certs/cert.pem"). + +sort_settings(MapOfListOfSettings) -> + maps:map(fun(_K,List) -> + lists:sort(fun({K1,_}, {K2,_}) -> K1 < K2 end, List) end, MapOfListOfSettings). diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl index d47350d2b926..970630b6aaf6 100644 --- a/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl +++ b/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl @@ -9,7 +9,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). - +-import(application, [set_env/3, unset_env/2]). +-import(rabbit_mgmt_wm_auth, [authSettings/0]). -compile(export_all). all() -> @@ -21,14 +22,32 @@ all() -> {group, verify_mgt_oauth_provider_url_with_single_resource_and_another_resource}, {group, verify_end_session_endpoint_with_single_resource}, {group, verify_end_session_endpoint_with_single_resource_and_another_resource}, + {group, verify_multi_resource_and_provider}, {group, verify_oauth_initiated_logon_type_for_sp_initiated}, {group, verify_oauth_initiated_logon_type_for_idp_initiated}, {group, verify_oauth_disable_basic_auth}, - {group, verify_oauth_scopes} + {group, verify_oauth_scopes}, + {group, verify_extra_endpoint_params} ]. groups() -> [ + + {verify_multi_resource_and_provider, [], [ + {with_oauth_enabled, [], [ + {with_oauth_providers_idp1_idp2, [], [ + {with_default_oauth_provider_idp1, [], [ + {with_resource_server_a, [], [ + should_return_disabled_auth_settings, + {with_mgt_resource_server_a_with_client_id_x, [], [ + should_return_oauth_enabled, + should_return_oauth_resource_server_a_with_client_id_x + ]} + ]} + ]} + ]} + ]} + ]}, {without_any_settings, [], [ should_return_disabled_auth_settings ]}, @@ -74,8 +93,13 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_mgt_oauth_provider_url_url1, + should_return_mgt_oauth_metadata_url_url1, {with_mgt_oauth_provider_url_url0, [], [ - should_return_mgt_oauth_provider_url_url0 + should_return_mgt_oauth_provider_url_url0, + should_return_mgt_oauth_metadata_url_url1, + {with_mgt_oauth_metadata_url_url0, [], [ + should_return_mgt_oauth_metadata_url_url0 + ]} ]} ]} ]} @@ -86,6 +110,7 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_mgt_oauth_provider_url_idp1_url, + should_return_mgt_oauth_matadata_url_idp1_url, {with_root_issuer_url1, [], [ should_return_mgt_oauth_provider_url_idp1_url ]}, @@ -102,7 +127,7 @@ groups() -> {with_resource_server_id_rabbit, [], [ {with_root_issuer_url1, [], [ {with_oauth_enabled, [], [ - {with_mgt_oauth_client_id_z, [], [ + {with_mgt_oauth_client_id_z, [], [ should_not_return_end_session_endpoint, {with_root_end_session_endpoint_0, [], [ should_return_end_session_endpoint_0 @@ -112,7 +137,7 @@ groups() -> ]}, {with_oauth_providers_idp1_idp2, [], [ {with_default_oauth_provider_idp1, [], [ - {with_oauth_enabled, [], [ + {with_oauth_enabled, [], [ {with_mgt_oauth_client_id_z, [], [ should_not_return_end_session_endpoint, {with_end_session_endpoint_for_idp1_1, [], [ @@ -141,7 +166,7 @@ groups() -> should_return_oauth_resource_server_a_without_end_session_endpoint, {with_root_end_session_endpoint_0, [], [ should_return_end_session_endpoint_0, - should_return_oauth_resource_server_a_with_end_session_endpoint_0 + should_return_oauth_resource_server_a_with_end_session_endpoint_0 ]}, {with_oauth_providers_idp1_idp2, [], [ {with_default_oauth_provider_idp1, [], [ @@ -151,11 +176,11 @@ groups() -> {with_oauth_provider_idp2_for_resource_server_a, [], [ {with_end_session_endpoint_for_idp2_2, [], [ should_return_oauth_resource_server_a_with_end_session_endpoint_2 - ]} + ]} ]} ]} ]} - ]} + ]} ]} ]} ]} @@ -170,13 +195,20 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url1, + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, should_return_oauth_resource_server_a_with_oauth_provider_url_url1, + should_return_oauth_resource_server_a_with_oauth_metadata_url_url1, {with_mgt_oauth_provider_url_url0, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1, should_return_oauth_resource_server_a_with_oauth_provider_url_url0, + should_return_oauth_resource_server_a_with_oauth_metadata_url_url1, {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, - should_return_oauth_resource_server_a_with_oauth_provider_url_url1 + should_return_oauth_resource_server_a_with_oauth_provider_url_url1, + {with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url0, [], [ + should_return_oauth_resource_server_a_with_oauth_metadata_url_url0 + ]} ]} ]} ]} @@ -188,11 +220,13 @@ groups() -> should_return_disabled_auth_settings, {with_mgt_oauth_client_id_z, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url, + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url, {with_root_issuer_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url ]}, {with_mgt_oauth_provider_url_url0, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, + should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url, {with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, [], [ should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0, should_return_oauth_resource_server_a_with_oauth_provider_url_url1 @@ -204,7 +238,7 @@ groups() -> ]} ]} ]} - ]}, + ]}, {verify_oauth_initiated_logon_type_for_sp_initiated, [], [ should_return_disabled_auth_settings, {with_resource_server_id_rabbit, [], [ @@ -287,6 +321,32 @@ groups() -> ]} ]} ]} + ]}, + {verify_extra_endpoint_params, [], [ + {with_resource_server_id_rabbit, [], [ + {with_root_issuer_url1, [], [ + {with_oauth_enabled, [], [ + {with_mgt_oauth_client_id_z, [], [ + should_return_mgt_oauth_resource_rabbit_without_authorization_endpoint_params, + should_return_mgt_oauth_resource_rabbit_without_token_endpoint_params, + {with_authorization_endpoint_params_0, [], [ + should_return_mgt_oauth_resource_rabbit_with_authorization_endpoint_params_0 + ]}, + {with_token_endpoint_params_0, [], [ + should_return_mgt_oauth_resource_rabbit_with_token_endpoint_params_0 + ]}, + {with_resource_server_a, [], [ + {with_mgt_resource_server_a_with_authorization_endpoint_params_1, [], [ + should_return_mgt_oauth_resource_a_with_authorization_endpoint_params_1 + ]}, + {with_mgt_resource_server_a_with_token_endpoint_params_1, [], [ + should_return_mgt_oauth_resource_a_with_token_endpoint_params_1 + ]} + ]} + ]} + ]} + ]} + ]} ]} ]. @@ -299,10 +359,15 @@ init_per_suite(Config) -> {idp2, <<"idp2">>}, {idp3, <<"idp3">>}, {idp1_url, <<"https://idp1">>}, + {meta_idp1_url, <<"https://idp1/.well-known/openid-configuration">>}, {idp2_url, <<"https://idp2">>}, + {meta_idp2_url, <<"https://idp2/.well-known/openid-configuration">>}, {idp3_url, <<"https://idp3">>}, + {meta_idp3_url, <<"https://idp3/.well-known/openid-configuration">>}, {url0, <<"https://url0">>}, + {meta_url0, <<"https://url0/.well-known/openid-configuration">>}, {url1, <<"https://url1">>}, + {meta_url1, <<"https://url1/.well-known/openid-configuration">>}, {logout_url_0, <<"https://logout_0">>}, {logout_url_1, <<"https://logout_1">>}, {logout_url_2, <<"https://logout_2">>}, @@ -312,6 +377,10 @@ init_per_suite(Config) -> {w, <<"w">>}, {z, <<"z">>}, {x, <<"x">>}, + {authorization_params_0, [{<<"a-param0">>, <<"value0">>}]}, + {authorization_params_1, [{<<"a-param1">>, <<"value1">>}]}, + {token_params_0, [{<<"t-param0">>, <<"value0">>}]}, + {token_params_1, [{<<"t-param1">>, <<"value1">>}]}, {admin_mgt, <<"admin mgt">>}, {read_write, <<"read write">>} | Config]. @@ -319,41 +388,44 @@ end_per_suite(_Config) -> ok. init_per_group(with_oauth_disabled, Config) -> - application:set_env(rabbitmq_management, oauth_enabled, false), + set_env(rabbitmq_management, oauth_enabled, false), Config; init_per_group(with_oauth_enabled, Config) -> - application:set_env(rabbitmq_management, oauth_enabled, true), + set_env(rabbitmq_management, oauth_enabled, true), Config; init_per_group(with_resource_server_id_rabbit, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?config(rabbit, Config)), + set_env(rabbitmq_auth_backend_oauth2, resource_server_id, ?config(rabbit, Config)), Config; init_per_group(with_mgt_oauth_client_id_z, Config) -> - application:set_env(rabbitmq_management, oauth_client_id, ?config(z, Config)), + set_env(rabbitmq_management, oauth_client_id, ?config(z, Config)), Config; init_per_group(with_mgt_resource_server_a_with_client_secret_w, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_client_secret, ?config(w, Config)), Config; init_per_group(with_mgt_oauth_client_secret_q, Config) -> - application:set_env(rabbitmq_management, oauth_client_secret, ?config(q, Config)), + set_env(rabbitmq_management, oauth_client_secret, ?config(q, Config)), Config; init_per_group(with_mgt_oauth_provider_url_url0, Config) -> - application:set_env(rabbitmq_management, oauth_provider_url, ?config(url0, Config)), + set_env(rabbitmq_management, oauth_provider_url, ?config(url0, Config)), + Config; +init_per_group(with_mgt_oauth_metadata_url_url0, Config) -> + set_env(rabbitmq_management, oauth_metadata_url, ?config(meta_url0, Config)), Config; init_per_group(with_root_issuer_url1, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, issuer, ?config(url1, Config)), + set_env(rabbitmq_auth_backend_oauth2, issuer, ?config(url1, Config)), Config; init_per_group(with_oauth_scopes_admin_mgt, Config) -> - application:set_env(rabbitmq_management, oauth_scopes, ?config(admin_mgt, Config)), + set_env(rabbitmq_management, oauth_scopes, ?config(admin_mgt, Config)), Config; init_per_group(with_oauth_scopes_write_read, Config) -> - application:set_env(rabbitmq_management, oauth_scopes, ?config(write_read, Config)), + set_env(rabbitmq_management, oauth_scopes, ?config(write_read, Config)), Config; init_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> - application:set_env(rabbitmq_management, oauth_initiated_logon_type, idp_initiated), + set_env(rabbitmq_management, oauth_initiated_logon_type, idp_initiated), Config; init_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> - application:set_env(rabbitmq_management, oauth_initiated_logon_type, sp_initiated), + set_env(rabbitmq_management, oauth_initiated_logon_type, sp_initiated), Config; init_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_sp_initiated, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, @@ -364,10 +436,10 @@ init_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_idp_in ?config(a, Config), oauth_initiated_logon_type, idp_initiated), Config; init_per_group(with_oauth_disable_basic_auth_false, Config) -> - application:set_env(rabbitmq_management, oauth_disable_basic_auth, false), + set_env(rabbitmq_management, oauth_disable_basic_auth, false), Config; init_per_group(with_oauth_providers_idp1_idp2, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ + set_env(rabbitmq_auth_backend_oauth2, oauth_providers, #{ ?config(idp1, Config) => [ { issuer, ?config(idp1_url, Config)} ], ?config(idp2, Config) => [ { issuer, ?config(idp2_url, Config)} ] }), @@ -388,18 +460,23 @@ init_per_group(with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, Co set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_provider_url, ?config(url1, Config)), Config; +init_per_group(with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url0, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_metadata_url, ?config(meta_url0, Config)), + Config; init_per_group(with_mgt_resource_server_a_with_client_id_x, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_client_id, ?config(x, Config)), Config; + init_per_group(with_default_oauth_provider_idp1, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp1, Config)), + set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp1, Config)), Config; init_per_group(with_default_oauth_provider_idp3, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp3, Config)), + set_env(rabbitmq_auth_backend_oauth2, default_oauth_provider, ?config(idp3, Config)), Config; init_per_group(with_root_end_session_endpoint_0, Config) -> - application:set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, ?config(logout_url_0, Config)), + set_env(rabbitmq_auth_backend_oauth2, end_session_endpoint, ?config(logout_url_0, Config)), Config; init_per_group(with_end_session_endpoint_for_idp1_1, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, @@ -409,53 +486,73 @@ init_per_group(with_end_session_endpoint_for_idp2_2, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, ?config(idp2, Config), end_session_endpoint, ?config(logout_url_2, Config)), Config; - init_per_group(with_oauth_provider_idp2_for_resource_server_a, Config) -> set_attribute_in_entry_for_env_variable(rabbitmq_auth_backend_oauth2, resource_servers, ?config(a, Config), oauth_provider_id, ?config(idp2, Config)), Config; +init_per_group(with_authorization_endpoint_params_0, Config) -> + set_env(rabbitmq_management, oauth_authorization_endpoint_params, + ?config(authorization_params_0, Config)), + Config; +init_per_group(with_token_endpoint_params_0, Config) -> + set_env(rabbitmq_management, oauth_token_endpoint_params, + ?config(token_params_0, Config)), + Config; +init_per_group(with_mgt_resource_server_a_with_authorization_endpoint_params_1, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_authorization_endpoint_params, ?config(authorization_params_1, Config)), + Config; +init_per_group(with_mgt_resource_server_a_with_token_endpoint_params_1, Config) -> + set_attribute_in_entry_for_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_token_endpoint_params, ?config(token_params_1, Config)), + Config; + init_per_group(_, Config) -> Config. end_per_group(with_oauth_providers_idp1_idp2, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), + unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), Config; end_per_group(with_mgt_oauth_client_secret_q, Config) -> - application:unset_env(rabbitmq_management, oauth_client_secret), + unset_env(rabbitmq_management, oauth_client_secret), Config; end_per_group(with_oauth_scopes_admin_mgt, Config) -> - application:unset_env(rabbitmq_management, oauth_scopes), + unset_env(rabbitmq_management, oauth_scopes), Config; end_per_group(with_oauth_scopes_write_read, Config) -> - application:unset_env(rabbitmq_management, oauth_scopes), + unset_env(rabbitmq_management, oauth_scopes), Config; end_per_group(with_oauth_disabled, Config) -> - application:unset_env(rabbitmq_management, oauth_enabled), + unset_env(rabbitmq_management, oauth_enabled), Config; end_per_group(with_oauth_enabled, Config) -> - application:unset_env(rabbitmq_management, oauth_enabled), + unset_env(rabbitmq_management, oauth_enabled), Config; end_per_group(with_oauth_disable_basic_auth_false, Config) -> - application:unset_env(rabbitmq_management, oauth_disable_basic_auth), + unset_env(rabbitmq_management, oauth_disable_basic_auth), Config; end_per_group(with_resource_server_id_rabbit, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), + unset_env(rabbitmq_auth_backend_oauth2, resource_server_id), Config; end_per_group(with_mgt_oauth_provider_url_url0, Config) -> - application:unset_env(rabbitmq_management, oauth_provider_url), + unset_env(rabbitmq_management, oauth_provider_url), + Config; +end_per_group(with_mgt_oauth_metadata_url_url0, Config) -> + unset_env(rabbitmq_management, oauth_metadata_url), Config; end_per_group(with_root_issuer_url1, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, issuer), + unset_env(rabbitmq_auth_backend_oauth2, issuer), + unset_env(rabbitmq_auth_backend_oauth2, discovery_endpoint), Config; end_per_group(with_mgt_oauth_client_id_z, Config) -> - application:unset_env(rabbitmq_management, oauth_client_id), + unset_env(rabbitmq_management, oauth_client_id), Config; end_per_group(with_oauth_initiated_logon_type_idp_initiated, Config) -> - application:unset_env(rabbitmq_management, oauth_initiated_logon_type), + unset_env(rabbitmq_management, oauth_initiated_logon_type), Config; end_per_group(with_oauth_initiated_logon_type_sp_initiated, Config) -> - application:unset_env(rabbitmq_management, oauth_initiated_logon_type), + unset_env(rabbitmq_management, oauth_initiated_logon_type), Config; end_per_group(with_mgt_resource_server_a_with_client_secret_w, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, @@ -477,6 +574,10 @@ end_per_group(with_mgt_oauth_resource_server_a_with_oauth_provider_url_url1, Con remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_provider_url), Config; +end_per_group(with_mgt_oauth_resource_server_a_with_oauth_metadata_url_url0, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_metadata_url), + Config; end_per_group(with_mgt_resource_server_a_with_oauth_initiated_logon_type_sp_initiated, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, ?config(a, Config), oauth_initiated_logon_type), @@ -490,13 +591,13 @@ end_per_group(with_mgt_resource_server_a_with_client_id_x, Config) -> ?config(a, Config), oauth_client_id), Config; end_per_group(with_default_oauth_provider_idp1, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), Config; end_per_group(with_default_oauth_provider_idp3, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), + unset_env(rabbitmq_auth_backend_oauth2, default_oauth_provider), Config; end_per_group(with_root_end_session_endpoint_0, Config) -> - application:unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), + unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), Config; end_per_group(with_end_session_endpoint_for_idp1_1, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, @@ -510,6 +611,21 @@ end_per_group(with_oauth_provider_idp2_for_resource_server_a, Config) -> remove_attribute_from_entry_from_env_variable(rabbitmq_auth_backend_oauth2, resource_servers, ?config(a, Config), oauth_provider_id), Config; +end_per_group(with_authorization_endpoint_params_0, Config) -> + unset_env(rabbitmq_management, oauth_authorization_endpoint_params), + Config; +end_per_group(with_token_endpoint_params_0, Config) -> + unset_env(rabbitmq_management, oauth_token_endpoint_params), + Config; +end_per_group(with_mgt_resource_server_a_with_authorization_endpoint_params_1, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_authorization_endpoint_params), + Config; +end_per_group(with_mgt_resource_server_a_with_token_endpoint_params_1, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_management, oauth_resource_servers, + ?config(a, Config), oauth_token_endpoint_params), + Config; + end_per_group(_, Config) -> Config. @@ -519,151 +635,207 @@ end_per_group(_, Config) -> %% Test cases. %% ------------------------------------------------------------------- should_not_return_oauth_client_secret(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(false, proplists:is_defined(oauth_client_secret, Actual)). should_return_oauth_client_secret_q(Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(?config(q, Config), proplists:get_value(oauth_client_secret, Actual)). should_return_oauth_resource_server_a_with_client_id_x(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_client_id, x). should_return_oauth_resource_server_a_with_client_secret_w(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_client_secret, w). should_not_return_oauth_resource_server_a_with_client_secret(Config) -> - assert_attribute_not_defined_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), Config, a, oauth_client_secret). should_return_mgt_oauth_provider_url_idp1_url(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, idp1_url). +should_return_mgt_oauth_matadata_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_idp1_url). + should_return_mgt_oauth_provider_url_url1(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, url1). +should_return_mgt_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url1). + +should_return_mgt_oauth_metadata_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url0). + should_return_mgt_oauth_provider_url_url0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, url0). should_return_oauth_scopes_admin_mgt(Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(?config(admin_mgt, Config), proplists:get_value(oauth_scopes, Actual)). should_return_mgt_oauth_resource_server_a_with_scopes_read_write(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, scopes, read_write). should_return_disabled_auth_settings(_Config) -> - [{oauth_enabled, false}] = rabbit_mgmt_wm_auth:authSettings(). + [{oauth_enabled, false}] = authSettings(). should_return_mgt_resource_server_a_oauth_provider_url_url0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_provider_url, url0). should_return_mgt_oauth_resource_server_a_with_client_id_x(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_client_id, x). should_return_oauth_resource_server_a_with_oauth_provider_url_idp1_url(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_provider_url, idp1_url). should_return_oauth_resource_server_a_with_oauth_provider_url_url1(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_provider_url, url1). +should_return_oauth_resource_server_a_with_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_metadata_url, meta_url1). + +should_return_oauth_resource_server_a_with_oauth_metadata_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_metadata_url, meta_url0). + should_return_oauth_resource_server_a_with_oauth_provider_url_url0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_provider_url, url0). should_return_oauth_resource_server_rabbit_with_oauth_provider_url_idp1_url(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, idp1_url). +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_idp1_url(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_idp1_url). + should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url1(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, url1). +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url1 ). + should_return_oauth_resource_server_rabbit_with_oauth_provider_url_url0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, oauth_provider_url, url0). +should_return_oauth_resource_server_rabbit_with_oauth_metadata_url_url0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_metadata_url, meta_url0). + should_not_return_oauth_initiated_logon_type(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(false, proplists:is_defined(oauth_initiated_logon_type, Actual)). should_return_oauth_initiated_logon_type_idp_initiated(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(<<"idp_initiated">>, proplists:get_value(oauth_initiated_logon_type, Actual)). should_not_return_oauth_resource_server_a(Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), assert_not_defined_oauth_resource_server(Actual, Config, a). should_not_return_oauth_resource_server_a_with_oauth_initiated_logon_type(Config) -> - assert_attribute_not_defined_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), Config, a, oauth_initiated_logon_type). should_return_oauth_resource_server_a_with_oauth_initiated_logon_type_idp_initiated(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_initiated_logon_type, <<"idp_initiated">>). should_return_oauth_resource_server_a_with_oauth_initiated_logon_type_sp_initiated(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, oauth_initiated_logon_type, <<"sp_initiated">>). should_not_return_oauth_scopes(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(false, proplists:is_defined(scopes, Actual)). should_return_oauth_enabled(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), - log(Actual), + Actual = authSettings(), ?assertEqual(true, proplists:get_value(oauth_enabled, Actual)). + should_return_oauth_idp_initiated_logon(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(<<"idp_initiated">>, proplists:get_value(oauth_initiated_logon_type, Actual)). should_return_oauth_disable_basic_auth_true(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(true, proplists:get_value(oauth_disable_basic_auth, Actual)). should_return_oauth_disable_basic_auth_false(_Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(false, proplists:get_value(oauth_disable_basic_auth, Actual)). should_return_oauth_client_id_z(Config) -> - Actual = rabbit_mgmt_wm_auth:authSettings(), + Actual = authSettings(), ?assertEqual(?config(z, Config), proplists:get_value(oauth_client_id, Actual)). should_not_return_end_session_endpoint(Config) -> - assert_attribute_not_defined_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), Config, rabbit, end_session_endpoint). should_return_end_session_endpoint_0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, end_session_endpoint, ?config(logout_url_0, Config)). should_return_end_session_endpoint_1(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, rabbit, end_session_endpoint, ?config(logout_url_1, Config)). should_return_oauth_resource_server_a_without_end_session_endpoint(Config) -> - assert_attribute_not_defined_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), Config, a, end_session_endpoint). should_return_oauth_resource_server_a_with_end_session_endpoint_0(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, end_session_endpoint, ?config(logout_url_0, Config)). should_return_oauth_resource_server_a_with_end_session_endpoint_1(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, end_session_endpoint, ?config(logout_url_1, Config)). should_return_oauth_resource_server_a_with_end_session_endpoint_2(Config) -> - assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth:authSettings(), + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), Config, a, end_session_endpoint, ?config(logout_url_2, Config)). +should_return_mgt_oauth_resource_rabbit_without_authorization_endpoint_params(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_authorization_endpoint_params). + +should_return_mgt_oauth_resource_rabbit_without_token_endpoint_params(Config) -> + assert_attribute_not_defined_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_token_endpoint_params). + +should_return_mgt_oauth_resource_rabbit_with_authorization_endpoint_params_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_authorization_endpoint_params, authorization_params_0). + +should_return_mgt_oauth_resource_rabbit_with_token_endpoint_params_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, rabbit, oauth_token_endpoint_params, token_params_0). + +should_return_mgt_oauth_resource_a_with_authorization_endpoint_params_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_authorization_endpoint_params, authorization_params_1). + +should_return_mgt_oauth_resource_a_with_token_endpoint_params_1(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(authSettings(), + Config, a, oauth_token_endpoint_params, token_params_1). + %% ------------------------------------------------------------------- %% Utility/helper functions %% ------------------------------------------------------------------- @@ -677,16 +849,16 @@ remove_entry_from_env_variable(Application, EnvVar, Key) -> Map = application:get_env(Application, EnvVar, #{}), NewMap = maps:remove(Key, Map), case maps:size(NewMap) of - 0 -> application:unset_env(Application, EnvVar); - _ -> application:set_env(Application, EnvVar, NewMap) + 0 -> unset_env(Application, EnvVar); + _ -> set_env(Application, EnvVar, NewMap) end. remove_attribute_from_entry_from_env_variable(Application, EnvVar, Key, Attribute) -> Map = application:get_env(Application, EnvVar, #{}), Proplist = proplists:delete(Attribute, maps:get(Key, Map, [])), NewMap = delete_key_with_empty_proplist(Key, maps:put(Key, Proplist, Map)), case maps:size(NewMap) of - 0 -> application:unset_env(Application, EnvVar); - _ -> application:set_env(Application, EnvVar, NewMap) + 0 -> unset_env(Application, EnvVar); + _ -> set_env(Application, EnvVar, NewMap) end. assertEqual_on_attribute_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute, ConfigValue) -> @@ -699,6 +871,12 @@ assertEqual_on_attribute_for_oauth_resource_server(Actual, Config, ConfigKey, At end, ?assertEqual(Value, proplists:get_value(Attribute, OauthResource)). +assert_attribute_is_defined_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute) -> + log(Actual), + OAuthResourceServers = proplists:get_value(oauth_resource_servers, Actual), + OauthResource = maps:get(?config(ConfigKey, Config), OAuthResourceServers), + ?assertEqual(true, proplists:is_defined(Attribute, OauthResource)). + assert_attribute_not_defined_for_oauth_resource_server(Actual, Config, ConfigKey, Attribute) -> log(Actual), OAuthResourceServers = proplists:get_value(oauth_resource_servers, Actual), @@ -715,7 +893,7 @@ set_attribute_in_entry_for_env_variable(Application, EnvVar, Key, Attribute, Val ct:log("set_attribute_in_entry_for_env_variable before ~p", [Map]), Map1 = maps:put(Key, [ { Attribute, Value} | maps:get(Key, Map, []) ], Map), ct:log("set_attribute_in_entry_for_env_variable after ~p", [Map1]), - application:set_env(Application, EnvVar, Map1). + set_env(Application, EnvVar, Map1). log(AuthSettings) -> logEnvVars(), diff --git a/moduleindex.yaml b/moduleindex.yaml index 1ce6bae902c0..08b3bdc8d0c7 100755 --- a/moduleindex.yaml +++ b/moduleindex.yaml @@ -864,7 +864,9 @@ rabbitmq_consistent_hash_exchange: rabbitmq_ct_client_helpers: - rabbit_ct_client_helpers rabbitmq_ct_helpers: +- ct_master_fork - cth_log_redirect_any_domains +- cth_parallel_ct_detect_failure - rabbit_control_helper - rabbit_ct_broker_helpers - rabbit_ct_config_schema diff --git a/rabbitmq-components.mk b/rabbitmq-components.mk index b6361f61d0cd..51ae1961dfc2 100644 --- a/rabbitmq-components.mk +++ b/rabbitmq-components.mk @@ -52,7 +52,7 @@ dep_osiris = git https://github.com/rabbitmq/osiris v1.8.3 dep_prometheus = hex 4.11.0 dep_ra = hex 2.14.0 dep_ranch = hex 2.1.0 -dep_recon = hex 2.5.3 +dep_recon = hex 2.5.6 dep_redbug = hex 2.0.7 dep_systemd = hex 0.6.1 dep_thoas = hex 1.0.0 diff --git a/selenium/bin/components/rabbitmq b/selenium/bin/components/rabbitmq index 9eea9e13c2a7..3fb9cb002f85 100644 --- a/selenium/bin/components/rabbitmq +++ b/selenium/bin/components/rabbitmq @@ -52,6 +52,7 @@ start_local_rabbitmq() { init_rabbitmq RABBITMQ_SERVER_ROOT=$(realpath ../) + MOUNT_RABBITMQ_CONF="/etc/rabbitmq/rabbitmq.conf" MOUNT_ADVANCED_CONFIG="/etc/rabbitmq/advanced.config" diff --git a/selenium/bin/gen-env-file b/selenium/bin/gen-env-file index 60c4b4bfc50d..731cefcecb8b 100755 --- a/selenium/bin/gen-env-file +++ b/selenium/bin/gen-env-file @@ -13,6 +13,7 @@ generate_env_file() { mkdir -p $parentdir echo "#!/usr/bin/env bash" > $ENV_FILE echo "set -u" >> $ENV_FILE + echo "export SELENIUM=${SCRIPT}/.." >> $ENV_FILE declare -a FILE_ARRAY for f in $($SCRIPT/find-template-files $FIND_PATH "env") diff --git a/selenium/test/authnz-msg-protocols/env.local b/selenium/test/authnz-msg-protocols/env.local index 69f43736edd4..3e6bec3ad0ff 100644 --- a/selenium/test/authnz-msg-protocols/env.local +++ b/selenium/test/authnz-msg-protocols/env.local @@ -1 +1 @@ -export IMPORT_DIR=test/authnz-msg-protocols/imports +export IMPORT_DIR=selenium/test/authnz-msg-protocols/imports diff --git a/selenium/test/basic-auth/env.local b/selenium/test/basic-auth/env.local index 26cc7522d3b9..bc20106b5b5d 100644 --- a/selenium/test/basic-auth/env.local +++ b/selenium/test/basic-auth/env.local @@ -1 +1 @@ -export IMPORT_DIR=deps/rabbitmq_management/selenium/test/basic-auth/imports +export IMPORT_DIR=selenium/test/basic-auth/imports diff --git a/selenium/test/basic-auth/rabbitmq.conf b/selenium/test/basic-auth/rabbitmq.conf index 7bacc14af27a..ece06fe128a1 100644 --- a/selenium/test/basic-auth/rabbitmq.conf +++ b/selenium/test/basic-auth/rabbitmq.conf @@ -1,6 +1,6 @@ auth_backends.1 = rabbit_auth_backend_internal management.login_session_timeout = 1 -load_definitions = ${IMPORT_DIR}/users.json +load_definitions = ${RABBITMQ_TEST_DIR}/imports/users.json loopback_users = none diff --git a/selenium/test/multi-oauth/env.local b/selenium/test/multi-oauth/env.local index d61f528c4e4a..3ae2df57c061 100644 --- a/selenium/test/multi-oauth/env.local +++ b/selenium/test/multi-oauth/env.local @@ -1 +1 @@ -export OAUTH_SERVER_CONFIG_BASEDIR=deps/rabbitmq_management/selenium/test +export OAUTH_SERVER_CONFIG_BASEDIR=${SELENIUM}/test diff --git a/selenium/test/multi-oauth/env.local.devkeycloak b/selenium/test/multi-oauth/env.local.devkeycloak index a1e2d5d596c2..1a2b7cb0c286 100644 --- a/selenium/test/multi-oauth/env.local.devkeycloak +++ b/selenium/test/multi-oauth/env.local.devkeycloak @@ -1,2 +1,2 @@ export DEVKEYCLOAK_URL=https://localhost:8442/realms/dev -export DEVKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/devkeycloak/ca_certificate.pem +export DEVKEYCLOAK_CA_CERT=${SELENIUM}/test/multi-oauth/devkeycloak/ca_certificate.pem diff --git a/selenium/test/multi-oauth/env.local.prodkeycloak b/selenium/test/multi-oauth/env.local.prodkeycloak index e267b558cd49..2a2e9845c704 100644 --- a/selenium/test/multi-oauth/env.local.prodkeycloak +++ b/selenium/test/multi-oauth/env.local.prodkeycloak @@ -1,2 +1,2 @@ export PRODKEYCLOAK_URL=https://localhost:8443/realms/prod -export PRODKEYCLOAK_CA_CERT=deps/rabbitmq_management/selenium/test/multi-oauth/prodkeycloak/ca_certificate.pem +export PRODKEYCLOAK_CA_CERT=${SELENIUM}/test/multi-oauth/prodkeycloak/ca_certificate.pem diff --git a/selenium/test/oauth/env.local b/selenium/test/oauth/env.local index 80cfe7430e52..3ae2df57c061 100644 --- a/selenium/test/oauth/env.local +++ b/selenium/test/oauth/env.local @@ -1 +1 @@ -export OAUTH_SERVER_CONFIG_BASEDIR=selenium/test +export OAUTH_SERVER_CONFIG_BASEDIR=${SELENIUM}/test diff --git a/selenium/test/oauth/env.local.keycloak b/selenium/test/oauth/env.local.keycloak index 1fa28ef79232..3ff0eb199ea0 100644 --- a/selenium/test/oauth/env.local.keycloak +++ b/selenium/test/oauth/env.local.keycloak @@ -1,3 +1,3 @@ export KEYCLOAK_URL=https://localhost:8443/realms/test export OAUTH_PROVIDER_URL=https://localhost:8443/realms/test -export OAUTH_PROVIDER_CA_CERT=deps/rabbitmq_management/selenium/test/oauth/keycloak/ca_certificate.pem +export OAUTH_PROVIDER_CA_CERT=selenium/test/oauth/keycloak/ca_certificate.pem diff --git a/selenium/test/oauth/rabbitmq.conf b/selenium/test/oauth/rabbitmq.conf index 02b0227d4bf8..f101bae111c0 100644 --- a/selenium/test/oauth/rabbitmq.conf +++ b/selenium/test/oauth/rabbitmq.conf @@ -10,6 +10,6 @@ auth_oauth2.resource_server_id = rabbitmq auth_oauth2.preferred_username_claims.1 = user_name auth_oauth2.preferred_username_claims.2 = preferred_username auth_oauth2.preferred_username_claims.3 = email - +auth_oauth2.preferred_username_claims.4 = sub loopback_users = none diff --git a/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf b/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf index 9e6e55f94073..b9e65845d55e 100644 --- a/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf +++ b/selenium/test/oauth/rabbitmq.keycloak-mgt-oauth-provider.conf @@ -1,2 +1,3 @@ # uaa requires a secret in order to renew tokens management.oauth_provider_url = ${KEYCLOAK_URL} +management.oauth_authorization_endpoint_params.resource = rabbitmq