diff --git a/deps/oauth2_client/include/oauth2_client.hrl b/deps/oauth2_client/include/oauth2_client.hrl index 7553b0b04a18..745eeec33a53 100644 --- a/deps/oauth2_client/include/oauth2_client.hrl +++ b/deps/oauth2_client/include/oauth2_client.hrl @@ -41,6 +41,7 @@ -define(RESPONSE_ISSUER, <<"issuer">>). -define(RESPONSE_TOKEN_ENDPOINT, <<"token_endpoint">>). -define(RESPONSE_AUTHORIZATION_ENDPOINT, <<"authorization_endpoint">>). +-define(RESPONSE_END_SESSION_ENDPOINT, <<"end_session_endpoint">>). -define(RESPONSE_JWKS_URI, <<"jwks_uri">>). -define(RESPONSE_TLS_OPTIONS, <<"ssl_options">>). @@ -51,6 +52,7 @@ 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()) }). diff --git a/deps/oauth2_client/src/oauth2_client.erl b/deps/oauth2_client/src/oauth2_client.erl index c7cee120e8a1..cb667ee72615 100644 --- a/deps/oauth2_client/src/oauth2_client.erl +++ b/deps/oauth2_client/src/oauth2_client.erl @@ -7,23 +7,13 @@ -module(oauth2_client). -export([get_access_token/2, get_expiration_time/1, refresh_access_token/2, - get_oauth_provider/1, get_oauth_provider/2, + get_oauth_provider/1, get_oauth_provider/2, extract_ssl_options_as_list/1 ]). -include("oauth2_client.hrl"). --include_lib("public_key/include/public_key.hrl"). - --spec get_access_token(oauth_provider_id() | oauth_provider(), access_token_request()) -> +-spec get_access_token(oauth_provider(), access_token_request()) -> {ok, successful_access_token_response()} | {error, unsuccessful_access_token_response() | any()}. -get_access_token(OAuth2ProviderId, Request) when is_binary(OAuth2ProviderId) -> - rabbit_log:debug("get_access_token using OAuth2ProviderId:~p and client_id:~p", - [OAuth2ProviderId, Request#access_token_request.client_id]), - case get_oauth_provider(OAuth2ProviderId, [token_endpoint]) of - {error, _Error } = Error0 -> Error0; - {ok, Provider} -> get_access_token(Provider, Request) - end; - 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]), @@ -104,14 +94,20 @@ do_update_oauth_provider_endpoints_configuration(OAuthProvider) -> case OAuthProvider#oauth_provider.token_endpoint of undefined -> do_nothing; - TokenEndPoint -> - application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, TokenEndPoint) + TokenEndpoint -> + application:set_env(rabbitmq_auth_backend_oauth2, 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) + AuthzEndpoint -> + application:set_env(rabbitmq_auth_backend_oauth2, 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) end, List = application:get_env(rabbitmq_auth_backend_oauth2, key_config, []), ModifiedList = case OAuthProvider#oauth_provider.jwks_uri of @@ -127,17 +123,21 @@ do_update_oauth_provider_endpoints_configuration(OAuthProviderId, OAuthProvider) LookupProviderPropList = maps:get(OAuthProviderId, OAuthProviders), ModifiedList0 = case OAuthProvider#oauth_provider.token_endpoint of undefined -> LookupProviderPropList; - TokenEndPoint -> [{token_endpoint, TokenEndPoint} | LookupProviderPropList] + TokenEndpoint -> [{token_endpoint, TokenEndpoint} | LookupProviderPropList] end, ModifiedList1 = case OAuthProvider#oauth_provider.authorization_endpoint of undefined -> ModifiedList0; - AuthzEndPoint -> [{authorization_endpoint, AuthzEndPoint} | ModifiedList0] + AuthzEndpoint -> [{authorization_endpoint, AuthzEndpoint} | ModifiedList0] end, - ModifiedList2 = case OAuthProvider#oauth_provider.jwks_uri of + ModifiedList2 = case OAuthProvider#oauth_provider.end_session_endpoint of undefined -> ModifiedList1; - JwksEndPoint -> [{jwks_uri, JwksEndPoint} | ModifiedList1] + EndSessionEndpoint -> [{end_session_endpoint, EndSessionEndpoint} | ModifiedList1] + end, + ModifiedList3 = case OAuthProvider#oauth_provider.jwks_uri of + undefined -> ModifiedList2; + JwksEndPoint -> [{jwks_uri, JwksEndPoint} | ModifiedList2] end, - ModifiedOAuthProviders = maps:put(OAuthProviderId, ModifiedList2, OAuthProviders), + ModifiedOAuthProviders = maps:put(OAuthProviderId, ModifiedList3, OAuthProviders), application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, ModifiedOAuthProviders), rabbit_log:debug("Replacing oauth_providers ~p", [ ModifiedOAuthProviders]), OAuthProvider. @@ -285,11 +285,15 @@ find_missing_attributes(#oauth_provider{} = OAuthProvider, 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, [])), #oauth_provider{ issuer = Issuer, 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, ssl_options = extract_ssl_options_as_list(Map) }. @@ -447,6 +451,7 @@ map_to_oauth_provider(Map) when is_map(Map) -> 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), jwks_uri = maps:get(?RESPONSE_JWKS_URI, Map, undefined) }; @@ -455,6 +460,7 @@ map_to_oauth_provider(PropList) when is_list(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, []))) }. diff --git a/deps/oauth2_client/test/system_SUITE.erl b/deps/oauth2_client/test/system_SUITE.erl index c59392de0519..1be0acc72815 100644 --- a/deps/oauth2_client/test/system_SUITE.erl +++ b/deps/oauth2_client/test/system_SUITE.erl @@ -100,6 +100,7 @@ {issuer, build_issuer("http") }, {authorization_endpoint, <<"http://localhost:8000/authorize">>}, {token_endpoint, build_token_endpoint_uri("http")}, + {end_session_endpoint, <<"http://localhost:8000/logout">>}, {jwks_uri, build_jwks_uri("http")} ]} ] @@ -117,6 +118,7 @@ {issuer, build_issuer("https") }, {authorization_endpoint, <<"https://localhost:8000/authorize">>}, {token_endpoint, build_token_endpoint_uri("https")}, + {end_session_endpoint, <<"http://localhost:8000/logout">>}, {jwks_uri, build_jwks_uri("https")} ]} ] @@ -166,8 +168,7 @@ groups() -> denies_access_token, auth_server_error, non_json_payload, - grants_refresh_token, - grants_access_token_using_oauth_provider_id + grants_refresh_token ]}, {verify_get_oauth_provider, [], [ get_oauth_provider, @@ -262,6 +263,8 @@ configure_all_oauth_provider_settings(Config) -> application:set_env(rabbitmq_auth_backend_oauth2, issuer, OAuthProvider#oauth_provider.issuer), application:set_env(rabbitmq_auth_backend_oauth2, oauth_providers, OAuthProviders), application:set_env(rabbitmq_auth_backend_oauth2, token_endpoint, OAuthProvider#oauth_provider.token_endpoint), + application:set_env(rabbitmq_auth_backend_oauth2, end_sessione_endpoint, OAuthProvider#oauth_provider.end_session_endpoint), + application:set_env(rabbitmq_auth_backend_oauth2, authorization_endpoint, OAuthProvider#oauth_provider.authorization_endpoint), KeyConfig = [ { jwks_url, OAuthProvider#oauth_provider.jwks_uri } ] ++ case OAuthProvider#oauth_provider.ssl_options of undefined -> @@ -313,6 +316,8 @@ end_per_testcase(_, Config) -> application:unset_env(rabbitmq_auth_backend_oauth2, oauth_providers), application:unset_env(rabbitmq_auth_backend_oauth2, issuer), application:unset_env(rabbitmq_auth_backend_oauth2, token_endpoint), + application:unset_env(rabbitmq_auth_backend_oauth2, authorization_endpoint), + application:unset_env(rabbitmq_auth_backend_oauth2, end_session_endpoint), application:unset_env(rabbitmq_auth_backend_oauth2, key_config), case ?config(group, Config) of http_up -> @@ -340,15 +345,6 @@ grants_access_token_dynamically_resolving_oauth_provider(Config) -> ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). -grants_access_token_using_oauth_provider_id(Config) -> - #{request := #{parameters := Parameters}, - response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } = lookup_expectation(token_endpoint, Config), - - {ok, #successful_access_token_response{access_token = AccessToken, token_type = TokenType} } = - oauth2_client:get_access_token(?config(oauth_provider_id, Config), build_access_token_request(Parameters)), - ?assertEqual(proplists:get_value(token_type, JsonPayload), TokenType), - ?assertEqual(proplists:get_value(access_token, JsonPayload), AccessToken). - grants_access_token(Config) -> #{request := #{parameters := Parameters}, response := [ {code, 200}, {content_type, _CT}, {payload, JsonPayload}] } @@ -417,11 +413,19 @@ get_oauth_provider_given_oauth_provider_id(Config) -> = lookup_expectation(get_openid_configuration, Config), ct:log("get_oauth_provider ~p", [?config(oauth_provider_id, Config)]), - {ok, #oauth_provider{issuer = Issuer, token_endpoint = TokenEndPoint, jwks_uri = Jwks_uri}} = - oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), [issuer, token_endpoint, jwks_uri]), + {ok, #oauth_provider{ + issuer = Issuer, + token_endpoint = TokenEndPoint, + authorization_endpoint = AuthorizationEndpoint, + end_session_endpoint = EndSessionEndpoint, + jwks_uri = Jwks_uri}} = + oauth2_client:get_oauth_provider(?config(oauth_provider_id, Config), + [issuer, token_endpoint, jwks_uri, authorization_endpoint, end_session_endpoint]), ?assertEqual(proplists:get_value(issuer, JsonPayload), Issuer), ?assertEqual(proplists:get_value(token_endpoint, JsonPayload), TokenEndPoint), + ?assertEqual(proplists:get_value(authorization_endpoint, JsonPayload), AuthorizationEndpoint), + ?assertEqual(proplists:get_value(end_session_endpoint, JsonPayload), EndSessionEndpoint), ?assertEqual(proplists:get_value(jwks_uri, JsonPayload), Jwks_uri). 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 98f22695fea8..c53c5d162b80 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 @@ -163,6 +163,16 @@ "rabbitmq_auth_backend_oauth2.key_config.jwks_url", [{datatype, string}, {validators, ["uri", "https_uri"]}]}. +{mapping, + "auth_oauth2.end_session_endpoint", + "rabbitmq_auth_backend_oauth2.end_session_endpoint", + [{datatype, string}, {validators, ["uri", "https_uri"]}]}. + +{mapping, + "auth_oauth2.authorization_endpoint", + "rabbitmq_auth_backend_oauth2.authorization_endpoint", + [{datatype, string}, {validators, ["uri", "https_uri"]}]}. + {mapping, "auth_oauth2.https.peer_verification", "rabbitmq_auth_backend_oauth2.key_config.peer_verification", @@ -240,6 +250,16 @@ [{datatype, string}, {validators, ["uri", "https_uri"]}] }. +{mapping, + "auth_oauth2.oauth_providers.$name.end_session_endpoint", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}, {validators, ["uri", "https_uri"]}]}. + +{mapping, + "auth_oauth2.oauth_providers.$name.authorization_endpoint", + "rabbitmq_auth_backend_oauth2.oauth_providers", + [{datatype, string}, {validators, ["uri", "https_uri"]}]}. + {mapping, "auth_oauth2.oauth_providers.$name.https.verify", "rabbitmq_auth_backend_oauth2.oauth_providers", 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 790f9d605d76..3d93e06d4d42 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 @@ -129,6 +129,8 @@ auth_oauth2.oauth_providers.uaa.issuer = https://uaa auth_oauth2.oauth_providers.keycloak.token_endpoint = https://keycloak/token auth_oauth2.oauth_providers.keycloak.jwks_uri = https://keycloak/keys + auth_oauth2.oauth_providers.keycloak.authorization_endpoint = https://keycloak/authorize + auth_oauth2.oauth_providers.keycloak.end_session_endpoint = https://keycloak/logout auth_oauth2.oauth_providers.keycloak.https.cacertfile = /mnt/certs/ca_certificate.pem auth_oauth2.oauth_providers.keycloak.https.verify = verify_none", [ @@ -149,6 +151,8 @@ {verify, verify_none}, {cacertfile, "/mnt/certs/ca_certificate.pem"} ]}, + {end_session_endpoint, <<"https://keycloak/logout">>}, + {authorization_endpoint, <<"https://keycloak/authorize">>}, {token_endpoint, <<"https://keycloak/token">>}, {jwks_uri, <<"https://keycloak/keys">>} ] diff --git a/deps/rabbitmq_management/Makefile b/deps/rabbitmq_management/Makefile index 59d5c1e66e3f..98998bfcdb48 100644 --- a/deps/rabbitmq_management/Makefile +++ b/deps/rabbitmq_management/Makefile @@ -22,7 +22,7 @@ define PROJECT_APP_EXTRA_KEYS endef DEPS = rabbit_common rabbit amqp_client cowboy cowlib rabbitmq_web_dispatch rabbitmq_management_agent oauth2_client -TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers proper amqp10_client LOCAL_DEPS += ranch ssl crypto public_key # FIXME: Add Ranch as a BUILD_DEPS to be sure the correct version is picked. 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 cd364022f97b..0439677a5a92 100644 --- a/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js +++ b/deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js @@ -103,6 +103,11 @@ function oauth_initialize_user_manager(resource_server) { 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; } diff --git a/deps/rabbitmq_management/selenium/package.json b/deps/rabbitmq_management/selenium/package.json index f0beecf5cfc2..051683abff60 100644 --- a/deps/rabbitmq_management/selenium/package.json +++ b/deps/rabbitmq_management/selenium/package.json @@ -4,7 +4,6 @@ "description": "", "main": "index.js", "scripts": { - "test": "mocha --recursive --trace-warnings --timeout 45000", "fakeportal": "node fakeportal/app.js", "fakeproxy": "node fakeportal/proxy.js", "amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest", diff --git a/deps/rabbitmq_management/selenium/test/basic-auth/env.docker.proxy b/deps/rabbitmq_management/selenium/test/basic-auth/env.docker.proxy index bc2b13861c11..91a1d12cdc21 100644 --- a/deps/rabbitmq_management/selenium/test/basic-auth/env.docker.proxy +++ b/deps/rabbitmq_management/selenium/test/basic-auth/env.docker.proxy @@ -1,2 +1,3 @@ export PUBLIC_RABBITMQ_HOST=proxy:9090 export RABBITMQ_HOST_FOR_PROXY=rabbitmq:15672 +export SELENIUM_INTERACTION_DELAY=250 \ No newline at end of file diff --git a/deps/rabbitmq_management/selenium/test/basic-auth/env.local.proxy b/deps/rabbitmq_management/selenium/test/basic-auth/env.local.proxy index f4cba30edfe3..91875b4c5611 100644 --- a/deps/rabbitmq_management/selenium/test/basic-auth/env.local.proxy +++ b/deps/rabbitmq_management/selenium/test/basic-auth/env.local.proxy @@ -1,2 +1,3 @@ export PUBLIC_RABBITMQ_HOST=localhost:9090 export RABBITMQ_HOST_FOR_PROXY=host.docker.internal:15672 +export SELENIUM_INTERACTION_DELAY=250 \ No newline at end of file diff --git a/deps/rabbitmq_management/selenium/test/pageobjects/BasePage.js b/deps/rabbitmq_management/selenium/test/pageobjects/BasePage.js index d8b620931033..f72f1c9f9b25 100644 --- a/deps/rabbitmq_management/selenium/test/pageobjects/BasePage.js +++ b/deps/rabbitmq_management/selenium/test/pageobjects/BasePage.js @@ -17,11 +17,14 @@ module.exports = class BasePage { driver timeout polling + interactionDelay constructor (webdriver) { this.driver = webdriver this.timeout = parseInt(process.env.SELENIUM_TIMEOUT) || 1000 // max time waiting to locate an element. Should be less that test timeout this.polling = parseInt(process.env.SELENIUM_POLLING) || 500 // how frequent selenium searches for an element + this.interactionDelay = parseInt(process.env.SELENIUM_INTERACTION_DELAY) || 0 // slow down interactions (when rabbit is behind a http proxy) + console.log("Interaction Delay : " + this.interactionDelay) } @@ -79,10 +82,11 @@ module.exports = class BasePage { return this.click(QUEUES_AND_STREAMS_TAB) } async waitForQueuesTab() { + await this.driver.sleep(250) return this.waitForDisplayed(QUEUES_AND_STREAMS_TAB) } - async clickOnStreamTab () { + async clickOnStreamTab () { return this.click(STREAM_CONNECTIONS_TAB) } async waitForStreamConnectionsTab() { @@ -153,8 +157,8 @@ module.exports = class BasePage { async isDisplayed(locator) { try { element = await driver.findElement(locator) - console.log("element:"+element) - return this.driver.wait(until.elementIsVisible(element), this.timeout / 2, + + return this.driver.wait(until.elementIsVisible(element), this.timeout, 'Timed out after [timeout=' + this.timeout + ';polling=' + this.polling + '] awaiting till visible ' + element, this.polling / 2) }catch(error) { @@ -186,7 +190,13 @@ module.exports = class BasePage { async waitForDisplayed (locator) { - return this.waitForVisible(await this.waitForLocated(locator)) + if (this.interactionDelay && this.interactionDelay > 0) await this.driver.sleep(this.interactionDelay) + try { + return this.waitForVisible(await this.waitForLocated(locator)) + }catch(error) { + console.error("Failed to waitForDisplayed for locator " + locator) + throw error + } } async getText (locator) { @@ -200,6 +210,8 @@ module.exports = class BasePage { } async click (locator) { + if (this.interactionDelay) await this.driver.sleep(this.interactionDelay) + const element = await this.waitForDisplayed(locator) try { return element.click() diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl index 44ae7b0189b6..1e7c9744a3c8 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl @@ -9,7 +9,7 @@ -export([init/2, to_json/2, content_types_provided/2, is_authorized/2]). -export([variances/2]). --export([authSettings/0,resolve_oauth_provider_url/1,resolve_oauth_provider_url/3]). %% for testing only +-export([authSettings/0]). %% for testing only -include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). -include_lib("rabbit_common/include/rabbit.hrl"). @@ -26,58 +26,52 @@ variances(Req, Context) -> content_types_provided(ReqData, Context) -> {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. -resolve_oauth_provider_url(ManagementProps) -> - case proplists:get_value(oauth_provider_url, ManagementProps) of - undefined -> - case oauth2_client:get_oauth_provider([issuer]) of - {ok, OAuthProvider} -> OAuthProvider#oauth_provider.issuer; - {error, _} = Error -> Error - end; - Value -> Value +merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, ManagementProps) -> + OAuthProviderResult = case proplists:get_value(oauth_provider_id, OAuthResourceServer) of + undefined -> oauth2_client:get_oauth_provider([issuer]); + OauthProviderId -> oauth2_client:get_oauth_provider(OauthProviderId, [issuer]) + end, + OAuthProviderInfo0 = case OAuthProviderResult of + {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), + 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 + undefined -> Map0; + V -> maps:put(end_session_endpoint, V, Map0) end. -resolve_oauth_provider_url(OAuthResourceServer, MgtResourceServer, ManagementProps) -> - case proplists:get_value(oauth_provider_url, MgtResourceServer) of - undefined -> - case proplists:get_value(oauth_provider_url, ManagementProps) of - undefined -> - OAuthProviderResult = case proplists:get_value(oauth_provider_id, OAuthResourceServer) of - undefined -> oauth2_client:get_oauth_provider([issuer]); - OauthProviderId -> oauth2_client:get_oauth_provider(OauthProviderId, [issuer]) - end, - case OAuthProviderResult of - {ok, OAuthProvider} -> OAuthProvider#oauth_provider.issuer; - {error, _} = Error -> Error - end; - MgtUrl -> MgtUrl - end; - ResourceMgtUrl -> ResourceMgtUrl - end. - - -skip_unknown_resource_servers(MgtOauthResources, OAuth2Resources) -> +skip_unknown_mgt_resource_servers(MgtOauthResources, OAuth2Resources) -> maps:filter(fun(Key, _Value) -> maps:is_key(Key, OAuth2Resources) end, MgtOauthResources). skip_disabled_mgt_resource_servers(MgtOauthResources) -> maps:filter(fun(_Key, Value) -> not proplists:get_value(disabled, Value, false) end, MgtOauthResources). extract_oauth2_and_mgt_resources(OAuth2BackendProps, ManagementProps) -> OAuth2Resources = getAllDeclaredOauth2Resources(OAuth2BackendProps), - MgtResources0 = skip_unknown_resource_servers(proplists:get_value(oauth_resource_servers, ManagementProps, #{}), OAuth2Resources), + MgtResources0 = skip_unknown_mgt_resource_servers(proplists:get_value(oauth_resource_servers, + ManagementProps, #{}), OAuth2Resources), MgtResources1 = maps:merge(MgtResources0, maps:filtermap(fun(K,_V) -> case maps:is_key(K, MgtResources0) of true -> false; false -> {true, [{id, K}]} end end, OAuth2Resources)), - MgtResources = skip_disabled_mgt_resource_servers(MgtResources1), - HasMulti = {true, OAuth2Resources, MgtResources}, - case maps:size(MgtResources) of - 0 -> - case maps:size(OAuth2Resources) of - 1 -> {}; - _ -> HasMulti - end; - _ -> HasMulti + 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 + 0 -> {}; + _ -> {MgtResources} end. + getAllDeclaredOauth2Resources(OAuth2BackendProps) -> OAuth2Resources = proplists:get_value(resource_servers, OAuth2BackendProps, #{}), case proplists:get_value(resource_server_id, OAuth2BackendProps) of @@ -93,48 +87,35 @@ authSettings() -> false -> [{oauth_enabled, false}]; true -> case extract_oauth2_and_mgt_resources(OAuth2BackendProps, ManagementProps) of - {true, OAuth2Resources, MgtResources} -> - produce_auth_settings(OAuth2Resources, MgtResources, ManagementProps); + {MgtResources} -> produce_auth_settings(MgtResources, ManagementProps); {} -> [{oauth_enabled, false}] end end. -skip_resource_servers_without_oauth_client_id_with_sp_initiated_logon(MgtResourceServers, ManagementProps) -> +skip_mgt_resource_servers_without_oauth_client_id_with_sp_initiated_logon(MgtResourceServers, ManagementProps) -> DefaultOauthInitiatedLogonType = proplists:get_value(oauth_initiated_logon_type, ManagementProps, sp_initiated), maps:filter(fun(_K,ResourceServer) -> - SpInitiated = case proplists:get_value(oauth_initiated_logon_type, ResourceServer, DefaultOauthInitiatedLogonType) of + SpInitiated = case maps:get(oauth_initiated_logon_type, ResourceServer, DefaultOauthInitiatedLogonType) of sp_initiated -> true; _ -> false end, not SpInitiated or - not is_invalid([proplists:get_value(oauth_client_id, ResourceServer)]) end, MgtResourceServers). + not is_invalid([maps:get(oauth_client_id, ResourceServer, undefined)]) end, MgtResourceServers). -filter_resource_servers_without_resolvable_oauth_client_id_for_sp_initiated(MgtResourceServers, ManagementProps) -> +filter_mgt_resource_servers_without_oauth_client_id_for_sp_initiated(MgtResourceServers, ManagementProps) -> case is_invalid([proplists:get_value(oauth_client_id, ManagementProps)]) of - true -> skip_resource_servers_without_oauth_client_id_with_sp_initiated_logon(MgtResourceServers, ManagementProps); + true -> skip_mgt_resource_servers_without_oauth_client_id_with_sp_initiated_logon(MgtResourceServers, ManagementProps); false -> MgtResourceServers end. -filter_resource_servers_without_resolvable_oauth_provider_url(OAuthResourceServers, MgtResourceServers, ManagementProps) -> - maps:filter(fun(_K1,V1) -> proplists:is_defined(oauth_provider_url, V1) end, maps:map(fun(K,V) -> - case proplists:is_defined(oauth_provider_url, V) of - true -> V; - false -> - case maps:get(K, OAuthResourceServers) of - {badkey, _} -> V; - OAuthResourceServer -> - case resolve_oauth_provider_url(OAuthResourceServer, V, ManagementProps) of - {error, _} -> V; - Url -> [ {oauth_provider_url, Url} | V ] - end - end - end end , MgtResourceServers)). +filter_mgt_resource_servers_without_oauth_provider_url(MgtResourceServers) -> + maps:filter(fun(_K1,V1) -> maps:is_key(oauth_provider_url, V1) end, MgtResourceServers). -produce_auth_settings(OAuthResourceServers, MgtResourceServers, ManagementProps) -> - ConvertValuesToBinary = fun(_K,V) -> [ {K1, to_binary(V1)} || {K1,V1} <- V ] end, - FilteredMgtResourceServers = filter_resource_servers_without_resolvable_oauth_provider_url(OAuthResourceServers, - filter_resource_servers_without_resolvable_oauth_client_id_for_sp_initiated(MgtResourceServers, ManagementProps), ManagementProps), +produce_auth_settings(MgtResourceServers, ManagementProps) -> + ConvertValuesToBinary = fun(_K,V) -> [ {K1, to_binary(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)), case maps:size(FilteredMgtResourceServers) of 0 -> [{oauth_enabled, false}]; @@ -150,7 +131,7 @@ produce_auth_settings(OAuthResourceServers, MgtResourceServers, ManagementProps) sp_initiated -> {}; idp_initiated -> {oauth_initiated_logon_type, <<"idp_initiated">>} end - ]) + ]) end. filter_empty_properties(ListOfProperties) -> 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 2e002725d695..d47350d2b926 100644 --- a/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl +++ b/deps/rabbitmq_management/test/rabbit_mgmt_wm_auth_SUITE.erl @@ -19,6 +19,8 @@ all() -> {group, verify_client_id_and_secret}, {group, verify_mgt_oauth_provider_url_with_single_resource}, {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_oauth_initiated_logon_type_for_sp_initiated}, {group, verify_oauth_initiated_logon_type_for_idp_initiated}, {group, verify_oauth_disable_basic_auth}, @@ -96,6 +98,70 @@ groups() -> ]} ]} ]}, + {verify_end_session_endpoint_with_single_resource, [], [ + {with_resource_server_id_rabbit, [], [ + {with_root_issuer_url1, [], [ + {with_oauth_enabled, [], [ + {with_mgt_oauth_client_id_z, [], [ + should_not_return_end_session_endpoint, + {with_root_end_session_endpoint_0, [], [ + should_return_end_session_endpoint_0 + ]} + ]} + ]} + ]}, + {with_oauth_providers_idp1_idp2, [], [ + {with_default_oauth_provider_idp1, [], [ + {with_oauth_enabled, [], [ + {with_mgt_oauth_client_id_z, [], [ + should_not_return_end_session_endpoint, + {with_end_session_endpoint_for_idp1_1, [], [ + should_return_end_session_endpoint_1 + ]}, + {with_root_end_session_endpoint_0, [], [ + should_not_return_end_session_endpoint, + {with_end_session_endpoint_for_idp1_1, [], [ + should_return_end_session_endpoint_1 + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]}, + {verify_end_session_endpoint_with_single_resource_and_another_resource, [], [ + {with_resource_server_id_rabbit, [], [ + {with_resource_server_a, [], [ + {with_root_issuer_url1, [], [ + {with_oauth_enabled, [], [ + should_return_disabled_auth_settings, + {with_mgt_oauth_client_id_z, [], [ + should_not_return_end_session_endpoint, + 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 + ]}, + {with_oauth_providers_idp1_idp2, [], [ + {with_default_oauth_provider_idp1, [], [ + {with_end_session_endpoint_for_idp1_1, [], [ + should_return_end_session_endpoint_1, + should_return_oauth_resource_server_a_with_end_session_endpoint_1, + {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 + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]} + ]}, {verify_mgt_oauth_provider_url_with_single_resource_and_another_resource, [], [ {with_resource_server_id_rabbit, [], [ {with_resource_server_a, [], [ @@ -237,6 +303,9 @@ init_per_suite(Config) -> {idp3_url, <<"https://idp3">>}, {url0, <<"https://url0">>}, {url1, <<"https://url1">>}, + {logout_url_0, <<"https://logout_0">>}, + {logout_url_1, <<"https://logout_1">>}, + {logout_url_2, <<"https://logout_2">>}, {a, <<"a">>}, {b, <<"b">>}, {q, <<"q">>}, @@ -329,6 +398,23 @@ init_per_group(with_default_oauth_provider_idp1, Config) -> init_per_group(with_default_oauth_provider_idp3, Config) -> application: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)), + 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, + ?config(idp1, Config), end_session_endpoint, ?config(logout_url_1, Config)), + Config; +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(_, Config) -> Config. @@ -409,6 +495,22 @@ end_per_group(with_default_oauth_provider_idp1, Config) -> end_per_group(with_default_oauth_provider_idp3, Config) -> application: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), + 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, + ?config(idp1, Config), end_session_endpoint), + Config; +end_per_group(with_end_session_endpoint_for_idp2_2, Config) -> + remove_attribute_from_entry_from_env_variable(rabbitmq_auth_backend_oauth2, oauth_providers, + ?config(idp2, Config), end_session_endpoint), + Config; +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(_, Config) -> Config. @@ -534,7 +636,33 @@ should_return_oauth_client_id_z(Config) -> Actual = rabbit_mgmt_wm_auth: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(), + Config, rabbit, end_session_endpoint). + +should_return_end_session_endpoint_0(Config) -> + assertEqual_on_attribute_for_oauth_resource_server(rabbit_mgmt_wm_auth: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(), + 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(), + 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(), + 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(), + 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(), + Config, a, end_session_endpoint, ?config(logout_url_2, Config)). %% ------------------------------------------------------------------- %% Utility/helper functions @@ -584,7 +712,9 @@ assert_not_defined_oauth_resource_server(Actual, Config, ConfigKey) -> set_attribute_in_entry_for_env_variable(Application, EnvVar, Key, Attribute, Value) -> Map = application:get_env(Application, EnvVar, #{}), + 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). log(AuthSettings) ->