Skip to content

Configure OIDC end_session_endpoint #11218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deps/oauth2_client/include/oauth2_client.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -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">>).

Expand All @@ -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())
}).
Expand Down
46 changes: 27 additions & 19 deletions deps/oauth2_client/src/oauth2_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +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").
-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]),
Expand Down Expand Up @@ -102,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
Expand All @@ -125,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.
Expand Down Expand Up @@ -283,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)
}.

Expand Down Expand Up @@ -445,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)
};

Expand All @@ -453,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, [])))
}.
Expand Down
30 changes: 17 additions & 13 deletions deps/oauth2_client/test/system_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
]}
]
Expand All @@ -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")}
]}
]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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 ->
Expand Down Expand 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}] }
Expand Down Expand Up @@ -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).


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand All @@ -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">>}
]
Expand Down
2 changes: 1 addition & 1 deletion deps/rabbitmq_management/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion deps/rabbitmq_management/selenium/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export PUBLIC_RABBITMQ_HOST=proxy:9090
export RABBITMQ_HOST_FOR_PROXY=rabbitmq:15672
export SELENIUM_INTERACTION_DELAY=250
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export PUBLIC_RABBITMQ_HOST=localhost:9090
export RABBITMQ_HOST_FOR_PROXY=host.docker.internal:15672
export SELENIUM_INTERACTION_DELAY=250
20 changes: 16 additions & 4 deletions deps/rabbitmq_management/selenium/test/pageobjects/BasePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}


Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
Loading
Loading