Split off WastelandPermissions into its own plugin.

master
James T. Martin 2020-11-22 23:40:54 -08:00
parent 261dc583df
commit e4ec1995da
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
77 changed files with 492 additions and 132 deletions

26
permissions/build.gradle Normal file
View File

@ -0,0 +1,26 @@
plugins {
id 'java-library'
}
group 'me.jamestmartin'
version '0.1.0'
sourceCompatibility = 8
repositories {
mavenCentral()
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' }
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
content {
includeGroup 'org.bukkit'
includeGroup 'org.spigotmc'
}
}
}
dependencies {
compileOnly 'org.spigotmc:spigot-api:1.16.4-R0.1-SNAPSHOT'
compileOnly 'com.google.guava:guava:21.0'
}

View File

@ -0,0 +1,30 @@
package me.jamestmartin.wasteland.permissions;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
class CommandWP implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length < 0) {
sender.sendMessage("Please specify an operation.");
return false;
}
if (args.length > 1) {
sender.sendMessage("Too many arguments.");
return false;
}
if (!args[0].equals("reload")) {
sender.sendMessage("Unknown operation: " + args[0] + ".");
return false;
}
WastelandPermissions.getInstance().reload();
sender.sendMessage("Reloaded Wasteland Permissions' configuration.");
return true;
}
}

View File

@ -7,14 +7,14 @@ import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachment;
import me.jamestmartin.wasteland.Wasteland;
import me.jamestmartin.wasteland.permissions.api.Permissions;
class PermissionsAttachments {
private final PermissionsConfig config;
private final Permissions config;
private final Map<UUID, PermissionAttachment> attachments = new ConcurrentHashMap<>();
public PermissionsAttachments(PermissionsConfig config) {
public PermissionsAttachments(Permissions config) {
this.config = config;
}
@ -26,12 +26,12 @@ class PermissionsAttachments {
}
void createAttachment(Player player) {
Map<String, Boolean> permissions = config.getPermissions(player);
Map<String, Boolean> permissions = config.getPlayerGroup(player).getPermissions();
if (permissions.isEmpty()) {
return;
}
PermissionAttachment attachment = player.addAttachment(Wasteland.getInstance());
PermissionAttachment attachment = player.addAttachment(WastelandPermissions.getInstance());
attachments.put(player.getUniqueId(), attachment);
for (Entry<String, Boolean> permission : permissions.entrySet()) {
attachment.setPermission(permission.getKey(), permission.getValue());
@ -44,14 +44,14 @@ class PermissionsAttachments {
}
void register() {
for (Player player : Wasteland.getInstance().getServer().getOnlinePlayers()) {
for (Player player : WastelandPermissions.getInstance().getServer().getOnlinePlayers()) {
createAttachment(player);
}
}
void unregister() {
for(Map.Entry<UUID, PermissionAttachment> attachment : attachments.entrySet()) {
Wasteland.getInstance().getServer().getPlayer(attachment.getKey())
WastelandPermissions.getInstance().getServer().getPlayer(attachment.getKey())
.removeAttachment(attachment.getValue());
attachments.remove(attachment.getKey());
}

View File

@ -1,6 +1,5 @@
package me.jamestmartin.wasteland.permissions;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@ -16,8 +15,7 @@ class PermissionsAttachmentsListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
attachments.createAttachment(player);
attachments.createAttachment(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)

View File

@ -0,0 +1,67 @@
package me.jamestmartin.wasteland.permissions;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
import me.jamestmartin.wasteland.permissions.api.Permissions;
import me.jamestmartin.wasteland.permissions.config.ConfigParser;
public class WastelandPermissions extends JavaPlugin {
private static WastelandPermissions instance;
private Permissions permissions;
private PermissionsAttachments attachments;
private PermissionsAttachmentsListener listener;
public static WastelandPermissions getInstance() {
return instance;
}
public Permissions getPermissions() {
return permissions;
}
@Override
public void onEnable() {
instance = this;
saveDefaultConfig();
load();
// This command doesn't depend on any config,
// so we don't need to register/unregister it when we reload.
getCommand("wp").setExecutor(new CommandWP());
}
@Override
public void onDisable() {
instance = null;
}
private void load() {
permissions = ConfigParser.parse(getConfig());
if (permissions.getPlayerGroups().size() == 0) {
getLogger().warning("No players have been assigned permissions!");
}
attachments = new PermissionsAttachments(permissions);
listener = new PermissionsAttachmentsListener(attachments);
attachments.register();
getServer().getPluginManager().registerEvents(listener, this);
}
private void unload() {
HandlerList.unregisterAll(listener);
attachments.unregister();
}
public void reload() {
getLogger().info("Reloading...");
saveDefaultConfig();
reloadConfig();
unload();
load();
getLogger().info("Done.");
}
}

View File

@ -0,0 +1,21 @@
package me.jamestmartin.wasteland.permissions.api;
import java.util.List;
import java.util.Map;
public class Group extends PseudoGroup {
private final String name;
public Group(String name, List<Group> parents, Map<String, Boolean> permissions) {
super(parents, permissions);
this.name = name;
}
public Group(String name, PseudoGroup group) {
this(name, group.getParents(), group.getDirectPermissions());
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,49 @@
package me.jamestmartin.wasteland.permissions.api;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.OfflinePlayer;
import me.jamestmartin.wasteland.permissions.WastelandPermissions;
public class Permissions {
private final Optional<Group> defaultGroup;
private final Map<String, Group> groups;
private final Map<UUID, PlayerGroup> playerGroups;
public Permissions(Map<String, Group> groups, Map<UUID, PlayerGroup> playerGroups) {
this.groups = groups;
this.playerGroups = playerGroups;
this.defaultGroup = Optional.ofNullable(groups.get("default"));
}
/** Get the permissions of all players specified in the configuration. */
public Map<UUID, PlayerGroup> getPlayerGroups() {
return playerGroups;
}
/** Get every group specified in the configuration. */
public Map<String, Group> getGroups() {
return groups;
}
public PlayerGroup getPlayerGroup(OfflinePlayer player) {
return Optional.ofNullable(getPlayerGroups().get(player.getUniqueId()))
.or(() -> defaultGroup.map(group -> new PlayerGroup(player, group)))
.orElseGet(() -> new PlayerGroup(player));
}
public PlayerGroup getPlayerGroup(UUID player) {
return getPlayerGroup(WastelandPermissions.getInstance().getServer().getOfflinePlayer(player));
}
public Optional<Group> getGroup(String id) {
return Optional.ofNullable(groups.get(id));
}
public Optional<Group> getDefaultGroup() {
return defaultGroup;
}
}

View File

@ -0,0 +1,31 @@
package me.jamestmartin.wasteland.permissions.api;
import java.util.List;
import java.util.Map;
import org.bukkit.OfflinePlayer;
public class PlayerGroup extends PseudoGroup {
private final OfflinePlayer player;
public PlayerGroup(OfflinePlayer player, List<Group> parents, Map<String, Boolean> permissions) {
super(parents, permissions);
this.player = player;
}
public PlayerGroup(OfflinePlayer player, PseudoGroup group) {
this(player, group.getParents(), group.getDirectPermissions());
}
public PlayerGroup(OfflinePlayer player, Group group) {
this(player, List.of(group), Map.of());
}
public PlayerGroup(OfflinePlayer player) {
this(player, List.of(), Map.of());
}
public OfflinePlayer getPlayer() {
return player;
}
}

View File

@ -0,0 +1,35 @@
package me.jamestmartin.wasteland.permissions.api;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** An actual named group, or just an unnamed set of permissions associated with a player. */
public class PseudoGroup {
private final List<Group> parents;
private final Map<String, Boolean> permissions;
private final Map<String, Boolean> allPermissions;
public PseudoGroup(List<Group> parents, Map<String, Boolean> permissions) {
this.parents = parents;
this.permissions = permissions;
this.allPermissions = new HashMap<>();
parents.stream().map(PseudoGroup::getPermissions).forEach(allPermissions::putAll);
allPermissions.putAll(permissions);
}
public List<Group> getParents() {
return parents;
}
/** Get only the permissions defined directly on this group. */
public Map<String, Boolean> getDirectPermissions() {
return permissions;
}
/** Get all of the permissions provided by this group. */
public Map<String, Boolean> getPermissions() {
return allPermissions;
}
}

View File

@ -0,0 +1,122 @@
package me.jamestmartin.wasteland.permissions.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import me.jamestmartin.wasteland.permissions.WastelandPermissions;
import me.jamestmartin.wasteland.permissions.api.Group;
import me.jamestmartin.wasteland.permissions.api.Permissions;
import me.jamestmartin.wasteland.permissions.api.PlayerGroup;
public class ConfigParser {
public static Permissions parse(ConfigurationSection c) {
Map<String, Group> groups = parseGroups(c.getConfigurationSection("groups"));
Map<UUID, PlayerGroup> players = parsePlayers(groups, c.getConfigurationSection("players"));
return new Permissions(groups, players);
}
private static Map<String, Group> parseGroups(ConfigurationSection c) {
Map<String, GroupConfig> groupConfigs = new HashMap<>();
for (String group : c.getKeys(false)) {
groupConfigs.put(group, parseGroup(group, c.getConfigurationSection(group)));
}
MutableGraph<GroupConfig> gb = GraphBuilder.directed().allowsSelfLoops(false).build();
for (GroupConfig group : groupConfigs.values()) {
gb.addNode(group);
}
for (GroupConfig group : groupConfigs.values()) {
for (String parent : group.getParents()) {
gb.putEdge(group, groupConfigs.get(parent));
}
}
Set<String> orderedGroupsSet = new HashSet<>();
List<GroupConfig> orderedGroups = new ArrayList<>();
Set<GroupConfig> nodes = gb.nodes();
while (!nodes.isEmpty()) {
int previousSize = nodes.size();
// You can't remove from a set that you're iterating over.
Set<GroupConfig> nextNodes = new HashSet<>(nodes);
nextNode: for (GroupConfig node : nodes) {
for (String parent : node.getParents()) {
if (!orderedGroupsSet.contains(parent)) {
continue nextNode;
}
}
orderedGroupsSet.add(node.getId());
orderedGroups.add(node);
nextNodes.remove(node);
}
if (previousSize == nextNodes.size()) {
throw new RuntimeException("Cyclic group dependencies");
}
nodes = nextNodes;
}
Map<String, Group> groups = new HashMap<>();
for (GroupConfig group : orderedGroups) {
List<Group> parents = group.getParents().stream().map(groups::get).collect(Collectors.toUnmodifiableList());
groups.put(group.getId(), new Group(group.getId(), parents, group.getPermissions()));
}
return groups;
}
private static GroupConfig parseGroup(String id, ConfigurationSection c) {
return new GroupConfig(id, parsePseudoGroup(c.getStringList("inherits"), c.getConfigurationSection("permissions")));
}
private static Map<UUID, PlayerGroup> parsePlayers(Map<String, Group> groups, ConfigurationSection c) {
Map<UUID, PlayerGroup> players = new HashMap<>();
for (String uuidStr : c.getKeys(false)) {
UUID uuid = UUID.fromString(uuidStr);
players.put(uuid, parsePlayer(groups, uuidStr, c.getConfigurationSection(uuidStr)));
}
return players;
}
private static PlayerGroup parsePlayer(Map<String, Group> groups, String uuid, ConfigurationSection c) {
OfflinePlayer player = WastelandPermissions.getInstance().getServer().getOfflinePlayer(UUID.fromString(uuid));
PseudoGroupConfig config = parsePseudoGroup(c.getStringList("groups"), c.getConfigurationSection("permissions"));
List<Group> playerGroups = config.getParents().stream().map(groups::get).collect(Collectors.toUnmodifiableList());
return new PlayerGroup(player, playerGroups, config.getPermissions());
}
private static PseudoGroupConfig parsePseudoGroup(List<String> inherits, ConfigurationSection permissions) {
return new PseudoGroupConfig(inherits, parsePermissions(permissions));
}
private static Map<String, Boolean> parsePermissions(ConfigurationSection c) {
Map<String, Boolean> permissions = new HashMap<>();
for (String node : c.getKeys(false)) {
if (c.isBoolean(node)) {
permissions.put(node, c.getBoolean(node));
} else {
for (Entry<String, Boolean> entry : parsePermissions(c.getConfigurationSection(node)).entrySet()) {
permissions.put(node + "." + entry.getKey(), entry.getValue());
}
}
}
return permissions;
}
}

View File

@ -0,0 +1,21 @@
package me.jamestmartin.wasteland.permissions.config;
import java.util.List;
import java.util.Map;
class GroupConfig extends PseudoGroupConfig {
private final String id;
public GroupConfig(String id, List<String> parents, Map<String, Boolean> permissions) {
super(parents, permissions);
this.id = id;
}
public GroupConfig(String id, PseudoGroupConfig group) {
this(id, group.getParents(), group.getPermissions());
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,22 @@
package me.jamestmartin.wasteland.permissions.config;
import java.util.List;
import java.util.Map;
class PseudoGroupConfig {
private final List<String> parents;
private final Map<String, Boolean> permissions;
public PseudoGroupConfig(List<String> parents, Map<String, Boolean> permissions) {
this.parents = parents;
this.permissions = permissions;
}
public List<String> getParents() {
return parents;
}
public Map<String, Boolean> getPermissions() {
return permissions;
}
}

View File

@ -0,0 +1,32 @@
#
# groups:
# default:
# permission.for.newbies: true # track a player's permission
# <groupName>:
# # Permissions in later groups take precedent over permissions in earlier groups,
# # e.g. if `first-group` disables `foo.bar` and `some-other-group` disables it, `foo.bar` will be disabled.
# inherits:
# - first-group
# - some-other-group
# permissions:
# my.permission: true
# some.bad.permission: false # disable a permission that was enabled by default
# shared.top.level.node:
# a.b: true # same as: `shared.top.lavel.node.a.b: true`
# c.d: true
#
# players:
# # You *must* use player UUIDs, not player names.
# # You can see a player's UUID in the server log when they join, or look it up online.
# # I would recommend including the player's name as a comment to help you remember whose UUID this is.
# <player-uuid>:
# permissions:
# - permission.just.for.me: true
# groups:
# - my-group
# - some-other-group
#
groups: {}
players: {}

View File

@ -0,0 +1,23 @@
name: WastelandPermissions
author: HuskFodder
main: me.jamestmartin.wasteland.permissions.WastelandPermissions
version: 0.1.0
api-version: 1.16
commands:
wp:
description: Manage the Wasteland Permissions plugin.
usage: "Usage: /<command> [reload]"
permission: wasteland.permissions
permission-message: You do not have permission to reload Wasteland Permission' config.
permissions:
wasteland.permissions:
description: Allows you to manage Wasteland Permissions.
default: op
children:
wasteland.permissions.reload: true
wasteland.permissions.reload:
description: Allows you to reload Wasteland Permissions' config.
default: op

View File

@ -1 +1,2 @@
rootProject.name = 'wasteland'
rootProject.name = 'wasteland-all'
include 'wasteland', 'permissions'

View File

@ -1,35 +0,0 @@
package me.jamestmartin.wasteland.permissions;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.entity.Player;
public class PermissionsConfig {
private final Map<String, Map<String, Boolean>> groups;
private final Map<UUID, Set<String>> players;
public PermissionsConfig(Map<String, Map<String, Boolean>> groups, Map<UUID, Set<String>> players) {
this.groups = groups;
this.players = players;
}
public Map<String, Boolean> getPermissions(Player player) {
Set<String> playerGroups = players.get(player.getUniqueId());
if (playerGroups == null) {
return getDefaultPermissions();
}
Map<String, Boolean> permissions = new HashMap<>();
for (String group : playerGroups) {
permissions.putAll(groups.get(group));
}
return permissions;
}
public Map<String, Boolean> getDefaultPermissions() {
return groups.getOrDefault("default", Map.of());
}
}

View File

@ -1,28 +0,0 @@
package me.jamestmartin.wasteland.permissions;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.jamestmartin.wasteland.Substate;
public class PermissionsState implements Substate {
private final PermissionsAttachments attachments;
private final PermissionsAttachmentsListener listener;
public PermissionsState(PermissionsConfig config) {
this.attachments = new PermissionsAttachments(config);
this.listener = new PermissionsAttachmentsListener(attachments);
}
@Override
public void register(JavaPlugin plugin) {
attachments.register();
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
@Override
public void unregister(JavaPlugin plugin) {
PlayerQuitEvent.getHandlerList().unregister(listener);
attachments.unregister();
}
}

View File

@ -4,7 +4,6 @@ import me.jamestmartin.wasteland.chat.ChatConfig;
import me.jamestmartin.wasteland.kills.KillsConfig;
import me.jamestmartin.wasteland.kit.KitConfig;
import me.jamestmartin.wasteland.manual.ManualConfig;
import me.jamestmartin.wasteland.permissions.PermissionsConfig;
import me.jamestmartin.wasteland.ranks.AllRanks;
import me.jamestmartin.wasteland.spawns.SpawnsConfig;
@ -16,7 +15,6 @@ public class WastelandConfig {
private final SpawnsConfig spawnsConfig;
private final KitConfig kitConfig;
private final ManualConfig manualConfig;
private final PermissionsConfig permissionsConfig;
public WastelandConfig(
String databaseFilename,
@ -25,8 +23,7 @@ public class WastelandConfig {
AllRanks ranks,
SpawnsConfig spawnsConfig,
KitConfig kitConfig,
ManualConfig manualConfig,
PermissionsConfig permissionsConfig) {
ManualConfig manualConfig) {
this.databaseFilename = databaseFilename;
this.chatConfig = chatConfig;
this.killsConfig = killsConfig;
@ -34,7 +31,6 @@ public class WastelandConfig {
this.spawnsConfig = spawnsConfig;
this.kitConfig = kitConfig;
this.manualConfig = manualConfig;
this.permissionsConfig = permissionsConfig;
}
public String getDatabaseFilename() {
@ -64,8 +60,4 @@ public class WastelandConfig {
public ManualConfig getManualConfig() {
return manualConfig;
}
public PermissionsConfig getPermissionsConfig() {
return permissionsConfig;
}
}

View File

@ -9,7 +9,6 @@ import me.jamestmartin.wasteland.chat.ChatState;
import me.jamestmartin.wasteland.kills.KillsState;
import me.jamestmartin.wasteland.kit.KitState;
import me.jamestmartin.wasteland.manual.ManualState;
import me.jamestmartin.wasteland.permissions.PermissionsState;
import me.jamestmartin.wasteland.ranks.PermissionsPlayerRankProvider;
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
import me.jamestmartin.wasteland.ranks.RanksState;
@ -27,7 +26,6 @@ public class WastelandState implements Substate {
private final SpawnsState spawnsState;
private final KitState kitState;
private final ManualState manualState;
private final PermissionsState permissionsState;
public WastelandState(WastelandConfig config) throws IOException, ClassNotFoundException, SQLException {
this.commandWasteland = new CommandWasteland();
@ -40,7 +38,6 @@ public class WastelandState implements Substate {
this.spawnsState = new SpawnsState(config.getSpawnsConfig());
this.kitState = new KitState(config.getKitConfig(), storeState.getKitStore());
this.manualState = new ManualState(config.getManualConfig());
this.permissionsState = new PermissionsState(config.getPermissionsConfig());
}
private Substate[] getSubstates() {
@ -52,7 +49,6 @@ public class WastelandState implements Substate {
spawnsState,
kitState,
manualState,
permissionsState
};
return substates;
}

View File

@ -9,7 +9,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
@ -29,7 +28,6 @@ import me.jamestmartin.wasteland.kills.KillsConfig;
import me.jamestmartin.wasteland.kit.KitConfig;
import me.jamestmartin.wasteland.manual.ManualConfig;
import me.jamestmartin.wasteland.manual.ManualSection;
import me.jamestmartin.wasteland.permissions.PermissionsConfig;
import me.jamestmartin.wasteland.ranks.AllRanks;
import me.jamestmartin.wasteland.ranks.EnlistedRank;
import me.jamestmartin.wasteland.ranks.EnlistedRanks;
@ -61,8 +59,6 @@ public class ConfigParser {
parseManualSectionList(castToSectionList(c.getMapList("faq"))));
ManualConfig manualConfig = new ManualConfig(rules, faq);
PermissionsConfig permissionsConfig = parsePermissionsConfig(c.getConfigurationSection("permissions"));
return new WastelandConfig(
databaseFilename,
chatConfig,
@ -70,8 +66,7 @@ public class ConfigParser {
ranks,
spawnsConfig,
kitConfig,
manualConfig,
permissionsConfig);
manualConfig);
}
private static ChatConfig parseChatConfig(ConfigurationSection c) {
@ -354,39 +349,6 @@ public class ConfigParser {
return sections;
}
public static PermissionsConfig parsePermissionsConfig(ConfigurationSection c) {
Map<String, Map<String, Boolean>> groupPermissions = new HashMap<>();
ConfigurationSection groupsSection = c.getConfigurationSection("groups");
for (String group : groupsSection.getKeys(false)) {
groupPermissions.put(group, parsePermissions(groupsSection.getConfigurationSection(group)));
}
Map<UUID, Set<String>> playerGroups = new HashMap<>();
ConfigurationSection playersSection = c.getConfigurationSection("players");
for (String player : playersSection.getKeys(false)) {
UUID uuid = UUID.fromString(player);
Set<String> groups = playersSection.getList(player).stream().map(x -> (String) x)
.collect(Collectors.toUnmodifiableSet());
playerGroups.put(uuid, groups);
}
return new PermissionsConfig(groupPermissions, playerGroups);
}
public static Map<String, Boolean> parsePermissions(ConfigurationSection c) {
Map<String, Boolean> permissions = new HashMap<>();
for (String node : c.getKeys(false)) {
if (c.isBoolean(node)) {
permissions.put(node, c.getBoolean(node));
} else {
for (Entry<String, Boolean> entry : parsePermissions(c.getConfigurationSection(node)).entrySet()) {
permissions.put(node + "." + entry.getKey(), entry.getValue());
}
}
}
return permissions;
}
/** Orphaned method. */
private static Optional<ChatColor> readColor(ConfigurationSection c, String path) {
return (Optional<ChatColor>) Optional.ofNullable(c.getString(path)).map(ChatColor::valueOf);

View File

@ -412,9 +412,4 @@ rules:
faq:
- summary: Nothing to see here.
details: The server administrator has not set an FAQ.
permissions:
default: {}
groups: {}
players: {}
details: The server administrator has not set an FAQ.