Replace command permissions with a new smarter permission system that allows for compound permissions

This commit is contained in:
Alexander Söderberg 2020-09-24 23:18:22 +02:00 committed by Alexander Söderberg
parent ce2fbe9746
commit e8a1a9a6cf
15 changed files with 455 additions and 41 deletions

View file

@ -28,6 +28,8 @@ import com.intellectualsites.commands.arguments.StaticArgument;
import com.intellectualsites.commands.execution.CommandExecutionHandler; import com.intellectualsites.commands.execution.CommandExecutionHandler;
import com.intellectualsites.commands.meta.CommandMeta; import com.intellectualsites.commands.meta.CommandMeta;
import com.intellectualsites.commands.meta.SimpleCommandMeta; 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.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -50,7 +52,7 @@ public class Command<C> {
@Nonnull private final Map<CommandArgument<C, ?>, Description> arguments; @Nonnull private final Map<CommandArgument<C, ?>, Description> arguments;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler; @Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType; @Nullable private final Class<? extends C> senderType;
@Nonnull private final String commandPermission; @Nonnull private final CommandPermission commandPermission;
@Nonnull private final CommandMeta commandMeta; @Nonnull private final CommandMeta commandMeta;
/** /**
@ -65,7 +67,7 @@ public class Command<C> {
public Command(@Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments, public Command(@Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nullable final Class<? extends C> senderType, @Nullable final Class<? extends C> senderType,
@Nonnull final String commandPermission, @Nonnull final CommandPermission commandPermission,
@Nonnull final CommandMeta commandMeta) { @Nonnull final CommandMeta commandMeta) {
this.arguments = Objects.requireNonNull(commandArguments, "Command arguments may not be null"); this.arguments = Objects.requireNonNull(commandArguments, "Command arguments may not be null");
if (this.arguments.size() == 0) { if (this.arguments.size() == 0) {
@ -103,7 +105,7 @@ public class Command<C> {
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nullable final Class<? extends C> senderType, @Nullable final Class<? extends C> senderType,
@Nonnull final CommandMeta commandMeta) { @Nonnull final CommandMeta commandMeta) {
this(commandArguments, commandExecutionHandler, senderType, "", commandMeta); this(commandArguments, commandExecutionHandler, senderType, Permission.empty(), commandMeta);
} }
/** /**
@ -116,7 +118,7 @@ public class Command<C> {
*/ */
public Command(@Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments, public Command(@Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nonnull final String commandPermission, @Nonnull final CommandPermission commandPermission,
@Nonnull final CommandMeta commandMeta) { @Nonnull final CommandMeta commandMeta) {
this(commandArguments, commandExecutionHandler, null, commandPermission, commandMeta); this(commandArguments, commandExecutionHandler, null, commandPermission, commandMeta);
} }
@ -140,7 +142,7 @@ public class Command<C> {
final Map<CommandArgument<C, ?>, Description> map = new LinkedHashMap<>(); final Map<CommandArgument<C, ?>, Description> map = new LinkedHashMap<>();
map.put(StaticArgument.required(commandName, aliases), description); map.put(StaticArgument.required(commandName, aliases), description);
return new Builder<>(null, commandMeta, null, map, return new Builder<>(null, commandMeta, null, map,
new CommandExecutionHandler.NullCommandExecutionHandler<>(), ""); new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty());
} }
/** /**
@ -160,7 +162,7 @@ public class Command<C> {
final Map<CommandArgument<C, ?>, Description> map = new LinkedHashMap<>(); final Map<CommandArgument<C, ?>, Description> map = new LinkedHashMap<>();
map.put(StaticArgument.required(commandName, aliases), Description.empty()); map.put(StaticArgument.required(commandName, aliases), Description.empty());
return new Builder<>(null, commandMeta, null, map, return new Builder<>(null, commandMeta, null, map,
new CommandExecutionHandler.NullCommandExecutionHandler<>(), ""); new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty());
} }
/** /**
@ -199,7 +201,7 @@ public class Command<C> {
* @return Command permission * @return Command permission
*/ */
@Nonnull @Nonnull
public String getCommandPermission() { public CommandPermission getCommandPermission() {
return this.commandPermission; return this.commandPermission;
} }
@ -237,7 +239,7 @@ public class Command<C> {
@Nonnull private final Map<CommandArgument<C, ?>, Description> commandArguments; @Nonnull private final Map<CommandArgument<C, ?>, Description> commandArguments;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler; @Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType; @Nullable private final Class<? extends C> senderType;
@Nonnull private final String commandPermission; @Nonnull private final CommandPermission commandPermission;
@Nullable private final CommandManager<C> commandManager; @Nullable private final CommandManager<C> commandManager;
private Builder(@Nullable final CommandManager<C> commandManager, private Builder(@Nullable final CommandManager<C> commandManager,
@ -245,7 +247,7 @@ public class Command<C> {
@Nullable final Class<? extends C> senderType, @Nullable final Class<? extends C> senderType,
@Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments, @Nonnull final Map<CommandArgument<C, ?>, Description> commandArguments,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nonnull final String commandPermission) { @Nonnull final CommandPermission commandPermission) {
this.commandManager = commandManager; this.commandManager = commandManager;
this.senderType = senderType; this.senderType = senderType;
this.commandArguments = Objects.requireNonNull(commandArguments, "Arguments may not be null"); this.commandArguments = Objects.requireNonNull(commandArguments, "Arguments may not be null");
@ -391,11 +393,23 @@ public class Command<C> {
* @return New builder instance using the command permission * @return New builder instance using the command permission
*/ */
@Nonnull @Nonnull
public Builder<C> withPermission(@Nonnull final String permission) { public Builder<C> withPermission(@Nonnull final CommandPermission permission) {
return new Builder<>(this.commandManager, this.commandMeta, this.senderType, this.commandArguments, return new Builder<>(this.commandManager, this.commandMeta, this.senderType, this.commandArguments,
this.commandExecutionHandler, permission); this.commandExecutionHandler, permission);
} }
/**
* 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.commandManager, this.commandMeta, this.senderType, this.commandArguments,
this.commandExecutionHandler, Permission.of(permission));
}
/** /**
* Build a command using the builder instance * Build a command using the builder instance
* *

View file

@ -46,6 +46,9 @@ import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessin
import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor; import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessor;
import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.internal.CommandRegistrationHandler;
import com.intellectualsites.commands.meta.CommandMeta; 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.ServicePipeline;
import com.intellectualsites.services.State; import com.intellectualsites.services.State;
@ -210,6 +213,29 @@ public abstract class CommandManager<C> {
this.commandRegistrationHandler = commandRegistrationHandler; 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 * Check if the command sender has the required permission. If the permission node is
* empty, this should return {@code true} * empty, this should return {@code true}

View file

@ -34,10 +34,13 @@ import com.intellectualsites.commands.exceptions.InvalidSyntaxException;
import com.intellectualsites.commands.exceptions.NoCommandInLeafException; import com.intellectualsites.commands.exceptions.NoCommandInLeafException;
import com.intellectualsites.commands.exceptions.NoPermissionException; import com.intellectualsites.commands.exceptions.NoPermissionException;
import com.intellectualsites.commands.exceptions.NoSuchCommandException; 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.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -130,7 +133,7 @@ public final class CommandTree<C> {
@Nonnull final CommandContext<C> commandContext, @Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue, @Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandArgument<C, ?>> root) { @Nonnull final Node<CommandArgument<C, ?>> root) {
String permission = this.isPermitted(commandContext.getSender(), root); CommandPermission permission = this.isPermitted(commandContext.getSender(), root);
if (permission != null) { if (permission != null) {
throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(root) throw new NoPermissionException(permission, commandContext.getSender(), this.getChain(root)
.stream() .stream()
@ -213,7 +216,7 @@ public final class CommandTree<C> {
@Nonnull final CommandContext<C> commandContext, @Nonnull final CommandContext<C> commandContext,
@Nonnull final Node<CommandArgument<C, ?>> root, @Nonnull final Node<CommandArgument<C, ?>> root,
@Nonnull final Queue<String> commandQueue) { @Nonnull final Queue<String> commandQueue) {
String permission; CommandPermission permission;
final List<Node<CommandArgument<C, ?>>> children = root.getChildren(); final List<Node<CommandArgument<C, ?>>> children = root.getChildren();
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticArgument)) { if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticArgument)) {
// The value has to be a variable // The value has to be a variable
@ -412,8 +415,8 @@ public final class CommandTree<C> {
} }
@Nullable @Nullable
private String isPermitted(@Nonnull final C sender, @Nonnull final Node<CommandArgument<C, ?>> node) { private CommandPermission isPermitted(@Nonnull final C sender, @Nonnull final Node<CommandArgument<C, ?>> node) {
final String permission = node.nodeMeta.get("permission"); final CommandPermission permission = (CommandPermission) node.nodeMeta.get("permission");
if (permission != null) { if (permission != null) {
return this.commandManager.hasPermission(sender, permission) ? null : permission; return this.commandManager.hasPermission(sender, permission) ? null : permission;
} }
@ -422,22 +425,24 @@ public final class CommandTree<C> {
Objects.requireNonNull( Objects.requireNonNull(
Objects.requireNonNull(node.value, "node.value").getOwningCommand(), Objects.requireNonNull(node.value, "node.value").getOwningCommand(),
"owning command").getCommandPermission()) "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 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 chain to execute, and so we allow them to execute the root
*/ */
final List<String> missingPermissions = new LinkedList<>(); final List<CommandPermission> missingPermissions = new LinkedList<>();
for (final Node<CommandArgument<C, ?>> child : node.getChildren()) { for (final Node<CommandArgument<C, ?>> child : node.getChildren()) {
final String check = this.isPermitted(sender, child); final CommandPermission check = this.isPermitted(sender, child);
if (check == null) { if (check == null) {
return null; return null;
} else { } else {
missingPermissions.add(check); missingPermissions.add(check);
} }
} }
return String.join(", ", missingPermissions);
return OrPermission.of(missingPermissions);
} }
/** /**
@ -466,20 +471,23 @@ public final class CommandTree<C> {
// Register command permissions // Register command permissions
this.getLeavesRaw(this.internalTree).forEach(node -> { this.getLeavesRaw(this.internalTree).forEach(node -> {
// noinspection all
final CommandPermission commandPermission = node.getValue().getOwningCommand().getCommandPermission();
/* All leaves must necessarily have an owning command */ /* All leaves must necessarily have an owning command */
// noinspection all // 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) // Get chain and order it tail->head then skip the tail (leaf node)
List<Node<CommandArgument<C, ?>>> chain = this.getChain(node); List<Node<CommandArgument<C, ?>>> chain = this.getChain(node);
Collections.reverse(chain); Collections.reverse(chain);
chain = chain.subList(1, chain.size()); chain = chain.subList(1, chain.size());
// Go through all nodes from the tail upwards until a collision occurs // Go through all nodes from the tail upwards until a collision occurs
for (final Node<CommandArgument<C, ?>> commandArgumentNode : chain) { for (final Node<CommandArgument<C, ?>> commandArgumentNode : chain) {
if (commandArgumentNode.nodeMeta.containsKey("permission") final CommandPermission existingPermission = (CommandPermission) commandArgumentNode.nodeMeta.get("permission");
&& !commandArgumentNode.nodeMeta.get("permission").equalsIgnoreCase(node.nodeMeta.get("permission"))) { if (existingPermission != null) {
commandArgumentNode.nodeMeta.put("permission", ""); commandArgumentNode.nodeMeta.put("permission",
OrPermission.of(Arrays.asList(commandPermission, existingPermission)));
} else { } else {
commandArgumentNode.nodeMeta.put("permission", node.nodeMeta.get("permission")); commandArgumentNode.nodeMeta.put("permission", commandPermission);
} }
} }
}); });
@ -589,7 +597,7 @@ public final class CommandTree<C> {
*/ */
public static final class Node<T> { public static final class Node<T> {
private final Map<String, String> nodeMeta = new HashMap<>(); private final Map<String, Object> nodeMeta = new HashMap<>();
private final List<Node<T>> children = new LinkedList<>(); private final List<Node<T>> children = new LinkedList<>();
private final T value; private final T value;
private Node<T> parent; private Node<T> parent;
@ -640,7 +648,7 @@ public final class CommandTree<C> {
* @return Node meta * @return Node meta
*/ */
@Nonnull @Nonnull
public Map<String, String> getNodeMeta() { public Map<String, Object> getNodeMeta() {
return this.nodeMeta; return this.nodeMeta;
} }

View file

@ -24,6 +24,7 @@
package com.intellectualsites.commands.exceptions; package com.intellectualsites.commands.exceptions;
import com.intellectualsites.commands.arguments.CommandArgument; import com.intellectualsites.commands.arguments.CommandArgument;
import com.intellectualsites.commands.permission.CommandPermission;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
@ -35,7 +36,7 @@ import java.util.List;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class NoPermissionException extends CommandParseException { public class NoPermissionException extends CommandParseException {
private final String missingPermission; private final CommandPermission missingPermission;
/** /**
* Construct a new no permission exception * Construct a new no permission exception
@ -44,7 +45,7 @@ public class NoPermissionException extends CommandParseException {
* @param commandSender Command sender * @param commandSender Command sender
* @param currentChain Chain leading up to the exception * @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 Object commandSender,
@Nonnull final List<CommandArgument<?, ?>> currentChain) { @Nonnull final List<CommandArgument<?, ?>> currentChain) {
super(commandSender, currentChain); super(commandSender, currentChain);
@ -63,7 +64,7 @@ public class NoPermissionException extends CommandParseException {
*/ */
@Nonnull @Nonnull
public String getMissingPermission() { public String getMissingPermission() {
return this.missingPermission; return this.missingPermission.toString();
} }
@Override @Override

View file

@ -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<CommandPermission> getPermissions();
/**
* Get a string representation of the permission
*
* @return String representation of the permission node
*/
@Override
@Nonnull
String toString();
}

View file

@ -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<CommandPermission> permissions;
private OrPermission(@Nonnull final Collection<CommandPermission> 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<CommandPermission> permissions) {
final Set<CommandPermission> permissionSet = new HashSet<>();
for (final CommandPermission permission : permissions) {
permissionSet.addAll(permission.getPermissions());
}
return new OrPermission(permissionSet);
}
@Nonnull
@Override
public Collection<CommandPermission> getPermissions() {
return this.permissions;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
final Iterator<CommandPermission> 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());
}
}

View file

@ -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<CommandPermission> 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());
}
}

View file

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

View file

@ -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<TestCommandSender> 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<TestCommandSender> {
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();
}
}
}

View file

@ -40,6 +40,8 @@ import com.intellectualsites.commands.arguments.standard.ShortArgument;
import com.intellectualsites.commands.arguments.standard.StringArgument; import com.intellectualsites.commands.arguments.standard.StringArgument;
import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; 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.LiteralMessage;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType;
@ -243,14 +245,15 @@ public final class CloudBrigadierManager<C, S> {
*/ */
public LiteralCommandNode<S> createLiteralCommandNode(@Nonnull final String label, public LiteralCommandNode<S> createLiteralCommandNode(@Nonnull final String label,
@Nonnull final Command<C> cloudCommand, @Nonnull final Command<C> cloudCommand,
@Nonnull final BiPredicate<S, String> permissionChecker, @Nonnull final BiPredicate<S, CommandPermission> permissionChecker,
@Nonnull final com.mojang.brigadier.Command<S> executor) { @Nonnull final com.mojang.brigadier.Command<S> executor) {
final CommandTree.Node<CommandArgument<C, ?>> node = this.commandManager final CommandTree.Node<CommandArgument<C, ?>> node = this.commandManager
.getCommandTree().getNamedNode(cloudCommand.getArguments().get(0).getName()); .getCommandTree().getNamedNode(cloudCommand.getArguments().get(0).getName());
final SuggestionProvider<S> provider = (context, builder) -> this.buildSuggestions(node.getValue(), context, builder); final SuggestionProvider<S> provider = (context, builder) -> this.buildSuggestions(node.getValue(), context, builder);
final LiteralArgumentBuilder<S> literalArgumentBuilder = LiteralArgumentBuilder final LiteralArgumentBuilder<S> literalArgumentBuilder = LiteralArgumentBuilder
.<S>literal(label) .<S>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); literalArgumentBuilder.executes(executor);
final LiteralCommandNode<S> constructedRoot = literalArgumentBuilder.build(); final LiteralCommandNode<S> constructedRoot = literalArgumentBuilder.build();
for (final CommandTree.Node<CommandArgument<C, ?>> child : node.getChildren()) { for (final CommandTree.Node<CommandArgument<C, ?>> child : node.getChildren()) {
@ -275,9 +278,10 @@ public final class CloudBrigadierManager<C, S> {
@Nonnull final LiteralCommandNode<S> root, @Nonnull final LiteralCommandNode<S> root,
@Nonnull final SuggestionProvider<S> suggestionProvider, @Nonnull final SuggestionProvider<S> suggestionProvider,
@Nonnull final com.mojang.brigadier.Command<S> executor, @Nonnull final com.mojang.brigadier.Command<S> executor,
@Nonnull final BiPredicate<S, String> permissionChecker) { @Nonnull final BiPredicate<S, CommandPermission> permissionChecker) {
final LiteralArgumentBuilder<S> literalArgumentBuilder = LiteralArgumentBuilder.<S>literal(root.getLiteral()) final LiteralArgumentBuilder<S> literalArgumentBuilder = LiteralArgumentBuilder.<S>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) { if (cloudCommand.isLeaf() && cloudCommand.getValue() != null) {
literalArgumentBuilder.executes(executor); literalArgumentBuilder.executes(executor);
} }
@ -291,14 +295,15 @@ public final class CloudBrigadierManager<C, S> {
private ArgumentBuilder<S, ?> constructCommandNode(final boolean forceExecutor, private ArgumentBuilder<S, ?> constructCommandNode(final boolean forceExecutor,
@Nonnull final CommandTree.Node<CommandArgument<C, ?>> root, @Nonnull final CommandTree.Node<CommandArgument<C, ?>> root,
@Nonnull final BiPredicate<S, String> permissionChecker, @Nonnull final BiPredicate<S, CommandPermission> permissionChecker,
@Nonnull final com.mojang.brigadier.Command<S> executor, @Nonnull final com.mojang.brigadier.Command<S> executor,
@Nonnull final SuggestionProvider<S> suggestionProvider) { @Nonnull final SuggestionProvider<S> suggestionProvider) {
ArgumentBuilder<S, ?> argumentBuilder; ArgumentBuilder<S, ?> argumentBuilder;
if (root.getValue() instanceof StaticArgument) { if (root.getValue() instanceof StaticArgument) {
argumentBuilder = LiteralArgumentBuilder.<S>literal(root.getValue().getName()) argumentBuilder = LiteralArgumentBuilder.<S>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); .executes(executor);
} else { } else {
final Pair<ArgumentType<?>, Boolean> pair = this.getArgument(root.getValue().getValueType(), final Pair<ArgumentType<?>, Boolean> pair = this.getArgument(root.getValue().getValueType(),
@ -308,7 +313,8 @@ public final class CloudBrigadierManager<C, S> {
argumentBuilder = RequiredArgumentBuilder argumentBuilder = RequiredArgumentBuilder
.<S, Object>argument(root.getValue().getName(), (ArgumentType<Object>) pair.getLeft()) .<S, Object>argument(root.getValue().getName(), (ArgumentType<Object>) pair.getLeft())
.suggests(provider) .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()) { if (forceExecutor || root.isLeaf() || !root.getValue().isRequired()) {
argumentBuilder.executes(executor); argumentBuilder.executes(executor);

View file

@ -152,7 +152,7 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
@Override @Override
public String getPermission() { public String getPermission() {
return this.cloudCommand.getCommandPermission(); return this.cloudCommand.getCommandPermission().toString();
} }
} }

View file

@ -57,6 +57,7 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
final com.mojang.brigadier.Command<?> cmd = o -> 1; final com.mojang.brigadier.Command<?> cmd = o -> 1;
final LiteralCommandNode<?> literalCommandNode = this.brigadierManager final LiteralCommandNode<?> literalCommandNode = this.brigadierManager
.createLiteralCommandNode(label, command, (o, p) -> true, cmd); .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()));
} }
} }

View file

@ -55,7 +55,7 @@ public final class BungeeCommand<C> extends Command implements TabExecutor {
@Nonnull final CommandArgument<C, ?> command, @Nonnull final CommandArgument<C, ?> command,
@Nonnull final BungeeCommandManager<C> manager) { @Nonnull final BungeeCommandManager<C> manager) {
super(command.getName(), super(command.getName(),
cloudCommand.getCommandPermission(), cloudCommand.getCommandPermission().toString(),
((StaticArgument<C>) command).getAlternativeAliases().toArray(new String[0])); ((StaticArgument<C>) command).getAlternativeAliases().toArray(new String[0]));
this.command = command; this.command = command;
this.manager = manager; this.manager = manager;

View file

@ -56,7 +56,7 @@ final class CloudburstCommand<C> extends PluginCommand<Plugin> {
@Nonnull final CloudburstCommandManager<C> manager) { @Nonnull final CloudburstCommandManager<C> manager) {
super(manager.getOwningPlugin(), CommandData.builder(label) super(manager.getOwningPlugin(), CommandData.builder(label)
.addAliases(aliases.toArray(new String[0])) .addAliases(aliases.toArray(new String[0]))
.addPermission(cloudCommand.getCommandPermission()) .addPermission(cloudCommand.getCommandPermission().toString())
.setDescription(cloudCommand.getCommandMeta().getOrDefault("description", "")) .setDescription(cloudCommand.getCommandMeta().getOrDefault("description", ""))
.build()); .build());
this.command = command; this.command = command;

View file

@ -128,8 +128,8 @@ class PaperBrigadierListener<C> implements Listener {
event.getLiteral(), event.getLiteral(),
event.getBrigadierCommand(), event.getBrigadierCommand(),
event.getBrigadierCommand(), event.getBrigadierCommand(),
(s, p) -> p.isEmpty() (s, p) -> p.toString().isEmpty()
|| s.getBukkitSender().hasPermission(p))); || s.getBukkitSender().hasPermission(p.toString())));
} }
} }