Let an auth plugin specify which vhosts a user can see.

This commit is contained in:
Simon MacMullen 2010-11-22 16:06:35 +00:00
parent d037b5d7cb
commit 7a642fb27b
12 changed files with 90 additions and 82 deletions

View File

@ -18,4 +18,4 @@
%%
%% Contributor(s): ______________________________________.
%%
-record(context, {username, password, is_admin}).
-record(context, {user, password}).

View File

@ -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},

View File

@ -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) ->

View File

@ -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)

View File

@ -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(

View File

@ -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),

View File

@ -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).

View File

@ -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).

View File

@ -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),

View File

@ -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()].

View File

@ -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()).

View File

@ -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) ->