Implement predicate permissions (#210)

Co-authored-by: Josh Taylor <me@broccol.ai>
This commit is contained in:
Alexander Söderberg 2021-01-13 13:23:30 +01:00
parent 78b081ccc2
commit 0b6a554946
7 changed files with 212 additions and 0 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Expose the Command which led to `InvalidCommandSenderException`s
- Expose the CommandContext which led to `CommandExecutionException`s
- Added helper methods for command flags to MutableCommandBuilder
- Add predicate permissions
## [1.3.0] - 2020-12-18

View file

@ -34,6 +34,7 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.permission.Permission;
import cloud.commandframework.permission.PredicatePermission;
import cloud.commandframework.types.tuples.Pair;
import cloud.commandframework.types.tuples.Triplet;
import io.leangen.geantyref.TypeToken;
@ -1032,6 +1033,24 @@ public class Command<C> {
);
}
/**
* Specify a command permission
*
* @param permission Command permission
* @return New builder instance using the command permission
*/
public @NonNull Builder<C> permission(final @NonNull PredicatePermission<C> permission) {
return new Builder<>(
this.commandManager,
this.commandMeta,
this.senderType,
this.commandComponents,
this.commandExecutionHandler,
permission,
this.flags
);
}
/**
* Specify a command permission
*

View file

@ -55,6 +55,7 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.permission.OrPermission;
import cloud.commandframework.permission.Permission;
import cloud.commandframework.permission.PredicatePermission;
import cloud.commandframework.services.ServicePipeline;
import cloud.commandframework.services.State;
import io.leangen.geantyref.TypeToken;
@ -277,6 +278,7 @@ public abstract class CommandManager<C> {
* @param permission Permission node
* @return {@code true} if the sender has the permission, else {@code false}
*/
@SuppressWarnings("unchecked")
public boolean hasPermission(
final @NonNull C sender,
final @NonNull CommandPermission permission
@ -287,6 +289,9 @@ public abstract class CommandManager<C> {
if (permission instanceof Permission) {
return hasPermission(sender, permission.toString());
}
if (permission instanceof PredicatePermission) {
return ((PredicatePermission<C>) permission).hasPermission(sender);
}
for (final CommandPermission innerPermission : permission.getPermissions()) {
final boolean hasPermission = this.hasPermission(sender, innerPermission);
if (permission instanceof OrPermission) {

View file

@ -0,0 +1,75 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.permission;
import cloud.commandframework.keys.CloudKey;
import cloud.commandframework.keys.CloudKeyHolder;
import cloud.commandframework.keys.SimpleCloudKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Predicate;
/**
* A functional {@link CommandPermission} implementation
*
* @param <C> Command sender type
* @since 1.4.0
*/
@FunctionalInterface
public interface PredicatePermission<C> extends CommandPermission, CloudKeyHolder<Void> {
/**
* Create a new predicate permission
*
* @param key Key that identifies the permission node
* @param predicate Predicate that determines whether or not the sender has the permission
* @param <C> Command sender type
* @return Created permission node
*/
static <C> PredicatePermission<C> of(final @NonNull CloudKey<Void> key, final @NonNull Predicate<C> predicate) {
return new WrappingPredicatePermission<>(key, predicate);
}
@Override
@SuppressWarnings("FunctionalInterfaceMethodChanged")
default @NonNull CloudKey<Void> getKey() {
return SimpleCloudKey.of(this.getClass().getSimpleName());
}
/**
* Check whether or not the given sender has this permission
*
* @param sender Sender to check for
* @return {@code true} if the sender has the given permission, else {@code false}
*/
boolean hasPermission(C sender);
@Override
default @NonNull Collection<@NonNull CommandPermission> getPermissions() {
return Collections.singleton(this);
}
}

View file

@ -0,0 +1,59 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.permission;
import cloud.commandframework.keys.CloudKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
final class WrappingPredicatePermission<C> implements PredicatePermission<C> {
private final CloudKey<Void> key;
private final Predicate<C> predicate;
WrappingPredicatePermission(
final @NonNull CloudKey<Void> key,
final @NonNull Predicate<C> predicate
) {
this.key = key;
this.predicate = predicate;
}
@Override
public boolean hasPermission(final C sender) {
return this.predicate.test(sender);
}
@Override
public @NonNull CloudKey<Void> getKey() {
return this.key;
}
@Override
public String toString() {
return this.key.getName();
}
}

View file

@ -25,13 +25,16 @@ package cloud.commandframework;
import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.keys.SimpleCloudKey;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import cloud.commandframework.permission.PredicatePermission;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicBoolean;
class CommandPermissionTest {
@ -64,6 +67,22 @@ class CommandPermissionTest {
);
}
@Test
void testPredicatePermissions() {
final AtomicBoolean condition = new AtomicBoolean(true);
manager.command(manager.commandBuilder("predicate").permission(PredicatePermission.of(
SimpleCloudKey.of("boolean"), $ -> condition.get()
)));
// First time should succeed
manager.executeCommand(new TestCommandSender(), "predicate").join();
// Now we force it to fail
condition.set(false);
Assertions.assertThrows(
CompletionException.class,
() -> manager.executeCommand(new TestCommandSender(), "predicate").join()
);
}
private static final class PermissionOutputtingCommandManager extends CommandManager<TestCommandSender> {

View file

@ -26,6 +26,8 @@ package cloud.commandframework.examples.bukkit;
import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.Command;
import cloud.commandframework.CommandTree;
import cloud.commandframework.Description;
import cloud.commandframework.keys.SimpleCloudKey;
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
import cloud.commandframework.minecraft.extras.MinecraftHelp;
import cloud.commandframework.annotations.AnnotationParser;
@ -59,6 +61,7 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.minecraft.extras.RichDescription;
import cloud.commandframework.minecraft.extras.TextColorArgument;
import cloud.commandframework.paper.PaperCommandManager;
import cloud.commandframework.permission.PredicatePermission;
import cloud.commandframework.tasks.TaskConsumer;
import cloud.commandframework.types.tuples.Triplet;
import io.leangen.geantyref.TypeToken;
@ -70,6 +73,7 @@ import net.kyori.adventure.text.format.TextDecoration;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@ -84,6 +88,10 @@ import org.bukkit.util.Vector;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -213,6 +221,15 @@ public final class ExamplePlugin extends JavaPlugin {
}
private void constructCommands() {
// Add a custom permission checker
this.annotationParser.registerBuilderModifier(
GameModeRequirement.class,
(requirement, builder) -> builder.permission(
PredicatePermission.of(SimpleCloudKey.of("gamemode"), sender ->
!(sender instanceof Player) || ((Player) sender).getGameMode() == requirement.value()
)
)
);
//
// Parse all @CommandMethod-annotated methods
//
@ -470,4 +487,21 @@ public final class ExamplePlugin extends JavaPlugin {
.execute(() -> sender.sendMessage("You have been teleported!"));
}
/**
* Command must have the given game mode
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GameModeRequirement {
/**
* The required game mode
*
* @return Required game mode
*/
GameMode value();
}
}