From e8a1a9a6cfaeccf89137562bd3aa62fbbe1ba250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Thu, 24 Sep 2020 23:18:22 +0200 Subject: [PATCH] Replace command permissions with a new smarter permission system that allows for compound permissions --- .../intellectualsites/commands/Command.java | 34 ++++-- .../commands/CommandManager.java | 26 ++++ .../commands/CommandTree.java | 38 +++--- .../exceptions/NoPermissionException.java | 7 +- .../permission/CommandPermission.java | 51 ++++++++ .../commands/permission/OrPermission.java | 96 +++++++++++++++ .../commands/permission/Permission.java | 112 ++++++++++++++++++ .../commands/permission/package-info.java | 28 +++++ .../commands/CommandPermissionTest.java | 71 +++++++++++ .../brigadier/CloudBrigadierManager.java | 20 ++-- .../commands/bukkit/BukkitCommand.java | 2 +- .../bukkit/CloudCommodoreManager.java | 3 +- .../commands/bungee/BungeeCommand.java | 2 +- .../cloudburst/CloudburstCommand.java | 2 +- .../paper/PaperBrigadierListener.java | 4 +- 15 files changed, 455 insertions(+), 41 deletions(-) create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/permission/CommandPermission.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/permission/OrPermission.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/permission/Permission.java create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/permission/package-info.java create mode 100644 cloud-core/src/test/java/com/intellectualsites/commands/CommandPermissionTest.java diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/Command.java b/cloud-core/src/main/java/com/intellectualsites/commands/Command.java index 0509e73d..eb7dabdf 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/Command.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/Command.java @@ -28,6 +28,8 @@ import com.intellectualsites.commands.arguments.StaticArgument; import com.intellectualsites.commands.execution.CommandExecutionHandler; import com.intellectualsites.commands.meta.CommandMeta; import com.intellectualsites.commands.meta.SimpleCommandMeta; +import com.intellectualsites.commands.permission.CommandPermission; +import com.intellectualsites.commands.permission.Permission; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,7 +52,7 @@ public class Command { @Nonnull private final Map, Description> arguments; @Nonnull private final CommandExecutionHandler commandExecutionHandler; @Nullable private final Class senderType; - @Nonnull private final String commandPermission; + @Nonnull private final CommandPermission commandPermission; @Nonnull private final CommandMeta commandMeta; /** @@ -65,7 +67,7 @@ public class Command { public Command(@Nonnull final Map, Description> commandArguments, @Nonnull final CommandExecutionHandler commandExecutionHandler, @Nullable final Class senderType, - @Nonnull final String commandPermission, + @Nonnull final CommandPermission commandPermission, @Nonnull final CommandMeta commandMeta) { this.arguments = Objects.requireNonNull(commandArguments, "Command arguments may not be null"); if (this.arguments.size() == 0) { @@ -103,7 +105,7 @@ public class Command { @Nonnull final CommandExecutionHandler commandExecutionHandler, @Nullable final Class senderType, @Nonnull final CommandMeta commandMeta) { - this(commandArguments, commandExecutionHandler, senderType, "", commandMeta); + this(commandArguments, commandExecutionHandler, senderType, Permission.empty(), commandMeta); } /** @@ -116,7 +118,7 @@ public class Command { */ public Command(@Nonnull final Map, Description> commandArguments, @Nonnull final CommandExecutionHandler commandExecutionHandler, - @Nonnull final String commandPermission, + @Nonnull final CommandPermission commandPermission, @Nonnull final CommandMeta commandMeta) { this(commandArguments, commandExecutionHandler, null, commandPermission, commandMeta); } @@ -140,7 +142,7 @@ public class Command { final Map, Description> map = new LinkedHashMap<>(); map.put(StaticArgument.required(commandName, aliases), description); return new Builder<>(null, commandMeta, null, map, - new CommandExecutionHandler.NullCommandExecutionHandler<>(), ""); + new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty()); } /** @@ -160,7 +162,7 @@ public class Command { final Map, Description> map = new LinkedHashMap<>(); map.put(StaticArgument.required(commandName, aliases), Description.empty()); return new Builder<>(null, commandMeta, null, map, - new CommandExecutionHandler.NullCommandExecutionHandler<>(), ""); + new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty()); } /** @@ -199,7 +201,7 @@ public class Command { * @return Command permission */ @Nonnull - public String getCommandPermission() { + public CommandPermission getCommandPermission() { return this.commandPermission; } @@ -237,7 +239,7 @@ public class Command { @Nonnull private final Map, Description> commandArguments; @Nonnull private final CommandExecutionHandler commandExecutionHandler; @Nullable private final Class senderType; - @Nonnull private final String commandPermission; + @Nonnull private final CommandPermission commandPermission; @Nullable private final CommandManager commandManager; private Builder(@Nullable final CommandManager commandManager, @@ -245,7 +247,7 @@ public class Command { @Nullable final Class senderType, @Nonnull final Map, Description> commandArguments, @Nonnull final CommandExecutionHandler commandExecutionHandler, - @Nonnull final String commandPermission) { + @Nonnull final CommandPermission commandPermission) { this.commandManager = commandManager; this.senderType = senderType; this.commandArguments = Objects.requireNonNull(commandArguments, "Arguments may not be null"); @@ -391,11 +393,23 @@ public class Command { * @return New builder instance using the command permission */ @Nonnull - public Builder withPermission(@Nonnull final String permission) { + public Builder withPermission(@Nonnull final CommandPermission permission) { return new Builder<>(this.commandManager, this.commandMeta, this.senderType, this.commandArguments, this.commandExecutionHandler, permission); } + /** + * Specify a command permission + * + * @param permission Command permission + * @return New builder instance using the command permission + */ + @Nonnull + public Builder withPermission(@Nonnull final String permission) { + return new Builder<>(this.commandManager, this.commandMeta, this.senderType, this.commandArguments, + this.commandExecutionHandler, Permission.of(permission)); + } + /** * Build a command using the builder instance * diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java index 81f855ae..4376f851 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -46,6 +46,9 @@ import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessin import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor; import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.meta.CommandMeta; +import com.intellectualsites.commands.permission.CommandPermission; +import com.intellectualsites.commands.permission.OrPermission; +import com.intellectualsites.commands.permission.Permission; import com.intellectualsites.services.ServicePipeline; import com.intellectualsites.services.State; @@ -210,6 +213,29 @@ public abstract class CommandManager { this.commandRegistrationHandler = commandRegistrationHandler; } + /** + * Check if the command sender has the required permission. If the permission node is + * empty, this should return {@code true} + * + * @param sender Command sender + * @param permission Permission node + * @return {@code true} if the sender has the permission, else {@code false} + */ + public boolean hasPermission(@Nonnull final C sender, @Nonnull final CommandPermission permission) { + if (permission instanceof Permission) { + return hasPermission(sender, permission.toString()); + } + for (final CommandPermission innerPermission : permission.getPermissions()) { + final boolean hasPermission = this.hasPermission(sender, innerPermission); + if (permission instanceof OrPermission) { + if (hasPermission) { + return true; + } + } + } + return !(permission instanceof OrPermission); + } + /** * Check if the command sender has the required permission. If the permission node is * empty, this should return {@code true} 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 0d30ed72..5816c020 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -34,10 +34,13 @@ import com.intellectualsites.commands.exceptions.InvalidSyntaxException; import com.intellectualsites.commands.exceptions.NoCommandInLeafException; import com.intellectualsites.commands.exceptions.NoPermissionException; import com.intellectualsites.commands.exceptions.NoSuchCommandException; +import com.intellectualsites.commands.permission.CommandPermission; +import com.intellectualsites.commands.permission.OrPermission; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -130,7 +133,7 @@ public final class CommandTree { @Nonnull final CommandContext commandContext, @Nonnull final Queue commandQueue, @Nonnull final Node> root) { - String permission = this.isPermitted(commandContext.getSender(), root); + CommandPermission permission = this.isPermitted(commandContext.getSender(), root); if (permission != null) { throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(root) .stream() @@ -213,7 +216,7 @@ public final class CommandTree { @Nonnull final CommandContext commandContext, @Nonnull final Node> root, @Nonnull final Queue commandQueue) { - String permission; + CommandPermission permission; final List>> children = root.getChildren(); if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticArgument)) { // The value has to be a variable @@ -412,8 +415,8 @@ public final class CommandTree { } @Nullable - private String isPermitted(@Nonnull final C sender, @Nonnull final Node> node) { - final String permission = node.nodeMeta.get("permission"); + private CommandPermission isPermitted(@Nonnull final C sender, @Nonnull final Node> node) { + final CommandPermission permission = (CommandPermission) node.nodeMeta.get("permission"); if (permission != null) { return this.commandManager.hasPermission(sender, permission) ? null : permission; } @@ -422,22 +425,24 @@ public final class CommandTree { Objects.requireNonNull( Objects.requireNonNull(node.value, "node.value").getOwningCommand(), "owning command").getCommandPermission()) - ? null : Objects.requireNonNull(node.value.getOwningCommand(), "owning command").getCommandPermission(); + ? null : Objects.requireNonNull(node.value.getOwningCommand(), "owning command") + .getCommandPermission(); } /* if any of the children would permit the execution, then the sender has a valid chain to execute, and so we allow them to execute the root */ - final List missingPermissions = new LinkedList<>(); + final List missingPermissions = new LinkedList<>(); for (final Node> child : node.getChildren()) { - final String check = this.isPermitted(sender, child); + final CommandPermission check = this.isPermitted(sender, child); if (check == null) { return null; } else { missingPermissions.add(check); } } - return String.join(", ", missingPermissions); + + return OrPermission.of(missingPermissions); } /** @@ -466,20 +471,23 @@ public final class CommandTree { // Register command permissions this.getLeavesRaw(this.internalTree).forEach(node -> { + // noinspection all + final CommandPermission commandPermission = node.getValue().getOwningCommand().getCommandPermission(); /* All leaves must necessarily have an owning command */ // noinspection all - node.nodeMeta.put("permission", node.getValue().getOwningCommand().getCommandPermission()); + node.nodeMeta.put("permission", commandPermission); // Get chain and order it tail->head then skip the tail (leaf node) List>> chain = this.getChain(node); Collections.reverse(chain); chain = chain.subList(1, chain.size()); // Go through all nodes from the tail upwards until a collision occurs for (final Node> commandArgumentNode : chain) { - if (commandArgumentNode.nodeMeta.containsKey("permission") - && !commandArgumentNode.nodeMeta.get("permission").equalsIgnoreCase(node.nodeMeta.get("permission"))) { - commandArgumentNode.nodeMeta.put("permission", ""); + final CommandPermission existingPermission = (CommandPermission) commandArgumentNode.nodeMeta.get("permission"); + if (existingPermission != null) { + commandArgumentNode.nodeMeta.put("permission", + OrPermission.of(Arrays.asList(commandPermission, existingPermission))); } else { - commandArgumentNode.nodeMeta.put("permission", node.nodeMeta.get("permission")); + commandArgumentNode.nodeMeta.put("permission", commandPermission); } } }); @@ -589,7 +597,7 @@ public final class CommandTree { */ public static final class Node { - private final Map nodeMeta = new HashMap<>(); + private final Map nodeMeta = new HashMap<>(); private final List> children = new LinkedList<>(); private final T value; private Node parent; @@ -640,7 +648,7 @@ public final class CommandTree { * @return Node meta */ @Nonnull - public Map getNodeMeta() { + public Map getNodeMeta() { return this.nodeMeta; } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java index cb69b053..3bc00b73 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java @@ -24,6 +24,7 @@ package com.intellectualsites.commands.exceptions; import com.intellectualsites.commands.arguments.CommandArgument; +import com.intellectualsites.commands.permission.CommandPermission; import javax.annotation.Nonnull; import java.util.List; @@ -35,7 +36,7 @@ import java.util.List; @SuppressWarnings("unused") public class NoPermissionException extends CommandParseException { - private final String missingPermission; + private final CommandPermission missingPermission; /** * Construct a new no permission exception @@ -44,7 +45,7 @@ public class NoPermissionException extends CommandParseException { * @param commandSender Command sender * @param currentChain Chain leading up to the exception */ - public NoPermissionException(@Nonnull final String missingPermission, + public NoPermissionException(@Nonnull final CommandPermission missingPermission, @Nonnull final Object commandSender, @Nonnull final List> currentChain) { super(commandSender, currentChain); @@ -63,7 +64,7 @@ public class NoPermissionException extends CommandParseException { */ @Nonnull public String getMissingPermission() { - return this.missingPermission; + return this.missingPermission.toString(); } @Override diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/permission/CommandPermission.java b/cloud-core/src/main/java/com/intellectualsites/commands/permission/CommandPermission.java new file mode 100644 index 00000000..f58a2e61 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/permission/CommandPermission.java @@ -0,0 +1,51 @@ +// +// 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.permission; + +import javax.annotation.Nonnull; +import java.util.Collection; + +/** + * A command permission string + */ +public interface CommandPermission { + + /** + * Get the permission nodes + * + * @return Permission nodes + */ + @Nonnull + Collection getPermissions(); + + /** + * Get a string representation of the permission + * + * @return String representation of the permission node + */ + @Override + @Nonnull + String toString(); + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/permission/OrPermission.java b/cloud-core/src/main/java/com/intellectualsites/commands/permission/OrPermission.java new file mode 100644 index 00000000..54814247 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/permission/OrPermission.java @@ -0,0 +1,96 @@ +// +// 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.permission; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; + +/** + * Accepts as long as at least one of the permissions is accepted + */ +public final class OrPermission implements CommandPermission { + + private final Collection permissions; + + private OrPermission(@Nonnull final Collection permissions) { + this.permissions = permissions; + } + + /** + * Create a new OR permission + * + * @param permissions Permissions to join + * @return Constructed permission + */ + @Nonnull + public static CommandPermission of(@Nonnull final Collection permissions) { + final Set permissionSet = new HashSet<>(); + for (final CommandPermission permission : permissions) { + permissionSet.addAll(permission.getPermissions()); + } + return new OrPermission(permissionSet); + } + + @Nonnull + @Override + public Collection getPermissions() { + return this.permissions; + } + + @Override + public String toString() { + final StringBuilder stringBuilder = new StringBuilder(); + final Iterator iterator = this.permissions.iterator(); + while (iterator.hasNext()) { + final CommandPermission permission = iterator.next(); + stringBuilder.append('(').append(permission.toString()).append(')'); + if (iterator.hasNext()) { + stringBuilder.append('|'); + } + } + return stringBuilder.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final OrPermission that = (OrPermission) o; + return Objects.equals(getPermissions(), that.getPermissions()); + } + + @Override + public int hashCode() { + return Objects.hash(getPermissions()); + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/permission/Permission.java b/cloud-core/src/main/java/com/intellectualsites/commands/permission/Permission.java new file mode 100644 index 00000000..88402e92 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/permission/Permission.java @@ -0,0 +1,112 @@ +// +// 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.permission; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** + * {@link com.intellectualsites.commands.arguments.CommandArgument} permission + */ +public final class Permission implements CommandPermission { + + /** + * Empty command permission + */ + private static final Permission EMPTY = Permission.of(""); + + private final String permission; + + private Permission(@Nonnull final String permission) { + this.permission = permission; + } + + /** + * Get an empty command permission + * + * @return Command permission + */ + @Nonnull + public static Permission empty() { + return EMPTY; + } + + /** + * Create a command permission instance + * + * @param string Command permission + * @return Created command permission + */ + @Nonnull + public static Permission of(@Nonnull final String string) { + return new Permission(string); + } + + /** + * Get the command permission + * + * @return Command permission + */ + @Nonnull + public String getPermission() { + return this.permission; + } + + @Nonnull + @Override + public Collection getPermissions() { + return Collections.singleton(this); + } + + /** + * Get the command permission + * + * @return Command permission + */ + @Nonnull + @Override + public String toString() { + return this.permission; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Permission that = (Permission) o; + return Objects.equals(getPermission(), that.getPermission()); + } + + @Override + public int hashCode() { + return Objects.hash(getPermission()); + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/permission/package-info.java b/cloud-core/src/main/java/com/intellectualsites/commands/permission/package-info.java new file mode 100644 index 00000000..389dac02 --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/permission/package-info.java @@ -0,0 +1,28 @@ +// +// 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. +// + +/** + * Command permissions + */ +package com.intellectualsites.commands.permission; diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandPermissionTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPermissionTest.java new file mode 100644 index 00000000..c35a0e97 --- /dev/null +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandPermissionTest.java @@ -0,0 +1,71 @@ +// +// 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.execution.CommandExecutionCoordinator; +import com.intellectualsites.commands.meta.CommandMeta; +import com.intellectualsites.commands.meta.SimpleCommandMeta; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; + +class CommandPermissionTest { + + private final static CommandManager manager = new PermissionOutputtingCommandManager(); + + @BeforeAll + static void setup() { + manager.command(manager.commandBuilder("test").literal("foo").withPermission("test.permission.one").build()); + manager.command(manager.commandBuilder("test").literal("bar").withPermission("test.permission.two").build()); + manager.command(manager.commandBuilder("test").literal("fizz").withPermission("test.permission.three").build()); + manager.command(manager.commandBuilder("test").literal("buzz").withPermission("test.permission.four").build()); + } + + @Test + void testCompoundPermission() { + Assertions.assertTrue(manager.suggest(new TestCommandSender(), "").isEmpty()); + } + + private static final class PermissionOutputtingCommandManager extends CommandManager { + + public PermissionOutputtingCommandManager() { + super(CommandExecutionCoordinator.simpleCoordinator(), cmd -> true); + } + + @Override + public boolean hasPermission(@Nonnull final TestCommandSender sender, + @Nonnull final String permission) { + return false; + } + + @Nonnull + @Override + public CommandMeta createDefaultCommandMeta() { + return SimpleCommandMeta.empty(); + } + } + +} diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java index beb48ec9..9baa17b4 100644 --- a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java +++ b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java @@ -40,6 +40,8 @@ import com.intellectualsites.commands.arguments.standard.ShortArgument; import com.intellectualsites.commands.arguments.standard.StringArgument; import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; +import com.intellectualsites.commands.permission.CommandPermission; +import com.intellectualsites.commands.permission.Permission; import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType; @@ -243,14 +245,15 @@ public final class CloudBrigadierManager { */ public LiteralCommandNode createLiteralCommandNode(@Nonnull final String label, @Nonnull final Command cloudCommand, - @Nonnull final BiPredicate permissionChecker, + @Nonnull final BiPredicate permissionChecker, @Nonnull final com.mojang.brigadier.Command executor) { final CommandTree.Node> node = this.commandManager .getCommandTree().getNamedNode(cloudCommand.getArguments().get(0).getName()); final SuggestionProvider provider = (context, builder) -> this.buildSuggestions(node.getValue(), context, builder); final LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder .literal(label) - .requires(sender -> permissionChecker.test(sender, node.getNodeMeta().getOrDefault("permission", ""))); + .requires(sender -> permissionChecker.test(sender, (CommandPermission) node.getNodeMeta() + .getOrDefault("permission", Permission.empty()))); literalArgumentBuilder.executes(executor); final LiteralCommandNode constructedRoot = literalArgumentBuilder.build(); for (final CommandTree.Node> child : node.getChildren()) { @@ -275,9 +278,10 @@ public final class CloudBrigadierManager { @Nonnull final LiteralCommandNode root, @Nonnull final SuggestionProvider suggestionProvider, @Nonnull final com.mojang.brigadier.Command executor, - @Nonnull final BiPredicate permissionChecker) { + @Nonnull final BiPredicate permissionChecker) { final LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder.literal(root.getLiteral()) - .requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", ""))); + .requires(sender -> permissionChecker.test(sender, (CommandPermission) cloudCommand.getNodeMeta() + .getOrDefault("permission", Permission.empty()))); if (cloudCommand.isLeaf() && cloudCommand.getValue() != null) { literalArgumentBuilder.executes(executor); } @@ -291,14 +295,15 @@ public final class CloudBrigadierManager { private ArgumentBuilder constructCommandNode(final boolean forceExecutor, @Nonnull final CommandTree.Node> root, - @Nonnull final BiPredicate permissionChecker, + @Nonnull final BiPredicate permissionChecker, @Nonnull final com.mojang.brigadier.Command executor, @Nonnull final SuggestionProvider suggestionProvider) { ArgumentBuilder argumentBuilder; if (root.getValue() instanceof StaticArgument) { argumentBuilder = LiteralArgumentBuilder.literal(root.getValue().getName()) - .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))) + .requires(sender -> permissionChecker.test(sender, (CommandPermission) root.getNodeMeta() + .getOrDefault("permission", Permission.empty()))) .executes(executor); } else { final Pair, Boolean> pair = this.getArgument(root.getValue().getValueType(), @@ -308,7 +313,8 @@ public final class CloudBrigadierManager { argumentBuilder = RequiredArgumentBuilder .argument(root.getValue().getName(), (ArgumentType) pair.getLeft()) .suggests(provider) - .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))); + .requires(sender -> permissionChecker.test(sender, (CommandPermission) root.getNodeMeta() + .getOrDefault("permission", Permission.empty()))); } if (forceExecutor || root.isLeaf() || !root.getValue().isRequired()) { argumentBuilder.executes(executor); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/BukkitCommand.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/BukkitCommand.java index 90d88c57..cd55e004 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/BukkitCommand.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/BukkitCommand.java @@ -152,7 +152,7 @@ final class BukkitCommand extends org.bukkit.command.Command implements Plugi @Override public String getPermission() { - return this.cloudCommand.getCommandPermission(); + return this.cloudCommand.getCommandPermission().toString(); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/CloudCommodoreManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/CloudCommodoreManager.java index b7a8ec54..e13b8d96 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/CloudCommodoreManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/bukkit/CloudCommodoreManager.java @@ -57,6 +57,7 @@ class CloudCommodoreManager extends BukkitPluginRegistrationHandler { final com.mojang.brigadier.Command cmd = o -> 1; final LiteralCommandNode literalCommandNode = this.brigadierManager .createLiteralCommandNode(label, command, (o, p) -> true, cmd); - this.commodore.register(bukkitCommand, literalCommandNode, p -> p.hasPermission(command.getCommandPermission())); + this.commodore.register(bukkitCommand, literalCommandNode, p -> + p.hasPermission(command.getCommandPermission().toString())); } } diff --git a/cloud-minecraft/cloud-bungee/src/main/java/com/intellectualsites/commands/bungee/BungeeCommand.java b/cloud-minecraft/cloud-bungee/src/main/java/com/intellectualsites/commands/bungee/BungeeCommand.java index 1ba28911..fad715b6 100644 --- a/cloud-minecraft/cloud-bungee/src/main/java/com/intellectualsites/commands/bungee/BungeeCommand.java +++ b/cloud-minecraft/cloud-bungee/src/main/java/com/intellectualsites/commands/bungee/BungeeCommand.java @@ -55,7 +55,7 @@ public final class BungeeCommand extends Command implements TabExecutor { @Nonnull final CommandArgument command, @Nonnull final BungeeCommandManager manager) { super(command.getName(), - cloudCommand.getCommandPermission(), + cloudCommand.getCommandPermission().toString(), ((StaticArgument) command).getAlternativeAliases().toArray(new String[0])); this.command = command; this.manager = manager; diff --git a/cloud-minecraft/cloud-cloudburst/src/main/java/com/intellectualsites/commands/cloudburst/CloudburstCommand.java b/cloud-minecraft/cloud-cloudburst/src/main/java/com/intellectualsites/commands/cloudburst/CloudburstCommand.java index 8f5b29ae..af45d20a 100644 --- a/cloud-minecraft/cloud-cloudburst/src/main/java/com/intellectualsites/commands/cloudburst/CloudburstCommand.java +++ b/cloud-minecraft/cloud-cloudburst/src/main/java/com/intellectualsites/commands/cloudburst/CloudburstCommand.java @@ -56,7 +56,7 @@ final class CloudburstCommand extends PluginCommand { @Nonnull final CloudburstCommandManager manager) { super(manager.getOwningPlugin(), CommandData.builder(label) .addAliases(aliases.toArray(new String[0])) - .addPermission(cloudCommand.getCommandPermission()) + .addPermission(cloudCommand.getCommandPermission().toString()) .setDescription(cloudCommand.getCommandMeta().getOrDefault("description", "")) .build()); this.command = command; diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/paper/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/paper/PaperBrigadierListener.java index fbaacdff..555f8b26 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/paper/PaperBrigadierListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/paper/PaperBrigadierListener.java @@ -128,8 +128,8 @@ class PaperBrigadierListener implements Listener { event.getLiteral(), event.getBrigadierCommand(), event.getBrigadierCommand(), - (s, p) -> p.isEmpty() - || s.getBukkitSender().hasPermission(p))); + (s, p) -> p.toString().isEmpty() + || s.getBukkitSender().hasPermission(p.toString()))); } }