diff --git a/lib/cog/commands/role.ex b/lib/cog/commands/role.ex index c940e77d..0df458ca 100644 --- a/lib/cog/commands/role.ex +++ b/lib/cog/commands/role.ex @@ -2,7 +2,7 @@ defmodule Cog.Commands.Role do use Cog.Command.GenCommand.Base, bundle: Cog.embedded_bundle require Cog.Commands.Helpers, as: Helpers - alias Cog.Commands.Role.{Create, Delete, Grant, Info, List, Revoke} + alias Cog.Commands.Role.{Create, Delete, Grant, Info, List, Rename, Revoke} Helpers.usage :root, """ Manipulate authorization roles. @@ -19,6 +19,7 @@ defmodule Cog.Commands.Role do grant Grant a role to a group info Get detailed information about a specific role list List all roles (default) + rename Rename a role revoke Revoke a role from a group """ @@ -34,6 +35,7 @@ defmodule Cog.Commands.Role do rule "when command is #{Cog.embedded_bundle}:role with arg[0] == info must have #{Cog.embedded_bundle}:manage_roles" rule "when command is #{Cog.embedded_bundle}:role with arg[0] == list must have #{Cog.embedded_bundle}:manage_roles" rule "when command is #{Cog.embedded_bundle}:role with arg[0] == grant must have #{Cog.embedded_bundle}:manage_groups" + rule "when command is #{Cog.embedded_bundle}:role with arg[0] == rename must have #{Cog.embedded_bundle}:manage_roles" rule "when command is #{Cog.embedded_bundle}:role with arg[0] == revoke must have #{Cog.embedded_bundle}:manage_groups" def handle_message(req, state) do @@ -45,6 +47,7 @@ defmodule Cog.Commands.Role do "grant" -> Grant.grant(req, args) "info" -> Info.info(req, args) "list" -> List.list(req, args) + "rename" -> Rename.rename(req, args) "revoke" -> Revoke.revoke(req, args) nil -> if Helpers.flag?(req.options, "help") do diff --git a/lib/cog/commands/role/rename.ex b/lib/cog/commands/role/rename.ex new file mode 100644 index 00000000..933ddc25 --- /dev/null +++ b/lib/cog/commands/role/rename.ex @@ -0,0 +1,48 @@ +defmodule Cog.Commands.Role.Rename do + require Cog.Commands.Helpers, as: Helpers + + alias Cog.Repository.Roles + alias Cog.Models.Role + + Helpers.usage """ + Rename a role + + USAGE + role rename [FLAGS] + + ARGS + name The role to rename + new-name The name you want to change to + + FLAGS + -h, --help Display this usage info + + EXAMPLES + + role rename aws-admin cloud-commander + """ + + def rename(%{options: %{"help" => true}}, _args), + do: show_usage + def rename(_req, [old, new]) when is_binary(old) and is_binary(new) do + case Roles.by_name(old) do + %Role{}=role -> + case Roles.rename(role, new) do + {:ok, role} -> + rendered = Cog.V1.RoleView.render("show.json", %{role: role}) + {:ok, "role-rename", Map.put(rendered[:role], :old_name, old)} + {:error, _}=error -> + error + end + nil -> + {:error, {:resource_not_found, "role", old}} + end + end + def rename(_, [_,_]), + do: {:error, :wrong_type} + def rename(_, args) do + error = if length(args) > 2, do: :too_many_args, else: :not_enough_args + {:error, {error, 2}} + end + +end diff --git a/lib/cog/repository/roles.ex b/lib/cog/repository/roles.ex index e5ba4cb9..4c1e2f47 100644 --- a/lib/cog/repository/roles.ex +++ b/lib/cog/repository/roles.ex @@ -44,6 +44,20 @@ defmodule Cog.Repository.Roles do preload(role) end + # We don't (yet) have need of general update + def rename(%Role{name: unquote(Cog.admin_role)=name}, _), + do: {:error, {:protected_role, name}} + def rename(%Role{}=role, new_name) do + case role + |> Role.changeset(%{name: new_name}) + |> Repo.update do + {:ok, role} -> + {:ok, preload(role)} + {:error, _}=error -> + error + end + end + ######################################################################## defp preload(role_or_roles), diff --git a/lib/cog/templates/slack/role-rename.mustache b/lib/cog/templates/slack/role-rename.mustache new file mode 100644 index 00000000..970c2085 --- /dev/null +++ b/lib/cog/templates/slack/role-rename.mustache @@ -0,0 +1 @@ +Renamed role `{{old_name}}` to `{{name}}` diff --git a/test/integration/commands/role_test.exs b/test/integration/commands/role_test.exs index 6c41bcef..dc6bfaeb 100644 --- a/test/integration/commands/role_test.exs +++ b/test/integration/commands/role_test.exs @@ -4,6 +4,8 @@ defmodule Integration.Commands.RoleTest do alias Cog.Repository.Roles alias Cog.Repository.Groups + alias Cog.Models.Role + import DatabaseAssertions, only: [assert_role_is_granted: 2, refute_role_is_granted: 2] @@ -283,6 +285,51 @@ defmodule Integration.Commands.RoleTest do assert_error_message_contains(response , "Unknown subcommand 'do-something'") end + test "renaming a role works", %{user: user} do + %Role{id: id} = role("foo") + + [payload] = payload_from(user, "operable:role rename foo bar") + assert %{id: ^id, + name: "bar", + old_name: "foo"} = payload + + refute Roles.by_name("foo") + assert %Role{id: ^id} = Roles.by_name("bar") + end + + test "the cog-admin role cannot be renamed", %{user: user} do + response = send_message(user, "@bot: operable:role rename cog-admin monkeys") + assert_error_message_contains(response , "Cannot alter protected role cog-admin") + end + + test "renaming a non-existent role fails", %{user: user} do + response = send_message(user, "@bot: operable:role rename not-here monkeys") + assert_error_message_contains(response , "Could not find 'role' with the name 'not-here'") + end + + test "renaming to an already-existing role fails", %{user: user} do + role("foo") + role("bar") + + response = send_message(user, "@bot: operable:role rename foo bar") + assert_error_message_contains(response , "name has already been taken") + end + + test "renaming requires a new name", %{user: user} do + response = send_message(user, "@bot: operable:role rename foo") + assert_error_message_contains(response , "Not enough args. Arguments required: exactly 2") + end + + test "rename requires a role and a name", %{user: user} do + response = send_message(user, "@bot: operable:role rename") + assert_error_message_contains(response , "Not enough args. Arguments required: exactly 2") + end + + test "renaming requires string arguments", %{user: user} do + response = send_message(user, "@bot: operable:role rename 123 456") + assert_error_message_contains(response , "Arguments must be strings") + end + ######################################################################## # TODO: pull this out to adapter test