Skip to content

Commit

Permalink
Merge pull request #11218 from rabbitmq/configure-oidc-endpoints
Browse files Browse the repository at this point in the history
Configure OIDC end_session_endpoint
  • Loading branch information
michaelklishin authored Jun 3, 2024
2 parents bd111f0 + 341f3da commit 6c86442
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 103 deletions.
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

0 comments on commit 6c86442

Please sign in to comment.