Let an auth plugin specify which vhosts a user can see.
This commit is contained in:
parent
d037b5d7cb
commit
7a642fb27b
|
|
@ -18,4 +18,4 @@
|
|||
%%
|
||||
%% Contributor(s): ______________________________________.
|
||||
%%
|
||||
-record(context, {username, password, is_admin}).
|
||||
-record(context, {user, password}).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
-export([format/2, print/2, pid/1, ip/1, amqp_table/1, tuple/1, timestamp/1]).
|
||||
-export([node_and_pid/1, protocol/1, resource/1, permissions/1, queue/1]).
|
||||
-export([exchange/1, user/1, binding/1, url/2, application/1]).
|
||||
-export([exchange/1, user/1, internal_user/1, binding/1, url/2, application/1]).
|
||||
-export([pack_binding_props/2, unpack_binding_props/1, tokenise/1]).
|
||||
-export([args_type/1, listener/1, properties/1]).
|
||||
|
||||
|
|
@ -99,11 +99,17 @@ permissions({User, VHost, Conf, Write, Read}) ->
|
|||
{write, Write},
|
||||
{read, Read}].
|
||||
|
||||
user(User) ->
|
||||
internal_user(User) ->
|
||||
[{name, User#internal_user.username},
|
||||
{password_hash, base64:encode(User#internal_user.password_hash)},
|
||||
{administrator, User#internal_user.is_admin}].
|
||||
|
||||
user(User) ->
|
||||
[{name, User#user.username},
|
||||
{administrator, User#user.is_admin},
|
||||
{auth_backend, User#user.auth_backend}].
|
||||
|
||||
|
||||
listener(#listener{node = Node, protocol = Protocol,
|
||||
host = Host, ip_address = IPAddress, port = Port}) ->
|
||||
[{node, Node},
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
-export([is_authorized_vhost/2, is_authorized/3, is_authorized_user/3]).
|
||||
-export([bad_request/3, id/2, parse_bool/1]).
|
||||
-export([with_decode/4, not_found/3, amqp_request/4]).
|
||||
-export([all_or_one_vhost/2, with_decode_vhost/4, reply/3, filter_vhost/3]).
|
||||
-export([filter_user/3, with_decode/5, redirect/2, args/1, vhosts/1]).
|
||||
-export([all_or_one_vhost/3, with_decode_vhost/4, reply/3, filter_vhost/3]).
|
||||
-export([filter_user/3, with_decode/5, redirect/2, args/1]).
|
||||
-export([reply_list/3, reply_list/4, sort_list/4, destination_type/1]).
|
||||
|
||||
-include("rabbit_mgmt.hrl").
|
||||
|
|
@ -37,14 +37,16 @@ is_authorized_admin(ReqData, Context) ->
|
|||
fun(#user{is_admin = IsAdmin}) -> IsAdmin end).
|
||||
|
||||
is_authorized_vhost(ReqData, Context) ->
|
||||
is_authorized(ReqData, Context,
|
||||
fun(#user{username = Username}) ->
|
||||
case vhost(ReqData) of
|
||||
not_found -> true;
|
||||
none -> true;
|
||||
V -> lists:member(V, vhosts(Username))
|
||||
end
|
||||
end).
|
||||
is_authorized(
|
||||
ReqData, Context,
|
||||
fun(User) ->
|
||||
case vhost(ReqData) of
|
||||
not_found -> true;
|
||||
none -> true;
|
||||
V -> lists:member(
|
||||
V, rabbit_access_control:list_vhosts(User))
|
||||
end
|
||||
end).
|
||||
|
||||
is_authorized_user(ReqData, Context, Item) ->
|
||||
is_authorized(
|
||||
|
|
@ -58,14 +60,14 @@ is_authorized(ReqData, Context, Fun) ->
|
|||
ReqData, Context},
|
||||
case rabbit_mochiweb_util:parse_auth_header(
|
||||
wrq:get_req_header("authorization", ReqData)) of
|
||||
[User, Pass] ->
|
||||
case rabbit_access_control:check_user_pass_login(User, Pass) of
|
||||
{ok, U = #user{is_admin = IsAdmin}} ->
|
||||
case Fun(U) of
|
||||
[Username, Password] ->
|
||||
case rabbit_access_control:check_user_pass_login(Username,
|
||||
Password) of
|
||||
{ok, User} ->
|
||||
case Fun(User) of
|
||||
true -> {true, ReqData,
|
||||
Context#context{username = User,
|
||||
password = Pass,
|
||||
is_admin = IsAdmin}};
|
||||
Context#context{user = User,
|
||||
password = Password}};
|
||||
false -> Unauthorized
|
||||
end;
|
||||
{refused, _} ->
|
||||
|
|
@ -223,18 +225,19 @@ parse_bool(V) -> throw({error, {not_boolean, V}}).
|
|||
|
||||
amqp_request(VHost, ReqData, Context, Method) ->
|
||||
try
|
||||
Params = #amqp_params{username = Context#context.username,
|
||||
Params = #amqp_params{username = Context#context.user#user.username,
|
||||
password = Context#context.password,
|
||||
virtual_host = VHost},
|
||||
{ok, Conn} = amqp_connection:start(direct, Params),
|
||||
%% No need to check for {error, {auth_failure_likely...
|
||||
%% since we will weed out failed logins in some webmachine
|
||||
%% is_authorized/2 anyway.
|
||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||
amqp_channel:call(Ch, Method),
|
||||
amqp_channel:close(Ch),
|
||||
amqp_connection:close(Conn),
|
||||
{true, ReqData, Context}
|
||||
case amqp_connection:start(direct, Params) of
|
||||
{ok, Conn} ->
|
||||
{ok, Ch} = amqp_connection:open_channel(Conn),
|
||||
amqp_channel:call(Ch, Method),
|
||||
amqp_channel:close(Ch),
|
||||
amqp_connection:close(Conn),
|
||||
{true, ReqData, Context};
|
||||
{error, auth_failure} ->
|
||||
not_authorised(<<"">>, ReqData, Context)
|
||||
end
|
||||
catch
|
||||
exit:{{server_initiated_close, ?NOT_FOUND, Reason}, _} ->
|
||||
not_found(list_to_binary(Reason), ReqData, Context);
|
||||
|
|
@ -244,28 +247,27 @@ amqp_request(VHost, ReqData, Context, Method) ->
|
|||
when ServerClose =:= server_initiated_close;
|
||||
ServerClose =:= server_initiated_hard_close ->
|
||||
bad_request(list_to_binary(io_lib:format("~p ~s", [Code, Reason])),
|
||||
ReqData, Context)
|
||||
ReqData, Context);
|
||||
E:R -> io:format("~p~n", [{E,R}])
|
||||
end.
|
||||
|
||||
all_or_one_vhost(ReqData, Fun) ->
|
||||
all_or_one_vhost(ReqData, #context{ user = User }, Fun) ->
|
||||
case rabbit_mgmt_util:vhost(ReqData) of
|
||||
none -> lists:append(
|
||||
[Fun(V) || V <- rabbit_access_control:list_vhosts()]);
|
||||
[Fun(V) ||
|
||||
V <- rabbit_access_control:list_vhosts(User)]);
|
||||
not_found -> vhost_not_found;
|
||||
VHost -> Fun(VHost)
|
||||
end.
|
||||
|
||||
filter_vhost(List, _ReqData, Context) ->
|
||||
VHosts = vhosts(Context#context.username),
|
||||
VHosts = rabbit_access_control:list_vhosts(Context#context.user),
|
||||
[I || I <- List, lists:member(proplists:get_value(vhost, I), VHosts)].
|
||||
|
||||
vhosts(Username) ->
|
||||
[VHost || {VHost, _ConfigurePerm, _WritePerm, _ReadPerm}
|
||||
<- rabbit_access_control:list_user_permissions(Username)].
|
||||
|
||||
filter_user(List, _ReqData, #context{is_admin = true}) ->
|
||||
filter_user(List, _ReqData, #context{user = #user{is_admin = true}}) ->
|
||||
List;
|
||||
filter_user(List, _ReqData, #context{username = Username, is_admin = false}) ->
|
||||
filter_user(List, _ReqData,
|
||||
#context{user = #user{username = Username, is_admin = false}}) ->
|
||||
[I || I <- List, proplists:get_value(user, I) == Username].
|
||||
|
||||
redirect(Location, ReqData) ->
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ resource_exists(ReqData, Context) ->
|
|||
end, ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context) ->
|
||||
Params = #amqp_params{username = Context#context.username,
|
||||
Params = #amqp_params{username = Context#context.user#user.username,
|
||||
password = Context#context.password,
|
||||
virtual_host = rabbit_mgmt_util:vhost(ReqData)},
|
||||
%% TODO use network connection (need to check what we're bound to)
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ create_path(ReqData, Context) ->
|
|||
{"dummy", ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context) ->
|
||||
Xs = [X || X <- rabbit_mgmt_wm_exchanges:exchanges(ReqData),
|
||||
Xs = [X || X <- rabbit_mgmt_wm_exchanges:exchanges(ReqData, Context),
|
||||
export_exchange(X)],
|
||||
Qs = [Q || Q <- rabbit_mgmt_wm_queues:queues(ReqData),
|
||||
Qs = [Q || Q <- rabbit_mgmt_wm_queues:queues(ReqData, Context),
|
||||
export_queue(Q)],
|
||||
QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs],
|
||||
Bs = [B || B <- rabbit_mgmt_wm_bindings:bindings(ReqData),
|
||||
Bs = [B || B <- rabbit_mgmt_wm_bindings:bindings(ReqData, Context),
|
||||
export_binding(B, QNames)],
|
||||
{ok, Vsn} = application:get_key(rabbit, vsn),
|
||||
rabbit_mgmt_util:reply(
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
-export([init/1, to_json/2, content_types_provided/2, is_authorized/2]).
|
||||
-export([allowed_methods/2, post_is_create/2, create_path/2]).
|
||||
-export([content_types_accepted/2, accept_content/2, resource_exists/2]).
|
||||
-export([bindings/1]).
|
||||
-export([bindings/2]).
|
||||
|
||||
-include("rabbit_mgmt.hrl").
|
||||
-include_lib("webmachine/include/webmachine.hrl").
|
||||
|
|
@ -32,7 +32,7 @@ content_types_provided(ReqData, Context) ->
|
|||
{[{"application/json", to_json}], ReqData, Context}.
|
||||
|
||||
resource_exists(ReqData, {Mode, Context}) ->
|
||||
{case list_bindings(Mode, ReqData) of
|
||||
{case list_bindings(Mode, ReqData, Context) of
|
||||
vhost_not_found -> false;
|
||||
_ -> true
|
||||
end, ReqData, {Mode, Context}}.
|
||||
|
|
@ -50,7 +50,8 @@ post_is_create(ReqData, Context) ->
|
|||
{true, ReqData, Context}.
|
||||
|
||||
to_json(ReqData, {Mode, Context}) ->
|
||||
Bs = [rabbit_mgmt_format:binding(B) || B <- list_bindings(Mode, ReqData)],
|
||||
Bs = [rabbit_mgmt_format:binding(B) ||
|
||||
B <- list_bindings(Mode, ReqData, Context)],
|
||||
rabbit_mgmt_util:reply_list(
|
||||
rabbit_mgmt_util:filter_vhost(Bs, ReqData, Context),
|
||||
["vhost", "exchange", "queue", "routing_key", "properties_key"],
|
||||
|
|
@ -102,24 +103,24 @@ is_authorized(ReqData, {Mode, Context}) ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
bindings(ReqData) ->
|
||||
bindings(ReqData, Context) ->
|
||||
[rabbit_mgmt_format:binding(B) ||
|
||||
B <- list_bindings(all, ReqData)].
|
||||
B <- list_bindings(all, ReqData, Context)].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
list_bindings(all, ReqData) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData,
|
||||
fun (VHost) ->
|
||||
rabbit_binding:list(VHost)
|
||||
end);
|
||||
list_bindings(exchange_source, ReqData) ->
|
||||
list_bindings(all, ReqData, Context) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData, Context,
|
||||
fun (VHost) ->
|
||||
rabbit_binding:list(VHost)
|
||||
end);
|
||||
list_bindings(exchange_source, ReqData, _Context) ->
|
||||
rabbit_binding:list_for_source(r(exchange, exchange, ReqData));
|
||||
list_bindings(exchange_destination, ReqData) ->
|
||||
list_bindings(exchange_destination, ReqData, _Context) ->
|
||||
rabbit_binding:list_for_destination(r(exchange, exchange, ReqData));
|
||||
list_bindings(queue, ReqData) ->
|
||||
list_bindings(queue, ReqData, _Context) ->
|
||||
rabbit_binding:list_for_destination(r(queue, destination, ReqData));
|
||||
list_bindings(source_destination, ReqData) ->
|
||||
list_bindings(source_destination, ReqData, _Context) ->
|
||||
DestType = rabbit_mgmt_util:destination_type(ReqData),
|
||||
rabbit_binding:list_for_source_and_destination(
|
||||
r(exchange, source, ReqData),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
-module(rabbit_mgmt_wm_exchanges).
|
||||
|
||||
-export([init/1, to_json/2, content_types_provided/2, is_authorized/2,
|
||||
resource_exists/2, exchanges/1]).
|
||||
resource_exists/2, exchanges/2]).
|
||||
|
||||
-include("rabbit_mgmt.hrl").
|
||||
-include_lib("webmachine/include/webmachine.hrl").
|
||||
|
|
@ -29,13 +29,13 @@ content_types_provided(ReqData, Context) ->
|
|||
{[{"application/json", to_json}], ReqData, Context}.
|
||||
|
||||
resource_exists(ReqData, Context) ->
|
||||
{case exchanges0(ReqData) of
|
||||
{case exchanges0(ReqData, Context) of
|
||||
vhost_not_found -> false;
|
||||
_ -> true
|
||||
end, ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context) ->
|
||||
Xs = rabbit_mgmt_db:get_exchanges(exchanges(ReqData)),
|
||||
Xs = rabbit_mgmt_db:get_exchanges(exchanges(ReqData, Context)),
|
||||
rabbit_mgmt_util:reply_list(
|
||||
rabbit_mgmt_util:filter_vhost(Xs, ReqData, Context),
|
||||
ReqData, Context).
|
||||
|
|
@ -45,8 +45,9 @@ is_authorized(ReqData, Context) ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
exchanges(ReqData) ->
|
||||
[rabbit_mgmt_format:exchange(X) || X <- exchanges0(ReqData)].
|
||||
exchanges(ReqData, Context) ->
|
||||
[rabbit_mgmt_format:exchange(X) || X <- exchanges0(ReqData, Context)].
|
||||
|
||||
exchanges0(ReqData) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_exchange:info_all/1).
|
||||
exchanges0(ReqData, Context) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData, Context,
|
||||
fun rabbit_exchange:info_all/1).
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
-module(rabbit_mgmt_wm_queues).
|
||||
|
||||
-export([init/1, to_json/2, content_types_provided/2, is_authorized/2,
|
||||
resource_exists/2, queues/1]).
|
||||
resource_exists/2, queues/2]).
|
||||
|
||||
-include("rabbit_mgmt.hrl").
|
||||
-include_lib("webmachine/include/webmachine.hrl").
|
||||
|
|
@ -29,13 +29,13 @@ content_types_provided(ReqData, Context) ->
|
|||
{[{"application/json", to_json}], ReqData, Context}.
|
||||
|
||||
resource_exists(ReqData, Context) ->
|
||||
{case queues0(ReqData) of
|
||||
{case queues0(ReqData, Context) of
|
||||
vhost_not_found -> false;
|
||||
_ -> true
|
||||
end, ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context) ->
|
||||
Qs = rabbit_mgmt_db:get_queues(queues(ReqData)),
|
||||
Qs = rabbit_mgmt_db:get_queues(queues(ReqData, Context)),
|
||||
rabbit_mgmt_util:reply_list(
|
||||
rabbit_mgmt_util:filter_vhost(Qs, ReqData, Context),
|
||||
ReqData, Context).
|
||||
|
|
@ -45,8 +45,9 @@ is_authorized(ReqData, Context) ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
queues(ReqData) ->
|
||||
[rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData)].
|
||||
queues(ReqData, Context) ->
|
||||
[rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData, Context)].
|
||||
|
||||
queues0(ReqData) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list/1).
|
||||
queues0(ReqData, Context) ->
|
||||
rabbit_mgmt_util:all_or_one_vhost(ReqData, Context,
|
||||
fun rabbit_amqqueue:list/1).
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ resource_exists(ReqData, Context) ->
|
|||
|
||||
to_json(ReqData, Context) ->
|
||||
{ok, User} = user(ReqData),
|
||||
rabbit_mgmt_util:reply(rabbit_mgmt_format:user(User), ReqData, Context).
|
||||
rabbit_mgmt_util:reply(rabbit_mgmt_format:internal_user(User),
|
||||
ReqData, Context).
|
||||
|
||||
accept_content(ReqData, Context) ->
|
||||
User = rabbit_mgmt_util:id(user, ReqData),
|
||||
|
|
|
|||
|
|
@ -39,5 +39,5 @@ is_authorized(ReqData, Context) ->
|
|||
users() ->
|
||||
[begin
|
||||
{ok, User} = rabbit_access_control:lookup_user(U),
|
||||
rabbit_mgmt_format:user(User)
|
||||
rabbit_mgmt_format:internal_user(User)
|
||||
end || {U, _} <- rabbit_access_control:list_users()].
|
||||
|
|
|
|||
|
|
@ -28,12 +28,8 @@ init(_Config) -> {ok, #context{}}.
|
|||
content_types_provided(ReqData, Context) ->
|
||||
{[{"application/json", to_json}], ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context = #context{username = Username,
|
||||
is_admin = IsAdmin}) ->
|
||||
VHosts = case IsAdmin of
|
||||
true -> vhosts();
|
||||
false -> format(rabbit_mgmt_util:vhosts(Username))
|
||||
end,
|
||||
to_json(ReqData, Context = #context{user = User}) ->
|
||||
VHosts = format(rabbit_access_control:list_vhosts(User)),
|
||||
rabbit_mgmt_util:reply_list(VHosts, ReqData, Context).
|
||||
|
||||
is_authorized(ReqData, Context) ->
|
||||
|
|
@ -41,6 +37,7 @@ is_authorized(ReqData, Context) ->
|
|||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% This is used by export config and so should list ones in Mnesia
|
||||
vhosts() ->
|
||||
format(rabbit_access_control:list_vhosts()).
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ init(_Config) -> {ok, #context{}}.
|
|||
content_types_provided(ReqData, Context) ->
|
||||
{[{"application/json", to_json}], ReqData, Context}.
|
||||
|
||||
to_json(ReqData, Context = #context{username = Username}) ->
|
||||
{ok, User} = rabbit_access_control:lookup_user(Username),
|
||||
to_json(ReqData, Context = #context{user = User}) ->
|
||||
rabbit_mgmt_util:reply(rabbit_mgmt_format:user(User), ReqData, Context).
|
||||
|
||||
is_authorized(ReqData, Context) ->
|
||||
|
|
|
|||
Loading…
Reference in New Issue