diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_login.erl b/deps/rabbitmq_management/src/rabbit_mgmt_login.erl
index 22b3aeff9631..4423565c12e5 100644
--- a/deps/rabbitmq_management/src/rabbit_mgmt_login.erl
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_login.erl
@@ -11,43 +11,138 @@
-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
-include("rabbit_mgmt.hrl").
+-include_lib("kernel/include/logger.hrl").
%%--------------------------------------------------------------------
+%% /api/login endpoint
+%%
+%% Scenario 1 : Users come to RabbitMQ management UI with an access token
+%% carried in the body's (POST) field called <<"access_token">>.
+%% Only for POST method.
+%%
+%% The endpoint redirects the user to the home page of the management UI with a
+%% short-lived cookie with the name <<"access_token">> and targed to the resource/path
+%% <<"js/oidc-oauth/bootstrap.js">> if the token is valid and the user is authorized
+%% to access the management UI.
+%%
+%% Scenario 2: Users come to RabbitMQ management UI with one of these fields
+%% <<"strict_auth_mechanism">> or <<"preferred_auth_mechanism">> defined via
+%% of these methods:
+%% - In the body payload
+%% - As request parameter (Only available for GET method)
+%% - As request header with the prefix <<"x-">>
+%%
+%% The endpoint redirects the user to the home page of the management UI with a
+%% short-lived cookie with the name matching either <<"strict_auth_mechanism">>
+%% or <<"preferred_auth_mechanism">> (regardless if the value was set as form
+%% field, or request parameter or header) and targeted to the resource/path
+%% <<"js/oidc-oauth/bootstrap.js">>.
+%%
+%% NOTE: The short-lived token is removed once it is read by the module
+%% rabbit_mgmt_oauth_bootstrap.erl which attends the resource/path
+%% <<"js/oidc-oauth/bootstrap.js">>.
init(Req0, State) ->
login(cowboy_req:method(Req0), Req0, State).
-login(<<"POST">>, Req0=#{scheme := Scheme}, State) ->
+login(<<"POST">>, Req0, State) ->
{ok, Body, _} = cowboy_req:read_urlencoded_body(Req0),
- AccessToken = proplists:get_value(<<"access_token">>, Body),
- case rabbit_mgmt_util:is_authorized_user(Req0, #context{}, <<"">>, AccessToken, false) of
- {true, Req1, _} ->
- CookieSettings = #{
- http_only => true,
- path => ?OAUTH2_ACCESS_TOKEN_COOKIE_PATH,
- max_age => 30,
- same_site => strict
- },
- SetCookie = cowboy_req:set_resp_cookie(?OAUTH2_ACCESS_TOKEN_COOKIE_NAME, AccessToken, Req1,
- case Scheme of
- <<"https">> -> CookieSettings#{ secure => true};
- _ -> CookieSettings
- end),
- Home = cowboy_req:uri(SetCookie, #{
- path => rabbit_mgmt_util:get_path_prefix() ++ "/"
- }),
- Redirect = cowboy_req:reply(302, #{
- <<"Location">> => iolist_to_binary(Home)
- }, <<>>, SetCookie),
- {ok, Redirect, State};
- {false, ReqData1, Reason} ->
- replyWithError(Reason, ReqData1, State)
+ case proplists:get_value(?OAUTH2_ACCESS_TOKEN, Body) of
+ undefined -> handleStrictOrPreferredAuthMechanism(Req0, Body, State);
+ AccessToken -> handleAccessToken(Req0, AccessToken, State)
+ end;
+
+login(<<"GET">>, Req, State) ->
+ Auth = case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Req) of
+ undefined ->
+ case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Req) of
+ undefined -> undefined;
+ Val -> validate_auth_mechanism({?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Val})
+ end;
+ Val -> validate_auth_mechanism({?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Val})
+ end,
+ case Auth of
+ undefined ->
+ case rabbit_mgmt_util:qs_val(?OAUTH2_ACCESS_TOKEN, Req) of
+ undefined ->
+ {ok, cowboy_req:reply(302, #{<<"Location">> => iolist_to_binary(get_home_uri(Req))},
+ <<>>, Req), State};
+ _ -> {ok, cowboy_req:reply(405, Req), State}
+ end;
+ {Type, Value} ->
+ redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH, Type, Value, Req, State)
end;
login(_, Req0, State) ->
- %% Method not allowed.
{ok, cowboy_req:reply(405, Req0), State}.
+handleStrictOrPreferredAuthMechanism(Req, Body, State) ->
+ case validate_auth_mechanism(get_auth_mechanism(Req, Body)) of
+ undefined ->
+ {ok, cowboy_req:reply(302, #{<<"Location">> => iolist_to_binary(get_home_uri(Req))},
+ <<>>, Req), State};
+ {Type, Value} ->
+ redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH, Type, Value, Req, State)
+ end.
+
+get_auth_mechanism(Req, Body) ->
+ case get_param_or_header(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Req, Body) of
+ undefined ->
+ case get_param_or_header(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Req, Body) of
+ undefined -> undefined;
+ Val -> {?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Val}
+ end;
+ Val -> {?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Val}
+ end.
+
+validate_auth_mechanism({_, <<"oauth2:", _Id/binary>>} = Auth) -> Auth;
+validate_auth_mechanism({_, <<"basic">>} = Auth) -> Auth;
+validate_auth_mechanism({_, _}) -> undefined;
+validate_auth_mechanism(_) -> undefined.
+
+get_param_or_header(ParamName, Req, Body) ->
+ case proplists:get_value(ParamName, Body) of
+ undefined ->
+ case rabbit_mgmt_util:qs_val(ParamName, Req) of
+ undefined -> cowboy_req:header(<<"x-", ParamName/binary>>, Req);
+ Val -> Val
+ end;
+ Val -> Val
+ end.
+
+handleAccessToken(Req0, AccessToken, State) ->
+ case rabbit_mgmt_util:is_authorized_user(Req0, #context{}, <<"">>, AccessToken, false) of
+ {true, Req1, _} ->
+ redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH,
+ ?OAUTH2_ACCESS_TOKEN,
+ AccessToken,
+ Req1, State);
+ {false, ReqData1, Reason} ->
+ replyWithError(Reason, ReqData1, State)
+ end.
+
+redirect_to_home_with_cookie(CookiePath, CookieName, CookieValue, Req=#{scheme := Scheme}, State) ->
+ CookieSettings0 = #{
+ http_only => true,
+ path => CookiePath,
+ max_age => 30,
+ same_site => strict
+ },
+ CookieSettings =
+ case Scheme of
+ <<"https">> -> CookieSettings0#{secure => true};
+ _ -> CookieSettings0
+ end,
+ SetCookie = cowboy_req:set_resp_cookie(CookieName, CookieValue, Req, CookieSettings),
+ Redirect = cowboy_req:reply(302, #{
+ <<"Location">> => iolist_to_binary(get_home_uri(SetCookie))
+ }, <<>>, SetCookie),
+ {ok, Redirect, State}.
+
+get_home_uri(Req0) ->
+ cowboy_req:uri(Req0, #{path => rabbit_mgmt_util:get_path_prefix() ++ "/", qs => undefined}).
+
+
replyWithError(Reason, Req, State) ->
Home = cowboy_req:uri(Req, #{
path => rabbit_mgmt_util:get_path_prefix() ++ "/",
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl b/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl
index e74d6530433b..d9c51a93b33d 100644
--- a/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_oauth_bootstrap.erl
@@ -9,25 +9,97 @@
-export([init/2]).
-include("rabbit_mgmt.hrl").
+-include_lib("kernel/include/logger.hrl").
%%--------------------------------------------------------------------
+%% js/oidc-oauth/bootstrap.js
+%% It produces a javascript file with all the oauth2 configuration needed
+%% in the client-side of the management ui.
+%% This endpoint only accepts GET method.
+%%
+%% It can work in conjunction with the /api/login endpoint. If the users are
+%% redirected to the home page of the management ui, and eventually to this endpoint,
+%% via the /api/login endpoint is very likely that the request carries a cookie.
+%% It can be the <<"access_token">> cookie or the cookies <<"strict_auth_mechanism">>
+%% or <<"preferred_auth_mechanism">>.
+%% These cookies are consumed by this endpoint and removed afterwards.
+%%
+%% Additionally, this endpoint may accept users' authentication mechanism preferences
+%% via its corresponding header, in addition to the two cookies mentioned above.
+%% But not via request parameters. If this endpoint would have accepted request parameters,
+%% it would have to use the "Referer" header to extract the original request parameters.
+%% It is possible that in some environments, these headers may be dropped before they reach this endpoint.
+%% Therefore, users who can only use request parameters, they have to use the /api/login
+%% endpoint instead.
init(Req0, State) ->
bootstrap_oauth(rabbit_mgmt_headers:set_no_cache_headers(
rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE), ?MODULE), State).
bootstrap_oauth(Req0, State) ->
- AuthSettings = rabbit_mgmt_wm_auth:authSettings(),
+ AuthSettings0 = rabbit_mgmt_wm_auth:authSettings(),
+ {Req1, AuthSettings} = enrich_oauth_settings(Req0, AuthSettings0),
Dependencies = oauth_dependencies(),
- {Req1, SetTokenAuth} = set_token_auth(AuthSettings, Req0),
+ {Req2, SetTokenAuth} = set_token_auth(AuthSettings, Req1),
JSContent = import_dependencies(Dependencies) ++
set_oauth_settings(AuthSettings) ++
SetTokenAuth ++
export_dependencies(Dependencies),
{ok, cowboy_req:reply(200, #{<<"content-type">> => <<"text/javascript; charset=utf-8">>},
- JSContent, Req1), State}.
+ JSContent, Req2), State}.
+enrich_oauth_settings(Req0, AuthSettings) ->
+ {Req1, Auth} = get_auth_mechanism(Req0),
+ ValidAuth = validate_auth_mechanism(Auth, AuthSettings),
+ case ValidAuth of
+ {preferred_auth_mechanism, Args} -> {Req1, [{preferred_auth_mechanism, Args} | AuthSettings]};
+ {strict_auth_mechanism, Args} -> {Req1, [{strict_auth_mechanism, Args} | AuthSettings]};
+ {error, Reason} -> ?LOG_DEBUG("~p", [Reason]),
+ {Req1, AuthSettings}
+ end.
+get_auth_mechanism(Req) ->
+ case get_auth_mechanism_from_cookies(Req) of
+ undefined ->
+ case cowboy_req:header(<<"x-", ?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM/binary>>, Req) of
+ undefined ->
+ case cowboy_req:header(<<"x-", ?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM/binary>>, Req) of
+ undefined -> {Req, undefined};
+ Val -> {Req, {preferred_auth_mechanism, Val}}
+ end;
+ Val -> {Req, {strict_auth_mechanism, Val}}
+ end;
+ {Type, _} = Auth -> { cowboy_req:set_resp_cookie(term_to_binary(Type),
+ <<"">>, Req, #{
+ max_age => 0,
+ http_only => true,
+ path => ?OAUTH2_BOOTSTRAP_PATH,
+ same_site => strict
+ }),
+ Auth
+ }
+ end.
+
+get_auth_mechanism_from_cookies(Req) ->
+ Cookies = cowboy_req:parse_cookies(Req),
+ case proplists:get_value(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Cookies) of
+ undefined ->
+ case proplists:get_value(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Cookies) of
+ undefined -> undefined;
+ Val -> {preferred_auth_mechanism, Val}
+ end;
+ Val -> {strict_auth_mechanism, Val}
+ end.
+validate_auth_mechanism({Type, <<"oauth2:", Id/binary>>}, AuthSettings) ->
+ case maps:is_key(Id, proplists:get_value(oauth_resource_servers, AuthSettings)) of
+ true -> {Type, [{type, <<"oauth2">>}, {resource_id, Id}]};
+ _ -> {error, {unknown_resource_id, Id}}
+ end;
+validate_auth_mechanism({Type, <<"basic">>}, _AuthSettings) ->
+ {Type, [{type, <<"basic">>}]};
+validate_auth_mechanism({_, _}, _AuthSettings) -> {error, unknown_auth_mechanism};
+validate_auth_mechanism(_, _) -> {error, unknown_auth_mechanism}.
+
set_oauth_settings(AuthSettings) ->
JsonAuthSettings = rabbit_json:encode(rabbit_mgmt_format:format_nulls(AuthSettings)),
["set_oauth_settings(", JsonAuthSettings, ");"].
@@ -42,15 +114,15 @@ set_token_auth(AuthSettings, Req0) ->
["set_token_auth('", Token, "');"]
};
_ ->
- Cookies = cowboy_req:parse_cookies(Req0),
- case lists:keyfind(?OAUTH2_ACCESS_TOKEN_COOKIE_NAME, 1, Cookies) of
+ Cookies = cowboy_req:parse_cookies(Req0),
+ case lists:keyfind(?OAUTH2_ACCESS_TOKEN, 1, Cookies) of
{_, Token} ->
{
cowboy_req:set_resp_cookie(
- ?OAUTH2_ACCESS_TOKEN_COOKIE_NAME, <<"">>, Req0, #{
+ ?OAUTH2_ACCESS_TOKEN, <<"">>, Req0, #{
max_age => 0,
http_only => true,
- path => ?OAUTH2_ACCESS_TOKEN_COOKIE_PATH,
+ path => ?OAUTH2_BOOTSTRAP_PATH,
same_site => strict
}),
["set_token_auth('", Token, "');"]
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl
index bbcf62181b0d..92c2165aa22f 100644
--- a/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_schema.erl
@@ -7,6 +7,7 @@
-module(rabbit_mgmt_schema).
+-include_lib("kernel/include/logger.hrl").
-export([
translate_oauth_resource_servers/1,
@@ -14,12 +15,16 @@
]).
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) ->
+ %% Note: Conf must be reversed because cuttlefish_generator:transform_datatypes
+ %% reverse the order of the configuration from rabbitmq.conf.
+ %% Exactly on this line: `{[{Variable, NewValue}|Acc], ErrorAcc};`
+
Settings = cuttlefish_variable:filter_by_prefix(
- "management.oauth_resource_servers", Conf),
+ "management.oauth_resource_servers", lists:reverse(Conf)),
+
Map = merge_list_of_maps([
extract_resource_server_properties(Settings),
extract_resource_server_endpoint_params(oauth_authorization_endpoint_params, Settings),
@@ -48,14 +53,16 @@ convert_list_to_binary(V) when is_list(V) ->
convert_list_to_binary(V) ->
V.
-extract_resource_server_properties(Settings) ->
- KeyFun = fun extract_key_as_binary/1,
- ValueFun = fun extract_value/1,
-
+extract_resource_server_properties(Settings) ->
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).
-
+ lists:foldl(fun ({K, Value}, Acc) ->
+ Key = list_to_binary(K),
+ Attrs = case maps:get(Key, Acc, []) of
+ [] -> [] ++ [{index, maps:size(Acc)+1}, Value];
+ List -> List ++ [Value]
+ end,
+ maps:put(Key, Attrs, Acc) end, #{}, OAuthResourceServers).
extract_resource_server_endpoint_params(Variable, Settings) ->
KeyFun = fun extract_key_as_binary/1,
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
index 26ff40a319a8..49e1a13d6647 100644
--- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
@@ -13,6 +13,7 @@
-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
-include_lib("oauth2_client/include/oauth2_client.hrl").
+-include_lib("kernel/include/logger.hrl").
%%--------------------------------------------------------------------
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 0627b364e433..7250c8ffa5d0 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
@@ -703,27 +703,30 @@
{oauth_resource_servers,
#{
<<"3">> => [
- {oauth_provider_url, <<"http://three">>},
+ {index,3},
{oauth_initiated_logon_type, idp_initiated},
+ {oauth_provider_url, <<"http://three">>},
{id, <<"3">>}
],
<<"resource-one">> => [
{oauth_token_endpoint_params, [
{<<"param2">>, <<"value2">>}
]},
- {oauth_scopes, <<"openid profile rabbitmq.*">>},
- {oauth_client_id, <<"one">>},
- {label, <<"One">>},
+ {index,1},
+ {oauth_provider_url, <<"http://one:8080">>},
{id, <<"resource-one">>},
- {oauth_provider_url, <<"http://one:8080">>}
+ {label, <<"One">>},
+ {oauth_client_id, <<"one">>},
+ {oauth_scopes, <<"openid profile rabbitmq.*">>}
],
<<"resource-two">> => [
{oauth_authorization_endpoint_params, [
{<<"param1">>, <<"value1">>}
]},
- {oauth_client_id, <<"two">>},
+ {index,2},
+ {oauth_provider_url, <<"http://two">>},
{id, <<"resource-two">>},
- {oauth_provider_url, <<"http://two">>}
+ {oauth_client_id, <<"two">>}
]
}
}
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl
index 0a69ff6d7cda..39a034d2b617 100644
--- a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl
@@ -201,8 +201,6 @@ all_tests() -> [
rates_test,
single_active_consumer_cq_test,
single_active_consumer_qq_test,
- %% This test needs the OAuth 2 plugin to be enabled
- %% oauth_test,
disable_basic_auth_test,
login_test,
csp_headers_test,
@@ -4003,34 +4001,6 @@ stats_redirect_test(Config) ->
assert_permanent_redirect(Config, "doc/stats.html", "/api/index.html"),
passed.
-oauth_test(Config) ->
- ok = rabbit_ct_broker_helpers:enable_plugin(Config, 0, "rabbitmq_auth_backend_oauth2"),
-
- Map1 = http_get(Config, "/auth", ?OK),
- %% Defaults
- ?assertEqual(false, maps:get(oauth_enabled, Map1)),
-
- %% Misconfiguration
- rpc(Config, application, set_env, [rabbitmq_management, oauth_enabled, true]),
- Map2 = http_get(Config, "/auth", ?OK),
- ?assertEqual(false, maps:get(oauth_enabled, Map2)),
- ?assertEqual(<<>>, maps:get(oauth_client_id, Map2)),
- ?assertEqual(<<>>, maps:get(oauth_provider_url, Map2)),
- %% Valid config requires non empty OAuthClientId, OAuthClientSecret, OAuthResourceId, OAuthProviderUrl
- rpc(Config, application, set_env, [rabbitmq_management, oauth_client_id, "rabbit_user"]),
- rpc(Config, application, set_env, [rabbitmq_management, oauth_client_secret, "rabbit_secret"]),
- rpc(Config, application, set_env, [rabbitmq_management, oauth_provider_url, "http://localhost:8080/uaa"]),
- rpc(Config, application, set_env, [rabbitmq_auth_backend_oauth2, resource_server_id, "rabbitmq"]),
- Map3 = http_get(Config, "/auth", ?OK),
- println(Map3),
- ?assertEqual(true, maps:get(oauth_enabled, Map3)),
- ?assertEqual(<<"rabbit_user">>, maps:get(oauth_client_id, Map3)),
- ?assertEqual(<<"rabbit_secret">>, maps:get(oauth_client_secret, Map3)),
- ?assertEqual(<<"rabbitmq">>, maps:get(resource_server_id, Map3)),
- ?assertEqual(<<"http://localhost:8080/uaa">>, maps:get(oauth_provider_url, Map3)),
- %% cleanup
- rpc(Config, application, unset_env, [rabbitmq_management, oauth_enabled]).
-
version_test(Config) ->
ActualVersion = http_get(Config, "/version"),
ct:log("ActualVersion : ~p", [ActualVersion]),
diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl
index 86ea4f14f907..caca0df5576c 100644
--- a/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl
+++ b/deps/rabbitmq_management/test/rabbit_mgmt_schema_SUITE.erl
@@ -19,7 +19,8 @@ all() ->
test_invalid_endpoint_params,
test_translate_endpoint_params,
test_with_one_resource_server,
- test_with_many_resource_servers
+ test_with_many_resource_servers,
+ test_preserve_order_when_using_many_resource_servers
].
@@ -47,26 +48,61 @@ test_with_one_resource_server(_) ->
],
#{
<<"rabbitmq1">> := [
+ {index, 1},
{id, <<"rabbitmq1">>}
]
} = translate_oauth_resource_servers(Conf).
test_with_many_resource_servers(_) ->
- Conf = [
+ Conf = lists:reverse([ %% cuttlefish reverse the order. So calling lists:reverse simulates cuttlefish
{["management","oauth_resource_servers","keycloak","label"],"Keycloak"},
{["management","oauth_resource_servers","uaa","label"],"Uaa"}
- ],
+ ]),
#{
<<"keycloak">> := [
+ {index, 1},
{label, <<"Keycloak">>},
{id, <<"keycloak">>}
],
<<"uaa">> := [
+ {index, 2},
{label, <<"Uaa">>},
{id, <<"uaa">>}
]
} = translate_oauth_resource_servers(Conf).
+test_preserve_order_when_using_many_resource_servers(_) ->
+ Conf = lists:reverse([
+ {["management","oauth_resource_servers","uaa","label"],"Uaa"},
+ {["management","oauth_resource_servers","uaa","oauth_client_id"],"uaa-client"},
+ {["management","oauth_resource_servers","spring","label"],"Spring"},
+ {["management","oauth_resource_servers","spring","oauth_client_id"],"spring-client"},
+ {["management","oauth_resource_servers","keycloak","label"],"Keycloak"},
+ {["management","oauth_resource_servers","keycloak","oauth_client_id"],"keycloak-client"}
+ ]),
+ SortByIndex = fun({_, A}, {_, B}) ->
+ proplists:get_value(index, A) =< proplists:get_value(index, B) end,
+
+ [
+ {<<"uaa">>, [
+ {index, 1},
+ {label, <<"Uaa">>},
+ {oauth_client_id, <<"uaa-client">>},
+ {id, <<"uaa">>}
+ ]},
+ {<<"spring">>, [
+ {index, 2},
+ {label, <<"Spring">>},
+ {oauth_client_id, <<"spring-client">>},
+ {id, <<"spring">>}
+ ]},
+ {<<"keycloak">>, [
+ {index, 3},
+ {label, <<"Keycloak">>},
+ {oauth_client_id, <<"keycloak-client">>},
+ {id, <<"keycloak">>}
+ ]}
+ ] = lists:sort(SortByIndex, maps:to_list(translate_oauth_resource_servers(Conf))).
cert_filename(Conf) ->
string:concat(?config(data_dir, Conf), "certs/cert.pem").
diff --git a/selenium/Dockerfile.local b/selenium/Dockerfile.local
new file mode 100644
index 000000000000..9f8acb36469c
--- /dev/null
+++ b/selenium/Dockerfile.local
@@ -0,0 +1,12 @@
+FROM node:24 AS base
+
+WORKDIR /code
+
+COPY package.json package.json
+COPY .npmrc .npmrc
+
+FROM base AS test
+RUN npm install --verbose
+
+ENTRYPOINT [ "npm" ]
+CMD [ "" ]
diff --git a/selenium/bin/components/devkeycloak b/selenium/bin/components/devkeycloak
index dbd2d368af6a..51acb8c4dd39 100644
--- a/selenium/bin/components/devkeycloak
+++ b/selenium/bin/components/devkeycloak
@@ -48,7 +48,7 @@ start_devkeycloak() {
--https-certificate-key-file=/opt/keycloak/data/import/server_devkeycloak_key.pem \
--hostname=devkeycloak --hostname-admin=devkeycloak --https-port=8442
- wait_for_oidc_endpoint devkeycloak $DEVKEYCLOAK_URL $MOUNT_DEVKEYCLOAK_CONF_DIR/ca_certificate.pem
+ wait_for_oidc_endpoint devkeycloak $DEVKEYCLOAK_URL $MOUNT_DEVKEYCLOAK_CONF_DIR/ca_devkeycloak_certificate.pem
end "devkeycloak is ready"
print " Note: If you modify devkeycloak configuration, make sure to run the following command to export the configuration."
print " docker exec -it devkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/"
diff --git a/selenium/bin/components/prodkeycloak b/selenium/bin/components/prodkeycloak
index 462d16b3e4b8..8dd2d6aa84f9 100644
--- a/selenium/bin/components/prodkeycloak
+++ b/selenium/bin/components/prodkeycloak
@@ -47,7 +47,7 @@ start_prodkeycloak() {
--https-certificate-key-file=/opt/keycloak/data/import/server_prodkeycloak_key.pem \
--hostname=prodkeycloak --hostname-admin=prodkeycloak --https-port=8443
- wait_for_oidc_endpoint prodkeycloak $PRODKEYCLOAK_URL $MOUNT_PRODKEYCLOAK_CONF_DIR/ca_certificate.pem
+ wait_for_oidc_endpoint prodkeycloak $PRODKEYCLOAK_URL $MOUNT_PRODKEYCLOAK_CONF_DIR/ca_prodkeycloak_certificate.pem
end "prodkeycloak is ready"
print " Note: If you modify prodkeycloak configuration, make sure to run the following command to export the configuration."
print " docker exec -it prodkeycloak /opt/keycloak/bin/kc.sh export --users realm_file --realm test --dir /opt/keycloak/data/import/"
diff --git a/selenium/test/multi-oauth/without-basic-auth/landing.js b/selenium/test/multi-oauth/without-basic-auth/landing.js
index 662bb09c1cdf..0613b1442ccd 100644
--- a/selenium/test/multi-oauth/without-basic-auth/landing.js
+++ b/selenium/test/multi-oauth/without-basic-auth/landing.js
@@ -27,15 +27,27 @@ describe('Given three oauth resources but only two enabled, an unauthenticated u
resources = await homePage.getOAuthResourceOptions()
if (hasProfile("with-resource-label")) {
assertAllOptions([
- { value : "rabbit_dev", text : "RabbitMQ Development" },
- { value : "rabbit_prod", text : "RabbitMQ Production" }
- ], resources)
+ { value : "rabbit_prod", text : "RabbitMQ Production" },
+ { value : "rabbit_dev", text : "RabbitMQ Development" }
+ ], resources);
}else {
assertAllOptions([
- { value : "rabbit_dev", text : "rabbit_dev" },
- { value : "rabbit_prod", text : "rabbit_prod" }
- ], resources)
- }
+ { value : "rabbit_prod", text : "rabbit_prod" },
+ { value : "rabbit_dev", text : "rabbit_dev" }
+ ], resources);
+ }
+ })
+
+ it('there should preserve the same order as they were configured (prod then dev)', async function () {
+ resources = await homePage.getOAuthResourceOptions()
+ if (hasProfile("with-resource-label")) {
+ // assert resources are rendered in the same order they are configured : prod and then dev
+ assert.equal("RabbitMQ Production", resources[0].text);
+ assert.equal("RabbitMQ Development", resources[1].text);
+ }else {
+ assert.equal("rabbit_prod", resources[0].text);
+ assert.equal("rabbit_dev", resources[1].text);
+ }
})
it('should not be presented with a login button to log in using Basic Auth', async function () {