Add initial permission logic

This commit is contained in:
Alexander Söderberg 2020-09-06 22:06:16 +02:00
parent a5748444ce
commit 6c813a209b
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
3 changed files with 124 additions and 19 deletions

View file

@ -41,15 +41,18 @@ import java.util.Optional;
*
* @param <C> Command sender type
*/
@SuppressWarnings("unused")
public class Command<C extends CommandSender> {
@Nonnull private final List<CommandComponent<C, ?>> components;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType;
@Nonnull private final String commandPermission;
protected Command(@Nonnull final List<CommandComponent<C, ?>> commandComponents,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nullable final Class<? extends C> senderType) {
@Nullable final Class<? extends C> 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<C extends CommandSender> {
}
this.commandExecutionHandler = commandExecutionHandler;
this.senderType = senderType;
this.commandPermission = commandPermission;
}
/**
@ -78,7 +82,7 @@ public class Command<C extends CommandSender> {
@Nonnull
public static <C extends CommandSender> Builder<C> 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<C extends CommandSender> {
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<C extends CommandSender> {
@Nonnull private final List<CommandComponent<C, ?>> commandComponents;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType;
@Nonnull private final String commandPermission;
private Builder(@Nullable final Class<? extends C> senderType,
@Nonnull final List<CommandComponent<C, ?>> commandComponents,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler) {
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nonnull final String commandPermission) {
this.commandComponents = commandComponents;
this.commandExecutionHandler = commandExecutionHandler;
this.senderType = senderType;
this.commandPermission = commandPermission;
}
/**
@ -155,7 +172,7 @@ public class Command<C extends CommandSender> {
public <T> Builder<C> withComponent(@Nonnull final CommandComponent<C, T> component) {
final List<CommandComponent<C, ?>> 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<C extends CommandSender> {
*/
@Nonnull
public Builder<C> withHandler(@Nonnull final CommandExecutionHandler<C> 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<C extends CommandSender> {
*/
@Nonnull
public Builder<C> withSenderType(@Nonnull final Class<? extends C> 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<C> withPermission(@Nonnull final String permission) {
return new Builder<>(this.senderType, this.commandComponents, this.commandExecutionHandler, permission);
}
/**
@ -187,9 +215,8 @@ public class Command<C extends CommandSender> {
*/
@Nonnull
public Command<C> 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);
}
}

View file

@ -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<C extends CommandSender> {
private Optional<Command<C>> parseCommand(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandComponent<C, ?>> root) throws NoSuchCommandException {
if (!this.isPermitted(commandContext.getCommandSender(), root)) {
/* TODO: Send not allowed */
throw new RuntimeException("Nope!");
}
final List<Node<CommandComponent<C, ?>>> children = root.getChildren();
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) {
// The value has to be a variable
final Node<CommandComponent<C, ?>> 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<C extends CommandSender> {
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<C extends CommandSender> {
.collect(Collectors.toList()));
}
} else {
/* TODO: Indicate that we could not resolve the command here */
final List<CommandComponent<C, ?>> 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<Node<CommandComponent<C, ?>>> childIterator = root.getChildren().iterator();
@ -178,7 +194,6 @@ public class CommandTree<C extends CommandSender> {
getChain(root).stream().map(Node::getValue).collect(Collectors.toList()),
stringOrEmpty(commandQueue.peek()));
}
return Optional.empty();
}
public List<String> getSuggestions(@Nonnull final CommandContext<C> context, @Nonnull final Queue<String> commandQueue) {
@ -188,6 +203,11 @@ public class CommandTree<C extends CommandSender> {
public List<String> getSuggestions(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandComponent<C, ?>> 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<Node<CommandComponent<C, ?>>> 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<C extends CommandSender> {
}
final List<String> suggestions = new LinkedList<>();
for (final Node<CommandComponent<C, ?>> 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<C extends CommandSender> {
this.verifyAndRegister();
}
private boolean isPermitted(@Nonnull final C sender, @Nonnull final Node<CommandComponent<C, ?>> 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<CommandComponent<C, ?>> 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<C extends CommandSender> {
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<Node<CommandComponent<C, ?>>> 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<CommandComponent<C, ?>> 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<C extends CommandSender> {
node.children.forEach(this::checkAmbiguity);
}
private List<Node<CommandComponent<C, ?>>> getLeavesRaw(@Nonnull final Node<CommandComponent<C, ?>> node) {
final List<Node<CommandComponent<C, ?>>> 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<CommandComponent<C, ?>> getLeaves(@Nonnull final Node<CommandComponent<C, ?>> node) {
final List<CommandComponent<C, ?>> leaves = new LinkedList<>();
if (node.isLeaf()) {
@ -342,6 +412,7 @@ public class CommandTree<C extends CommandSender> {
private static final class Node<T> {
private final Map<String, String> nodeMeta = new HashMap<>();
private final List<Node<T>> children = new LinkedList<>();
private final T value;
private Node<T> parent;

View file

@ -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;
}
}