From 6c813a209b9e16a5d5ed699c6e817f1874d875cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sun, 6 Sep 2020 22:06:16 +0200 Subject: [PATCH] Add initial permission logic --- .../intellectualsites/commands/Command.java | 45 +++++++-- .../commands/CommandTree.java | 91 +++++++++++++++++-- .../commands/sender/CommandSender.java | 7 ++ 3 files changed, 124 insertions(+), 19 deletions(-) diff --git a/commands-core/src/main/java/com/intellectualsites/commands/Command.java b/commands-core/src/main/java/com/intellectualsites/commands/Command.java index 46a3e7c1..fc7cb869 100644 --- a/commands-core/src/main/java/com/intellectualsites/commands/Command.java +++ b/commands-core/src/main/java/com/intellectualsites/commands/Command.java @@ -41,15 +41,18 @@ import java.util.Optional; * * @param Command sender type */ +@SuppressWarnings("unused") public class Command { @Nonnull private final List> components; @Nonnull private final CommandExecutionHandler commandExecutionHandler; @Nullable private final Class senderType; + @Nonnull private final String commandPermission; protected Command(@Nonnull final List> commandComponents, @Nonnull final CommandExecutionHandler commandExecutionHandler, - @Nullable final Class senderType) { + @Nullable final Class senderType, + @Nonnull final String commandPermission) { this.components = Objects.requireNonNull(commandComponents, "Command components may not be null"); if (this.components.size() == 0) { throw new IllegalArgumentException("At least one command component is required"); @@ -67,6 +70,7 @@ public class Command { } this.commandExecutionHandler = commandExecutionHandler; this.senderType = senderType; + this.commandPermission = commandPermission; } /** @@ -78,7 +82,7 @@ public class Command { @Nonnull public static Builder newBuilder(@Nonnull final String commandName) { return new Builder<>(null, Collections.singletonList(StaticComponent.required(commandName)), - new CommandExecutionHandler.NullCommandExecutionHandler<>()); + new CommandExecutionHandler.NullCommandExecutionHandler<>(), ""); } /** @@ -111,6 +115,16 @@ public class Command { return Optional.ofNullable(this.senderType); } + /** + * Get the command permission + * + * @return Command permission + */ + @Nonnull + public String getCommandPermission() { + return this.commandPermission; + } + /** * Get the longest chain of similar components for * two commands @@ -135,13 +149,16 @@ public class Command { @Nonnull private final List> commandComponents; @Nonnull private final CommandExecutionHandler commandExecutionHandler; @Nullable private final Class senderType; + @Nonnull private final String commandPermission; private Builder(@Nullable final Class senderType, @Nonnull final List> commandComponents, - @Nonnull final CommandExecutionHandler commandExecutionHandler) { + @Nonnull final CommandExecutionHandler commandExecutionHandler, + @Nonnull final String commandPermission) { this.commandComponents = commandComponents; this.commandExecutionHandler = commandExecutionHandler; this.senderType = senderType; + this.commandPermission = commandPermission; } /** @@ -155,7 +172,7 @@ public class Command { public Builder withComponent(@Nonnull final CommandComponent component) { final List> commandComponents = new LinkedList<>(this.commandComponents); commandComponents.add(component); - return new Builder<>(this.senderType, commandComponents, this.commandExecutionHandler); + return new Builder<>(this.senderType, commandComponents, this.commandExecutionHandler, this.commandPermission); } /** @@ -166,7 +183,7 @@ public class Command { */ @Nonnull public Builder withHandler(@Nonnull final CommandExecutionHandler commandExecutionHandler) { - return new Builder<>(this.senderType, this.commandComponents, commandExecutionHandler); + return new Builder<>(this.senderType, this.commandComponents, commandExecutionHandler, this.commandPermission); } /** @@ -177,7 +194,18 @@ public class Command { */ @Nonnull public Builder withSenderType(@Nonnull final Class senderType) { - return new Builder<>(senderType, this.commandComponents, this.commandExecutionHandler); + return new Builder<>(senderType, this.commandComponents, this.commandExecutionHandler, this.commandPermission); + } + + /** + * 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.senderType, this.commandComponents, this.commandExecutionHandler, permission); } /** @@ -187,9 +215,8 @@ public class Command { */ @Nonnull public Command build() { - return new Command<>(Collections.unmodifiableList(this.commandComponents), - this.commandExecutionHandler, - this.senderType); + return new Command<>(Collections.unmodifiableList(this.commandComponents), this.commandExecutionHandler, + this.senderType, this.commandPermission); } } diff --git a/commands-core/src/main/java/com/intellectualsites/commands/CommandTree.java b/commands-core/src/main/java/com/intellectualsites/commands/CommandTree.java index 037232a1..91623893 100644 --- a/commands-core/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/commands-core/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -36,9 +36,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Queue; @@ -84,10 +86,18 @@ public class CommandTree { private Optional> parseCommand(@Nonnull final CommandContext commandContext, @Nonnull final Queue commandQueue, @Nonnull final Node> root) throws NoSuchCommandException { + if (!this.isPermitted(commandContext.getCommandSender(), root)) { + /* TODO: Send not allowed */ + throw new RuntimeException("Nope!"); + } final List>> children = root.getChildren(); if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) { // The value has to be a variable final Node> child = children.get(0); + if (!this.isPermitted(commandContext.getCommandSender(), child)) { + /* TODO: Send not allowed */ + throw new RuntimeException("Nope!"); + } if (child.getValue() != null) { if (commandQueue.isEmpty()) { if (child.isLeaf()) { @@ -120,7 +130,7 @@ public class CommandTree { throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() .apply(Objects.requireNonNull(child.getValue() .getOwningCommand()) - .getComponents()), + .getComponents()), commandContext.getCommandSender(), this.getChain(root) .stream() .map(Node::getValue) @@ -153,11 +163,17 @@ public class CommandTree { .collect(Collectors.toList())); } } else { - /* TODO: Indicate that we could not resolve the command here */ - final List> components = this.getChain(root) - .stream() - .map(Node::getValue) - .collect(Collectors.toList()); + /* Too many arguments. We have a unique path, so we can send the entire context */ + throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() + .apply(Objects.requireNonNull( + Objects.requireNonNull(root.getValue()) + .getOwningCommand()) + .getComponents()), + commandContext.getCommandSender(), this.getChain(root) + .stream() + .map(Node::getValue) + .collect( + Collectors.toList())); } } else { final Iterator>> childIterator = root.getChildren().iterator(); @@ -178,7 +194,6 @@ public class CommandTree { getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), stringOrEmpty(commandQueue.peek())); } - return Optional.empty(); } public List getSuggestions(@Nonnull final CommandContext context, @Nonnull final Queue commandQueue) { @@ -188,6 +203,11 @@ public class CommandTree { public List getSuggestions(@Nonnull final CommandContext commandContext, @Nonnull final Queue commandQueue, @Nonnull final Node> root) { + + /* If the sender isn't allowed to access the root node, no suggestions are needed */ + if (!this.isPermitted(commandContext.getCommandSender(), root)) { + return Collections.emptyList(); + } final List>> children = root.getChildren(); if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) { // The value has to be a variable @@ -235,11 +255,10 @@ public class CommandTree { } final List suggestions = new LinkedList<>(); for (final Node> component : root.getChildren()) { - if (component.getValue() == null) { + if (component.getValue() == null || !this.isPermitted(commandContext.getCommandSender(), component)) { continue; } - suggestions.addAll( - component.getValue().getParser().suggestions(commandContext, stringOrEmpty(commandQueue.peek()))); + suggestions.addAll(component.getValue().getParser().suggestions(commandContext, stringOrEmpty(commandQueue.peek()))); } return suggestions; } @@ -278,6 +297,27 @@ public class CommandTree { this.verifyAndRegister(); } + private boolean isPermitted(@Nonnull final C sender, @Nonnull final Node> node) { + final String permission = node.nodeMeta.get("permission"); + if (permission != null) { + return sender.hasPermission(permission); + } + if (node.isLeaf()) { + // noinspection all + return sender.hasPermission(node.value.getOwningCommand().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 + */ + for (final Node> child : node.getChildren()) { + if (this.isPermitted(sender, child)) { + return true; + } + } + return false; + } + /** * Go through all commands and register them, and verify the * command tree contracts @@ -299,6 +339,24 @@ public class CommandTree { this.commandRegistrationHandler.registerCommand(leaf.getOwningCommand()); } }); + // Register command permissions + this.getLeavesRaw(this.internalTree).forEach(node -> { + /* All leaves must necessarily have an owning command */ + // noinspection all + node.nodeMeta.put("permission", node.getValue().getOwningCommand().getCommandPermission()); + // 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> commandComponentNode : chain) { + if (commandComponentNode.nodeMeta.containsKey("permission")) { + commandComponentNode.nodeMeta.remove("permission"); + break; + } + commandComponentNode.nodeMeta.put("permission", node.nodeMeta.get("permission")); + } + }); /* TODO: Figure out a way to register all combinations along a command component path */ } @@ -316,6 +374,18 @@ public class CommandTree { node.children.forEach(this::checkAmbiguity); } + private List>> getLeavesRaw(@Nonnull final Node> node) { + final List>> leaves = new LinkedList<>(); + if (node.isLeaf()) { + if (node.getValue() != null) { + leaves.add(node); + } + } else { + node.children.forEach(child -> leaves.addAll(getLeavesRaw(child))); + } + return leaves; + } + private List> getLeaves(@Nonnull final Node> node) { final List> leaves = new LinkedList<>(); if (node.isLeaf()) { @@ -342,6 +412,7 @@ public class CommandTree { private static final class Node { + private final Map nodeMeta = new HashMap<>(); private final List> children = new LinkedList<>(); private final T value; private Node parent; diff --git a/commands-core/src/main/java/com/intellectualsites/commands/sender/CommandSender.java b/commands-core/src/main/java/com/intellectualsites/commands/sender/CommandSender.java index d80a60f3..65dcefbd 100644 --- a/commands-core/src/main/java/com/intellectualsites/commands/sender/CommandSender.java +++ b/commands-core/src/main/java/com/intellectualsites/commands/sender/CommandSender.java @@ -23,5 +23,12 @@ // package com.intellectualsites.commands.sender; +import javax.annotation.Nonnull; + public interface CommandSender { + + default boolean hasPermission(@Nonnull final String permission) { + return true; + } + }