From 987de21b3e8b4a3050c22cc10a31e00caf3de6d1 Mon Sep 17 00:00:00 2001 From: cnr Date: Thu, 10 Aug 2017 14:36:12 -0400 Subject: [PATCH] Bake permissions ahead of time and prepare GroupPermission refactor --- .../core/account/permissions/Permission.java | 18 ++ .../account/permissions/PermissionGroup.java | 158 ++++++++++++++---- .../permissions/PermissionManager.java | 146 ++-------------- .../SalesAnnouncementRepository.java | 6 +- Plugins/Mineplex.ServerData/pom.xml | 2 +- .../game/arcade/managers/GameHostManager.java | 15 -- Plugins/mineplex-questmanager/pom.xml | 2 +- 7 files changed, 159 insertions(+), 188 deletions(-) create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/account/permissions/Permission.java diff --git a/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/Permission.java b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/Permission.java new file mode 100644 index 000000000..8bd4e9402 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/Permission.java @@ -0,0 +1,18 @@ +package mineplex.core.account.permissions; + +/** + * A Permission that can be assigned to {@link PermissionGroup}s + * + * This interface is intended to be paired with enum data types, as only + * enum-based permissions are accepted by {@link PermissionGroup#setPermission(Enum, boolean, boolean)} + * + * Example usage: + *
+ * {@code
+ * enum ExamplePerm implements Permission { EXAMPLE_ONE, EXAMPLE_TWO }
+ *
+ * PermissionGroup.PLAYER.setPermission(ExamplePerm.EXAMPLE_ONE, true, true);
+ * }
+ * 
+ */ +public interface Permission {} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionGroup.java b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionGroup.java index d243bc409..f415e83ec 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionGroup.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionGroup.java @@ -1,64 +1,90 @@ package mineplex.core.account.permissions; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.bukkit.ChatColor; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.ImmutableGraph; +import com.google.common.graph.MutableGraph; + import mineplex.core.common.util.F; public enum PermissionGroup { - BUILDER("builder", "Builder", "These creative staff members help \nbuild maps for your favorite games!", ChatColor.BLUE, 26, true, "eternal"), - MAPPER("mapper", "Mapper", "These senior staff members work closely with \nthe development and design teams to build new \nmaps for new and old content!", ChatColor.BLUE, 100, true, "builder"), - MAPLEAD("maplead", "MapLead", "Map Leaders are leaders of the Mineplex Build Team. \nThey oversee the creation of new maps and manage Builders.", ChatColor.BLUE, 25, true, "mapper"), - TRAINEE("trainee", "Trainee", "Trainees are moderators-in-training. \nTheir duties include enforcing the rules and \nproviding help to anyone with questions or concerns. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 24, true, "maplead"), - MOD("mod", "Mod", "Moderators enforce rules and provide help to \nanyone with questions or concerns. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 32, true, "trainee"), - SRMOD("srmod", "Sr.Mod", "Senior Moderators are members of a special \nSenior Moderator team where they have to fulfill specific tasks. \nJust like Moderators, you can always ask them for help. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 44, true, "mod"), - SUPPORT("support", "Support", "Support agents handle tickets and \nprovide customer service.", ChatColor.BLUE, 47, true, "srmod"), - ADMIN("admin", "Admin", "An Administrator’s role is to manage \ntheir respective Senior Moderator team \nand all moderators within it.", ChatColor.DARK_RED, 10, true, "support", "content"), - DEV("dev", "Dev", "Developers work behind the scenes to \ncreate new games and features, and fix bugs to \ngive the best experience.", ChatColor.DARK_RED, 5, true, "admin"), - LT("lt", "Leader", "Leaders manage the operation of their respective team \nor projects. They usually operate on affairs within \nthe staff, development, or management team.", ChatColor.DARK_RED, 11, true, "dev"), - OWNER("owner", "Owner", "Owners are the core managers of Mineplex. \nEach owner manages a different aspect of the \nserver and ensures its efficient operation.", ChatColor.DARK_RED, 55, true, "lt"), + //PLAYER + PLAYER("player", "", "", ChatColor.WHITE, -1, true), + ULTRA("ultra", "Ultra", "A first step into the stories of the mist. \nOnly those brave enough may enter. \n\nThe first purchasable rank at Mineplex.com/shop", ChatColor.AQUA, 12, true, PermissionGroup.PLAYER), + HERO("hero", "Hero", "There are many stories of a \nvaliant Hero who was brave enough to \ntame the most fearsome dragon in the land. \n\nThe second purchasable rank at Mineplex.com/shop", ChatColor.LIGHT_PURPLE, 13, true, PermissionGroup.ULTRA), + LEGEND("legend", "Legend", "Years they have told stories of this rank, \nonly for the legends to be true. \n\nThe third purchasable rank at Mineplex.com/shop", ChatColor.GREEN, 14, true, PermissionGroup.HERO), + TITAN("titan", "Titan", "Ancient myths spoke of a gigantic being \nwith immense power... \n\nThe fourth purchasable rank at Mineplex.com/shop", ChatColor.RED, 15, true, PermissionGroup.LEGEND), + ETERNAL("eternal", "Eternal", "Fantastic and magical, no one \nexcept the time lords truly understand \nthe power of this rank.\n\nThe fifth purchasable rank at Mineplex.com/shop", ChatColor.DARK_AQUA, 18, true, PermissionGroup.TITAN), + + //CONTENT + CONTENT("content", "", "", ChatColor.WHITE, -1, false, PermissionGroup.ETERNAL), + TWITCH("twitch", "Twitch", "A Twitch streamer who often features \nMineplex in their streams.", ChatColor.DARK_PURPLE, 21, true, PermissionGroup.CONTENT), + YT("yt", "YT", "A YouTuber who creates content for \nor related to Mineplex. \n\nThey have fewer subscribers than full YouTubers.", ChatColor.DARK_PURPLE, 20, true, PermissionGroup.CONTENT), + YOUTUBE("youtube", "YouTube", "A YouTuber who creates content for \nor related to Mineplex.", ChatColor.RED, 22, true, PermissionGroup.CONTENT), + + BUILDER("builder", "Builder", "These creative staff members help \nbuild maps for your favorite games!", ChatColor.BLUE, 26, true, PermissionGroup.ETERNAL), + MAPPER("mapper", "Mapper", "These senior staff members work closely with \nthe development and design teams to build new \nmaps for new and old content!", ChatColor.BLUE, 100, true, PermissionGroup.BUILDER), + MAPLEAD("maplead", "MapLead", "Map Leaders are leaders of the Mineplex Build Team. \nThey oversee the creation of new maps and manage Builders.", ChatColor.BLUE, 25, true, PermissionGroup.MAPPER), + TRAINEE("trainee", "Trainee", "Trainees are moderators-in-training. \nTheir duties include enforcing the rules and \nproviding help to anyone with questions or concerns. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 24, true, PermissionGroup.MAPLEAD), + MOD("mod", "Mod", "Moderators enforce rules and provide help to \nanyone with questions or concerns. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 32, true, PermissionGroup.TRAINEE), + SRMOD("srmod", "Sr.Mod", "Senior Moderators are members of a special \nSenior Moderator team where they have to fulfill specific tasks. \nJust like Moderators, you can always ask them for help. \n\nFor assistance, contact them using " + F.elem("/a ") + ".", ChatColor.GOLD, 44, true, PermissionGroup.MOD), + SUPPORT("support", "Support", "Support agents handle tickets and \nprovide customer service.", ChatColor.BLUE, 47, true, PermissionGroup.SRMOD), + ADMIN("admin", "Admin", "An Administrator’s role is to manage \ntheir respective Senior Moderator team \nand all moderators within it.", ChatColor.DARK_RED, 10, true, PermissionGroup.SUPPORT, PermissionGroup.CONTENT), + DEV("dev", "Dev", "Developers work behind the scenes to \ncreate new games and features, and fix bugs to \ngive the best experience.", ChatColor.DARK_RED, 5, true, PermissionGroup.ADMIN), + LT("lt", "Leader", "Leaders manage the operation of their respective team \nor projects. They usually operate on affairs within \nthe staff, development, or management team.", ChatColor.DARK_RED, 11, true, PermissionGroup.DEV), + OWNER("owner", "Owner", "Owners are the core managers of Mineplex. \nEach owner manages a different aspect of the \nserver and ensures its efficient operation.", ChatColor.DARK_RED, 55, true, PermissionGroup.LT), //SUB-GROUPS - QAM("qam", "", "Managers of the Quality Assurance team.", ChatColor.WHITE, 50, false, "qa"), QA("qa", "", "Members of the Quality Assurance team.", ChatColor.WHITE, 50, false), + QAM("qam", "", "Managers of the Quality Assurance team.", ChatColor.WHITE, 50, false, PermissionGroup.QA), CMOD("cmod", "", "Members of the Clans Management team.", ChatColor.WHITE, 32, false), TM("tm", "", "Members of the Trainee Management team.", ChatColor.WHITE, 52, false), MC("mc", "", "Members of the Moderator Coordination team.", ChatColor.WHITE, 49, false), EVENTMOD("eventmod", "", "Members of the Event Management team.", ChatColor.WHITE, -1, false), CMA("cma", "", "Members of the Clans Management Assistance team.", ChatColor.WHITE, -1, false), - //CONTENT - CONTENT("content", "", "", ChatColor.WHITE, -1, false, "eternal"), - TWITCH("twitch", "Twitch", "A Twitch streamer who often features \nMineplex in their streams.", ChatColor.DARK_PURPLE, 21, true, "content"), - YT("yt", "YT", "A YouTuber who creates content for \nor related to Mineplex. \n\nThey have fewer subscribers than full YouTubers.", ChatColor.DARK_PURPLE, 20, true, "content"), - YOUTUBE("youtube", "YouTube", "A YouTuber who creates content for \nor related to Mineplex.", ChatColor.RED, 22, true, "content"), - - //PLAYER - PLAYER("player", "", "", ChatColor.WHITE, -1, true), - ULTRA("ultra", "Ultra", "A first step into the stories of the mist. \nOnly those brave enough may enter. \n\nThe first purchasable rank at Mineplex.com/shop", ChatColor.AQUA, 12, true, "player"), - HERO("hero", "Hero", "There are many stories of a \nvaliant Hero who was brave enough to \ntame the most fearsome dragon in the land. \n\nThe second purchasable rank at Mineplex.com/shop", ChatColor.LIGHT_PURPLE, 13, true, "ultra"), - LEGEND("legend", "Legend", "Years they have told stories of this rank, \nonly for the legends to be true. \n\nThe third purchasable rank at Mineplex.com/shop", ChatColor.GREEN, 14, true, "hero"), - TITAN("titan", "Titan", "Ancient myths spoke of a gigantic being \nwith immense power... \n\nThe fourth purchasable rank at Mineplex.com/shop", ChatColor.RED, 15, true, "legend"), - ETERNAL("eternal", "Eternal", "Fantastic and magical, no one \nexcept the time lords truly understand \nthe power of this rank.\n\nThe fifth purchasable rank at Mineplex.com/shop", ChatColor.DARK_AQUA, 18, true, "titan"), ; - final Object LOCK = new Object(); - + static + { + MutableGraph _builder = GraphBuilder.directed().build(); + + // Add each group as a node, and add edges between parent (inherited) and child nodes + Stream.of(PermissionGroup.values()).peek(_builder::addNode).forEach(group -> + group._parentGroups.forEach(parent -> _builder.putEdge(parent, group))); + + _groupHierarchy = ImmutableGraph.copyOf(_builder); + } + + // We want a graph so we can walk the hierarchy downward and recalculate permissions when needed + private static final ImmutableGraph _groupHierarchy; + private static final Object PERMS_LOCK = new Object(); + private final String _id, _display, _description; private final ChatColor _color; private final int _forumId; private final boolean _canBePrimary; - private final Set _inheritedGroups; + private final Set _parentGroups; - PermissionGroup(String identifier, String display, String description, ChatColor color, int forumId, boolean canBePrimary, String... inheritedGroups) + private Map _specificPerms = new IdentityHashMap<>(); + private Map _inheritablePerms = new IdentityHashMap<>(); + + private Map _bakedPerms = ImmutableMap.of(); + + PermissionGroup(String identifier, String display, String description, ChatColor color, int forumId, boolean canBePrimary, PermissionGroup... parentGroups) { _id = Objects.requireNonNull(identifier, "Group identifier cannot be null").toLowerCase(); _display = Objects.requireNonNull(display, "Group display cannot be null"); @@ -66,7 +92,73 @@ public enum PermissionGroup _color = Objects.requireNonNull(color, "Group color cannot be null"); _forumId = forumId; _canBePrimary = canBePrimary; - _inheritedGroups = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(inheritedGroups))); + _parentGroups = ImmutableSet.copyOf(Arrays.asList(parentGroups)); + } + + // Note the constraints on T: this ensures we'll have reference equality on permissions, so we can put them in `IdentityHashMap`s + public & Permission> void setPermission(T permission, boolean inheritable, boolean value) + { + synchronized(PERMS_LOCK) + { + (inheritable ? _inheritablePerms : _specificPerms).put(permission, value); // Add new permission under the correct category + (inheritable ? _specificPerms : _inheritablePerms).remove(permission); // Remove permission from the other category, if present + + bakePermissions(); + } + } + + public void revokePermission(Permission permission) + { + synchronized(PERMS_LOCK) + { + _specificPerms.remove(permission); + _inheritablePerms.remove(permission); + + bakePermissions(); + } + } + + public boolean getPermission(Permission permission) + { + synchronized(PERMS_LOCK) + { + return _bakedPerms.getOrDefault(permission, false); + } + } + + private void bakePermissions() + { + // Calculate inherited permissions + Map inherited = calculateInheritable(); + + // Now: walk down the group hierarchy and bake permissions + bakeForward(inherited); + } + + private void bakeForward(Map inherited) + { + _bakedPerms = new IdentityHashMap<>(); + _bakedPerms.putAll(inherited); + _bakedPerms.putAll(_specificPerms); // Specific permissions override inheritable ones + + _groupHierarchy.successors(this).forEach(successor -> + { + Map newInherited = new IdentityHashMap<>(inherited); + newInherited.putAll(successor._inheritablePerms); + successor.bakeForward(newInherited); + }); + } + + // Calculate inheritable permissions from parent nodes and this node + private Map calculateInheritable() + { + Map inheritable = _groupHierarchy.predecessors(this).stream() // For each predecessor, + .map(PermissionGroup::calculateInheritable) // calculate their inheritable permissions + .flatMap(perms -> perms.entrySet().stream()) // and merge with logical OR + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Boolean::logicalOr, IdentityHashMap::new)); + + inheritable.putAll(_inheritablePerms); // Add our own inheritable permissions + return inheritable; } public String getDisplay(boolean color, boolean uppercase, boolean bold, boolean defaultIdentifier) @@ -116,7 +208,7 @@ public enum PermissionGroup public boolean inherits(PermissionGroup group) { - return _inheritedGroups.contains(group._id); + return _parentGroups.contains(group); } public static Optional getGroup(String name) diff --git a/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionManager.java b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionManager.java index 37949c045..a3fb9d352 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionManager.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/account/permissions/PermissionManager.java @@ -1,7 +1,6 @@ package mineplex.core.account.permissions; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -29,58 +28,10 @@ public class PermissionManager extends MiniPlugin private Set getPermissions(PermissionGroup group, boolean allowSpecific) { - Set perms = new HashSet<>(); - - if (group == null) - { - return perms; - } - - _groups.values().forEach(g -> - { - if (group.inherits(g)) - { - perms.addAll(getPermissions(g, false)); - } - }); - - synchronized (group.LOCK) - { - _inheritable.get(group).forEach((key, value) -> { - if (value) - { - perms.add(key); - } else - { - perms.remove(key); - } - }); - if (allowSpecific) - { - _specific.get(group).forEach((key, value) -> { - if (value) - { - perms.add(key); - } else - { - perms.remove(key); - } - }); - } - } - - return perms; + // TODO: replace + return null; } - public PermissionGroup getGroup(String identifier) - { - if (identifier == null) - { - return null; - } - return _groups.get(identifier.toLowerCase()); - } - public PermissionGroup getGroupFromLegacy(String legacyValue) { if (legacyValue == null) @@ -144,112 +95,41 @@ public class PermissionManager extends MiniPlugin */ public boolean inheritsFully(PermissionGroup base, PermissionGroup check) { - if (base == null || check == null) - { - return false; - } - if (base.inherits(check)) - { - return true; - } - for (PermissionGroup group : _groups.values()) - { - if (base.inherits(group)) - { - boolean inherits = inheritsFully(group, check); - if (inherits) - { - return true; - } - } - } - + // TODO: replace return false; } public boolean hasPermission(PermissionGroup group, GroupPermission permission) { - Set total = getPermissions(group, true); - - return total.contains(permission); + // TODO: replace + return false; } public boolean hasPermission(CoreClient client, GroupPermission permission) { - if (hasPermission(client.getPrimaryGroup(), permission)) - { - return true; - } - for (PermissionGroup group : client.getAdditionalGroups()) - { - if (hasPermission(group, permission)) - { - return true; - } - } - - return false; + // TODO: replace + return false; } public boolean hasPermission(Pair> groups, GroupPermission permission) { - if (hasPermission(groups.getLeft(), permission)) - { - return true; - } - for (PermissionGroup group : groups.getRight()) - { - if (hasPermission(group, permission)) - { - return true; - } - } - - return false; + // TODO: replace + return false; } public boolean hasPermission(Player player, GroupPermission permission) { - CoreClient client = _clientManager.Get(player); - - return hasPermission(client, permission); + // TODO: replace + return false; } public void setPermission(PermissionGroup group, GroupPermission permission, boolean inheritable, boolean value) { - if (group == null) - { - return; - } - synchronized (group.LOCK) - { - if (inheritable) - { - _inheritable.computeIfAbsent(group, (g) -> new HashMap<>()).put(permission, Boolean.valueOf(value)); - } - else - { - _specific.computeIfAbsent(group, (g) -> new HashMap<>()).put(permission, Boolean.valueOf(value)); - } - } + // TODO: replace } public void revokePermission(PermissionGroup group, GroupPermission permission, boolean inheritable) { - if (group == null) - { - return; - } - synchronized (group.LOCK) - { - if (inheritable) - { - _inheritable.computeIfAbsent(group, (g) -> new HashMap<>()).remove(permission); - } - else - { - _specific.computeIfAbsent(group, (g) -> new HashMap<>()).remove(permission); - } - } + // TODO: replace } } \ No newline at end of file diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/salesannouncements/SalesAnnouncementRepository.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/salesannouncements/SalesAnnouncementRepository.java index 9504c75ec..8a1046cb5 100644 --- a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/salesannouncements/SalesAnnouncementRepository.java +++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/salesannouncements/SalesAnnouncementRepository.java @@ -112,11 +112,7 @@ public class SalesAnnouncementRepository extends RepositoryBase } else { - PermissionGroup group = _pm.getGroup(rankString); - if (group == null) - { - group = _pm.getGroupFromLegacy(rankString); - } + PermissionGroup group = PermissionGroup.valueOf(rankString); ranks.add(group); } PermissionGroup[] displayTo = ranks.toArray(new PermissionGroup[ranks.size()]); diff --git a/Plugins/Mineplex.ServerData/pom.xml b/Plugins/Mineplex.ServerData/pom.xml index 85d99c4e7..299f27964 100644 --- a/Plugins/Mineplex.ServerData/pom.xml +++ b/Plugins/Mineplex.ServerData/pom.xml @@ -10,7 +10,7 @@ - 18.0 + 23.0 mineplex-serverdata diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameHostManager.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameHostManager.java index 3ae9d535b..01045f9a9 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameHostManager.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/managers/GameHostManager.java @@ -634,21 +634,6 @@ public class GameHostManager implements Listener } } - public boolean inherits(PermissionGroup group) - { - PermissionManager pm = Manager.GetClients().getPermissionManager(); - if (isCommunityServer()) - { - return PermissionGroup.ETERNAL.inherits(group); - } - if (_hostRank == null) - { - return false; - } - - return _hostRank.inherits(group); - } - public boolean hasPermission(GroupPermission permission) { PermissionManager pm = Manager.GetClients().getPermissionManager(); diff --git a/Plugins/mineplex-questmanager/pom.xml b/Plugins/mineplex-questmanager/pom.xml index 49a9fd32c..5678aecff 100644 --- a/Plugins/mineplex-questmanager/pom.xml +++ b/Plugins/mineplex-questmanager/pom.xml @@ -11,7 +11,7 @@ A centralized service that selects daily quests - 18.0 + 23.0 2.12 1.8.8-1.9-SNAPSHOT