rabbitmq-server/deps/rabbit/test/amqp_auth_SUITE.erl

1218 lines
52 KiB
Erlang

%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
-module(amqp_auth_SUITE).
-compile([export_all,
nowarn_export_all]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
-include_lib("amqp10_common/include/amqp10_framing.hrl").
-define(TIMEOUT, 30_000).
-import(rabbit_ct_broker_helpers,
[rpc/4]).
-import(rabbit_ct_helpers,
[eventually/1]).
-import(event_recorder,
[assert_event_type/2,
assert_event_prop/2]).
-import(amqp_utils,
[web_amqp/1,
flush/1,
wait_for_credit/1,
end_session_sync/1,
close_connection_sync/1]).
all() ->
[
{group, address_v1},
{group, address_v2}
].
groups() ->
[
{address_v1, [shuffle],
[
%% authz
v1_attach_target_queue,
v1_attach_source_exchange,
v1_send_to_topic,
v1_send_to_topic_using_subject,
v1_attach_source_topic,
v1_attach_target_internal_exchange,
%% limits
v1_vhost_queue_limit
]
},
{address_v2, [shuffle],
[
%% authz
attach_source_queue,
attach_source_queue_dynamic_exclusive,
attach_source_queue_dynamic_volatile,
attach_target_exchange,
attach_target_topic_exchange,
attach_target_queue,
attach_target_queue_dynamic_exchange_write,
attach_target_queue_dynamic_queue_configure,
target_per_message_exchange,
target_per_message_internal_exchange,
target_per_message_topic,
%% authn
authn_failure_event,
sasl_anonymous_success,
sasl_plain_success,
sasl_anonymous_failure,
sasl_plain_failure,
sasl_none_failure,
vhost_absent,
%% limits
vhost_connection_limit,
user_connection_limit,
%% AMQP Management operations against HTTP API v2
declare_exchange,
delete_exchange,
declare_queue,
declare_queue_dlx_queue,
declare_queue_dlx_exchange,
declare_queue_vhost_queue_limit,
delete_queue,
purge_queue,
bind_queue_source,
bind_queue_destination,
bind_exchange_source,
bind_exchange_destination,
bind_to_topic_exchange,
unbind_queue_source,
unbind_queue_target,
unbind_from_topic_exchange
]
}
].
init_per_suite(Config) ->
{ok, _} = application:ensure_all_started(amqp10_client),
rabbit_ct_helpers:log_environment(),
Config.
end_per_suite(Config) ->
Config.
init_per_group(Group, Config0) ->
PermitV1 = case Group of
address_v1 -> true;
address_v2 -> false
end,
Config1 = rabbit_ct_helpers:merge_app_env(
Config0, {rabbit,
[{permit_deprecated_features,
#{amqp_address_v1 => PermitV1}}]}),
Config = rabbit_ct_helpers:run_setup_steps(
Config1,
rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps()),
case Config of
_ when is_list(Config) ->
Vhost = <<"test vhost">>,
User = <<"test user">>,
ok = rabbit_ct_broker_helpers:add_vhost(Config, Vhost),
ok = rabbit_ct_broker_helpers:add_user(Config, User),
[{test_vhost, Vhost},
{test_user, User}] ++ Config;
{skip, _} = Skip ->
Skip
end.
end_per_group(_Group, Config) ->
ok = rabbit_ct_broker_helpers:delete_user(Config, ?config(test_user, Config)),
ok = rabbit_ct_broker_helpers:delete_vhost(Config, ?config(test_vhost, Config)),
rabbit_ct_helpers:run_teardown_steps(
Config,
rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_broker_helpers:teardown_steps()).
init_per_testcase(Testcase, Config) ->
ok = set_permissions(Config, <<>>, <<>>, <<"^some vhost permission">>),
rabbit_ct_helpers:testcase_started(Config, Testcase).
end_per_testcase(Testcase, Config) ->
delete_all_queues(Config),
ok = clear_permissions(Config),
rabbit_ct_helpers:testcase_finished(Config, Testcase).
v1_attach_target_queue(Config) ->
QName = <<"test queue">>,
%% This target address means RabbitMQ will create a queue
%% requiring configure access on the queue.
%% We will also need write access to the default exchange to send to this queue.
TargetAddress = <<"/queue/", QName/binary>>,
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session1} = amqp10_client:begin_session_sync(Connection),
{ok, _Sender1} = amqp10_client:attach_sender_link(
Session1, <<"test-sender-1">>, TargetAddress),
ExpectedErr1 = error_unauthorized(
<<"configure access to queue 'test queue' in vhost "
"'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session1, {ended, ExpectedErr1}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure permissions on the queue.
ok = set_permissions(Config, QName, <<>>, <<>>),
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
{ok, _Sender2} = amqp10_client:attach_sender_link(
Session2, <<"test-sender-2">>, TargetAddress),
ExpectedErr2 = error_unauthorized(
<<"write access to exchange 'amq.default' in vhost "
"'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session2, {ended, ExpectedErr2}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure permissions on the queue and
%% write access to the default exchange.
ok = set_permissions(Config, QName, <<"amq\.default">>, <<>>),
{ok, Session3} = amqp10_client:begin_session_sync(Connection),
{ok, Sender3} = amqp10_client:attach_sender_link(
Session3, <<"test-sender-3">>, TargetAddress),
receive {amqp10_event, {link, Sender3, attached}} -> ok
after ?TIMEOUT -> flush(missing_attached),
ct:fail("missing ATTACH from server")
end,
ok = close_connection_sync(Connection).
v1_attach_source_exchange(Config) ->
%% This source address means RabbitMQ will create a queue with a generated name
%% prefixed with amq.gen requiring configure access on the queue.
%% The queue is bound to the fanout exchange requiring write access on the queue
%% and read access on the fanout exchange.
%% To consume from the queue, we will also need read access on the queue.
SourceAddress = <<"/exchange/amq.fanout/ignored">>,
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session1} = amqp10_client:begin_session_sync(Connection),
{ok, _Recv1} = amqp10_client:attach_receiver_link(
Session1, <<"receiver-1">>, SourceAddress),
receive
{amqp10_event,
{session, Session1,
{ended,
#'v1_0.error'{
condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, <<"configure access to queue 'amq.gen", _/binary>>}}}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure permissions on the queue.
ok = set_permissions(Config, <<"^amq\.gen">>, <<>>, <<>>),
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
{ok, _Recv2} = amqp10_client:attach_receiver_link(
Session2, <<"receiver-2">>, SourceAddress),
receive
{amqp10_event,
{session, Session2,
{ended,
#'v1_0.error'{
condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, <<"write access to queue 'amq.gen", _/binary>>}}}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure and write permissions on the queue.
ok = set_permissions(Config, <<"^amq\.gen">>, <<"^amq\.gen">>, <<>>),
{ok, Session3} = amqp10_client:begin_session_sync(Connection),
{ok, _Recv3} = amqp10_client:attach_receiver_link(
Session3, <<"receiver-3">>, SourceAddress),
ExpectedErr1 = error_unauthorized(
<<"read access to exchange 'amq.fanout' in vhost "
"'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session3, {ended, ExpectedErr1}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure and write permissions on the queue, and read access on the exchange.
ok = set_permissions(Config, <<"^amq\.gen">>, <<"^amq\.gen">>, <<"amq\.fanout">>),
{ok, Session4} = amqp10_client:begin_session_sync(Connection),
{ok, _Recv4} = amqp10_client:attach_receiver_link(
Session4, <<"receiver-4">>, SourceAddress),
receive
{amqp10_event,
{session, Session4,
{ended,
#'v1_0.error'{
condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, <<"read access to queue 'amq.gen", _/binary>>}}}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
%% Give the user configure, write, and read permissions on the queue,
%% and read access on the exchange.
ok = set_permissions(Config, <<"^amq\.gen">>, <<"^amq\.gen">>, <<"^(amq\.gen|amq\.fanout)">>),
{ok, Session5} = amqp10_client:begin_session_sync(Connection),
{ok, Recv5} = amqp10_client:attach_receiver_link(
Session5, <<"receiver-5">>, SourceAddress),
receive {amqp10_event, {link, Recv5, attached}} -> ok
after ?TIMEOUT -> flush(missing_attached),
ct:fail("missing ATTACH from server")
end,
ok = close_connection_sync(Connection).
v1_send_to_topic(Config) ->
TargetAddresses = [<<"/topic/test vhost.test user.a.b">>,
<<"/exchange/amq.topic/test vhost.test user.a.b">>],
lists:foreach(fun(Address) ->
ok = send_to_topic(Address, Config)
end, TargetAddresses).
send_to_topic(TargetAddress, Config) ->
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^$">>, <<"^$">>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session1} = amqp10_client:begin_session_sync(Connection),
{ok, Sender1} = amqp10_client:attach_sender_link_sync(
Session1, <<"sender-1">>, TargetAddress),
ok = wait_for_credit(Sender1),
Msg1 = amqp10_msg:new(<<255>>, <<1>>, true),
ok = amqp10_client:send_msg(Sender1, Msg1),
ExpectedErr = error_unauthorized(
<<"write access to topic 'test vhost.test user.a.b' in exchange "
"'amq.topic' in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session1, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^{vhost}\.{username}\.a\.b$">>, <<"^$">>),
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
{ok, Sender2} = amqp10_client:attach_sender_link_sync(
Session2, <<"sender-2">>, TargetAddress),
ok = wait_for_credit(Sender2),
Dtag = <<0, 0>>,
Msg2 = amqp10_msg:new(Dtag, <<2>>, false),
ok = amqp10_client:send_msg(Sender2, Msg2),
%% We expect RELEASED since no queue is bound.
receive {amqp10_disposition, {released, Dtag}} -> ok
after ?TIMEOUT -> ct:fail(released_timeout)
end,
ok = amqp10_client:detach_link(Sender2),
ok = close_connection_sync(Connection).
v1_send_to_topic_using_subject(Config) ->
TargetAddress = <<"/exchange/amq.topic">>,
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^\.a$">>, <<"^$">>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
{ok, Sender} = amqp10_client:attach_sender_link_sync(
Session, <<"sender">>, TargetAddress),
ok = wait_for_credit(Sender),
Dtag1 = <<"dtag 1">>,
Msg1a = amqp10_msg:new(Dtag1, <<"m1">>, false),
Msg1b = amqp10_msg:set_properties(#{subject => <<".a">>}, Msg1a),
ok = amqp10_client:send_msg(Sender, Msg1b),
%% We have sufficient authorization, but expect RELEASED since no queue is bound.
receive {amqp10_disposition, {released, Dtag1}} -> ok
after ?TIMEOUT -> ct:fail(released_timeout)
end,
Dtag2 = <<"dtag 2">>,
Msg2a = amqp10_msg:new(Dtag2, <<"m2">>, false),
%% We don't have sufficient authorization.
Msg2b = amqp10_msg:set_properties(#{subject => <<".a.b">>}, Msg2a),
ok = amqp10_client:send_msg(Sender, Msg2b),
ExpectedErr = error_unauthorized(
<<"write access to topic '.a.b' in exchange 'amq.topic' in "
"vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
ok = close_connection_sync(Connection).
v1_attach_source_topic(Config) ->
%% These source addresses mean RabbitMQ will bind a queue to the default topic
%% exchange with binding key 'test vhost.test user.a.b'.
%% Therefore, we need read access to that topic.
%% We also test variable expansion in topic permission patterns.
SourceAddresses = [<<"/topic/test vhost.test user.a.b">>,
<<"/exchange/amq.topic/test vhost.test user.a.b">>],
lists:foreach(fun(Address) ->
ok = attach_source_topic0(Address, Config)
end, SourceAddresses).
attach_source_topic0(SourceAddress, Config) ->
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^$">>, <<"^$">>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session1} = amqp10_client:begin_session_sync(Connection),
{ok, _Recv1} = amqp10_client:attach_receiver_link(
Session1, <<"receiver-1">>, SourceAddress),
ExpectedErr = error_unauthorized(
<<"read access to topic 'test vhost.test user.a.b' in exchange "
"'amq.topic' in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session1, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^$">>, <<"^{vhost}\.{username}\.a\.b$">>),
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
{ok, Recv2} = amqp10_client:attach_receiver_link(
Session2, <<"receiver-2">>, SourceAddress),
receive {amqp10_event, {link, Recv2, attached}} -> ok
after ?TIMEOUT -> flush(missing_attached),
ct:fail("missing ATTACH from server")
end,
ok = close_connection_sync(Connection).
v1_attach_target_internal_exchange(Config) ->
XName = <<"test exchange">>,
Ch = rabbit_ct_client_helpers:open_channel(Config),
#'exchange.declare_ok'{} = amqp_channel:call(Ch, #'exchange.declare'{internal = true,
exchange = XName}),
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := anon},
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
Address = <<"/exchange/", XName/binary, "/some-routing-key">>,
{ok, Sender} = amqp10_client:attach_sender_link(
Session, <<"test-sender">>, Address),
ExpectedErr = error_unauthorized(
<<"forbidden to publish to internal exchange 'test exchange' in vhost '/'">>),
receive {amqp10_event, {link, Sender, {detached, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_event),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
ok = end_session_sync(Session),
ok = close_connection_sync(Connection),
#'exchange.delete_ok'{} = amqp_channel:call(Ch, #'exchange.delete'{exchange = XName}),
ok = rabbit_ct_client_helpers:close_channel(Ch).
attach_source_queue(Config) ->
{Conn, Session, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
Address = rabbitmq_amqp_address:queue(QName),
%% missing read permission to queue
ok = set_permissions(Config, QName, <<>>, <<>>),
{ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
{ok, _Recv} = amqp10_client:attach_receiver_link(Session, <<"receiver">>, Address),
ExpectedErr = error_unauthorized(
<<"read access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event,
{session, Session,
{ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_ended),
ct:fail("did not receive AMQP_ERROR_UNAUTHORIZED_ACCESS")
end,
ok = close_connection_sync(Conn).
attach_source_queue_dynamic_exclusive(Config) ->
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
%% missing configure permission to queue
ok = set_permissions(Config, <<>>, <<".*">>, <<".*">>),
Source = #{address => undefined,
dynamic => true,
capabilities => [<<"temporary-queue">>],
durable => none},
AttachArgs = #{name => <<"my link">>,
role => {receiver, Source, self()},
snd_settle_mode => unsettled,
rcv_settle_mode => first,
filter => #{}},
{ok, _Recv} = amqp10_client:attach_link(Session, AttachArgs),
receive {amqp10_event,
{session, Session,
{ended, Error}}} ->
#'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, Description}} = Error,
?assertEqual(
match,
re:run(Description,
<<"^configure access to queue 'amq\.dyn-.*' in vhost "
"'test vhost' refused for user 'test user'$">>,
[{capture, none}]))
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
attach_source_queue_dynamic_volatile(Config) ->
ok = rabbit_ct_broker_helpers:enable_feature_flag(Config, 'rabbitmq_4.2.0'),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
%% missing read permission on volatile queue
ok = set_permissions(Config, <<".*">>, <<".*">>, <<>>),
Source = #{address => undefined,
durable => none,
expiry_policy => <<"link-detach">>,
dynamic => true,
capabilities => [<<"rabbitmq:volatile-queue">>]},
AttachArgs = #{name => <<"receiver">>,
role => {receiver, Source, self()},
snd_settle_mode => settled,
rcv_settle_mode => first},
{ok, _Recv} = amqp10_client:attach_link(Session, AttachArgs),
receive {amqp10_event,
{session, Session,
{ended, Error}}} ->
#'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, Description}} = Error,
?assertEqual(
match,
re:run(Description,
<<"^read access to queue 'amq\.rabbitmq\.reply-to\..*' in vhost "
"'test vhost' refused for user 'test user'$">>,
[{capture, none}]))
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
attach_target_exchange(Config) ->
XName = <<"amq.fanout">>,
Address1 = rabbitmq_amqp_address:exchange(XName),
Address2 = rabbitmq_amqp_address:exchange(XName, <<"some-key">>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session1} = amqp10_client:begin_session_sync(Connection),
{ok, _} = amqp10_client:attach_sender_link(Session1, <<"test-sender">>, Address1),
ExpectedErr = error_unauthorized(
<<"write access to exchange '", XName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session1, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
{ok, _} = amqp10_client:attach_sender_link(Session2, <<"test-sender">>, Address2),
receive {amqp10_event, {session, Session2, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = amqp10_client:close_connection(Connection).
attach_target_topic_exchange(Config) ->
TargetAddress = rabbitmq_amqp_address:exchange(
<<"amq.topic">>, <<"test vhost.test user.a.b">>),
ok = send_to_topic(TargetAddress, Config).
attach_target_queue(Config) ->
{Conn, Session, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
Address = rabbitmq_amqp_address:queue(QName),
%% missing write permission to default exchange
ok = set_permissions(Config, QName, <<>>, <<>>),
{ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
{ok, _} = amqp10_client:attach_sender_link(Session, <<"sender">>, Address),
ExpectedErr = error_unauthorized(
<<"write access to exchange 'amq.default' ",
"in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = amqp10_client:close_connection(Conn).
attach_target_queue_dynamic_exchange_write(Config) ->
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
%% missing write permission to default exchange
ok = set_permissions(Config, <<".*">>, <<>>, <<".*">>),
Target = #{address => undefined,
dynamic => true,
capabilities => [<<"temporary-queue">>]},
AttachArgs = #{name => <<"my link">>,
role => {sender, Target},
snd_settle_mode => mixed,
rcv_settle_mode => first},
{ok, _Recv} = amqp10_client:attach_link(Session, AttachArgs),
ExpectedErr = error_unauthorized(
<<"write access to exchange 'amq.default' ",
"in vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
attach_target_queue_dynamic_queue_configure(Config) ->
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
%% missing configure permission to queue
ok = set_permissions(Config, <<>>, <<".*">>, <<".*">>),
Target = #{address => undefined,
dynamic => true,
capabilities => [<<"temporary-queue">>]},
AttachArgs = #{name => <<"my link">>,
role => {sender, Target},
snd_settle_mode => mixed,
rcv_settle_mode => first},
{ok, _Recv} = amqp10_client:attach_link(Session, AttachArgs),
receive {amqp10_event,
{session, Session,
{ended, Error}}} ->
#'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
description = {utf8, Description}} = Error,
?assertEqual(
match,
re:run(Description,
<<"^configure access to queue 'amq\.dyn-.*' in vhost "
"'test vhost' refused for user 'test user'$">>,
[{capture, none}]))
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
target_per_message_exchange(Config) ->
TargetAddress = null,
To1 = rabbitmq_amqp_address:exchange(<<"amq.fanout">>),
To2 = rabbitmq_amqp_address:queue(<<"q1">>),
%% missing write permission to default exchange
ok = set_permissions(Config, <<>>, <<"amq.fanout">>, <<>>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
{ok, Sender} = amqp10_client:attach_sender_link_sync(Session, <<"sender">>, TargetAddress),
ok = wait_for_credit(Sender),
%% We have sufficient authorization, but expect RELEASED since no queue is bound.
Tag1 = <<"dtag 1">>,
Msg1 = amqp10_msg:set_properties(#{to => To1}, amqp10_msg:new(Tag1, <<"m1">>)),
ok = amqp10_client:send_msg(Sender, Msg1),
receive {amqp10_disposition, {released, Tag1}} -> ok
after ?TIMEOUT -> ct:fail(released_timeout)
end,
%% We don't have sufficient authorization.
Tag2 = <<"dtag 2">>,
Msg2 = amqp10_msg:set_properties(#{to => To2}, amqp10_msg:new(Tag2, <<"m2">>)),
ok = amqp10_client:send_msg(Sender, Msg2),
ExpectedErr = error_unauthorized(
<<"write access to exchange 'amq.default' in "
"vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
target_per_message_internal_exchange(Config) ->
XName = <<"my internal exchange">>,
XProps = #{internal => true},
TargetAddress = null,
To = rabbitmq_amqp_address:exchange(XName),
ok = set_permissions(Config, XName, XName, <<>>),
{_, Session, LinkPair} = Init = init_pair(Config),
ok = rabbitmq_amqp_client:declare_exchange(LinkPair, XName, XProps),
{ok, Sender} = amqp10_client:attach_sender_link_sync(Session, <<"sender">>, TargetAddress),
ok = wait_for_credit(Sender),
Tag = <<"tag">>,
Msg = amqp10_msg:set_properties(#{to => To}, amqp10_msg:new(Tag, <<"msg">>, true)),
ok = amqp10_client:send_msg(Sender, Msg),
ExpectedErr = error_unauthorized(
<<"forbidden to publish to internal exchange '",
XName/binary, "' in vhost 'test vhost'">>),
receive {amqp10_event, {link, Sender, {detached, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_event),
ct:fail({missing_event, ?LINE})
end,
ok = rabbitmq_amqp_client:delete_exchange(LinkPair, XName),
ok = cleanup_pair(Init).
target_per_message_topic(Config) ->
TargetAddress = null,
To1 = rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<".a">>),
To2 = rabbitmq_amqp_address:exchange(<<"amq.topic">>, <<".a.b">>),
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
ok = set_topic_permissions(Config, <<"amq.topic">>, <<"^\.a$">>, <<"^$">>),
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
{ok, Sender} = amqp10_client:attach_sender_link_sync(Session, <<"sender">>, TargetAddress),
ok = wait_for_credit(Sender),
%% We have sufficient authorization, but expect RELEASED since no queue is bound.
Tag1 = <<"dtag 1">>,
Msg1 = amqp10_msg:set_properties(#{to => To1}, amqp10_msg:new(Tag1, <<"m1">>)),
ok = amqp10_client:send_msg(Sender, Msg1),
receive {amqp10_disposition, {released, Tag1}} -> ok
after ?TIMEOUT -> ct:fail(released_timeout)
end,
%% We don't have sufficient authorization.
Tag2 = <<"dtag 2">>,
Msg2 = amqp10_msg:set_properties(#{to => To2}, amqp10_msg:new(Tag2, <<"m2">>)),
ok = amqp10_client:send_msg(Sender, Msg2),
ExpectedErr = error_unauthorized(
<<"write access to topic '.a.b' in exchange 'amq.topic' in "
"vhost 'test vhost' refused for user 'test user'">>),
receive {amqp10_event, {session, Session, {ended, ExpectedErr}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(Connection).
authn_failure_event(Config) ->
ok = event_recorder:start(Config),
User = ?config(test_user, Config),
OpnConf0 = connection_config(Config),
OpnConf = maps:update(sasl, {plain, User, <<"wrong password">>}, OpnConf0),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, {closed, sasl_auth_failure}}} -> ok
after ?TIMEOUT -> flush(missing_closed),
ct:fail("did not receive sasl_auth_failure")
end,
[E | _] = event_recorder:get_events(Config),
ok = event_recorder:stop(Config),
Proto = case web_amqp(Config) of
true -> {'Web AMQP', {1, 0}};
false -> {1, 0}
end,
assert_event_type(user_authentication_failure, E),
assert_event_prop([{name, <<"test user">>},
{auth_mechanism, <<"PLAIN">>},
{ssl, false},
{protocol, Proto}],
E).
sasl_anonymous_success(Config) ->
Mechanism = anon,
ok = sasl_success(Mechanism, Config).
sasl_plain_success(Config) ->
Mechanism = {plain, <<"guest">>, <<"guest">>},
ok = sasl_success(Mechanism, Config).
sasl_success(Mechanism, Config) ->
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := Mechanism},
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, opened}} -> ok
after ?TIMEOUT -> ct:fail(missing_opened)
end,
ok = amqp10_client:close_connection(Connection).
sasl_anonymous_failure(Config) ->
App = rabbit,
Par = anonymous_login_user,
{ok, Default} = rpc(Config, application, get_env, [App, Par]),
%% Prohibit anonymous login.
ok = rpc(Config, application, set_env, [App, Par, none]),
Mechanism = anon,
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := Mechanism},
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, {closed, Reason}}} ->
?assertEqual({sasl_not_supported, Mechanism}, Reason)
after ?TIMEOUT -> ct:fail(missing_closed)
end,
ok = rpc(Config, application, set_env, [App, Par, Default]).
sasl_plain_failure(Config) ->
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := {plain, <<"guest">>, <<"wrong password">>}},
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, {closed, Reason}}} ->
?assertEqual(sasl_auth_failure, Reason)
after ?TIMEOUT -> ct:fail(missing_closed)
end.
%% Skipping SASL is disallowed in RabbitMQ.
sasl_none_failure(Config) ->
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := none},
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, {closed, _Reason}}} -> ok
after ?TIMEOUT -> ct:fail(missing_closed)
end.
vhost_absent(Config) ->
OpnConf = connection_config(Config, <<"this vhost does not exist">>),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, Connection, {closed, _}}} -> ok
after ?TIMEOUT -> ct:fail(missing_closed)
end.
vhost_connection_limit(Config) ->
Vhost = proplists:get_value(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_vhost_limit(Config, 0, Vhost, max_connections, 1),
OpnConf = connection_config(Config),
{ok, C1} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, C1, opened}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
{ok, C2} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, C2, {closed, _}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf1 = OpnConf0#{sasl := anon},
{ok, C3} = amqp10_client:open_connection(OpnConf1),
receive {amqp10_event, {connection, C3, opened}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
{ok, C4} = amqp10_client:open_connection(OpnConf1),
receive {amqp10_event, {connection, C4, opened}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
[ok = close_connection_sync(C) || C <- [C1, C3, C4]],
ok = rabbit_ct_broker_helpers:clear_vhost_limit(Config, 0, Vhost).
user_connection_limit(Config) ->
DefaultUser = <<"guest">>,
Limit = max_connections,
ok = rabbit_ct_broker_helpers:set_user_limits(Config, DefaultUser, #{Limit => 0}),
OpnConf0 = connection_config(Config, <<"/">>),
OpnConf = OpnConf0#{sasl := anon},
{ok, C1} = amqp10_client:open_connection(OpnConf),
receive {amqp10_event, {connection, C1, {closed, _}}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
{ok, C2} = amqp10_client:open_connection(connection_config(Config)),
receive {amqp10_event, {connection, C2, opened}} -> ok
after ?TIMEOUT -> ct:fail({missing_event, ?LINE})
end,
ok = close_connection_sync(C2),
ok = rabbit_ct_broker_helpers:clear_user_limits(Config, DefaultUser, Limit).
v1_vhost_queue_limit(Config) ->
Vhost = proplists:get_value(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_vhost_limit(Config, 0, Vhost, max_queues, 0),
QName = <<"q1">>,
ok = set_permissions(Config, QName, <<>>, <<>>),
OpnConf1 = connection_config(Config),
{ok, C1} = amqp10_client:open_connection(OpnConf1),
{ok, Session1} = amqp10_client:begin_session_sync(C1),
TargetAddress = <<"/queue/", QName/binary>>,
{ok, Sender1} = amqp10_client:attach_sender_link(
Session1, <<"test-sender-1">>, TargetAddress),
ExpectedErr = amqp_error(
?V_1_0_AMQP_ERROR_RESOURCE_LIMIT_EXCEEDED,
<<"cannot declare queue 'q1': queue limit in vhost 'test vhost' (0) is reached">>),
receive {amqp10_event, {link, Sender1, {detached, ExpectedErr}}} -> ok
after ?TIMEOUT -> flush(missing_event),
ct:fail("did not receive expected error")
end,
OpnConf2 = connection_config(Config, <<"/">>),
OpnConf3 = OpnConf2#{sasl := anon},
{ok, C2} = amqp10_client:open_connection(OpnConf3),
{ok, Session2} = amqp10_client:begin_session_sync(C2),
{ok, Sender2} = amqp10_client:attach_sender_link(
Session2, <<"test-sender-2">>, TargetAddress),
receive {amqp10_event, {link, Sender2, attached}} -> ok
after ?TIMEOUT -> flush(missing_attached),
ct:fail("missing ATTACH from server")
end,
ok = close_connection_sync(C1),
ok = close_connection_sync(C2),
ok = rabbit_ct_broker_helpers:clear_vhost_limit(Config, 0, Vhost).
declare_exchange(Config) ->
{Conn, _Session, LinkPair} = init_pair(Config),
XName = <<"📮"/utf8>>,
ExpectedErr = error_unauthorized(
<<"configure access to exchange '", XName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:declare_exchange(LinkPair, XName, #{})),
ok = close_connection_sync(Conn).
delete_exchange(Config) ->
{Conn, Session1, LinkPair1} = init_pair(Config),
XName = <<"📮"/utf8>>,
ok = set_permissions(Config, XName, <<>>, <<>>),
ok = rabbitmq_amqp_client:declare_exchange(LinkPair1, XName, #{}),
ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair1),
ok = amqp10_client:end_session(Session1),
ok = clear_permissions(Config),
{ok, Session2} = amqp10_client:begin_session_sync(Conn),
{ok, LinkPair2} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session2, <<"pair 2">>),
ExpectedErr = error_unauthorized(
<<"configure access to exchange '", XName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:delete_exchange(LinkPair2, XName)),
ok = close_connection_sync(Conn),
ok = set_permissions(Config, XName, <<>>, <<>>),
Init = {_, _, LinkPair3} = init_pair(Config),
ok = rabbitmq_amqp_client:delete_exchange(LinkPair3, XName),
ok = cleanup_pair(Init).
declare_queue(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
ExpectedErr = error_unauthorized(
<<"configure access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{})),
ok = close_connection_sync(Conn).
declare_queue_dlx_queue(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
DlxName = <<"📥"/utf8>>,
QProps = #{arguments => #{<<"x-dead-letter-exchange">> => {utf8, DlxName}}},
%% missing read permission to queue
ok = set_permissions(Config, QName, DlxName, <<>>),
ExpectedErr = error_unauthorized(
<<"read access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:declare_queue(LinkPair, QName, QProps)),
ok = close_connection_sync(Conn).
declare_queue_dlx_exchange(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
DlxName = <<"📥"/utf8>>,
QProps = #{arguments => #{<<"x-dead-letter-exchange">> => {utf8, DlxName}}},
%% missing write permission to dead letter exchange
ok = set_permissions(Config, QName, <<>>, QName),
ExpectedErr = error_unauthorized(
<<"write access to exchange '", DlxName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:declare_queue(LinkPair, QName, QProps)),
ok = close_connection_sync(Conn).
declare_queue_vhost_queue_limit(Config) ->
QName = <<"🍿"/utf8>>,
ok = set_permissions(Config, QName, <<>>, <<>>),
Vhost = proplists:get_value(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_vhost_limit(Config, 0, Vhost, max_queues, 0),
Init = {_, _, LinkPair} = init_pair(Config),
{error, Resp} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
?assertMatch(#{subject := <<"403">>}, amqp10_msg:properties(Resp)),
?assertEqual(
#'v1_0.amqp_value'{
content = {utf8, <<"cannot declare queue '", QName/binary, "': queue limit in vhost 'test vhost' (0) is reached">>}},
amqp10_msg:body(Resp)),
ok = cleanup_pair(Init),
ok = rabbit_ct_broker_helpers:clear_vhost_limit(Config, 0, Vhost).
delete_queue(Config) ->
{Conn, Session1, LinkPair1} = init_pair(Config),
QName = <<"🍿"/utf8>>,
ok = set_permissions(Config, QName, <<>>, <<>>),
{ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair1, QName, #{}),
ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair1),
ok = amqp10_client:end_session(Session1),
ok = clear_permissions(Config),
{ok, Session2} = amqp10_client:begin_session_sync(Conn),
{ok, LinkPair2} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session2, <<"pair 2">>),
ExpectedErr = error_unauthorized(
<<"configure access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:delete_queue(LinkPair2, QName)),
ok = close_connection_sync(Conn).
purge_queue(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = <<"🍿"/utf8>>,
%% missing read permission to queue
ok = set_permissions(Config, QName, <<>>, <<>>),
{ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
ExpectedErr = error_unauthorized(
<<"read access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:purge_queue(LinkPair, QName)),
ok = close_connection_sync(Conn).
bind_queue_source(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = atom_to_binary(?FUNCTION_NAME),
%% missing read permission to source exchange
ok = set_permissions(Config, QName, QName, QName),
{ok, #{}} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
XName = <<"amq.direct">>,
ExpectedErr = error_unauthorized(
<<"read access to exchange '", XName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:bind_queue(LinkPair, QName, XName, <<"key">>, #{})),
ok = close_connection_sync(Conn).
bind_queue_destination(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
QName = <<"my 🐇"/utf8>>,
XName = <<"amq.direct">>,
%% missing write permission to destination queue
ok = set_permissions(Config, QName, <<>>, XName),
{ok, #{}} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}),
ExpectedErr = error_unauthorized(
<<"write access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:bind_queue(LinkPair, QName, XName, <<"key">>, #{})),
ok = close_connection_sync(Conn).
bind_exchange_source(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
SrcXName = <<"amq.fanout">>,
DstXName = <<"amq.direct">>,
%% missing read permission to source exchange
ok = set_permissions(Config, <<>>, DstXName, <<>>),
ExpectedErr = error_unauthorized(
<<"read access to exchange '", SrcXName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:bind_exchange(LinkPair, DstXName, SrcXName, <<"key">>, #{})),
ok = close_connection_sync(Conn).
bind_exchange_destination(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
SrcXName = <<"amq.fanout">>,
DstXName = <<"amq.direct">>,
%% missing write permission to destination exchange
ok = set_permissions(Config, <<>>, <<>>, SrcXName),
ExpectedErr = error_unauthorized(
<<"write access to exchange '", DstXName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:bind_exchange(LinkPair, DstXName, SrcXName, <<"key">>, #{})),
ok = close_connection_sync(Conn).
bind_to_topic_exchange(Config) ->
{Conn, _, LinkPair} = init_pair(Config),
SrcXName = <<"amq.topic">>,
DstXName = <<"amq.direct">>,
Topic = <<"a.b.🐇"/utf8>>,
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
%% missing read permission to Topic
ok = set_topic_permissions(Config, SrcXName, <<".*">>, <<"wrong.topic">>),
ExpectedErr = error_unauthorized(
<<"read access to topic '", Topic/binary,
"' in exchange 'amq.topic' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:bind_exchange(LinkPair, DstXName, SrcXName, Topic, #{})),
ok = close_connection_sync(Conn).
unbind_queue_source(Config) ->
{Conn, Session1, LinkPair1} = init_pair(Config),
QName = BindingKey = atom_to_binary(?FUNCTION_NAME),
XName = <<"amq.direct">>,
ok = set_permissions(Config, QName, QName, XName),
{ok, #{}} = rabbitmq_amqp_client:declare_queue(LinkPair1, QName, #{}),
ok = rabbitmq_amqp_client:bind_queue(LinkPair1, QName, XName, BindingKey, #{}),
ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair1),
ok = amqp10_client:end_session(Session1),
%% remove read permission to source exchange
ok = set_permissions(Config, QName, QName, <<"^$">>),
{ok, Session2} = amqp10_client:begin_session_sync(Conn),
{ok, LinkPair2} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session2, <<"pair 2">>),
ExpectedErr = error_unauthorized(
<<"read access to exchange '", XName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:unbind_queue(LinkPair2, QName, XName, BindingKey, #{})),
ok = close_connection_sync(Conn).
unbind_queue_target(Config) ->
{Conn, Session1, LinkPair1} = init_pair(Config),
QName = BindingKey = atom_to_binary(?FUNCTION_NAME),
XName = <<"amq.direct">>,
ok = set_permissions(Config, QName, QName, XName),
{ok, #{}} = rabbitmq_amqp_client:declare_queue(LinkPair1, QName, #{}),
ok = rabbitmq_amqp_client:bind_queue(LinkPair1, QName, XName, BindingKey, #{}),
ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair1),
ok = amqp10_client:end_session(Session1),
%% remove write permission to destination queue
ok = set_permissions(Config, QName, <<"^$">>, XName),
{ok, Session2} = amqp10_client:begin_session_sync(Conn),
{ok, LinkPair2} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session2, <<"pair 2">>),
ExpectedErr = error_unauthorized(
<<"write access to queue '", QName/binary,
"' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:unbind_queue(LinkPair2, QName, XName, BindingKey, #{})),
ok = close_connection_sync(Conn).
unbind_from_topic_exchange(Config) ->
Init = {_, _, LinkPair1} = init_pair(Config),
SrcXName = <<"amq.topic">>,
DstXName = <<"amq.direct">>,
Topic = <<"a.b.🐇"/utf8>>,
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, Vhost),
ok = set_topic_permissions(Config, SrcXName, <<"^$">>, Topic),
ok = rabbitmq_amqp_client:bind_exchange(LinkPair1, DstXName, SrcXName, Topic, #{}),
%% remove Topic read permission
ok = set_topic_permissions(Config, SrcXName, <<"^$">>, <<"^$">>),
%% Start a new connection since topic permissions are cached by the AMQP session process.
ok = cleanup_pair(Init),
{Conn, _, LinkPair2} = init_pair(Config),
ExpectedErr = error_unauthorized(
<<"read access to topic '", Topic/binary,
"' in exchange 'amq.topic' in vhost 'test vhost' refused for user 'test user'">>),
?assertEqual({error, {session_ended, ExpectedErr}},
rabbitmq_amqp_client:unbind_exchange(LinkPair2, DstXName, SrcXName, Topic, #{})),
ok = close_connection_sync(Conn).
init_pair(Config) ->
OpnConf = connection_config(Config),
{ok, Connection} = amqp10_client:open_connection(OpnConf),
{ok, Session} = amqp10_client:begin_session_sync(Connection),
{ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"mgmt link pair">>),
{Connection, Session, LinkPair}.
cleanup_pair({Connection, Session, LinkPair}) ->
ok = rabbitmq_amqp_client:detach_management_link_pair_sync(LinkPair),
ok = amqp10_client:end_session(Session),
ok = amqp10_client:close_connection(Connection).
connection_config(Config) ->
Vhost = ?config(test_vhost, Config),
connection_config(Config, Vhost).
connection_config(Config, Vhost) ->
Cfg = amqp_utils:connection_config(Config),
User = Password = ?config(test_user, Config),
Cfg#{hostname => <<"vhost:", Vhost/binary>>,
sasl := {plain, User, Password}}.
set_permissions(Config, ConfigurePerm, WritePerm, ReadPerm) ->
ok = rabbit_ct_broker_helpers:set_permissions(Config,
?config(test_user, Config),
?config(test_vhost, Config),
ConfigurePerm,
WritePerm,
ReadPerm).
set_topic_permissions(Config, Exchange, WritePat, ReadPat) ->
ok = rpc(Config,
rabbit_auth_backend_internal,
set_topic_permissions,
[?config(test_user, Config),
?config(test_vhost, Config),
Exchange,
WritePat,
ReadPat,
<<"acting-user">>]).
clear_permissions(Config) ->
User = ?config(test_user, Config),
Vhost = ?config(test_vhost, Config),
ok = rabbit_ct_broker_helpers:clear_permissions(Config, User, Vhost),
ok = rpc(Config,
rabbit_auth_backend_internal,
clear_topic_permissions,
[User, Vhost, <<"acting-user">>]).
error_unauthorized(Description) ->
amqp_error(?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, Description).
amqp_error(Condition, Description)
when is_binary(Description) ->
#'v1_0.error'{
condition = Condition,
description = {utf8, Description}}.
delete_all_queues(Config) ->
Qs = rpc(Config, rabbit_amqqueue, list, []),
[{ok, _QLen} = rpc(Config, rabbit_amqqueue, delete, [Q, false, false, <<"fake-user">>])
|| Q <- Qs].