diff --git a/src/main/java/org/ultramine/permission/MetaHolder.java b/src/main/java/org/ultramine/permission/MetaHolder.java index e8480c3..887d297 100644 --- a/src/main/java/org/ultramine/permission/MetaHolder.java +++ b/src/main/java/org/ultramine/permission/MetaHolder.java @@ -27,10 +27,20 @@ innerMeta.remove(key); } + public void clearMeta() + { + innerMeta.clear(); + } + public int getPriority() { return getMeta().getInt("priority"); } + public Map getInnerMeta() + { + return new HashMap(innerMeta); + } + public abstract MetaResolver getMeta(); } diff --git a/src/main/java/org/ultramine/permission/NegativePermission.java b/src/main/java/org/ultramine/permission/NegativePermission.java index ce6f72e..054baae 100644 --- a/src/main/java/org/ultramine/permission/NegativePermission.java +++ b/src/main/java/org/ultramine/permission/NegativePermission.java @@ -1,91 +1,58 @@ package org.ultramine.permission; -public class NegativePermission implements IChangeablePermission +public class NegativePermission extends PermissionRepository.ProxyPermission implements IDirtyListener { - private IPermission permission; - private boolean isChangeable; private PermissionResolver resolver; + private boolean dirty; public NegativePermission(IPermission permission) { - this.permission = permission; - this.resolver = PermissionResolver.createInverted(permission.getPermissions()); - this.isChangeable = permission instanceof IChangeablePermission; + super(permission); + super.subscribe(this); + this.dirty = true; } @Override public String getKey() { - return "^" + permission.getKey(); + return "^" + super.getKey(); } @Override public String getName() { - return "NOT: " + permission.getName(); + return "NOT: " + super.getName(); } @Override public String getDescription() { - if (!permission.getDescription().isEmpty()) - return "NOT: " + permission.getDescription(); + if (!super.getDescription().isEmpty()) + return "NOT: " + super.getDescription(); else return ""; } @Override - public int getPriority() - { - return permission.getPriority(); - } - - @Override public PermissionResolver getPermissions() { - if (isDirty()) - resolver = PermissionResolver.createInverted(permission.getPermissions()); + if (dirty) + { + resolver = PermissionResolver.createInverted(super.getPermissions()); + dirty = false; + } return resolver; } @Override - public MetaResolver getMeta() - { - return permission.getMeta(); - } - - @Override public boolean isDirty() { - return isChangeable && ((IChangeablePermission) permission).isDirty(); + return (getType() == ProxyType.CHANGEABLE) && dirty; } @Override - public void subscribe(IDirtyListener listener) + public void makeDirty() { - if (isChangeable) - ((IChangeablePermission) permission).subscribe(listener); - } - - @Override - public void unsubscribe(IDirtyListener listener) - { - if (isChangeable) - ((IChangeablePermission) permission).unsubscribe(listener); - } - - @Override - public int hashCode() - { - return getKey().hashCode(); - } - - @Override - public boolean equals(Object obj) - { - if (obj instanceof IPermission) - return getKey().equals(((IPermission)obj).getKey()); - - return super.equals(obj); + dirty = true; } } diff --git a/src/main/java/org/ultramine/permission/PermissionHolder.java b/src/main/java/org/ultramine/permission/PermissionHolder.java index 97366e5..bdb3971 100644 --- a/src/main/java/org/ultramine/permission/PermissionHolder.java +++ b/src/main/java/org/ultramine/permission/PermissionHolder.java @@ -1,6 +1,8 @@ package org.ultramine.permission; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class PermissionHolder extends MetaHolder implements IDirtyListener @@ -69,6 +71,22 @@ makeDirty(); } + public void clearPermissions() + { + for (IPermission perm : permissions.values()) + { + if (perm instanceof IChangeablePermission) + ((IChangeablePermission) perm).unsubscribe(this); + } + permissions.clear(); + makeDirty(); + } + + public List getInnerPermissions() + { + return new ArrayList(permissions.keySet()); + } + public boolean isDirty() { return dirty; @@ -94,6 +112,13 @@ makeDirty(); } + @Override + public void clearMeta() + { + super.clearMeta(); + makeDirty(); + } + public void calculate() { if (!isDirty()) diff --git a/src/main/java/org/ultramine/permission/PermissionRepository.java b/src/main/java/org/ultramine/permission/PermissionRepository.java index e8bdcab..6039894 100644 --- a/src/main/java/org/ultramine/permission/PermissionRepository.java +++ b/src/main/java/org/ultramine/permission/PermissionRepository.java @@ -2,26 +2,37 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; public class PermissionRepository { - private Map permissions = new HashMap(); + private Set registeredPermissions = new HashSet(); private Map proxyPermissions = new HashMap(); public ProxyPermission getPermission(String key) { key = key.toLowerCase(); + if (!proxyPermissions.containsKey(key)) - proxyPermissions.put(key, new ProxyPermission(key)); + { + if (key.startsWith("^")) + { + proxyPermissions.put(key, new NegativePermission(getPermission(key.substring(1)))); + registeredPermissions.add(key); + } + else + proxyPermissions.put(key, new ProxyPermission(key)); + } return proxyPermissions.get(key); } public void registerPermission(IPermission permission) { - if (permissions.containsKey(permission.getKey())) + if (registeredPermissions.contains(permission.getKey())) throw new IllegalArgumentException("Permission already registered"); ProxyPermission proxy = getPermission(permission.getKey()); @@ -30,7 +41,7 @@ else proxy.linkSimple(permission); - permissions.put(permission.getKey(), permission); + registeredPermissions.add(permission.getKey()); } public static class ProxyPermission implements IChangeablePermission @@ -40,13 +51,25 @@ private ProxyType proxyType; private List listeners = new ArrayList(); - private ProxyPermission(String key) + public ProxyPermission(String key) { this.key = key; this.wrappedPermission = new Permission(key); this.proxyType = ProxyType.DUMMY; } + public ProxyPermission(IPermission permission) + { + this.key = permission.getKey(); + this.wrappedPermission = permission; + if (permission instanceof ProxyPermission) + proxyType = ProxyType.DUMMY; + else if (permission instanceof IChangeablePermission) + proxyType = ProxyType.CHANGEABLE; + else + proxyType = ProxyType.SIMPLE; + } + @Override public String getKey() { diff --git a/src/main/java/org/ultramine/permission/UserContainer.java b/src/main/java/org/ultramine/permission/UserContainer.java index 48049fa..03333c3 100644 --- a/src/main/java/org/ultramine/permission/UserContainer.java +++ b/src/main/java/org/ultramine/permission/UserContainer.java @@ -3,9 +3,11 @@ import java.util.HashMap; import java.util.Map; +import static org.ultramine.permission.PermissionResolver.CheckResult; + public class UserContainer { - private Map users; + protected Map users; protected UserContainer parentContainer; public UserContainer() @@ -19,23 +21,28 @@ this.parentContainer = parentContainer; } - public boolean checkUserPermission(String userName, String permissionKey) + public final boolean checkUserPermission(String userName, String permissionKey) { - if (parentContainer != null && parentContainer.contains(userName)) - switch (parentContainer.get(userName).getPermissions().check(permissionKey)) - { - case TRUE: return true; - case FALSE: return false; - } + switch (check(userName, permissionKey)) + { + case TRUE: + return true; + default: + return false; + } + } - if (contains(userName)) - switch (get(userName).getPermissions().check(permissionKey)) - { - case TRUE: return true; - case FALSE: return false; - } + protected CheckResult check(String userName, String permissionKey) + { + CheckResult result = CheckResult.UNRESOLVED; - return false; + if (parentContainer != null) + result = parentContainer.check(userName, permissionKey); + + if (result == CheckResult.UNRESOLVED && contains(userName)) + result = get(userName).getPermissions().check(permissionKey); + + return result; } public T get(String name) @@ -61,6 +68,11 @@ remove(user.getName()); } + public void clear() + { + users.clear(); + } + public boolean contains(String name) { return users.containsKey(name); diff --git a/src/main/java/org/ultramine/permission/YamlBasedContainer.java b/src/main/java/org/ultramine/permission/YamlBasedContainer.java new file mode 100644 index 0000000..45ac42e --- /dev/null +++ b/src/main/java/org/ultramine/permission/YamlBasedContainer.java @@ -0,0 +1,103 @@ +package org.ultramine.permission; + +import org.apache.commons.io.FilenameUtils; +import org.ultramine.server.util.YamlConfigProvider; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class YamlBasedContainer extends UserContainer +{ + private static final String DP_PREFIX = "yaml-dp."; + + private File config; + private PermissionRepository repository; + private GroupPermission defaultPermissions; + private User defaultUser; + + public YamlBasedContainer(PermissionRepository permissionRepository, File config) + { + this.config = config; + this.repository = permissionRepository; + + String name = FilenameUtils.getBaseName(config.getName()).toLowerCase(); + defaultPermissions = new GroupPermission(DP_PREFIX + name); + defaultPermissions.setMeta("description", "Default permissions for " + name); + + defaultUser = new User(DP_PREFIX + name); + defaultUser.addPermission(defaultPermissions); + + reload(); + } + + public void reload() + { + WorldData data = YamlConfigProvider.getOrCreateConfig(config, WorldData.class); + + defaultPermissions.clearPermissions(); + for (String permission : data.default_permissions) + defaultPermissions.addPermission(repository.getPermission(permission)); + repository.registerPermission(defaultPermissions); + + clear(); + for (Map.Entry userData : data.users.entrySet()) + { + User user = new User(userData.getKey(), userData.getValue().meta); + for (String permission : userData.getValue().permissions) + user.addPermission(repository.getPermission(permission)); + add(user); + } + } + + public void save() + { + WorldData data = new WorldData(); + + data.default_permissions = defaultPermissions.getInnerPermissions(); + data.users = new HashMap(users.size()); + + for (User user : users.values()) + { + WorldData.UserData userData = new WorldData.UserData(); + userData.permissions = user.getInnerPermissions(); + userData.meta = user.getInnerMeta(); + } + + YamlConfigProvider.saveConfig(config, data); + } + + public GroupPermission getDefaultPermissions() + { + return defaultPermissions; + } + + public void setParentContainer(UserContainer container) + { + parentContainer = container; + } + + @Override + protected PermissionResolver.CheckResult check(String userName, String permissionKey) + { + PermissionResolver.CheckResult result = super.check(userName, permissionKey); + + if (result == PermissionResolver.CheckResult.UNRESOLVED) + result = defaultUser.getPermissions().check(permissionKey); + + return result; + } + + public static class WorldData + { + public List default_permissions = new ArrayList(); + public Map users = new HashMap(); + + public static class UserData { + public List permissions = new ArrayList(); + public Map meta = new HashMap(); + } + } +} diff --git a/src/main/java/org/ultramine/server/ConfigurationHandler.java b/src/main/java/org/ultramine/server/ConfigurationHandler.java index 68ae906..b23b36f 100644 --- a/src/main/java/org/ultramine/server/ConfigurationHandler.java +++ b/src/main/java/org/ultramine/server/ConfigurationHandler.java @@ -1,21 +1,15 @@ package org.ultramine.server; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.yaml.snakeyaml.Yaml; +import org.ultramine.server.util.YamlConfigProvider; import cpw.mods.fml.relauncher.FMLLaunchHandler; public class ConfigurationHandler { public static Logger log = LogManager.getLogger(); - - private static final Yaml YAML = new Yaml(); private static File settingsDir = new File(FMLLaunchHandler.getMinecraftHome(), "settings"); private static File worldsDir = new File(FMLLaunchHandler.getMinecraftHome(), "worlds"); @@ -31,7 +25,7 @@ public static void load() { - serverConfig = getOrCreateConfig(serverConfigFile, UltramineServerConfig.class); + serverConfig = YamlConfigProvider.getOrCreateConfig(serverConfigFile, UltramineServerConfig.class); } public static File getSettingDir() @@ -51,69 +45,6 @@ public static void saveServerConfig() { - saveConfig(serverConfigFile, serverConfig); - } - - private static T getOrCreateConfig(File configFile, Class clazz) - { - T ret; - - if(!configFile.exists()) - { - try - { - ret = clazz.newInstance(); - } - catch (Exception e) - { - throw new RuntimeException("impossible exception", e); - } - - saveConfig(configFile, ret); - } - else - { - FileReader reader = null; - try - { - reader = new FileReader(configFile); - ret = YAML.loadAs(reader, clazz); - } - catch (IOException e) - { - throw new RuntimeException("Failed to read config: " + configFile.getPath(), e); - } - finally - { - try - { - reader.close(); - } catch (IOException ignored) {} - } - } - - return ret; - } - - private static void saveConfig(File configFile, Object o) - { - FileWriter writer = null; - try - { - configFile.createNewFile(); - writer = new FileWriter(configFile); - writer.write(YAML.dumpAsMap(o)); - } - catch (IOException e) - { - throw new RuntimeException("Failed to save default config: " + configFile.getPath(), e); - } - finally - { - try - { - writer.close(); - } catch (IOException ignored) {} - } + YamlConfigProvider.saveConfig(serverConfigFile, serverConfig); } } diff --git a/src/main/java/org/ultramine/server/util/YamlConfigProvider.java b/src/main/java/org/ultramine/server/util/YamlConfigProvider.java new file mode 100644 index 0000000..6844cd0 --- /dev/null +++ b/src/main/java/org/ultramine/server/util/YamlConfigProvider.java @@ -0,0 +1,76 @@ +package org.ultramine.server.util; + +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +public class YamlConfigProvider +{ + private static final Yaml YAML = new Yaml(); + + public static T getOrCreateConfig(File configFile, Class clazz) + { + T ret; + + if(!configFile.exists()) + { + try + { + ret = clazz.newInstance(); + } + catch (Exception e) + { + throw new RuntimeException("impossible exception", e); + } + + saveConfig(configFile, ret); + } + else + { + FileReader reader = null; + try + { + reader = new FileReader(configFile); + ret = YAML.loadAs(reader, clazz); + } + catch (IOException e) + { + throw new RuntimeException("Failed to read config: " + configFile.getPath(), e); + } + finally + { + try + { + reader.close(); + } catch (IOException ignored) {} + } + } + + return ret; + } + + public static void saveConfig(File configFile, Object o) + { + FileWriter writer = null; + try + { + configFile.createNewFile(); + writer = new FileWriter(configFile); + writer.write(YAML.dumpAsMap(o)); + } + catch (IOException e) + { + throw new RuntimeException("Failed to save default config: " + configFile.getPath(), e); + } + finally + { + try + { + writer.close(); + } catch (IOException ignored) {} + } + } +} diff --git a/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy index f433981..e3331cb 100644 --- a/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy +++ b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy @@ -1,7 +1,5 @@ package org.ultramine.permission -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger import spock.lang.Specification /** @@ -66,45 +64,6 @@ 0 * sPerm.unsubscribe(_) } - def "Test dirty methods"() { - setup: - def listener = Mock(IDirtyListener) - def group = new GroupPermission("group") - group.subscribe(listener) - - when: "Call setMeta method" - group.calculate() - group.setMeta("test", 21) - - then: "Group becomes dirty" - group.isDirty() - 1 * listener.makeDirty() - - when: "Call removeMeta method" - group.calculate() - group.removeMeta("test") - - then: "Group becomes dirty" - group.isDirty() - 1 * listener.makeDirty() - - when: "Call addPermission method" - group.calculate() - group.addPermission(new Permission("test")) - - then: "Group becomes dirty" - group.isDirty() - 1 * listener.makeDirty() - - when: "Call removePermission method" - group.calculate() - group.removePermission("test") - - then: "Group becomes dirty" - group.isDirty() - 1 * listener.makeDirty() - } - def "Test meta parsing"() { setup: def group = new GroupPermission("group.test", [ diff --git a/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy index 91835de..435ceaf 100644 --- a/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy +++ b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy @@ -97,9 +97,9 @@ when: "Wrapped permission dirty changes" permission.isDirty() >>> [true, false] - then: "Negative permission dirty is changing too" + then: "Negative permission dirty is not changing" perm.isDirty() - !perm.isDirty() + perm.isDirty() } def "Test subscribe/unsubscribe IChangeablePermission"() { diff --git a/src/test/groovy/org/ultramine/permission/PermissionHolderTest.groovy b/src/test/groovy/org/ultramine/permission/PermissionHolderTest.groovy index d7fbd33..7c35bf8 100644 --- a/src/test/groovy/org/ultramine/permission/PermissionHolderTest.groovy +++ b/src/test/groovy/org/ultramine/permission/PermissionHolderTest.groovy @@ -2,6 +2,8 @@ import spock.lang.Specification +import static org.ultramine.permission.PermissionResolver.CheckResult.UNRESOLVED + class PermissionHolderTest extends Specification { MetaResolver createMetaResolver(Map meta) @@ -55,36 +57,116 @@ 0 * resolver._ } - def "Test dirty methods"() { + def "Test clearPermissions"() { + setup: + def perm = Mock(IChangeablePermission) { + getKey() >> "p1" + getPermissions() >> PermissionResolver.createForKey("p1", 0) + getMeta() >> createMetaResolver([p1: 1]) + } + def holder = new PermissionHolder([a: 1, b: 2]) + holder.addPermission(new Permission("p2")) + holder.addPermission(perm) + + when: "Clear holder's permissions" + holder.clearPermissions() + + then: "It contains only inner meta" + !holder.getMeta().getInt("p1") + holder.getMeta().getInt("a") == 1 + holder.getMeta().getInt("b") == 2 + + and: "It contains no permissions" + holder.getPermissions().check("p1") == UNRESOLVED + holder.getPermissions().check("p2") == UNRESOLVED + + and: "It unsubscribed from all permissions" + 1 * perm.unsubscribe(holder) + } + + def "Test clearMeta"() { + setup: + def perm = Mock(IChangeablePermission) { + getKey() >> "p1" + getPermissions() >> PermissionResolver.createForKey("p1", 0) + getMeta() >> createMetaResolver([p1: 1]) + } + def holder = new PermissionHolder([a: 1, b: 2]) + holder.addPermission(new Permission("p2")) + holder.addPermission(perm) + + when: "Clear holder's meta" + holder.clearMeta() + + then: "It contains only permission's meta" + holder.getMeta().getInt("p1") == 1 + !holder.getMeta().getInt("a") + !holder.getMeta().getInt("b") + + and: "It contains all permissions" + holder.getPermissions().check("p1") != UNRESOLVED + holder.getPermissions().check("p2") != UNRESOLVED + + and: "It did not unsubscribe from all permissions" + 0 * perm.unsubscribe(holder) + } + + def "Test makeDirty"() { setup: def holder = new PermissionHolder() + holder.calculate() + + when: "makeDirty is called" + holder.makeDirty() + + then: "holder is dirty" + holder.isDirty() + } + + def "Test dirty methods"() { + setup: + def holder = Spy(PermissionHolder) when: "Call setMeta method" holder.calculate() holder.setMeta("test", 21) then: "Group becomes dirty" - holder.isDirty() + 1 * holder.makeDirty() when: "Call removeMeta method" holder.calculate() holder.removeMeta("test") then: "Group becomes dirty" - holder.isDirty() + 1 * holder.makeDirty() when: "Call addPermission method" holder.calculate() holder.addPermission(new Permission("test")) then: "Group becomes dirty" - holder.isDirty() + 1 * holder.makeDirty() when: "Call removePermission method" holder.calculate() holder.removePermission("test") then: "Group becomes dirty" - holder.isDirty() + 1 * holder.makeDirty() + + when: "Call clearPermissions method" + holder.calculate() + holder.clearPermissions() + + then: "Group becomes dirty" + 1 * holder.makeDirty() + + when: "Call clearMeta method" + holder.calculate() + holder.clearMeta() + + then: "Group becomes dirty" + 1 * holder.makeDirty() } } diff --git a/src/test/groovy/org/ultramine/permission/PermissionRepositoryTest.groovy b/src/test/groovy/org/ultramine/permission/PermissionRepositoryTest.groovy index 8e6f496..1d93cc7 100644 --- a/src/test/groovy/org/ultramine/permission/PermissionRepositoryTest.groovy +++ b/src/test/groovy/org/ultramine/permission/PermissionRepositoryTest.groovy @@ -141,4 +141,19 @@ repository.getPermission("c").isDirty() !repository.getPermission("c").isDirty() } + + def "Test negative key"() { + setup: + def repository = new PermissionRepository() + + when: "Try to get permission with negative key" + def perm = repository.getPermission("^group.admin") + + then: "Proxy of negative permission is return" + perm.class == NegativePermission + + and: "Negative permission linked to proxy" + perm.getWrappedPermission().getName() == "group.admin" + perm.getWrappedPermission().getType() == DUMMY + } } diff --git a/src/test/groovy/org/ultramine/permission/UserContainerTest.groovy b/src/test/groovy/org/ultramine/permission/UserContainerTest.groovy index dc95748..743e3e6 100644 --- a/src/test/groovy/org/ultramine/permission/UserContainerTest.groovy +++ b/src/test/groovy/org/ultramine/permission/UserContainerTest.groovy @@ -43,4 +43,17 @@ parent.checkUserPermission("parent", "parent") !parent.checkUserPermission("parent", "child") } + + def "Test second user cannot overwrite first"() { + setup: + def container = new UserContainer() + container.add(stubUser("u", [p1: true])) + + when: "Try to add user with same name" + container.add(stubUser("u", [p1:false, p2: true])) + + then: "User is not overwritten" + container.checkUserPermission("u", "p1") + !container.checkUserPermission("u", "p2") + } } diff --git a/src/test/groovy/org/ultramine/permission/YamlBasedContainerTest.groovy b/src/test/groovy/org/ultramine/permission/YamlBasedContainerTest.groovy new file mode 100644 index 0000000..1ff728c --- /dev/null +++ b/src/test/groovy/org/ultramine/permission/YamlBasedContainerTest.groovy @@ -0,0 +1,52 @@ +package org.ultramine.permission + +import org.apache.commons.lang3.RandomStringUtils +import spock.lang.Specification + +class YamlBasedContainerTest extends Specification { + + def "Test config parsing"() { + setup: + def container = new YamlBasedContainer(new PermissionRepository(), testYaml) + + expect: "Permissions are loaded correctly" + container.checkUserPermission("user1", "d") + container.checkUserPermission("user1", "p.1") + !container.checkUserPermission("user1", "p.2") + !container.checkUserPermission("user1", "p.3") + !container.checkUserPermission("user1", "group.admin") + + !container.checkUserPermission("user2", "d") + !container.checkUserPermission("user2", "p.1") + !container.checkUserPermission("user2", "p.2") + container.checkUserPermission("user2", "p.3") + !container.checkUserPermission("user2", "group.admin") + + and: "Meta is loaded correctly" + container.get("user1").getMeta().getString("a") == "a" + container.get("user1").getMeta().getInt("b") == 1 + + !container.get("user2").getMeta().getString("a") + !container.get("user2").getMeta().getInt("b") + } + + + def testYaml = File.createTempFile(RandomStringUtils.randomNumeric(10), ".yml") + .with { write(""" +default_permissions: +- d +users: + user1: + permissions: + - p.1 + - ^p.2 + meta: + a: a + b: 1 + user2: + permissions: + - ^d + - p.3 + meta: {} +"""); it } +}