diff --git a/deps/rabbit/src/rabbit_auth_backend_internal.erl b/deps/rabbit/src/rabbit_auth_backend_internal.erl index 25c5852db055..622eafed85c2 100644 --- a/deps/rabbit/src/rabbit_auth_backend_internal.erl +++ b/deps/rabbit/src/rabbit_auth_backend_internal.erl @@ -17,7 +17,7 @@ -export([add_user/3, add_user/4, add_user/5, delete_user/2, lookup_user/1, exists/1, change_password/3, clear_password/2, hash_password/2, change_password_hash/2, change_password_hash/3, - set_tags/3, set_permissions/6, clear_permissions/3, clear_permissions_for_vhost/2, + set_tags/3, set_permissions/6, clear_permissions/3, clear_permissions_for_vhost/2, set_permissions_globally/5, set_topic_permissions/6, clear_topic_permissions/3, clear_topic_permissions/4, clear_topic_permissions_for_vhost/2, add_user_sans_validation/3, put_user/2, put_user/3, update_user/5, @@ -536,6 +536,11 @@ clear_permissions(Username, VirtualHost, ActingUser) -> clear_permissions_for_vhost(VirtualHost, _ActingUser) -> rabbit_db_user:clear_matching_user_permissions('_', VirtualHost). +set_permissions_globally(Username, ConfigurePerm, WritePerm, ReadPerm, ActingUser) -> + VirtualHosts = rabbit_vhost:list_names(), + [set_permissions(Username, VH, ConfigurePerm, WritePerm, ReadPerm, ActingUser) || VH <- VirtualHosts], + ok. + set_topic_permissions(Username, VirtualHost, Exchange, WritePerm, ReadPerm, ActingUser) -> rabbit_log:debug("Asked to set topic permissions on exchange '~ts' for " "user '~ts' in virtual host '~ts' to '~ts', '~ts'", diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_globally.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_globally.ex new file mode 100644 index 000000000000..f2f7ffc44c25 --- /dev/null +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/set_permissions_globally.ex @@ -0,0 +1,75 @@ +## 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. + +defmodule RabbitMQ.CLI.Ctl.Commands.SetPermissionsGloballyCommand do + alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers} + + @behaviour RabbitMQ.CLI.CommandBehaviour + + def merge_defaults(args, opts) do + {args, Map.merge(%{vhost: "/"}, opts)} + end + + def validate([], _) do + {:validation_failure, :not_enough_args} + end + + def validate([_ | _] = args, _) when length(args) < 4 do + {:validation_failure, :not_enough_args} + end + + def validate([_ | _] = args, _) when length(args) > 4 do + {:validation_failure, :too_many_args} + end + + def validate(_, _), do: :ok + + use RabbitMQ.CLI.Core.RequiresRabbitAppRunning + + def run([user, conf, write, read], %{node: node_name}) do + :rabbit_misc.rpc_call( + node_name, + :rabbit_auth_backend_internal, + :set_permissions_globally, + [user, conf, write, read, Helpers.cli_acting_user()] + ) + end + + def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do + {:error, + %{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}} + end + + def output({:error, {:no_such_user, username}}, _) do + {:error, ExitCodes.exit_nouser(), "User #{username} does not exist"} + end + + use RabbitMQ.CLI.DefaultOutput + + def usage, do: "set_permissions_globally " + + def usage_additional() do + [ + ["", "Self-explanatory"], + ["", "Configuration permission pattern"], + ["", "Write permission pattern"], + ["", "Read permission pattern"] + ] + end + + def usage_doc_guides() do + [ + DocGuide.access_control(), + DocGuide.virtual_hosts() + ] + end + + def help_section(), do: :access_control + def description(), do: "Sets user permissions for all virtual hosts." + + def banner([user | _], _opts), + do: "Setting permissions for user \"#{user}\" in all virtual hosts ..." +end diff --git a/deps/rabbitmq_cli/test/ctl/set_permissions_globally_command_test.exs b/deps/rabbitmq_cli/test/ctl/set_permissions_globally_command_test.exs new file mode 100644 index 000000000000..1bbd975909ed --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/set_permissions_globally_command_test.exs @@ -0,0 +1,115 @@ +## 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-2020 VMware, Inc. or its affiliates. All rights reserved. + +defmodule SetPermissionsGloballyCommandTest do + use ExUnit.Case, async: false + import TestHelper + + @command RabbitMQ.CLI.Ctl.Commands.SetPermissionsGloballyCommand + + @vhost1 "test1" + @vhost2 "test2" + @vhost3 "test3" + + @user "guest" + + setup_all do + RabbitMQ.CLI.Core.Distribution.start() + + for v <- [@vhost1, @vhost2, @vhost3], do: add_vhost(v) + + on_exit([], fn -> + for v <- [@vhost1, @vhost2, @vhost3], do: delete_vhost(v) + end) + + :ok + end + + setup context do + on_exit(context, fn -> + set_permissions_globally(context[:user], [".*", ".*", ".*"]) + end) + + { + :ok, + opts: %{ + node: get_rabbit_hostname() + } + } + end + + test "merge_defaults: defaults can be overridden" do + assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}} + assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}} + end + + test "validate: wrong number of arguments leads to an arg count error" do + assert @command.validate([], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args} + assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args} + + assert @command.validate(["not", "quite", "enough"], %{}) == + {:validation_failure, :not_enough_args} + + assert @command.validate(["this", "is", "way", "too", "many"], %{}) == + {:validation_failure, :too_many_args} + end + + @tag user: @user + test "run: a well-formed, host-specific command returns okay", context do + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", ".*"], + context[:opts] + ) == :ok + + p1 = Enum.find(list_permissions(@vhost1), fn x -> x[:user] == context[:user] end) + p2 = Enum.find(list_permissions(@vhost2), fn x -> x[:user] == context[:user] end) + p3 = Enum.find(list_permissions(@vhost3), fn x -> x[:user] == context[:user] end) + + assert p1[:configure] == "^#{context[:user]}-.*" + assert p2[:configure] == "^#{context[:user]}-.*" + assert p3[:configure] == "^#{context[:user]}-.*" + end + + test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do + opts = %{node: :jake@thedog, timeout: 200} + + assert match?({:badrpc, _}, @command.run([@user, ".*", ".*", ".*"], opts)) + end + + @tag user: "interloper" + test "run: an invalid user returns a no-such-user error", context do + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", ".*"], + context[:opts] + ) == {:error, {:no_such_user, context[:user]}} + end + + @tag user: @user + test "run: invalid regex patterns returns an error", context do + assert @command.run( + [context[:user], "^#{context[:user]}-.*", ".*", "*"], + context[:opts] + ) == {:error, {:invalid_regexp, '*', {'nothing to repeat', 0}}} + + # asserts that the failed command didn't change anything + p1 = Enum.find(list_permissions(@vhost1), fn x -> x[:user] == context[:user] end) + p2 = Enum.find(list_permissions(@vhost2), fn x -> x[:user] == context[:user] end) + p3 = Enum.find(list_permissions(@vhost3), fn x -> x[:user] == context[:user] end) + + assert p1 == [user: context[:user], configure: ".*", write: ".*", read: ".*"] + assert p2 == [user: context[:user], configure: ".*", write: ".*", read: ".*"] + assert p3 == [user: context[:user], configure: ".*", write: ".*", read: ".*"] + end + + @tag user: @user + test "banner", context do + vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]}) + + assert @command.banner([context[:user], "^#{context[:user]}-.*", ".*", ".*"], vhost_opts) =~ + ~r/Setting permissions for user \"#{context[:user]}\" in all virtual hosts \.\.\./ + end +end diff --git a/deps/rabbitmq_cli/test/test_helper.exs b/deps/rabbitmq_cli/test/test_helper.exs index 2f6013569379..c9d8c5599065 100644 --- a/deps/rabbitmq_cli/test/test_helper.exs +++ b/deps/rabbitmq_cli/test/test_helper.exs @@ -173,6 +173,16 @@ defmodule TestHelper do ]) end + def set_permissions_globally(user, [conf, write, read]) do + :rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_permissions_globally, [ + user, + conf, + write, + read, + "acting-user" + ]) + end + def list_policies(vhost) do :rpc.call(get_rabbit_hostname(), :rabbit_policy, :list_formatted, [vhost]) end