From 2a3b32f4892c05f8951e0a8cdcb2591a6cc1b60d Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Sun, 15 Aug 2021 03:26:50 +0200 Subject: [PATCH] Support for permission expression (complex permissions that looks like boolean expressions) --- Core/pom.xml | 6 + .../lib/core/permissions/PermEntity.java | 13 +- .../PermissionExpressionParser.java | 115 ++++++++++++++++++ .../lib/core/players/IOffPlayer.java | 21 ++++ .../lib/core/players/IOnlinePlayer.java | 8 ++ 5 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionExpressionParser.java diff --git a/Core/pom.xml b/Core/pom.xml index 5b84eff..fdd87ea 100644 --- a/Core/pom.xml +++ b/Core/pom.xml @@ -52,6 +52,12 @@ 4.8.108 compile + + + com.fathzer + javaluator + 3.0.3 + diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java index 40d2643..6d3bba9 100644 --- a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermEntity.java @@ -101,12 +101,19 @@ public abstract class PermEntity { public Boolean hasPermission(String permission, String server, String world) { Boolean ret = Permissions.resolver.getEffectivePermission(name, type, permission, server, world); - if (Log.isDebugEnabled()) { - Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + ret); - } + Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + ret); return ret; } + public boolean hasPermissionOr(String permission, String server, String world, boolean deflt) { + Boolean ret = hasPermission(permission, server, world); + return ret != null ? ret : deflt; + } + + public boolean hasPermissionExpression(String permExpression, String server, String world) { + return PermissionExpressionParser.evaluate(permExpression, p -> hasPermissionOr(p, server, world, false)); + } + public DisplayTreeNode debugPermission(String permission) { return debugPermission(permission, null, null); diff --git a/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionExpressionParser.java b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionExpressionParser.java new file mode 100644 index 0000000..cb20e5e --- /dev/null +++ b/Core/src/main/java/fr/pandacube/lib/core/permissions/PermissionExpressionParser.java @@ -0,0 +1,115 @@ +package fr.pandacube.lib.core.permissions; + +import java.util.Iterator; +import java.util.function.Function; + +import com.fathzer.soft.javaluator.AbstractEvaluator; +import com.fathzer.soft.javaluator.BracketPair; +import com.fathzer.soft.javaluator.Operator; +import com.fathzer.soft.javaluator.Operator.Associativity; +import com.fathzer.soft.javaluator.Parameters; + +public class PermissionExpressionParser { + + private static final PermissionEvaluator PERMISSION_EVALUATOR = new PermissionEvaluator(); + + public static boolean evaluate(String permString, LitteralPermissionTester permTester) { + try { + return PERMISSION_EVALUATOR.evaluate(permString, permTester); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Can’t evaluate the provided permission expression: '" + permString + "'", e); + } + } + + public interface LitteralPermissionTester extends Function { } + + + + + + private static class PermissionEvaluator extends AbstractEvaluator { + + private static final Operator NOT = new Operator("!", 1, Associativity.LEFT, 3); + private static final Operator AND = new Operator("&&", 2, Associativity.LEFT, 2); + private static final Operator OR = new Operator("||", 2, Associativity.LEFT, 1); + + + private static final Parameters PARAMETERS; + + static { + PARAMETERS = new Parameters(); + PARAMETERS.add(NOT); + PARAMETERS.add(AND); + PARAMETERS.add(OR); + PARAMETERS.addExpressionBracket(BracketPair.PARENTHESES); + } + + + + public PermissionEvaluator() { + super(PARAMETERS); + } + + @Override + protected Boolean toValue(String literal, Object evaluationContext) { + if (literal.contains(" ") || literal.contains("|") || literal.contains("&")) + throw new IllegalArgumentException("Unable to parse the following part of permission expression as one permission node: '" + literal + "'"); + return evaluationContext instanceof LitteralPermissionTester pt ? pt.apply(literal) : false; + } + + @Override + protected Boolean evaluate(Operator operator, Iterator operands, Object evaluationContext) { + if (operator == NOT) { + return !operands.next(); + } else if (operator == OR) { + Boolean o1 = operands.next(); + Boolean o2 = operands.next(); + return o1 || o2; + } else if (operator == AND) { + Boolean o1 = operands.next(); + Boolean o2 = operands.next(); + return o1 && o2; + } else { + return super.evaluate(operator, operands, evaluationContext); + } + } + } + + + /* + public static void main(String[] args) { + java.util.List pList = java.util.Arrays.asList("p1.cmd", "p1.toto", "p2.lol"); + LitteralPermissionTester tester = p -> pList.contains(p); + + for (String permExpr : java.util.Arrays.asList( + "p1.cmd", // true + "p1.notexist", // false + "p2lol.lol", // false + "!p1.notexist", // true + "!p1.cmd", // false + "p1.cmd!", // false + "p1.cmd! p2.lol", // exception + "p1.cmd || p1.toto", // true || true == true + "p1.cmd || p1.notexist", // true || false == true + "p1.fefef || p2.lol", // false || true == true + "p1.fefef || p2.lolilol", // false || false == false + "p1.cmd && p1.toto", // true && true == true + "p1.cmd && p1.notexist", // true && false == false + "p1.fefef && p2.lol", // false && true == false + "p1.fefef && p2.lolilol", // false && false == false + "p1.cmd && !p1.toto ", // true && !true == false + " !p1.cmd && p1.toto", // !true && true == false + "!p1.cmd & p1.toto", // exception + "!p1.cmd | p1.toto", // exception + "p1.not exist" // exception + )) { + try { + System.out.println(permExpr + " -> " + evaluate(permExpr, tester)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + */ + +} diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java b/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java index 5a84203..ee1c602 100644 --- a/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java +++ b/Core/src/main/java/fr/pandacube/lib/core/players/IOffPlayer.java @@ -122,6 +122,27 @@ public interface IOffPlayer { Boolean res = getPermissionUser().hasPermission(permission); return res != null ? res : false; } + + /** + * Tells if this player has the permission resulted from the provided expression. + * If the player is online, this will redirect the + * method call to the {@link IOnlinePlayer} instance, + * that MUST override this current method to avoid recussive + * loop. + * If the player is offline, it just call the Pandacube + * permission system. + * @param permission the permission node to test + * @return whether this player has the provided permission + */ + public default boolean hasPermissionExpression(String permissionExpression) { + IOnlinePlayer online = getOnlineInstance(); + + if (online != null) + return online.hasPermissionExpression(permissionExpression); + + // at this point, the player is offline + return getPermissionUser().hasPermissionExpression(permissionExpression, null, null); + } /** * Tells if the this player is part of the specified group diff --git a/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java b/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java index 85df13f..783aa7f 100644 --- a/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java +++ b/Core/src/main/java/fr/pandacube/lib/core/players/IOnlinePlayer.java @@ -70,6 +70,14 @@ public interface IOnlinePlayer extends IOffPlayer { * or it may result in a {@link StackOverflowError}. */ public abstract boolean hasPermission(String permission); + + /** + * Tells if this online player has the permission resulted from the provided expression. + * @implSpec the implementation of this method must not directly or + * indirectly call the method {@link IOffPlayer#hasPermissionExpression(String)}, + * or it may result in a {@link StackOverflowError}. + */ + public abstract boolean hasPermissionExpression(String permission);