|
11 | 11 |
|
12 | 12 | -include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). |
13 | 13 | -include("rabbit_mgmt.hrl"). |
| 14 | +-include_lib("kernel/include/logger.hrl"). |
14 | 15 |
|
15 | 16 | %%-------------------------------------------------------------------- |
| 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">>. |
16 | 44 |
|
17 | 45 | init(Req0, State) -> |
18 | 46 | login(cowboy_req:method(Req0), Req0, State). |
19 | 47 |
|
20 | | -login(<<"POST">>, Req0=#{scheme := Scheme}, State) -> |
| 48 | +login(<<"POST">>, Req0, State) -> |
21 | 49 | {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) |
45 | 74 | end; |
46 | 75 |
|
47 | 76 | login(_, Req0, State) -> |
48 | | - %% Method not allowed. |
49 | 77 | {ok, cowboy_req:reply(405, Req0), State}. |
50 | 78 |
|
| 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 | + |
51 | 146 | replyWithError(Reason, Req, State) -> |
52 | 147 | Home = cowboy_req:uri(Req, #{ |
53 | 148 | path => rabbit_mgmt_util:get_path_prefix() ++ "/", |
|
0 commit comments