disguisePerms = new ArrayList<>();
+ int inheritance = 0;
+
+ for (DisguisePerm disguisePerm : DisguiseParser.getDisguisePerms()) {
+ int inherit = getInheritance(disguisePerm, disguiseName);
+
+ if (inherit < 0) {
+ continue;
+ }
+
+ inheritance = inherit;
+
+ disguisePerms.add(disguisePerm);
+ }
+
+ // If there were no disguises that can be found by that name
+ if (disguisePerms.isEmpty()) {
+ return null;
+ }
+
+ return new ParsedPermission(disguisePerms.toArray(new DisguisePerm[0]), options, (byte) inheritance,
+ split[1].equals("*"));
+ }
+
+ /**
+ * Calculate permissions.
+ *
+ * A specified disguise (cow) and disguise range (animal) differs in that
+ * A disguise range cannot negate a specific disguise, players will be allowed to use cow if animal is negated
+ *
+ * Options on a permission limits the player, if the options start with a - then only those options can't be used
+ * on a disguise
+ * If they're using multiple permissions targetting the same disguise, it attempts to check for a permission that
+ * can be used with the provided requirements
+ * If a permission is negated, then unless specifically permitted, those permissions can't be used. It obeys the
+ * laws of ranges and specific disguises
+ */
+ private void loadPermissions(Permissible sender, String commandName) {
+ String permissionNode = "libsdisguises." + commandName + ".";
+ Map permissions = new HashMap<>();
+
+ // If the command sender is OP, then this will work even as the below code doesn't
+ for (String perm : new String[]{permissionNode + "*", "libsdisguises.*.*"}) {
+ if (!sender.hasPermission(perm)) {
+ continue;
+ }
+
+ permissions.put(perm, true);
+ }
+
+ for (PermissionAttachmentInfo permission : sender.getEffectivePermissions()) {
+ String perm = permission.getPermission().toLowerCase();
+
+ String[] split = perm.split("\\.");
+
+ // If there are not enough arguments
+ if (split.length < 3) {
+ continue;
+ }
+
+ // If this is not a lib's disguises permission
+ if (!split[0].equals("libsdisguises")) {
+ continue;
+ }
+
+ // If the command name does not match
+ if (!split[1].equals("*") && !split[1].equals(commandName)) {
+ continue;
+ }
+
+ // If it's already contained in the map, and is true. Allow negating permissions to continue
+ if (permissions.containsKey(perm) && permission.getValue()) {
+ continue;
+ }
+
+ permissions.put(perm, permission.getValue());
+ }
+
+ // First get all the disguises that can be affected
+ // Then load all the permissions we can
+ // Each time there's a parent permission set, the child inherits unless specified in a child of that parent
+
+ // DisguisePerm[]
+ // Option[]
+ // Negated
+
+ List list = new ArrayList<>();
+
+ for (Map.Entry entry : permissions.entrySet()) {
+ ParsedPermission temp = parsePermission(entry.getKey());
+
+ if (temp == null) {
+ continue;
+ }
+
+ temp.setNegated(!entry.getValue());
+
+ list.add(temp);
+ }
+
+ // Sorted from 5 to 0 where "*" is first and "Cow" is last
+ // Negated permissions are last in each inheritance, so false, false, true, true
+
+ list.sort((t1, t2) -> {
+ // Wilcard commands have little say, so they go first so they can be negated by following permissions
+ if (t1.isWildcardCommand() != t2.isWildcardCommand()) {
+ return Boolean.compare(t2.isWildcardCommand(), t1.isWildcardCommand());
+ }
+
+ if (t1.getInheritance() == t2.getInheritance()) {
+ return Boolean.compare(t1.isNegated(), t2.isNegated());
+ }
+
+ return t2.getInheritance() - t1.getInheritance();
+ });
+
+ for (DisguisePerm disguisePerm : DisguiseParser.getDisguisePerms()) {
+ // Use boolean instead of setting to null, to inherit
+ boolean disabled = true;
+ PermissionStorage storage = new PermissionStorage(disguisePerm);
+
+ for (ParsedPermission parsedPermission : list) {
+ // If this parsed permission doesn't handle this disguise type
+ if (!parsedPermission.isDisguise(disguisePerm)) {
+ continue;
+ }
+
+ // A negated permission with no options, disables the disguise
+ if (parsedPermission.isNegated() && parsedPermission.options.isEmpty()) {
+ // Remove disguise
+ disabled = true;
+ continue;
+ }
+
+ // The permission is negated, and only has negated options. Should mean something, but to most people
+ // it's nonsense and should be ignored.
+ if (parsedPermission.isNegated() && !parsedPermission.options.values().contains(true)) {
+ continue;
+ }
+
+ if (!parsedPermission.isNegated()) {
+ // Enable the disguise if permission isn't negated
+ // A negated permission cannot enable access
+ if (disabled) {
+ disabled = false;
+ }
+
+ // If the child disguise does not have any options defined, give them wildcard by default
+ if (parsedPermission.options.isEmpty()) {
+ storage.wildcardAllow = true;
+ // If this disguise has options defined, unless wildcard was explictly given then remove it
+ } else if (!storage.permittedOptions.contains("*")) {
+ storage.wildcardAllow = false;
+ }
+ }
+
+ for (Map.Entry entry : parsedPermission.options.entrySet()) {
+ // If permission is negated, reverse the option from 'allowed' to 'denied' or vice versa
+ boolean allowUse = parsedPermission.isNegated() ? !entry.getValue() : entry.getValue();
+
+ storage.permittedOptions.remove(entry.getKey());
+ storage.negatedOptions.remove(entry.getKey());
+
+ // Handle wildcard options
+ if (entry.getKey().equals("*")) {
+ // If it's a negated wildcard, then they don't want the user to use anything
+ // If it's a permitted wildcard, then they want the user to use everything
+
+ // Both want to clear the existing restrictions
+ storage.permittedOptions.clear();
+ storage.negatedOptions.clear();
+
+ // Add wildcard allow so if the user later defines "setBaby" just to be sure, it doesn't
+ // limit them to setbaby
+ storage.wildcardAllow = allowUse;
+
+ // Negated wants to prevent the use of all options
+ if (!allowUse) {
+ storage.permittedOptions.add("nooptions");
+ }
+ }
+
+ if (allowUse) {
+ storage.permittedOptions.add(entry.getKey());
+ } else {
+ storage.negatedOptions.add(entry.getKey());
+ }
+ }
+ }
+
+ // Disguise is not allowed, continue
+ if (disabled) {
+ continue;
+ }
+
+ // If invisibility was disabled in the config, ignore permissions and make sure it's disabled
+ if (DisguiseConfig.isDisabledInvisibility()) {
+ storage.permittedOptions.remove("setinvisible");
+ storage.negatedOptions.add("setinvisible");
+ }
+
+ disguises.add(storage);
+ }
+ }
+
+ private int getInheritance(DisguisePerm disguisePerm, String permissionName) {
+ DisguiseType disguiseType = disguisePerm.getType();
+
+ if (permissionName.equals("ageable")) {
+ if (Ageable.class.isAssignableFrom(disguiseType.getEntityClass())) {
+ return 1;
+ }
+ } else if (permissionName.equals("monster") || permissionName.equals("monsters")) {
+ if (Monster.class.isAssignableFrom(disguiseType.getEntityClass())) {
+ return 2;
+ }
+ } else if (permissionName.equals("animal") || permissionName.equals("animals")) {
+ if (Animals.class.isAssignableFrom(disguiseType.getEntityClass())) {
+ return 2;
+ }
+ } else if (permissionName.equals("mob")) {
+ if (disguiseType.isMob()) {
+ return 3;
+ }
+ } else if (permissionName.equals("misc")) {
+ if (disguiseType.isMisc()) {
+ return 3;
+ }
+ } else if (permissionName.equals("*")) {
+ return 4;
+ }
+
+ return -1;
+ }
+
+ private HashMap getOptions(String perm) {
+ HashMap options = new HashMap<>();
+ String[] split = perm.split("\\.");
+
+ for (int i = 3; i < split.length; i++) {
+ String option = split[i];
+ boolean negated = option.startsWith("-");
+
+ if (negated) {
+ option = option.substring(1);
+ }
+
+ if (option.equals("baby")) {
+ option = "setbaby";
+ }
+
+ options.put(option, !negated);
+ }
+
+ return options;
+ }
+
+ /**
+ * If this DisguisePermission can use the provided disguise and options
+ *
+ * @param disguisePerm
+ * @param disguiseOptions
+ * @return true if permitted
+ */
+ public boolean isAllowedDisguise(DisguisePerm disguisePerm, Collection disguiseOptions) {
+ PermissionStorage storage = getStorage(disguisePerm);
+
+ if (storage == null) {
+ return false;
+ }
+
+ // If the disguise doesn't have a wildcard allow on it
+ // If the user is limited to a select range of options, and not all the options were found in the allowed
+ // options
+ if (!storage.wildcardAllow && !storage.permittedOptions.isEmpty() &&
+ !disguiseOptions.stream().allMatch(option -> storage.permittedOptions.contains(option.toLowerCase()))) {
+ return false;
+ }
+
+ // If the user is using a forbidden option, return false. Otherwise true
+ return disguiseOptions.stream().noneMatch(option -> storage.negatedOptions.contains(option.toLowerCase()));
+ }
+
+ public boolean isAllowedDisguise(DisguisePerm disguisePerm) {
+ return getStorage(disguisePerm) != null;
+ }
+
+ private PermissionStorage getStorage(DisguisePerm disguisePerm) {
+ return disguises.stream().filter(disguise -> disguise.getDisguise().equals(disguisePerm)).findAny()
+ .orElse(null);
+ }
+}
diff --git a/src/test/java/me/libraryaddict/disguise/utilities/parser/DisguisePermissionsTest.java b/src/test/java/me/libraryaddict/disguise/utilities/parser/DisguisePermissionsTest.java
new file mode 100644
index 00000000..427b4419
--- /dev/null
+++ b/src/test/java/me/libraryaddict/disguise/utilities/parser/DisguisePermissionsTest.java
@@ -0,0 +1,303 @@
+package me.libraryaddict.disguise.utilities.parser;
+
+import org.bukkit.permissions.Permissible;
+import org.bukkit.permissions.Permission;
+import org.bukkit.permissions.PermissionAttachment;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.bukkit.plugin.Plugin;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.*;
+
+/**
+ * Created by libraryaddict on 21/10/2018.
+ */
+public class DisguisePermissionsTest {
+ @Test
+ public void testDisguisesExist() {
+ Assert.assertNull("There should not be a reindeer disguise", DisguiseParser.getDisguisePerm("Reindeer"));
+
+ Assert.assertNotNull("There should be a cow disguise", DisguiseParser.getDisguisePerm("Cow"));
+
+ Assert.assertNotNull("There should be a firework disguise", DisguiseParser.getDisguisePerm("Firework"));
+ }
+
+ @Test
+ public void testPermissionNames() {
+ Assert.assertFalse("There should not be permissions", createPermissions("Disguise", false).hasPermissions());
+
+ Assert.assertFalse("The commands should not match",
+ createPermissions("Disguise", false, "libsdisguises.disguiseentity.cow").hasPermissions());
+
+ Assert.assertFalse("The commands should not match",
+ createPermissions("Disguised", false, "libsdisguises.disguise.cow").hasPermissions());
+
+ Assert.assertTrue("There should be permissions",
+ createPermissions("Disguise", false, "libsdisguises.*.animal").hasPermissions());
+ }
+
+ @Test
+ public void testOperatorPermissions() {
+ DisguisePermissions permissions = createPermissions("Disguise", true, "-libsdisguises.disguise.sheep",
+ "-libsdisguises.disguise.horse.setBaby");
+
+ Assert.assertTrue("There should be permissions", permissions.hasPermissions());
+
+ Assert.assertTrue("The disguise cow should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow")));
+
+ Assert.assertFalse("The disguise sheep should not be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Sheep")));
+
+ Assert.assertTrue("The disguise horse should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Horse")));
+
+ Assert.assertFalse("The disguise horse should not be allowed with setBaby", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Horse"), Collections.singletonList("setBaby")));
+ }
+
+ @Test
+ public void testWildcardsPermissions() {
+ Assert.assertTrue("The cow disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.*.animal")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow")));
+
+ Assert.assertFalse("The firework disguise should not be allowed",
+ createPermissions("Disguise", false, "libsdisguises.*.animal")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertTrue("The firework disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.*.*")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertTrue("The firework disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.disguise.*")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertTrue("The firework disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.*.Firework")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertFalse("The firework disguise should not be allowed",
+ createPermissions("Disguise", false, "libsdisguises.*.*", "-libsdisguises.*.misc")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertTrue("The firework disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.disguise.*", "-libsdisguises.*.*")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+
+ Assert.assertTrue("The firework disguise should be allowed",
+ createPermissions("Disguise", false, "libsdisguises.disguise.firework", "-libsdisguises.disguise.misc")
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+ }
+
+ @Test
+ public void testInheritedPermissions() {
+ testInheritedPermissions(createPermissions("Disguise", false, "libsdisguises.disguise.animal.setBaby",
+ "-libsdisguises.disguise.sheep.setBaby"));
+
+ testInheritedPermissions(createPermissions("Disguise", false, "libsdisguises.disguise.animal.setBaby",
+ "libsdisguises.disguise.sheep.-setBaby"));
+ }
+
+ private void testInheritedPermissions(DisguisePermissions permissions) {
+ Assert.assertTrue("The sheep disguise should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Sheep")));
+
+ Assert.assertTrue("The cow disguise should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow")));
+
+ Assert.assertTrue("The cow disguise should be allowed with setBaby", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Collections.singletonList("setBaby")));
+
+ Assert.assertFalse("The sheep disguise should not be allowed with setBaby", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Sheep"), Collections.singletonList("setBaby")));
+
+ Assert.assertFalse("The firework disguise should not be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Firework")));
+ }
+
+ @Test
+ public void testNegatedPermissions() {
+ DisguisePermissions permissions = createPermissions("Disguise", false, "libsdisguises.disguise.sheep",
+ "-libsdisguises.disguise.cow.setSprinting", "-libsdisguises.disguise.donkey",
+ "-libsdisguises.disguise.horse.setRearing", "libsdisguises.disguise.horse");
+
+ Assert.assertFalse("The cow disguise should not be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow")));
+
+ Assert.assertTrue("The sheep disguise should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Sheep")));
+
+ Assert.assertFalse("The donkey disguise should not be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Donkey")));
+
+ Assert.assertTrue("The horse disguise should be allowed",
+ permissions.isAllowedDisguise(DisguiseParser.getDisguisePerm("Horse")));
+
+ Assert.assertTrue("The horse disguise should be allowed with options", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Horse"), Collections.singletonList("setBaby")));
+
+ Assert.assertFalse("The horse disguise should not be allowed setRearing", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Horse"), Collections.singletonList("setRearing")));
+ }
+
+ @Test
+ public void testMultiDisguises() {
+ DisguisePermissions permissions = createPermissions("Disguise", false, "libsdisguises.disguise.cow.setBaby",
+ "libsdisguises.disguise.cow.setHealth", "libsdisguises.disguise.cow.-setBurning");
+
+ Assert.assertTrue("The cow disguise should be able to use setBaby", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Collections.singletonList("setBaby")));
+
+ Assert.assertTrue("The cow disguise should be able to use setHealth", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Collections.singletonList("setHealth")));
+
+ Assert.assertTrue("The cow disguise should be able to use setBaby and setHealth", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Arrays.asList("setBaby", "setHealth")));
+
+ Assert.assertFalse("The cow disguise should not be able to use setBurning", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Collections.singletonList("setBurning")));
+
+ Assert.assertFalse("The cow disguise should not be able to use setSprinting", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Collections.singletonList("setSprinting")));
+
+ Assert.assertFalse("The cow disguise should not be able to use setSprinting with setBaby", permissions
+ .isAllowedDisguise(DisguiseParser.getDisguisePerm("Cow"), Arrays.asList("setSprinting", "setBaby")));
+ }
+
+ @Test
+ public void testOptions() {
+ Assert.assertFalse("The disguise should not be valid",
+ createPermissions("Disguise", false, "libsdisguises.disguise.cow", "-libsdisguises.disguise.cow")
+ .hasPermissions());
+
+ DisguisePermissions permissions = createPermissions("Disguise", false, "libsdisguises.disguise.cow",
+ "libsdisguises.disguise.sheep.setColor.setSprinting", "libsdisguises.disguise.animal.-setSprinting");
+
+ Assert.assertTrue("There should be a valid disguise", permissions.hasPermissions());
+
+ DisguisePerm cow = DisguiseParser.getDisguisePerm("Cow");
+
+ Assert.assertTrue("The cow disguise should be allowed", permissions.isAllowedDisguise(cow));
+
+ Assert.assertTrue("The cow disguise should be allowed with options",
+ permissions.isAllowedDisguise(cow, Arrays.asList("setBaby", "setBurning")));
+
+ Assert.assertFalse("The cow disguise should not be allowed with options setSprinting",
+ permissions.isAllowedDisguise(cow, Arrays.asList("setBaby", "setSprinting")));
+
+ Assert.assertFalse("The cow disguise should not be allowed with options",
+ permissions.isAllowedDisguise(cow, Collections.singletonList("setSprinting")));
+
+ DisguisePerm sheep = DisguiseParser.getDisguisePerm("Sheep");
+
+ Assert.assertFalse("The sheep disguise should not be allowed with options",
+ permissions.isAllowedDisguise(sheep, Arrays.asList("setBaby", "setBurning")));
+
+ Assert.assertTrue("The sheep disguise should be allowed setColor",
+ permissions.isAllowedDisguise(sheep, Collections.singletonList("setColor")));
+
+ Assert.assertTrue("The sheep disguise should be allowed setSprinting",
+ permissions.isAllowedDisguise(sheep, Collections.singletonList("setSprinting")));
+
+ Assert.assertFalse("The sheep disguise should not be allowed setColor and setBaby",
+ permissions.isAllowedDisguise(sheep, Arrays.asList("setColor", "setBaby")));
+
+ DisguisePerm firework = DisguiseParser.getDisguisePerm("Firework");
+
+ Assert.assertFalse("The firework disguise should not be allowed", permissions.isAllowedDisguise(firework));
+
+ Assert.assertFalse("The disguise should not be allowed even with options",
+ permissions.isAllowedDisguise(firework, Arrays.asList("setBaby", "setBurning")));
+ }
+
+ private DisguisePermissions createPermissions(String commandName, boolean isOp, String... perms) {
+ List permitted = new ArrayList<>();
+ List negated = new ArrayList<>();
+ Set attachments = new HashSet<>();
+
+ Permissible permissible = new Permissible() {
+ @Override
+ public boolean isPermissionSet(String s) {
+ return permitted.contains(s) || negated.contains(s);
+ }
+
+ @Override
+ public boolean isPermissionSet(Permission permission) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public boolean hasPermission(String s) {
+ return permitted.contains(s) || (isOp() && !negated.contains(s));
+ }
+
+ @Override
+ public boolean hasPermission(Permission permission) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public PermissionAttachment addAttachment(Plugin plugin, int i) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public void removeAttachment(PermissionAttachment permissionAttachment) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public void recalculatePermissions() {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+
+ @Override
+ public Set getEffectivePermissions() {
+ return attachments;
+ }
+
+ @Override
+ public boolean isOp() {
+ return isOp;
+ }
+
+ @Override
+ public void setOp(boolean b) {
+ throw new UnsupportedOperationException("Not Supported");
+ }
+ };
+
+ // If permission starts with a - then it was negated
+ Arrays.stream(perms).forEach(perm -> {
+ boolean setTrue = !perm.startsWith("-");
+
+ if (setTrue) {
+ permitted.add(perm);
+ } else {
+ negated.add(perm = perm.substring(1));
+ }
+
+ attachments.add(new PermissionAttachmentInfo(permissible, perm, null, setTrue));
+ });
+
+ return new DisguisePermissions(permissible, commandName);
+ }
+}