summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Hood <0x6e6562@gmail.com>2009-01-22 13:47:46 +0000
committerBen Hood <0x6e6562@gmail.com>2009-01-22 13:47:46 +0000
commit25964d18a56f65e94b64dace82bb409e64dab4e9 (patch)
treea785b370cc61158e63b198e75ad7757b75de90d4
parent33d0c4627618c5fbde6b2a85483ed038f4dba293 (diff)
parentf02d49ed637a9033ea0dd841522ea6417be34375 (diff)
downloadrabbitmq-server-git-25964d18a56f65e94b64dace82bb409e64dab4e9.tar.gz
Merged 20173 into default
-rw-r--r--docs/rabbitmqctl.1.pod24
-rw-r--r--ebin/rabbit_app.in1
-rw-r--r--include/rabbit.hrl6
-rw-r--r--src/rabbit.erl6
-rw-r--r--src/rabbit_access_control.erl148
-rw-r--r--src/rabbit_channel.erl46
-rw-r--r--src/rabbit_control.erl93
-rw-r--r--src/rabbit_mnesia.erl6
-rw-r--r--src/rabbit_tests.erl33
9 files changed, 250 insertions, 113 deletions
diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod
index 68c26b14c9..e9b9514ef6 100644
--- a/docs/rabbitmqctl.1.pod
+++ b/docs/rabbitmqctl.1.pod
@@ -114,17 +114,23 @@ delete_vhost I<vhostpath>
list_vhosts
list all virtual hosts.
-map_user_vhost I<username> I<vhostpath>
- grant the user named I<username> access to the virtual host called
+set_permissions [-p I<vhostpath>] I<username> I<regexp> I<regexp>
+ set the permissions for the user named I<username> in the virtual
+ host I<vhostpath>, granting them configuration access to resources
+ with names matching the first I<regexp> and messaging access to
+ resources with names matching the second I<regexp>.
+
+clear_permissions [-p I<vhostpath>] I<username>
+ remove the permissions for the user named I<username> in the
+ virtual host I<vhostpath>.
+
+list_permissions [-p I<vhostpath>]
+ list all the users and their permissions in the virtual host
I<vhostpath>.
-unmap_user_vhost I<username> I<vhostpath>
- deny the user named I<username> access to the virtual host called
- I<vhostpath>.
-
-list_user_vhost I<username>
- list all the virtual hosts to which the user named I<username> has
- been granted access.
+list_user_permissions I<username>
+ list the permissions of the user named I<username> across all
+ virtual hosts.
=head2 SERVER STATUS
diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in
index e2f36c0f5f..77f9d299b7 100644
--- a/ebin/rabbit_app.in
+++ b/ebin/rabbit_app.in
@@ -17,4 +17,5 @@
{default_user, <<"guest">>},
{default_pass, <<"guest">>},
{default_vhost, <<"/">>},
+ {default_permissions, [<<".*">>, <<".*">>]},
{memory_alarms, auto}]}]}.
diff --git a/include/rabbit.hrl b/include/rabbit.hrl
index d07aeaf845..8aba8a6fb6 100644
--- a/include/rabbit.hrl
+++ b/include/rabbit.hrl
@@ -30,7 +30,9 @@
%%
-record(user, {username, password}).
+-record(permission, {configuration, messaging}).
-record(user_vhost, {username, virtual_host}).
+-record(user_permission, {user_vhost, permission}).
-record(vhost, {virtual_host, dummy}).
@@ -74,6 +76,7 @@
-type(thunk(T) :: fun(() -> T)).
-type(info_key() :: atom()).
-type(info() :: {info_key(), any()}).
+-type(regexp() :: binary()).
%% this is really an abstract type, but dialyzer does not support them
-type(guid() :: any()).
@@ -88,6 +91,9 @@
-type(user() ::
#user{username :: username(),
password :: password()}).
+-type(permission() ::
+ #permission{configuration :: regexp(),
+ messaging :: regexp()}).
-type(amqqueue() ::
#amqqueue{name :: queue_name(),
durable :: bool(),
diff --git a/src/rabbit.erl b/src/rabbit.erl
index 30b8c39475..7ad13a7d28 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -264,9 +264,13 @@ insert_default_data() ->
{ok, DefaultUser} = application:get_env(default_user),
{ok, DefaultPass} = application:get_env(default_pass),
{ok, DefaultVHost} = application:get_env(default_vhost),
+ {ok, [DefaultConfigurationPerm, DefaultMessagingPerm]} =
+ application:get_env(default_permissions),
ok = rabbit_access_control:add_vhost(DefaultVHost),
ok = rabbit_access_control:add_user(DefaultUser, DefaultPass),
- ok = rabbit_access_control:map_user_vhost(DefaultUser, DefaultVHost),
+ ok = rabbit_access_control:set_permissions(DefaultUser, DefaultVHost,
+ DefaultConfigurationPerm,
+ DefaultMessagingPerm),
ok.
start_builtin_amq_applications() ->
diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl
index 36270efddc..0d9632b5d8 100644
--- a/src/rabbit_access_control.erl
+++ b/src/rabbit_access_control.erl
@@ -34,11 +34,12 @@
-include("rabbit.hrl").
-export([check_login/2, user_pass_login/2,
- check_vhost_access/2]).
+ check_vhost_access/2, check_resource_access/3]).
-export([add_user/2, delete_user/1, change_password/2, list_users/0,
lookup_user/1]).
--export([add_vhost/1, delete_vhost/1, list_vhosts/0, list_vhost_users/1]).
--export([list_user_vhosts/1, map_user_vhost/2, unmap_user_vhost/2]).
+-export([add_vhost/1, delete_vhost/1, list_vhosts/0]).
+-export([set_permissions/4, clear_permissions/2,
+ list_vhost_permissions/1, list_user_permissions/1]).
%%----------------------------------------------------------------------------
@@ -47,6 +48,8 @@
-spec(check_login/2 :: (binary(), binary()) -> user()).
-spec(user_pass_login/2 :: (username(), password()) -> user()).
-spec(check_vhost_access/2 :: (user(), vhost()) -> 'ok').
+-spec(check_resource_access/3 ::
+ (username(), r(atom()), non_neg_integer()) -> 'ok').
-spec(add_user/2 :: (username(), password()) -> 'ok').
-spec(delete_user/1 :: (username()) -> 'ok').
-spec(change_password/2 :: (username(), password()) -> 'ok').
@@ -55,10 +58,12 @@
-spec(add_vhost/1 :: (vhost()) -> 'ok').
-spec(delete_vhost/1 :: (vhost()) -> 'ok').
-spec(list_vhosts/0 :: () -> [vhost()]).
--spec(list_vhost_users/1 :: (vhost()) -> [username()]).
--spec(list_user_vhosts/1 :: (username()) -> [vhost()]).
--spec(map_user_vhost/2 :: (username(), vhost()) -> 'ok').
--spec(unmap_user_vhost/2 :: (username(), vhost()) -> 'ok').
+-spec(set_permissions/4 :: (username(), vhost(), regexp(), regexp()) -> 'ok').
+-spec(clear_permissions/2 :: (username(), vhost()) -> 'ok').
+-spec(list_vhost_permissions/1 ::
+ (vhost()) -> [{username(), regexp(), regexp()}]).
+-spec(list_user_permissions/1 ::
+ (username()) -> [{vhost(), regexp(), regexp()}]).
-endif.
@@ -112,9 +117,9 @@ internal_lookup_vhost_access(Username, VHostPath) ->
%% TODO: use dirty ops instead
rabbit_misc:execute_mnesia_transaction(
fun () ->
- case mnesia:match_object(
- #user_vhost{username = Username,
- virtual_host = VHostPath}) of
+ case mnesia:read({user_permission,
+ #user_vhost{username = Username,
+ virtual_host = VHostPath}}) of
[] -> not_found;
[R] -> {ok, R}
end
@@ -131,6 +136,38 @@ check_vhost_access(#user{username = Username}, VHostPath) ->
[VHostPath, Username])
end.
+check_resource_access(Username,
+ R = #resource{kind = exchange, name = <<"">>},
+ Permission) ->
+ check_resource_access(Username,
+ R#resource{name = <<"amq.default">>},
+ Permission);
+check_resource_access(_Username,
+ #resource{name = <<"amq.gen",_/binary>>},
+ _Permission) ->
+ ok;
+check_resource_access(Username,
+ R = #resource{virtual_host = VHostPath, name = Name},
+ Permission) ->
+ Res = case mnesia:dirty_read({user_permission,
+ #user_vhost{username = Username,
+ virtual_host = VHostPath}}) of
+ [] ->
+ false;
+ [#user_permission{permission = P}] ->
+ case regexp:match(
+ binary_to_list(Name),
+ binary_to_list(element(Permission, P))) of
+ {match, _, _} -> true;
+ nomatch -> false
+ end
+ end,
+ if Res -> ok;
+ true -> rabbit_misc:protocol_error(
+ access_refused, "access to ~s refused for user '~s'",
+ [rabbit_misc:rs(R), Username])
+ end.
+
add_user(Username, Password) ->
R = rabbit_misc:execute_mnesia_transaction(
fun () ->
@@ -151,7 +188,13 @@ delete_user(Username) ->
Username,
fun () ->
ok = mnesia:delete({user, Username}),
- ok = mnesia:delete({user_vhost, Username})
+ [ok = mnesia:delete_object(R) ||
+ R <- mnesia:match_object(
+ #user_permission{user_vhost = #user_vhost{
+ username = Username,
+ virtual_host = '_'},
+ permission = '_'})],
+ ok
end)),
rabbit_log:info("Deleted user ~p~n", [Username]),
R.
@@ -220,53 +263,74 @@ internal_delete_vhost(VHostPath) ->
ok = rabbit_exchange:delete(Name, false)
end,
rabbit_exchange:list(VHostPath)),
- lists:foreach(fun (Username) ->
- ok = unmap_user_vhost(Username, VHostPath)
+ lists:foreach(fun ({Username, _, _}) ->
+ ok = clear_permissions(Username, VHostPath)
end,
- list_vhost_users(VHostPath)),
+ list_vhost_permissions(VHostPath)),
ok = mnesia:delete({vhost, VHostPath}),
ok.
list_vhosts() ->
mnesia:dirty_all_keys(vhost).
-list_vhost_users(VHostPath) ->
- [Username ||
- #user_vhost{username = Username} <-
- %% TODO: use dirty ops instead
- rabbit_misc:execute_mnesia_transaction(
- rabbit_misc:with_vhost(
- VHostPath,
- fun () -> mnesia:index_read(user_vhost, VHostPath,
- #user_vhost.virtual_host)
- end))].
-
-list_user_vhosts(Username) ->
- [VHostPath ||
- #user_vhost{virtual_host = VHostPath} <-
- %% TODO: use dirty ops instead
- rabbit_misc:execute_mnesia_transaction(
- rabbit_misc:with_user(
- Username,
- fun () -> mnesia:read({user_vhost, Username}) end))].
+validate_regexp(RegexpBin) ->
+ Regexp = binary_to_list(RegexpBin),
+ case regexp:parse(Regexp) of
+ {ok, _} -> ok;
+ {error, Reason} -> throw({error, {invalid_regexp, Regexp, Reason}})
+ end.
-map_user_vhost(Username, VHostPath) ->
+set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) ->
+ validate_regexp(ConfigurationPerm),
+ validate_regexp(MessagingPerm),
rabbit_misc:execute_mnesia_transaction(
rabbit_misc:with_user_and_vhost(
Username, VHostPath,
- fun () ->
- ok = mnesia:write(
- #user_vhost{username = Username,
- virtual_host = VHostPath})
+ fun () -> ok = mnesia:write(
+ #user_permission{user_vhost = #user_vhost{
+ username = Username,
+ virtual_host = VHostPath},
+ permission = #permission{
+ configuration = ConfigurationPerm,
+ messaging = MessagingPerm}})
end)).
-unmap_user_vhost(Username, VHostPath) ->
+clear_permissions(Username, VHostPath) ->
rabbit_misc:execute_mnesia_transaction(
rabbit_misc:with_user_and_vhost(
Username, VHostPath,
fun () ->
- ok = mnesia:delete_object(
- #user_vhost{username = Username,
- virtual_host = VHostPath})
+ ok = mnesia:delete({user_permission,
+ #user_vhost{username = Username,
+ virtual_host = VHostPath}})
end)).
+list_vhost_permissions(VHostPath) ->
+ [{Username, ConfigurationPerm, MessagingPerm} ||
+ {Username, _, ConfigurationPerm, MessagingPerm} <-
+ list_permissions(rabbit_misc:with_vhost(
+ VHostPath, match_user_vhost('_', VHostPath)))].
+
+list_user_permissions(Username) ->
+ [{VHostPath, ConfigurationPerm, MessagingPerm} ||
+ {_, VHostPath, ConfigurationPerm, MessagingPerm} <-
+ list_permissions(rabbit_misc:with_user(
+ Username, match_user_vhost(Username, '_')))].
+
+list_permissions(QueryThunk) ->
+ [{Username, VHostPath, ConfigurationPerm, MessagingPerm} ||
+ #user_permission{user_vhost = #user_vhost{username = Username,
+ virtual_host = VHostPath},
+ permission = #permission{
+ configuration = ConfigurationPerm,
+ messaging = MessagingPerm}} <-
+ %% TODO: use dirty ops instead
+ rabbit_misc:execute_mnesia_transaction(QueryThunk)].
+
+match_user_vhost(Username, VHostPath) ->
+ fun () -> mnesia:match_object(
+ #user_permission{user_vhost = #user_vhost{
+ username = Username,
+ virtual_host = VHostPath},
+ permission = '_'})
+ end.
diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl
index 376e39c60d..39867a4b61 100644
--- a/src/rabbit_channel.erl
+++ b/src/rabbit_channel.erl
@@ -48,6 +48,8 @@
-define(HIBERNATE_AFTER, 1000).
+-define(MAX_PERMISSION_CACHE_SIZE, 12).
+
%%----------------------------------------------------------------------------
-ifdef(use_specs).
@@ -144,6 +146,7 @@ handle_cast({deliver, ConsumerTag, AckRequired, Msg},
noreply(State1#ch{next_tag = DeliveryTag + 1});
handle_cast({conserve_memory, Conserve}, State) ->
+ ok = clear_permission_cache(),
ok = rabbit_writer:send_command(
State#ch.writer_pid, #'channel.flow'{active = not(Conserve)}),
noreply(State).
@@ -152,6 +155,7 @@ handle_info({'EXIT', _Pid, Reason}, State) ->
{stop, Reason, State};
handle_info(timeout, State) ->
+ ok = clear_permission_cache(),
%% TODO: Once we drop support for R11B-5, we can change this to
%% {noreply, State, hibernate};
proc_lib:hibernate(gen_server2, enter_loop, [?MODULE, [], State]).
@@ -200,6 +204,32 @@ return_queue_declare_ok(State, NoWait, Q) ->
{reply, Reply, NewState}
end.
+check_resource_access(Username, Resource, Perm) ->
+ V = {Resource, Perm},
+ Cache = case get(permission_cache) of
+ undefined -> [];
+ Other -> Other
+ end,
+ CacheTail =
+ case lists:member(V, Cache) of
+ true -> lists:delete(V, Cache);
+ false -> ok = rabbit_access_control:check_resource_access(
+ Username, Resource, Perm),
+ lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE - 1)
+ end,
+ put(permission_cache, [V | CacheTail]),
+ ok.
+
+clear_permission_cache() ->
+ erase(permission_cache),
+ ok.
+
+check_configuration_permitted(Resource, #ch{ username = Username}) ->
+ check_resource_access(Username, Resource, #permission.configuration).
+
+check_messaging_permitted(Resource, #ch{ username = Username}) ->
+ check_resource_access(Username, Resource, #permission.messaging).
+
expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) ->
rabbit_misc:protocol_error(
not_allowed, "no previously declared queue", []);
@@ -269,6 +299,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin,
immediate = Immediate},
Content, State = #ch{ virtual_host = VHostPath}) ->
ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin),
+ check_messaging_permitted(ExchangeName, State),
Exchange = rabbit_exchange:lookup_or_die(ExchangeName),
%% We decode the content's properties here because we're almost
%% certain to want to look at delivery-mode and priority.
@@ -312,6 +343,7 @@ handle_method(#'basic.get'{queue = QueueNameBin,
_, State = #ch{ writer_pid = WriterPid,
next_tag = DeliveryTag }) ->
QueueName = expand_queue_name_shortcut(QueueNameBin, State),
+ check_messaging_permitted(QueueName, State),
case rabbit_amqqueue:with_or_die(
QueueName,
fun (Q) -> rabbit_amqqueue:basic_get(Q, self(), NoAck) end) of
@@ -346,6 +378,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin,
case dict:find(ConsumerTag, ConsumerMapping) of
error ->
QueueName = expand_queue_name_shortcut(QueueNameBin, State),
+ check_messaging_permitted(QueueName, State),
ActualConsumerTag =
case ConsumerTag of
<<>> -> rabbit_misc:binstring_guid("amq.ctag");
@@ -504,6 +537,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin,
_, State = #ch{ virtual_host = VHostPath }) ->
CheckedType = rabbit_exchange:check_type(TypeNameBin),
ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin),
+ check_configuration_permitted(ExchangeName, State),
X = case rabbit_exchange:lookup(ExchangeName) of
{ok, FoundX} -> FoundX;
{error, not_found} ->
@@ -523,6 +557,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin,
nowait = NoWait},
_, State = #ch{ virtual_host = VHostPath }) ->
ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin),
+ check_configuration_permitted(ExchangeName, State),
X = rabbit_exchange:lookup_or_die(ExchangeName),
ok = rabbit_exchange:assert_type(X, rabbit_exchange:check_type(TypeNameBin)),
return_ok(State, NoWait, #'exchange.declare_ok'{});
@@ -532,6 +567,7 @@ handle_method(#'exchange.delete'{exchange = ExchangeNameBin,
nowait = NoWait},
_, State = #ch { virtual_host = VHostPath }) ->
ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin),
+ check_configuration_permitted(ExchangeName, State),
case rabbit_exchange:delete(ExchangeName, IfUnused) of
{error, not_found} ->
rabbit_misc:protocol_error(
@@ -582,9 +618,12 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
Other -> check_name('queue', Other)
end,
QueueName = rabbit_misc:r(VHostPath, queue, ActualNameBin),
+ check_configuration_permitted(QueueName, State),
Finish(rabbit_amqqueue:declare(QueueName,
Durable, AutoDelete, Args));
- Other -> Other
+ Other = #amqqueue{name = QueueName} ->
+ check_configuration_permitted(QueueName, State),
+ Other
end,
return_queue_declare_ok(State, NoWait, Q);
@@ -593,6 +632,7 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
nowait = NoWait},
_, State = #ch{ virtual_host = VHostPath }) ->
QueueName = rabbit_misc:r(VHostPath, queue, QueueNameBin),
+ check_configuration_permitted(QueueName, State),
Q = rabbit_amqqueue:with_or_die(QueueName, fun (Q) -> Q end),
return_queue_declare_ok(State, NoWait, Q);
@@ -603,6 +643,7 @@ handle_method(#'queue.delete'{queue = QueueNameBin,
},
_, State) ->
QueueName = expand_queue_name_shortcut(QueueNameBin, State),
+ check_configuration_permitted(QueueName, State),
case rabbit_amqqueue:with_or_die(
QueueName,
fun (Q) -> rabbit_amqqueue:delete(Q, IfUnused, IfEmpty) end) of
@@ -639,6 +680,7 @@ handle_method(#'queue.purge'{queue = QueueNameBin,
nowait = NoWait},
_, State) ->
QueueName = expand_queue_name_shortcut(QueueNameBin, State),
+ check_messaging_permitted(QueueName, State),
{ok, PurgedMessageCount} = rabbit_amqqueue:with_or_die(
QueueName,
fun (Q) -> rabbit_amqqueue:purge(Q) end),
@@ -688,9 +730,11 @@ binding_action(Fun, ExchangeNameBin, QueueNameBin, RoutingKey, Arguments,
%% FIXME: don't allow binding to internal exchanges -
%% including the one named "" !
QueueName = expand_queue_name_shortcut(QueueNameBin, State),
+ check_configuration_permitted(QueueName, State),
ActualRoutingKey = expand_routing_key_shortcut(QueueNameBin, RoutingKey,
State),
ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin),
+ check_configuration_permitted(ExchangeName, State),
case Fun(ExchangeName, QueueName, ActualRoutingKey, Arguments) of
{error, queue_not_found} ->
rabbit_misc:protocol_error(
diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl
index cbc11b4031..293cd79751 100644
--- a/src/rabbit_control.erl
+++ b/src/rabbit_control.erl
@@ -114,10 +114,10 @@ Available commands:
delete_vhost <VHostPath>
list_vhosts
- map_user_vhost <UserName> <VHostPath>
- unmap_user_vhost <UserName> <VHostPath>
- list_user_vhosts <UserName>
- list_vhost_users <VHostPath>
+ set_permissions [-p <VHostPath>] <UserName> <Regexp> <Regexp>
+ clear_permissions [-p <VHostPath>] <UserName>
+ list_permissions [-p <VHostPath>]
+ list_user_permissions <UserName>
list_queues [-p <VHostPath>] [<QueueInfoItem> ...]
list_exchanges [-p <VHostPath>] [<ExchangeInfoItem> ...]
@@ -223,25 +223,14 @@ action(list_vhosts, Node, [], Inform) ->
Inform("Listing vhosts", []),
display_list(call(Node, {rabbit_access_control, list_vhosts, []}));
-action(map_user_vhost, Node, Args = [_Username, _VHostPath], Inform) ->
- Inform("Mapping user ~p to vhost ~p", Args),
- call(Node, {rabbit_access_control, map_user_vhost, Args});
-
-action(unmap_user_vhost, Node, Args = [_Username, _VHostPath], Inform) ->
- Inform("Unmapping user ~p from vhost ~p", Args),
- call(Node, {rabbit_access_control, unmap_user_vhost, Args});
-
-action(list_user_vhosts, Node, Args = [_Username], Inform) ->
- Inform("Listing vhosts for user ~p", Args),
- display_list(call(Node, {rabbit_access_control, list_user_vhosts, Args}));
-
-action(list_vhost_users, Node, Args = [_VHostPath], Inform) ->
- Inform("Listing users for vhosts ~p", Args),
- display_list(call(Node, {rabbit_access_control, list_vhost_users, Args}));
+action(list_user_permissions, Node, Args = [_Username], Inform) ->
+ Inform("Listing permissions for user ~p", Args),
+ display_list(call(Node, {rabbit_access_control, list_user_permissions,
+ Args}));
action(list_queues, Node, Args, Inform) ->
Inform("Listing queues", []),
- {VHostArg, RemainingArgs} = parse_vhost_flag(Args),
+ {VHostArg, RemainingArgs} = parse_vhost_flag_bin(Args),
ArgAtoms = list_replace(node, pid,
default_if_empty(RemainingArgs, [name, messages])),
display_info_list(rpc_call(Node, rabbit_amqqueue, info_all,
@@ -250,7 +239,7 @@ action(list_queues, Node, Args, Inform) ->
action(list_exchanges, Node, Args, Inform) ->
Inform("Listing exchanges", []),
- {VHostArg, RemainingArgs} = parse_vhost_flag(Args),
+ {VHostArg, RemainingArgs} = parse_vhost_flag_bin(Args),
ArgAtoms = default_if_empty(RemainingArgs, [name, type]),
display_info_list(rpc_call(Node, rabbit_exchange, info_all,
[VHostArg, ArgAtoms]),
@@ -258,7 +247,7 @@ action(list_exchanges, Node, Args, Inform) ->
action(list_bindings, Node, Args, Inform) ->
Inform("Listing bindings", []),
- {VHostArg, _} = parse_vhost_flag(Args),
+ {VHostArg, _} = parse_vhost_flag_bin(Args),
InfoKeys = [exchange_name, routing_key, queue_name, args],
display_info_list(
[lists:zip(InfoKeys, tuple_to_list(X)) ||
@@ -272,15 +261,37 @@ action(list_connections, Node, Args, Inform) ->
default_if_empty(Args, [user, peer_address, peer_port])),
display_info_list(rpc_call(Node, rabbit_networking, connection_info_all,
[ArgAtoms]),
- ArgAtoms).
+ ArgAtoms);
+
+action(Command, Node, Args, Inform) ->
+ {VHost, RemainingArgs} = parse_vhost_flag(Args),
+ action(Command, Node, VHost, RemainingArgs, Inform).
+
+action(set_permissions, Node, VHost, [Username, CPerm, MPerm], Inform) ->
+ Inform("Setting permissions for user ~p in vhost ~p", [Username, VHost]),
+ call(Node, {rabbit_access_control, set_permissions,
+ [Username, VHost, CPerm, MPerm]});
+
+action(clear_permissions, Node, VHost, [Username], Inform) ->
+ Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]),
+ call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]});
+
+action(list_permissions, Node, VHost, [], Inform) ->
+ Inform("Listing permissions in vhost ~p", [VHost]),
+ display_list(call(Node, {rabbit_access_control, list_vhost_permissions,
+ [VHost]})).
parse_vhost_flag(Args) when is_list(Args) ->
- case Args of
- ["-p", VHost | RemainingArgs] ->
- {list_to_binary(VHost), RemainingArgs};
- RemainingArgs ->
- {<<"/">>, RemainingArgs}
- end.
+ case Args of
+ ["-p", VHost | RemainingArgs] ->
+ {VHost, RemainingArgs};
+ RemainingArgs ->
+ {"/", RemainingArgs}
+ end.
+
+parse_vhost_flag_bin(Args) ->
+ {VHost, RemainingArgs} = parse_vhost_flag(Args),
+ {list_to_binary(VHost), RemainingArgs}.
default_if_empty(List, Default) when is_list(List) ->
if List == [] ->
@@ -290,21 +301,17 @@ default_if_empty(List, Default) when is_list(List) ->
end.
display_info_list(Results, InfoItemKeys) when is_list(Results) ->
- lists:foreach(
- fun (Result) ->
- io:fwrite(
- lists:flatten(
- rabbit_misc:intersperse(
- "\t",
- [format_info_item(Result, X) || X <- InfoItemKeys]))),
- io:nl()
- end,
- Results),
+ lists:foreach(fun (Result) -> display_row([format_info_item(Result, X) ||
+ X <- InfoItemKeys])
+ end, Results),
ok;
-
display_info_list(Other, _) ->
Other.
+display_row(Row) ->
+ io:fwrite(lists:flatten(rabbit_misc:intersperse("\t", Row))),
+ io:nl().
+
format_info_item(Items, Key) ->
{value, Info = {Key, Value}} = lists:keysearch(Key, 1, Items),
case Info of
@@ -321,8 +328,10 @@ format_info_item(Items, Key) ->
end.
display_list(L) when is_list(L) ->
- lists:foreach(fun (I) ->
- io:format("~s~n", [binary_to_list(I)])
+ lists:foreach(fun (I) when is_binary(I) ->
+ io:format("~s~n", [url_encode(I)]);
+ (I) when is_tuple(I) ->
+ display_row([url_encode(V) || V <- tuple_to_list(I)])
end,
lists:sort(L)),
ok;
diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl
index eebb38fa61..b7f3dd0a89 100644
--- a/src/rabbit_mnesia.erl
+++ b/src/rabbit_mnesia.erl
@@ -102,10 +102,8 @@ force_reset() -> reset(true).
table_definitions() ->
[{user, [{disc_copies, [node()]},
{attributes, record_info(fields, user)}]},
- {user_vhost, [{type, bag},
- {disc_copies, [node()]},
- {attributes, record_info(fields, user_vhost)},
- {index, [virtual_host]}]},
+ {user_permission, [{disc_copies, [node()]},
+ {attributes, record_info(fields, user_permission)}]},
{vhost, [{disc_copies, [node()]},
{attributes, record_info(fields, vhost)}]},
{rabbit_config, [{disc_copies, [node()]}]},
diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl
index df2e71d9e6..ef390e4de6 100644
--- a/src/rabbit_tests.erl
+++ b/src/rabbit_tests.erl
@@ -444,17 +444,18 @@ test_user_management() ->
{error, {no_such_vhost, _}} =
control_action(delete_vhost, ["/testhost"]),
{error, {no_such_user, _}} =
- control_action(map_user_vhost, ["foo", "/"]),
+ control_action(set_permissions, ["foo", ".*", ".*"]),
{error, {no_such_user, _}} =
- control_action(unmap_user_vhost, ["foo", "/"]),
+ control_action(clear_permissions, ["foo"]),
{error, {no_such_user, _}} =
- control_action(list_user_vhosts, ["foo"]),
+ control_action(list_user_permissions, ["foo"]),
{error, {no_such_vhost, _}} =
- control_action(map_user_vhost, ["guest", "/testhost"]),
- {error, {no_such_vhost, _}} =
- control_action(unmap_user_vhost, ["guest", "/testhost"]),
- {error, {no_such_vhost, _}} =
- control_action(list_vhost_users, ["/testhost"]),
+ control_action(list_permissions, ["-p", "/testhost"]),
+ {error, {invalid_regexp, _, _}} =
+ control_action(set_permissions, ["guest", "+foo", ".*"]),
+ {error, {invalid_regexp, _, _}} =
+ control_action(set_permissions, ["guest", ".*", "+foo"]),
+
%% user creation
ok = control_action(add_user, ["foo", "bar"]),
{error, {user_already_exists, _}} =
@@ -469,13 +470,16 @@ test_user_management() ->
ok = control_action(list_vhosts, []),
%% user/vhost mapping
- ok = control_action(map_user_vhost, ["foo", "/testhost"]),
- ok = control_action(map_user_vhost, ["foo", "/testhost"]),
- ok = control_action(list_user_vhosts, ["foo"]),
+ ok = control_action(set_permissions, ["-p", "/testhost",
+ "foo", ".*", ".*"]),
+ ok = control_action(set_permissions, ["-p", "/testhost",
+ "foo", ".*", ".*"]),
+ ok = control_action(list_permissions, ["-p", "/testhost"]),
+ ok = control_action(list_user_permissions, ["foo"]),
%% user/vhost unmapping
- ok = control_action(unmap_user_vhost, ["foo", "/testhost"]),
- ok = control_action(unmap_user_vhost, ["foo", "/testhost"]),
+ ok = control_action(clear_permissions, ["-p", "/testhost", "foo"]),
+ ok = control_action(clear_permissions, ["-p", "/testhost", "foo"]),
%% vhost deletion
ok = control_action(delete_vhost, ["/testhost"]),
@@ -484,7 +488,8 @@ test_user_management() ->
%% deleting a populated vhost
ok = control_action(add_vhost, ["/testhost"]),
- ok = control_action(map_user_vhost, ["foo", "/testhost"]),
+ ok = control_action(set_permissions, ["-p", "/testhost",
+ "foo", ".*", ".*"]),
ok = control_action(delete_vhost, ["/testhost"]),
%% user deletion