From bc261676e728dc60027709a90f4e2cc7ad61ec02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Wed, 16 Sep 2020 21:22:46 +0200 Subject: [PATCH] Add required sender checking and add more tests --- .../commands/CommandTree.java | 9 +- .../InvalidCommandSenderException.java | 69 ++++++++++++++ .../commands/CommandSuggestionsTest.java | 89 +++++++++++++++++++ .../commands/CommandTreeTest.java | 25 ++++-- .../commands/BukkitCommandManager.java | 4 +- .../BukkitPluginRegistrationHandler.java | 13 +-- .../commands/PaperCommandManager.java | 2 +- 7 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidCommandSenderException.java create mode 100644 cloud-core/src/test/java/com/intellectualsites/commands/CommandSuggestionsTest.java diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java index 4b49460c..d91a6ba6 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -29,6 +29,7 @@ import com.intellectualsites.commands.components.parser.ComponentParseResult; import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.exceptions.AmbiguousNodeException; import com.intellectualsites.commands.exceptions.ComponentParseException; +import com.intellectualsites.commands.exceptions.InvalidCommandSenderException; import com.intellectualsites.commands.exceptions.InvalidSyntaxException; import com.intellectualsites.commands.exceptions.NoCommandInLeafException; import com.intellectualsites.commands.exceptions.NoPermissionException; @@ -101,7 +102,13 @@ public final class CommandTree { public Optional> parse(@Nonnull final CommandContext commandContext, @Nonnull final Queue args) throws NoSuchCommandException, NoPermissionException, InvalidSyntaxException { - return parseCommand(commandContext, args, this.internalTree); + final Optional> commandOptional = parseCommand(commandContext, args, this.internalTree); + commandOptional.flatMap(Command::getSenderType).ifPresent(requiredType -> { + if (!requiredType.isAssignableFrom(commandContext.getSender().getClass())) { + throw new InvalidCommandSenderException(commandContext.getSender(), requiredType, Collections.emptyList()); + } + }); + return commandOptional; } private Optional> parseCommand(@Nonnull final CommandContext commandContext, diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidCommandSenderException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidCommandSenderException.java new file mode 100644 index 00000000..971f56dc --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidCommandSenderException.java @@ -0,0 +1,69 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands.exceptions; + +import com.intellectualsites.commands.components.CommandComponent; +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Exception thrown when an invalid command sender tries to execute a command + */ +public final class InvalidCommandSenderException extends CommandParseException { + + private final Class requiredSender; + + /** + * Construct a new command parse exception + * + * @param commandSender Sender who executed the command + * @param requiredSender The sender type that is required + * @param currentChain Chain leading up to the exception + */ + public InvalidCommandSenderException(@Nonnull final CommandSender commandSender, + @Nonnull final Class requiredSender, + @Nonnull final List> currentChain) { + super(commandSender, currentChain); + this.requiredSender = requiredSender; + } + + /** + * Get the required sender type + * + * @return Required sender type + */ + @Nonnull + public Class getRequiredSender() { + return this.requiredSender; + } + + @Override + public String getMessage() { + return String.format("%s is not allowed to execute that command. Must be of type %s", + getCommandSender().toString(), + requiredSender.getSimpleName()); + } +} diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandSuggestionsTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandSuggestionsTest.java new file mode 100644 index 00000000..d25e261b --- /dev/null +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandSuggestionsTest.java @@ -0,0 +1,89 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands; + +import com.intellectualsites.commands.components.StaticComponent; +import com.intellectualsites.commands.components.standard.EnumComponent; +import com.intellectualsites.commands.components.standard.StringComponent; +import com.intellectualsites.commands.meta.SimpleCommandMeta; +import com.intellectualsites.commands.sender.CommandSender; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class CommandSuggestionsTest { + + private static CommandManager manager; + + @BeforeAll + static void setupManager() { + manager = new TestCommandManager(); + manager.command(manager.commandBuilder("test").component(StaticComponent.required("one")).build()); + manager.command(manager.commandBuilder("test").component(StaticComponent.required("two")).build()); + manager.command(manager.commandBuilder("test") + .component(StaticComponent.required("var")) + .component(StringComponent.newBuilder("str") + .withSuggestionsProvider((c, s) -> Arrays.asList("one", "two")) + .build()) + .component(EnumComponent.required(TestEnum.class, "enum")) + .build()); + } + + @Test + void testSimple() { + final String input = "test"; + final List suggestions = manager.suggest(new TestCommandSender(), input); + Assertions.assertEquals(Arrays.asList("one", "two", "var"), suggestions); + } + + @Test + void testVar() { + final String input = "test var"; + final List suggestions = manager.suggest(new TestCommandSender(), input); + Assertions.assertEquals(Arrays.asList("one", "two"), suggestions); + final String input2 = "test var one"; + final List suggestions2 = manager.suggest(new TestCommandSender(), input2); + Assertions.assertEquals(Arrays.asList("foo", "bar"), suggestions2); + final String input3 = "test var one f"; + final List suggestions3 = manager.suggest(new TestCommandSender(), input3); + Assertions.assertEquals(Collections.singletonList("foo"), suggestions3); + } + + @Test + void testEmpty() { + final String input = "kenny"; + final List suggestions = manager.suggest(new TestCommandSender(), input); + Assertions.assertTrue(suggestions.isEmpty()); + } + + + public enum TestEnum { + FOO, BAR + } + +} diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java index bfe0bef0..ef2ab441 100644 --- a/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.Optional; +import java.util.concurrent.CompletionException; class CommandTreeTest { @@ -47,15 +48,16 @@ class CommandTreeTest { static void newTree() { commandManager = new TestCommandManager(); commandManager.command(commandManager.commandBuilder("test", SimpleCommandMeta.empty()) - .component(StaticComponent.required("one")).build()) + .component(StaticComponent.required("one")).build()) .command(commandManager.commandBuilder("test", SimpleCommandMeta.empty()) - .component(StaticComponent.required("two")).withPermission("no").build()) + .component(StaticComponent.required("two")).withPermission("no").build()) .command(commandManager.commandBuilder("test", Collections.singleton("other"), - SimpleCommandMeta.empty()) - .component(StaticComponent.required("opt", "öpt")) - .component(IntegerComponent - .optional("num", EXPECTED_INPUT_NUMBER)) - .build()); + SimpleCommandMeta.empty()) + .component(StaticComponent.required("opt", "öpt")) + .component(IntegerComponent + .optional("num", EXPECTED_INPUT_NUMBER)) + .build()) + .command(commandManager.commandBuilder("req").withSenderType(SpecificCommandSender.class).build()); } @Test @@ -94,4 +96,13 @@ class CommandTreeTest { Collections.singletonList("test"))).isEmpty()); } + @Test + void testRequiredSender() { + Assertions.assertThrows(CompletionException.class, () -> + commandManager.executeCommand(new TestCommandSender(), "req").join()); + } + + public static final class SpecificCommandSender extends TestCommandSender { + } + } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommandManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommandManager.java index ce90f1ab..81b524b6 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommandManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommandManager.java @@ -58,8 +58,8 @@ public class BukkitCommandManager> commandExecutionCoordinator, @Nonnull final Function commandSenderMapper) throws Exception { - super(commandExecutionCoordinator, new BukkitPluginRegistrationHandler()); - ((BukkitPluginRegistrationHandler) this.getCommandRegistrationHandler()).initialize(this); + super(commandExecutionCoordinator, new BukkitPluginRegistrationHandler<>()); + ((BukkitPluginRegistrationHandler) this.getCommandRegistrationHandler()).initialize(this); this.owningPlugin = owningPlugin; this.commandSenderMapper = commandSenderMapper; diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitPluginRegistrationHandler.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitPluginRegistrationHandler.java index 79a8d92d..b8c3fea2 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitPluginRegistrationHandler.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitPluginRegistrationHandler.java @@ -25,6 +25,7 @@ package com.intellectualsites.commands; import com.intellectualsites.commands.components.CommandComponent; import com.intellectualsites.commands.internal.CommandRegistrationHandler; +import com.intellectualsites.commands.sender.CommandSender; import org.bukkit.Bukkit; import org.bukkit.command.CommandMap; import org.bukkit.command.SimpleCommandMap; @@ -36,18 +37,18 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -final class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { +final class BukkitPluginRegistrationHandler implements CommandRegistrationHandler { private final Map, org.bukkit.command.Command> registeredCommands = new HashMap<>(); private Map bukkitCommands; - private BukkitCommandManager bukkitCommandManager; + private BukkitCommandManager bukkitCommandManager; private CommandMap commandMap; BukkitPluginRegistrationHandler() { } - void initialize(@Nonnull final BukkitCommandManager bukkitCommandManager) throws Exception { + void initialize(@Nonnull final BukkitCommandManager bukkitCommandManager) throws Exception { final Method getCommandMap = Bukkit.getServer().getClass().getDeclaredMethod("getCommandMap"); getCommandMap.setAccessible(true); this.commandMap = (CommandMap) getCommandMap.invoke(Bukkit.getServer()); @@ -73,9 +74,9 @@ final class BukkitPluginRegistrationHandler implements CommandRegistrationHandle } else { label = commandComponent.getName(); } - @SuppressWarnings("unchecked") final BukkitCommand bukkitCommand = new BukkitCommand( - (Command) command, - (CommandComponent) commandComponent, + @SuppressWarnings("unchecked") final BukkitCommand bukkitCommand = new BukkitCommand<>( + (Command) command, + (CommandComponent) commandComponent, this.bukkitCommandManager); this.registeredCommands.put(commandComponent, bukkitCommand); this.commandMap.register(commandComponent.getName(), this.bukkitCommandManager.getOwningPlugin().getName().toLowerCase(), diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java index 95931fe4..1c5ed01b 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java @@ -69,7 +69,7 @@ public class PaperCommandManager