diff --git a/build.gradle b/build.gradle index fc57cfc04..0164d3ef1 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,10 @@ sourceSets { } } +test { + useJUnitPlatform() +} + dependencies { // Junit Testing Framework testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' diff --git a/src/main/java/net/minestom/server/command/CommandSender.java b/src/main/java/net/minestom/server/command/CommandSender.java index 523471b7c..589edbf21 100644 --- a/src/main/java/net/minestom/server/command/CommandSender.java +++ b/src/main/java/net/minestom/server/command/CommandSender.java @@ -1,6 +1,9 @@ package net.minestom.server.command; import net.minestom.server.entity.Player; +import net.minestom.server.permission.Permission; + +import java.util.Collection; /** * Represent something which can send commands to the server @@ -27,6 +30,62 @@ public interface CommandSender { } } + /** + * Return all permissions associated to this command sender. + * The returned collection should be modified only by subclasses + * @return + */ + Collection getAllPermissions(); + + /** + * Adds a permission to this commandSender + * @param permission + */ + default void addPermission(Permission permission) { + getAllPermissions().add(permission); + } + + /** + * Removes a permission from this commandSender + * @param permission + */ + default void removePermission(Permission permission) { + getAllPermissions().remove(permission); + } + + /** + * Checks if the given permission is possessed by this command sender. + * Simple shortcut to
getAllPermissions().contains(permission)
for readability. + * @param p permission to check against + * @return + */ + default boolean hasPermission(Permission p) { + return getAllPermissions().contains(p); + } + + /** + * Checks if the given permission is possessed by this command sender. + * Will call {@link #hasPermission(Permission)} on all permissions that are an instance of permissionClass. + * If no matching permission is found, this result returns false. + * + * @param permissionClass + * @see #getAllPermissions() + * @return + */ + default boolean hasPermission(Class permissionClass) { + boolean result = true; + boolean foundPerm = false; + for(Permission p : getAllPermissions()) { + if(permissionClass.isInstance(p)) { + foundPerm = true; + result &= p.isValidFor(this); + } + } + if(!foundPerm) + return false; + return result; + } + /** * Get if the sender is a player * @@ -45,4 +104,21 @@ public interface CommandSender { return this instanceof ConsoleSender; } + /** + * Casts this object to a Player + * No checks are performed, {@link ClassCastException} can very much happen + * @see #isPlayer() + */ + default Player asPlayer() { + return (Player)this; + } + + /** + * Casts this object to a ConsoleSender + * No checks are performed, {@link ClassCastException} can very much happen + * @see #isConsole() + */ + default ConsoleSender asConsole() { + return (ConsoleSender)this; + } } diff --git a/src/main/java/net/minestom/server/command/ConsoleSender.java b/src/main/java/net/minestom/server/command/ConsoleSender.java index 445c02a5c..2f1e9d266 100644 --- a/src/main/java/net/minestom/server/command/ConsoleSender.java +++ b/src/main/java/net/minestom/server/command/ConsoleSender.java @@ -1,12 +1,25 @@ package net.minestom.server.command; +import net.minestom.server.permission.Permission; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + /** * Represent the console when sending a command to the server */ public class ConsoleSender implements CommandSender { + private final List permissions = new LinkedList<>(); + @Override public void sendMessage(String message) { System.out.println(message); } + + @Override + public Collection getAllPermissions() { + return permissions; + } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 6351b80be..09b5a7208 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -31,6 +31,7 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.login.JoinGamePacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.permission.Permission; import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.resourcepack.ResourcePack; @@ -138,6 +139,8 @@ public class Player extends LivingEntity implements CommandSender { // Tick related private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this); + private final List permissions = new LinkedList<>(); + public Player(UUID uuid, String username, PlayerConnection playerConnection) { super(EntityType.PLAYER); this.uuid = uuid; // Override Entity#uuid defined in the constructor @@ -627,6 +630,11 @@ public class Player extends LivingEntity implements CommandSender { sendMessage(ColoredText.of(message)); } + @Override + public Collection getAllPermissions() { + return permissions; + } + /** * Send a message to the player * diff --git a/src/main/java/net/minestom/server/permission/BasicPermission.java b/src/main/java/net/minestom/server/permission/BasicPermission.java new file mode 100644 index 000000000..c79919723 --- /dev/null +++ b/src/main/java/net/minestom/server/permission/BasicPermission.java @@ -0,0 +1,14 @@ +package net.minestom.server.permission; + +import net.minestom.server.command.CommandSender; + +/** + * Basic Permission implementation that only requires the permission to be given to a player to be considered applied + * (eg. no arguments) + */ +public class BasicPermission implements Permission { + @Override + public boolean isValidFor(CommandSender commandSender) { + return true; + } +} diff --git a/src/main/java/net/minestom/server/permission/Permission.java b/src/main/java/net/minestom/server/permission/Permission.java new file mode 100644 index 000000000..1bd40a25e --- /dev/null +++ b/src/main/java/net/minestom/server/permission/Permission.java @@ -0,0 +1,18 @@ +package net.minestom.server.permission; + +import net.minestom.server.command.CommandSender; + +/** + * Representation of a permission granted to a CommandSender + */ +public interface Permission { + + /** + * Does the given commandSender have the permission represented by this object? + * @param commandSender + * @return true if the commandSender possesses this permission + */ + boolean isValidFor(CommandSender commandSender); + + // TODO: Serialization? +} diff --git a/src/test/java/permissions/TestPermissions.java b/src/test/java/permissions/TestPermissions.java new file mode 100644 index 000000000..e7c448414 --- /dev/null +++ b/src/test/java/permissions/TestPermissions.java @@ -0,0 +1,99 @@ +package permissions; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.CommandSender; +import net.minestom.server.entity.Player; +import net.minestom.server.permission.Permission; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// TODO: more tests +public class TestPermissions { + + private Player player; + + @BeforeEach + public void init() { + MinecraftServer.init(); // for entity manager + player = new Player(UUID.randomUUID(), "TestPlayer", null) { + @Override + protected void playerConnectionInit() {} + + @Override + public boolean isOnline() { + return false; + } + }; + } + + @Test + public void noPermission() { + assertFalse(player.hasPermission(Permission.class)); + } + + class PermTest1 implements Permission { + @Override + public boolean isValidFor(CommandSender commandSender) { + return true; + } + } + class PermTest2 implements Permission { + @Override + public boolean isValidFor(CommandSender commandSender) { + return true; + } + } + + @Test + public void hasPermissionClass() { + assertFalse(player.hasPermission(Permission.class)); + player.addPermission(new PermTest1()); + assertTrue(player.hasPermission(PermTest1.class)); + assertFalse(player.hasPermission(PermTest2.class)); + assertTrue(player.hasPermission(Permission.class)); // allow superclasses + + player.addPermission(new PermTest2()); + assertTrue(player.hasPermission(PermTest2.class)); + } + + class BooleanPerm implements Permission { + private final boolean value; + + BooleanPerm(boolean v) { + this.value = v; + } + + @Override + public boolean isValidFor(CommandSender commandSender) { + return value; + } + } + + @Test + public void hasTwoPermissionsOfSameClassButContradictEachOther() { + player.addPermission(new BooleanPerm(true)); + assertTrue(player.hasPermission(BooleanPerm.class)); + player.addPermission(new BooleanPerm(false)); + assertFalse(player.hasPermission(BooleanPerm.class)); // all permissions must be valid + } + + @Test + public void singlePermission() { + Permission p = commandSender -> true; + player.addPermission(p); + assertTrue(p.isValidFor(player)); + assertTrue(player.hasPermission(p)); + assertTrue(player.hasPermission(Permission.class)); + } + + @AfterEach + public void cleanup() { + + } +}