Skip to content

Commit 99aaa05

Browse files
/login endpoint accepts login preferences
1 parent 2ebcc86 commit 99aaa05

File tree

15 files changed

+285
-144
lines changed

15 files changed

+285
-144
lines changed

deps/rabbitmq_management/include/rabbit_mgmt.hrl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@
1414

1515
-define(MANAGEMENT_DEFAULT_HTTP_MAX_BODY_SIZE, 20000000).
1616

17-
-define(OAUTH2_ACCESS_TOKEN_COOKIE_NAME, <<"access_token">>).
18-
-define(OAUTH2_ACCESS_TOKEN_COOKIE_PATH, <<"js/oidc-oauth/bootstrap.js">>).
17+
-define(OAUTH2_ACCESS_TOKEN, <<"access_token">>).
18+
-define(OAUTH2_BOOTSTRAP_PATH, <<"js/oidc-oauth/bootstrap.js">>).
19+
-define(MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, <<"strict_auth_mechanism">>).
20+
-define(MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, <<"preferred_auth_mechanism">>).

deps/rabbitmq_management/priv/www/js/main.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ function startWithOAuthLogin (oauth) {
4141
}
4242
}
4343
function render_login_oauth(oauth, messages) {
44-
let formatData = {}
45-
formatData.warnings = []
46-
formatData.notAuthorized = false
47-
formatData.resource_servers = oauth.resource_servers
48-
formatData.declared_resource_servers_count = oauth.declared_resource_servers_count
49-
formatData.oauth_disable_basic_auth = oauth.oauth_disable_basic_auth
44+
let formatData = {};
45+
formatData.warnings = [];
46+
formatData.notAuthorized = false;
47+
formatData.resource_servers = oauth.resource_servers;
48+
formatData.declared_resource_servers_count = oauth.declared_resource_servers_count;
49+
formatData.oauth_disable_basic_auth = oauth.oauth_disable_basic_auth;
50+
formatData.strict_auth_mechanism = oauth.strict_auth_mechanism;
51+
formatData.preferred_auth_mechanism = oauth.preferred_auth_mechanism;
5052

5153
if (Array.isArray(messages)) {
5254
formatData.warnings = messages
@@ -1133,6 +1135,9 @@ function update_truncate() {
11331135

11341136
function setup_visibility() {
11351137
$('div.section,div.section-hidden').each(function(_index) {
1138+
if ($(this).hasClass("disable-pref")) {
1139+
return;
1140+
}
11361141
var pref = section_pref(current_template,
11371142
$(this).children('h2').text());
11381143
var show = get_pref(pref);

deps/rabbitmq_management/priv/www/js/oidc-oauth/helper.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,24 @@ function oauth_initialize_user_manager(resource_server) {
193193
});
194194

195195
}
196+
196197
export function oauth_initialize(authSettings) {
197198
authSettings = auth_settings_apply_defaults(authSettings);
198199
let oauth = {
199200
"logged_in": false,
200201
"enabled" : authSettings.oauth_enabled,
201202
"resource_servers" : authSettings.resource_servers,
202-
"oauth_disable_basic_auth" : authSettings.oauth_disable_basic_auth
203+
"oauth_disable_basic_auth" : authSettings.oauth_disable_basic_auth,
203204
}
204205
if (!oauth.enabled) return oauth;
205-
206+
207+
if (authSettings.resource_servers.length > 1 || !authSettings.oauth_disable_basic_auth) {
208+
if (authSettings.strict_auth_mechanism) {
209+
oauth["strict_auth_mechanism"] = authSettings.strict_auth_mechanism;
210+
}else if (authSettings.preferred_auth_mechanism) {
211+
oauth["preferred_auth_mechanism"] = authSettings.preferred_auth_mechanism;
212+
}
213+
}
206214
let resource_server = null;
207215

208216
if (oauth.resource_servers.length == 1) {

deps/rabbitmq_management/priv/www/js/tmpl/login_oauth.ejs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,33 @@
1212
<% } %>
1313
</div>
1414
<% if (!notAuthorized) { %>
15-
<% if ((typeof resource_servers == 'object' && resource_servers.length == 1) && oauth_disable_basic_auth) { %>
15+
<% if (strict_auth_mechanism !== undefined && strict_auth_mechanism.type === "oauth2") { %>
16+
<button id="login" onclick="oauth_initiateLogin('<%=strict_auth_mechanism.resource_id%>')">Click here to log in</button>
17+
<% } else if ((typeof resource_servers == 'object' && resource_servers.length == 1) && oauth_disable_basic_auth) { %>
1618
<button id="login" onclick="oauth_initiateLogin('<%=resource_servers[0].id%>')">Click here to log in</button>
17-
<% } else if (typeof resource_servers == 'object' && resource_servers.length >= 1) { %>
19+
<% } else if (typeof resource_servers == 'object' && resource_servers.length >= 1 && strict_auth_mechanism == undefined) { %>
1820
1921
<b>Login with :</b>
2022
<p/>
23+
<% const OAuth2Visible = (strict_auth_mechanism === undefined || strict_auth_mechanism.type === "oauth2") ||
24+
(preferred_auth_mechanism === undefined || preferred_auth_mechanism === "oauth2"); %>
25+
<% const OAuth2Invisible = (preferred_auth_mechanism !== undefined && preferred_auth_mechanism.type !== "oauth2"); %>
26+
<% const OAuth2Hidden = (strict_auth_mechanism !== undefined && strict_auth_mechanism.type !== "oauth2"); %>
27+
<% const preferredResourceId = preferred_auth_mechanism !== undefined && preferred_auth_mechanism.type === "oauth2" ? preferred_auth_mechanism.resource_id : null; %>
2128
<!-- begin login with oauth2 -->
22-
<div class="section" id="login-with-oauth2">
29+
<% if (!OAuth2Hidden) { %>
30+
<div class="section disable-pref <%= OAuth2Visible ? 'section-visible' : '' %> <%= OAuth2Invisible ? 'section-invisible' : '' %> " id="login-with-oauth2">
2331
<h2>OAuth 2.0</h2>
2432
<div class="hider">
2533
<div class="updatable">
2634
<% if (resource_servers.length == 1 && declared_resource_servers_count == 1) { %>
2735
<button id="login" onclick="oauth_initiateLogin('<%=resource_servers[0].id%>')">Click here to log in</button>
2836
<% } else { %>
2937
<form onsubmit="oauth_initiateLogin(document.getElementById('oauth2-resource').value)">
30-
<label for="oauth2-resource">Resource:</label>
38+
<label for="oauth2-resource">Resource: </label>
3139
<select id="oauth2-resource">
3240
<% for (var i = 0; i < resource_servers.length; i++) { %>
33-
<option value="<%= fmt_string(resource_servers[i].id) %>">
41+
<option value="<%= fmt_string(resource_servers[i].id) %>" <%= (preferredResourceId === resource_servers[i].id) ? 'selected="selected"' : '' %>>
3442
<%= fmt_string(resource_servers[i].label != null ? resource_servers[i].label : resource_servers[i].id) %>
3543
</option>
3644
<% } %>
@@ -42,12 +50,17 @@
4250
</div>
4351
</div>
4452
</div>
53+
<% } %>
4554
<!-- end login with oauth2 -->
4655
<% } %>
4756
48-
<!-- begin login with basic auth -->
49-
<% if (!oauth_disable_basic_auth) { %>
50-
<div class="section-hidden" id="login-with-basic-auth">
57+
<!-- begin login with basic auth -->
58+
<% const basicAuthVisible = (strict_auth_mechanism !== undefined && strict_auth_mechanism.type === "basic") ||
59+
(preferred_auth_mechanism !== undefined && preferred_auth_mechanism.type === "basic"); %>
60+
<% const basicAuthInvisible = (strict_auth_mechanism === undefined && preferred_auth_mechanism === undefined || (preferred_auth_mechanism !== undefined && preferred_auth_mechanism.type !== "basic"));%>
61+
<% const basicAuthHidden = (strict_auth_mechanism !== undefined && strict_auth_mechanism.type !== "basic"); %>
62+
<% if (!oauth_disable_basic_auth && !basicAuthHidden) { %>
63+
<div class="section disable-pref <%= basicAuthInvisible ? 'section-invisible' : ''%> <%= basicAuthVisible ? 'section-visible' : ''%> " id="login-with-basic-auth">
5164
<h2>Basic Authentication</h2>
5265
<div class="hider">
5366
<div class="updatable">

deps/rabbitmq_management/src/rabbit_mgmt_login.erl

Lines changed: 120 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,138 @@
1111

1212
-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
1313
-include("rabbit_mgmt.hrl").
14+
-include_lib("kernel/include/logger.hrl").
1415

1516
%%--------------------------------------------------------------------
17+
%% /api/login endpoint
18+
%%
19+
%% Scenario 1 : Users come to RabbitMQ management UI with an access token
20+
%% carried in the body's (POST) field called <<"access_token">>.
21+
%% Only for POST method.
22+
%%
23+
%% The endpoint redirects the user to the home page of the management UI with a
24+
%% short-lived cookie with the name <<"access_token">> and targed to the resource/path
25+
%% <<"js/oidc-oauth/bootstrap.js">> if the token is valid and the user is authorized
26+
%% to access the management UI.
27+
%%
28+
%% Scenario 2: Users come to RabbitMQ management UI with one of these fields
29+
%% <<"strict_auth_mechanism">> or <<"preferred_auth_mechanism">> defined via
30+
%% of these methods:
31+
%% - In the body payload
32+
%% - As request parameter (Only available for GET method)
33+
%% - As request header with the prefix <<"x-">>
34+
%%
35+
%% The endpoint redirects the user to the home page of the management UI with a
36+
%% short-lived cookie with the name matching either <<"strict_auth_mechanism">>
37+
%% or <<"preferred_auth_mechanism">> (regardless if the value was set as form
38+
%% field, or request parameter or header) and targeted to the resource/path
39+
%% <<"js/oidc-oauth/bootstrap.js">>.
40+
%%
41+
%% NOTE: The short-lived token is removed once it is read by the module
42+
%% rabbit_mgmt_oauth_bootstrap.erl which attends the resource/path
43+
%% <<"js/oidc-oauth/bootstrap.js">>.
1644

1745
init(Req0, State) ->
1846
login(cowboy_req:method(Req0), Req0, State).
1947

20-
login(<<"POST">>, Req0=#{scheme := Scheme}, State) ->
48+
login(<<"POST">>, Req0, State) ->
2149
{ok, Body, _} = cowboy_req:read_urlencoded_body(Req0),
22-
AccessToken = proplists:get_value(<<"access_token">>, Body),
23-
case rabbit_mgmt_util:is_authorized_user(Req0, #context{}, <<"">>, AccessToken, false) of
24-
{true, Req1, _} ->
25-
CookieSettings = #{
26-
http_only => true,
27-
path => ?OAUTH2_ACCESS_TOKEN_COOKIE_PATH,
28-
max_age => 30,
29-
same_site => strict
30-
},
31-
SetCookie = cowboy_req:set_resp_cookie(?OAUTH2_ACCESS_TOKEN_COOKIE_NAME, AccessToken, Req1,
32-
case Scheme of
33-
<<"https">> -> CookieSettings#{ secure => true};
34-
_ -> CookieSettings
35-
end),
36-
Home = cowboy_req:uri(SetCookie, #{
37-
path => rabbit_mgmt_util:get_path_prefix() ++ "/"
38-
}),
39-
Redirect = cowboy_req:reply(302, #{
40-
<<"Location">> => iolist_to_binary(Home)
41-
}, <<>>, SetCookie),
42-
{ok, Redirect, State};
43-
{false, ReqData1, Reason} ->
44-
replyWithError(Reason, ReqData1, State)
50+
case proplists:get_value(?OAUTH2_ACCESS_TOKEN, Body) of
51+
undefined -> handleStrictOrPreferredAuthMechanism(Req0, Body, State);
52+
AccessToken -> handleAccessToken(Req0, AccessToken, State)
53+
end;
54+
55+
login(<<"GET">>, Req, State) ->
56+
Auth = case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Req) of
57+
undefined ->
58+
case rabbit_mgmt_util:qs_val(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Req) of
59+
undefined -> undefined;
60+
Val -> validate_auth_mechanism({?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Val})
61+
end;
62+
Val -> validate_auth_mechanism({?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Val})
63+
end,
64+
case Auth of
65+
undefined ->
66+
case rabbit_mgmt_util:qs_val(?OAUTH2_ACCESS_TOKEN, Req) of
67+
undefined ->
68+
{ok, cowboy_req:reply(302, #{<<"Location">> => iolist_to_binary(get_home_uri(Req))},
69+
<<>>, Req), State};
70+
_ -> {ok, cowboy_req:reply(405, Req), State}
71+
end;
72+
{Type, Value} ->
73+
redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH, Type, Value, Req, State)
4574
end;
4675

4776
login(_, Req0, State) ->
48-
%% Method not allowed.
4977
{ok, cowboy_req:reply(405, Req0), State}.
5078

79+
handleStrictOrPreferredAuthMechanism(Req, Body, State) ->
80+
case validate_auth_mechanism(get_auth_mechanism(Req, Body)) of
81+
undefined ->
82+
{ok, cowboy_req:reply(302, #{<<"Location">> => iolist_to_binary(get_home_uri(Req))},
83+
<<>>, Req), State};
84+
{Type, Value} ->
85+
redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH, Type, Value, Req, State)
86+
end.
87+
88+
get_auth_mechanism(Req, Body) ->
89+
case get_param_or_header(?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Req, Body) of
90+
undefined ->
91+
case get_param_or_header(?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Req, Body) of
92+
undefined -> undefined;
93+
Val -> {?MANAGEMENT_LOGIN_PREFERRED_AUTH_MECHANISM, Val}
94+
end;
95+
Val -> {?MANAGEMENT_LOGIN_STRICT_AUTH_MECHANISM, Val}
96+
end.
97+
98+
validate_auth_mechanism({_, <<"oauth2:", _Id/binary>>} = Auth) -> Auth;
99+
validate_auth_mechanism({_, <<"basic">>} = Auth) -> Auth;
100+
validate_auth_mechanism({_, _}) -> undefined;
101+
validate_auth_mechanism(_) -> undefined.
102+
103+
get_param_or_header(ParamName, Req, Body) ->
104+
case proplists:get_value(ParamName, Body) of
105+
undefined ->
106+
case rabbit_mgmt_util:qs_val(ParamName, Req) of
107+
undefined -> cowboy_req:header(<<"x-", ParamName/binary>>, Req);
108+
Val -> Val
109+
end;
110+
Val -> Val
111+
end.
112+
113+
handleAccessToken(Req0, AccessToken, State) ->
114+
case rabbit_mgmt_util:is_authorized_user(Req0, #context{}, <<"">>, AccessToken, false) of
115+
{true, Req1, _} ->
116+
redirect_to_home_with_cookie(?OAUTH2_BOOTSTRAP_PATH,
117+
?OAUTH2_ACCESS_TOKEN,
118+
AccessToken,
119+
Req1, State);
120+
{false, ReqData1, Reason} ->
121+
replyWithError(Reason, ReqData1, State)
122+
end.
123+
124+
redirect_to_home_with_cookie(CookiePath, CookieName, CookieValue, Req=#{scheme := Scheme}, State) ->
125+
CookieSettings0 = #{
126+
http_only => true,
127+
path => CookiePath,
128+
max_age => 30,
129+
same_site => strict
130+
},
131+
CookieSettings =
132+
case Scheme of
133+
<<"https">> -> CookieSettings0#{secure => true};
134+
_ -> CookieSettings0
135+
end,
136+
SetCookie = cowboy_req:set_resp_cookie(CookieName, CookieValue, Req, CookieSettings),
137+
Redirect = cowboy_req:reply(302, #{
138+
<<"Location">> => iolist_to_binary(get_home_uri(SetCookie))
139+
}, <<>>, SetCookie),
140+
{ok, Redirect, State}.
141+
142+
get_home_uri(Req0) ->
143+
cowboy_req:uri(Req0, #{path => rabbit_mgmt_util:get_path_prefix() ++ "/", qs => undefined}).
144+
145+
51146
replyWithError(Reason, Req, State) ->
52147
Home = cowboy_req:uri(Req, #{
53148
path => rabbit_mgmt_util:get_path_prefix() ++ "/",

0 commit comments

Comments
 (0)