Bake permissions ahead of time and prepare GroupPermission refactor

This commit is contained in:
cnr 2017-08-10 14:36:12 -04:00
parent ba8f4e0d5a
commit 987de21b3e
7 changed files with 159 additions and 188 deletions

View File

@ -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:
* <pre>
* {@code
* enum ExamplePerm implements Permission { EXAMPLE_ONE, EXAMPLE_TWO }
*
* PermissionGroup.PLAYER.setPermission(ExamplePerm.EXAMPLE_ONE, true, true);
* }
* </pre>
*/
public interface Permission {}

View File

@ -1,64 +1,90 @@
package mineplex.core.account.permissions; package mineplex.core.account.permissions;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.IdentityHashMap;
import java.util.HashSet; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.bukkit.ChatColor; 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; import mineplex.core.common.util.F;
public enum PermissionGroup public enum PermissionGroup
{ {
BUILDER("builder", "Builder", "These creative staff members help \nbuild maps for your favorite games!", ChatColor.BLUE, 26, true, "eternal"), //PLAYER
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"), PLAYER("player", "", "", ChatColor.WHITE, -1, true),
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"), 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),
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 <message>") + ".", ChatColor.GOLD, 24, true, "maplead"), 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),
MOD("mod", "Mod", "Moderators enforce rules and provide help to \nanyone with questions or concerns. \n\nFor assistance, contact them using " + F.elem("/a <message>") + ".", ChatColor.GOLD, 32, true, "trainee"), 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),
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 <message>") + ".", ChatColor.GOLD, 44, true, "mod"), 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),
SUPPORT("support", "Support", "Support agents handle tickets and \nprovide customer service.", ChatColor.BLUE, 47, true, "srmod"), 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),
ADMIN("admin", "Admin", "An Administrators 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"), //CONTENT
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"), CONTENT("content", "", "", ChatColor.WHITE, -1, false, PermissionGroup.ETERNAL),
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"), 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 <message>") + ".", 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 <message>") + ".", 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 <message>") + ".", 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 Administrators 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 //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), 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), CMOD("cmod", "", "Members of the Clans Management team.", ChatColor.WHITE, 32, false),
TM("tm", "", "Members of the Trainee Management team.", ChatColor.WHITE, 52, 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), MC("mc", "", "Members of the Moderator Coordination team.", ChatColor.WHITE, 49, false),
EVENTMOD("eventmod", "", "Members of the Event Management team.", ChatColor.WHITE, -1, 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), 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<PermissionGroup> _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<PermissionGroup> _groupHierarchy;
private static final Object PERMS_LOCK = new Object();
private final String _id, _display, _description; private final String _id, _display, _description;
private final ChatColor _color; private final ChatColor _color;
private final int _forumId; private final int _forumId;
private final boolean _canBePrimary; private final boolean _canBePrimary;
private final Set<String> _inheritedGroups; private final Set<PermissionGroup> _parentGroups;
PermissionGroup(String identifier, String display, String description, ChatColor color, int forumId, boolean canBePrimary, String... inheritedGroups) private Map<Permission, Boolean> _specificPerms = new IdentityHashMap<>();
private Map<Permission, Boolean> _inheritablePerms = new IdentityHashMap<>();
private Map<Permission, Boolean> _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(); _id = Objects.requireNonNull(identifier, "Group identifier cannot be null").toLowerCase();
_display = Objects.requireNonNull(display, "Group display cannot be null"); _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"); _color = Objects.requireNonNull(color, "Group color cannot be null");
_forumId = forumId; _forumId = forumId;
_canBePrimary = canBePrimary; _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 <T extends Enum<T> & 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<Permission, Boolean> inherited = calculateInheritable();
// Now: walk down the group hierarchy and bake permissions
bakeForward(inherited);
}
private void bakeForward(Map<Permission, Boolean> inherited)
{
_bakedPerms = new IdentityHashMap<>();
_bakedPerms.putAll(inherited);
_bakedPerms.putAll(_specificPerms); // Specific permissions override inheritable ones
_groupHierarchy.successors(this).forEach(successor ->
{
Map<Permission, Boolean> newInherited = new IdentityHashMap<>(inherited);
newInherited.putAll(successor._inheritablePerms);
successor.bakeForward(newInherited);
});
}
// Calculate inheritable permissions from parent nodes and this node
private Map<Permission, Boolean> calculateInheritable()
{
Map<Permission, Boolean> 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) public String getDisplay(boolean color, boolean uppercase, boolean bold, boolean defaultIdentifier)
@ -116,7 +208,7 @@ public enum PermissionGroup
public boolean inherits(PermissionGroup group) public boolean inherits(PermissionGroup group)
{ {
return _inheritedGroups.contains(group._id); return _parentGroups.contains(group);
} }
public static Optional<PermissionGroup> getGroup(String name) public static Optional<PermissionGroup> getGroup(String name)

View File

@ -1,7 +1,6 @@
package mineplex.core.account.permissions; package mineplex.core.account.permissions;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -29,57 +28,9 @@ public class PermissionManager extends MiniPlugin
private Set<GroupPermission> getPermissions(PermissionGroup group, boolean allowSpecific) private Set<GroupPermission> getPermissions(PermissionGroup group, boolean allowSpecific)
{ {
Set<GroupPermission> perms = new HashSet<>(); // TODO: replace
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;
}
public PermissionGroup getGroup(String identifier)
{
if (identifier == null)
{
return null; return null;
} }
return _groups.get(identifier.toLowerCase());
}
public PermissionGroup getGroupFromLegacy(String legacyValue) public PermissionGroup getGroupFromLegacy(String legacyValue)
{ {
@ -144,112 +95,41 @@ public class PermissionManager extends MiniPlugin
*/ */
public boolean inheritsFully(PermissionGroup base, PermissionGroup check) public boolean inheritsFully(PermissionGroup base, PermissionGroup check)
{ {
if (base == null || check == null) // TODO: replace
{
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;
}
}
}
return false; return false;
} }
public boolean hasPermission(PermissionGroup group, GroupPermission permission) public boolean hasPermission(PermissionGroup group, GroupPermission permission)
{ {
Set<GroupPermission> total = getPermissions(group, true); // TODO: replace
return false;
return total.contains(permission);
} }
public boolean hasPermission(CoreClient client, GroupPermission permission) public boolean hasPermission(CoreClient client, GroupPermission permission)
{ {
if (hasPermission(client.getPrimaryGroup(), permission)) // TODO: replace
{
return true;
}
for (PermissionGroup group : client.getAdditionalGroups())
{
if (hasPermission(group, permission))
{
return true;
}
}
return false; return false;
} }
public boolean hasPermission(Pair<PermissionGroup, Set<PermissionGroup>> groups, GroupPermission permission) public boolean hasPermission(Pair<PermissionGroup, Set<PermissionGroup>> groups, GroupPermission permission)
{ {
if (hasPermission(groups.getLeft(), permission)) // TODO: replace
{
return true;
}
for (PermissionGroup group : groups.getRight())
{
if (hasPermission(group, permission))
{
return true;
}
}
return false; return false;
} }
public boolean hasPermission(Player player, GroupPermission permission) public boolean hasPermission(Player player, GroupPermission permission)
{ {
CoreClient client = _clientManager.Get(player); // TODO: replace
return false;
return hasPermission(client, permission);
} }
public void setPermission(PermissionGroup group, GroupPermission permission, boolean inheritable, boolean value) public void setPermission(PermissionGroup group, GroupPermission permission, boolean inheritable, boolean value)
{ {
if (group == null) // TODO: replace
{
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));
}
}
} }
public void revokePermission(PermissionGroup group, GroupPermission permission, boolean inheritable) public void revokePermission(PermissionGroup group, GroupPermission permission, boolean inheritable)
{ {
if (group == null) // TODO: replace
{
return;
}
synchronized (group.LOCK)
{
if (inheritable)
{
_inheritable.computeIfAbsent(group, (g) -> new HashMap<>()).remove(permission);
}
else
{
_specific.computeIfAbsent(group, (g) -> new HashMap<>()).remove(permission);
}
}
} }
} }

View File

@ -112,11 +112,7 @@ public class SalesAnnouncementRepository extends RepositoryBase
} }
else else
{ {
PermissionGroup group = _pm.getGroup(rankString); PermissionGroup group = PermissionGroup.valueOf(rankString);
if (group == null)
{
group = _pm.getGroupFromLegacy(rankString);
}
ranks.add(group); ranks.add(group);
} }
PermissionGroup[] displayTo = ranks.toArray(new PermissionGroup[ranks.size()]); PermissionGroup[] displayTo = ranks.toArray(new PermissionGroup[ranks.size()]);

View File

@ -10,7 +10,7 @@
</parent> </parent>
<properties> <properties>
<version.guava>18.0</version.guava> <version.guava>23.0</version.guava>
</properties> </properties>
<artifactId>mineplex-serverdata</artifactId> <artifactId>mineplex-serverdata</artifactId>

View File

@ -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) public boolean hasPermission(GroupPermission permission)
{ {
PermissionManager pm = Manager.GetClients().getPermissionManager(); PermissionManager pm = Manager.GetClients().getPermissionManager();

View File

@ -11,7 +11,7 @@
<description>A centralized service that selects daily quests</description> <description>A centralized service that selects daily quests</description>
<properties> <properties>
<version.guava>18.0</version.guava> <version.guava>23.0</version.guava>
<version.jline>2.12</version.jline> <version.jline>2.12</version.jline>
<version.spigot>1.8.8-1.9-SNAPSHOT</version.spigot> <version.spigot>1.8.8-1.9-SNAPSHOT</version.spigot>
</properties> </properties>