Skip to content

Commit

Permalink
✨ Implement predicate permissions (Incendo#210)
Browse files Browse the repository at this point in the history
Co-authored-by: Josh Taylor <me@broccol.ai>
  • Loading branch information
Citymonstret and broccolai committed Mar 29, 2021
1 parent 81dabff commit ee106d6
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions cloud-core/src/main/java/cloud/commandframework/Command.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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;
Expand Down Expand Up @@ -1032,6 +1033,24 @@ private Builder(
);
}

/**
* 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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
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;
Expand Down Expand Up @@ -277,6 +278,7 @@ protected final void setCommandRegistrationHandler(final @NonNull CommandRegistr
* @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
Expand All @@ -287,6 +289,9 @@ public boolean hasPermission(
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@

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 {

Expand Down Expand Up @@ -64,6 +67,22 @@ void testComplexPermissions() {
);
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
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;
Expand Down Expand Up @@ -59,6 +61,7 @@
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;
Expand All @@ -70,6 +73,7 @@
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;
Expand All @@ -84,6 +88,10 @@
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;
Expand Down Expand Up @@ -213,6 +221,15 @@ public void onEnable() {
}

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
//
Expand Down Expand Up @@ -470,4 +487,21 @@ private void teleportComplex(
.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();

}

}

0 comments on commit ee106d6

Please sign in to comment.