From aff229164cf282f9dd98fdd9fdf388530b48367a Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Fri, 22 Jul 2022 00:17:36 +0200 Subject: [PATCH] New modules again + some pom refactor --- pandalib-bungee-permissions/pom.xml | 43 +++ .../PandalibBungeePermissions.java | 57 ++++ pandalib-bungee-players/pom.xml | 49 +++ .../lib/bungee/players/BungeeOffPlayer.java | 26 ++ .../bungee/players/BungeeOnlinePlayer.java | 208 ++++++++++++ pandalib-bungee/pom.xml | 10 +- pandalib-cli/pom.xml | 19 +- pandalib-core/pom.xml | 31 -- pandalib-net/Readme.md | 4 +- pandalib-paper-permissions/pom.xml | 93 ++++++ .../permissions/PandalibPaperPermissions.java | 143 ++++++++ .../PermissionsInjectorBukkit.java | 313 ++++++++++++++++++ .../permissions/PermissionsInjectorVault.java | 255 ++++++++++++++ .../permissions/PermissionsInjectorWEPIF.java | 93 ++++++ pandalib-paper/pom.xml | 16 +- pandalib-players-permissible/pom.xml | 1 - pom.xml | 3 + 17 files changed, 1314 insertions(+), 50 deletions(-) create mode 100644 pandalib-bungee-permissions/pom.xml create mode 100644 pandalib-bungee-permissions/src/main/java/fr/pandacube/lib/bungee/permissions/PandalibBungeePermissions.java create mode 100644 pandalib-bungee-players/pom.xml create mode 100644 pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java create mode 100644 pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java create mode 100644 pandalib-paper-permissions/pom.xml create mode 100644 pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java create mode 100644 pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java create mode 100644 pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java create mode 100644 pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java diff --git a/pandalib-bungee-permissions/pom.xml b/pandalib-bungee-permissions/pom.xml new file mode 100644 index 0000000..2f47ae7 --- /dev/null +++ b/pandalib-bungee-permissions/pom.xml @@ -0,0 +1,43 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-bungee-permissions + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + fr.pandacube.lib + pandalib-players-permissible + ${project.version} + provided + + + fr.pandacube.lib + pandalib-permissions + ${project.version} + + + + net.md-5 + bungeecord-api + ${bungeecord.version} + provided + + + + + \ No newline at end of file diff --git a/pandalib-bungee-permissions/src/main/java/fr/pandacube/lib/bungee/permissions/PandalibBungeePermissions.java b/pandalib-bungee-permissions/src/main/java/fr/pandacube/lib/bungee/permissions/PandalibBungeePermissions.java new file mode 100644 index 0000000..cbc2b7c --- /dev/null +++ b/pandalib-bungee-permissions/src/main/java/fr/pandacube/lib/bungee/permissions/PandalibBungeePermissions.java @@ -0,0 +1,57 @@ +package fr.pandacube.lib.bungee.permissions; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PermissionCheckEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import fr.pandacube.lib.permissions.Permissions; +import fr.pandacube.lib.players.permissible.PermissibleOnlinePlayer; +import fr.pandacube.lib.players.permissible.PermissiblePlayerManager; + +public class PandalibBungeePermissions implements Listener { + + public static void init(Plugin bungeePlugin) { + ProxyServer.getInstance().getPluginManager().registerListener(bungeePlugin, new PandalibBungeePermissions()); + } + + + @EventHandler(priority = Byte.MAX_VALUE) + public void onPermissionCheck(PermissionCheckEvent event) + { + CommandSender s = event.getSender(); + if (s instanceof ProxiedPlayer p) { + event.setHasPermission(hasPerm(p, event.getPermission())); + } + else { + event.setHasPermission(true); + } + } + + private volatile boolean tryPermPlayerManager = true; + + private boolean hasPerm(ProxiedPlayer p, String permission) { + if (tryPermPlayerManager) { + try { + PermissiblePlayerManager pm = PermissiblePlayerManager.getInstance(); + if (pm != null) { + PermissibleOnlinePlayer op = pm.get(p.getUniqueId()); + if (op != null) { + return op.hasPermission(permission); + } + } + } catch (NoClassDefFoundError ignored) { + tryPermPlayerManager = false; + } + } + + // if not using player manager, fallback to directly call permissions API + Server sv = p.getServer(); + String server = sv == null ? null : sv.getInfo().getName(); + return Permissions.getPlayer(p.getUniqueId()).hasPermissionOr(permission, server, null, false); + } +} diff --git a/pandalib-bungee-players/pom.xml b/pandalib-bungee-players/pom.xml new file mode 100644 index 0000000..42fbd04 --- /dev/null +++ b/pandalib-bungee-players/pom.xml @@ -0,0 +1,49 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-bungee-players + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + fr.pandacube.lib + pandalib-players-standalone + ${project.version} + + + fr.pandacube.lib + pandalib-reflect + ${project.version} + + + + net.md-5 + bungeecord-api + ${bungeecord.version} + provided + + + + net.md-5 + bungeecord-protocol + ${bungeecord.version} + provided + + + + + \ No newline at end of file diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java new file mode 100644 index 0000000..881c2f7 --- /dev/null +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java @@ -0,0 +1,26 @@ +package fr.pandacube.lib.bungee.players; + +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import fr.pandacube.lib.players.standalone.StandaloneOffPlayer; + +public interface BungeeOffPlayer extends StandaloneOffPlayer { + + /* + * Related class instances + */ + + /** + * @return l'instance Bungee du joueur en ligne, ou null si il n'est pas en + * ligne + */ + default ProxiedPlayer getBungeeProxiedPlayer() { + return ProxyServer.getInstance().getPlayer(getUniqueId()); + } + + @Override + BungeeOnlinePlayer getOnlineInstance(); + + +} diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java new file mode 100644 index 0000000..7f33036 --- /dev/null +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java @@ -0,0 +1,208 @@ +package fr.pandacube.lib.bungee.players; + +import java.util.Locale; +import java.util.UUID; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import net.kyori.adventure.identity.Identified; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.SkinConfiguration; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.ProxiedPlayer.ChatMode; +import net.md_5.bungee.api.connection.ProxiedPlayer.MainHand; +import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.packet.ClientSettings; +import net.md_5.bungee.protocol.packet.PluginMessage; + +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer; +import fr.pandacube.lib.reflect.Reflect; + +public interface BungeeOnlinePlayer extends BungeeOffPlayer, StandaloneOnlinePlayer { + + + @Override + ProxiedPlayer getBungeeProxiedPlayer(); + + + + + + + + /* + * Sending packet and stuff to player + */ + + @Override + default void sendMessage(Component message) { + getBungeeProxiedPlayer().sendMessage(Chat.toBungee(message)); + } + + @Override + default void sendMessage(ComponentLike message, UUID sender) { + getBungeeProxiedPlayer().sendMessage(sender, Chat.toBungee(message.asComponent())); + } + + @Override + default void sendMessage(Component message, Identified sender) { + getBungeeProxiedPlayer().sendMessage(sender.identity().uuid(), Chat.toBungee(message)); + } + + default void sendMessage(ComponentLike message, ProxiedPlayer sender) { + getBungeeProxiedPlayer().sendMessage(sender.getUniqueId(), Chat.toBungee(message.asComponent())); + } + + @Override + default void sendTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut) { + ProxyServer.getInstance().createTitle() + .title(Chat.toBungee(title)).subTitle(Chat.toBungee(subtitle)) + .fadeIn(fadeIn).stay(stay).fadeOut(fadeOut) + .send(getBungeeProxiedPlayer()); + } + + @Override + default void sendServerBrand(String legacyText) { + try { + ByteBuf payload = ByteBufAllocator.DEFAULT.heapBuffer(); + DefinedPacket.writeString(legacyText, payload); + getBungeeProxiedPlayer().unsafe().sendPacket(new PluginMessage("minecraft:brand", DefinedPacket.toArray(payload), false)); + payload.release(); + } catch (Exception ignored) { } + } + + + + + + + /* + * Client options + */ + + @Override + default BungeeClientOptions getClientOptions() { + return new BungeeClientOptions(this); + } + + class BungeeClientOptions implements StandaloneOnlinePlayer.ClientOptions { + + BungeeOnlinePlayer op; + public BungeeClientOptions(BungeeOnlinePlayer op) { + this.op = op; + } + + private ClientSettings getBungeeSettings() { + ProxiedPlayer pp = op.getBungeeProxiedPlayer(); + try { + return (ClientSettings) Reflect.ofClassOfInstance(pp).method("getSettings").invoke(pp); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean hasChatColorEnabled() { + return op.getBungeeProxiedPlayer().hasChatColors(); + } + + public ChatMode getChatMode() { + return op.getBungeeProxiedPlayer().getChatMode(); + } + + @Override + public boolean isChatFullyVisible() { + ChatMode v = getChatMode(); + return v == ChatMode.SHOWN || v == null; + } + + @Override + public boolean isChatOnlyDisplayingSystemMessages() { + return getChatMode() == ChatMode.COMMANDS_ONLY; + } + + @Override + public boolean isChatHidden() { + return getChatMode() == ChatMode.HIDDEN; + } + + @Override + public Locale getLocale() { + return op.getBungeeProxiedPlayer().getLocale(); + } + + @Override + public int getViewDistance() { + return op.getBungeeProxiedPlayer().getViewDistance(); + } + + public MainHand getMainHand() { + return op.getBungeeProxiedPlayer().getMainHand(); + } + + @Override + public boolean isLeftHanded() { + return getMainHand() == MainHand.LEFT; + } + + @Override + public boolean isRightHanded() { + return getMainHand() == MainHand.RIGHT; + } + + @Override + public boolean isTextFilteringEnabled() { + ClientSettings settings = getBungeeSettings(); + return settings != null && settings.isDisableTextFiltering(); // Bungee badly named the method + } + + @Override + public boolean allowsServerListing() { + ClientSettings settings = getBungeeSettings(); + return settings != null && settings.isAllowServerListing(); + } + + public SkinConfiguration getSkinParts() { + return op.getBungeeProxiedPlayer().getSkinParts(); + } + + @Override + public boolean hasSkinCapeEnabled() { + return getSkinParts().hasCape(); + } + + @Override + public boolean hasSkinJacketEnabled() { + return getSkinParts().hasJacket(); + } + + @Override + public boolean hasSkinLeftSleeveEnabled() { + return getSkinParts().hasLeftSleeve(); + } + + @Override + public boolean hasSkinRightSleeveEnabled() { + return getSkinParts().hasRightSleeve(); + } + + @Override + public boolean hasSkinLeftPantsEnabled() { + return getSkinParts().hasLeftPants(); + } + + @Override + public boolean hasSkinRightPantsEnabled() { + return getSkinParts().hasRightPants(); + } + + @Override + public boolean hasSkinHatsEnabled() { + return getSkinParts().hasHat(); + } + } + +} diff --git a/pandalib-bungee/pom.xml b/pandalib-bungee/pom.xml index 3a813aa..96e294d 100644 --- a/pandalib-bungee/pom.xml +++ b/pandalib-bungee/pom.xml @@ -18,20 +18,12 @@ - - fr.pandacube.lib - pandalib-core - ${project.version} - - net.md-5 bungeecord-api ${bungeecord.version} + provided - - - \ No newline at end of file diff --git a/pandalib-cli/pom.xml b/pandalib-cli/pom.xml index 2cfe346..324e5d5 100644 --- a/pandalib-cli/pom.xml +++ b/pandalib-cli/pom.xml @@ -23,12 +23,19 @@ - - fr.pandacube.lib - pandalib-core - ${project.version} - compile - + + fr.pandacube.lib + pandalib-core + ${project.version} + compile + + + + fr.pandacube.lib + pandalib-reflect + ${project.version} + compile + net.md-5 diff --git a/pandalib-core/pom.xml b/pandalib-core/pom.xml index 4d402a6..5efbd3e 100644 --- a/pandalib-core/pom.xml +++ b/pandalib-core/pom.xml @@ -35,36 +35,5 @@ pandalib-chat ${project.version} - - - fr.pandacube.lib - pandalib-db - ${project.version} - - - - fr.pandacube.lib - pandalib-reflect - ${project.version} - - - - fr.pandacube.lib - pandalib-permissions - ${project.version} - - - - fr.pandacube.lib - pandalib-players-standalone - ${project.version} - - - - - org.geysermc.floodgate - api - 2.0-SNAPSHOT - diff --git a/pandalib-net/Readme.md b/pandalib-net/Readme.md index 80dddfb..2e33d79 100644 --- a/pandalib-net/Readme.md +++ b/pandalib-net/Readme.md @@ -1,10 +1,10 @@ -# pandacube-net +# pandalib-net A TCP network library that uses the standard Java socket API, to ease the ommunication between the different processes running the server network Pandacube. It’s still in development (actually not touched since years), and it’s supposed to be a replacement for the old -`pandalib-network-api`. This module is then marked as Beta using the Google Guava annotation. +`pandalib-netapi`. This module is then marked as Beta using the Google Guava annotation. - Packet based communication - Supports Request/Answer packets diff --git a/pandalib-paper-permissions/pom.xml b/pandalib-paper-permissions/pom.xml new file mode 100644 index 0000000..21d3c06 --- /dev/null +++ b/pandalib-paper-permissions/pom.xml @@ -0,0 +1,93 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-paper-permissions + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + sk89q-repo + https://maven.enginehub.org/repo/ + + + + + jitpack.io + https://jitpack.io + + + + + + fr.pandacube.lib + pandalib-players-permissible + ${project.version} + provided + + + fr.pandacube.lib + pandalib-permissions + ${project.version} + + + fr.pandacube.lib + pandalib-reflect + ${project.version} + + + + + io.papermc.paper + paper-api + ${paper.version}-SNAPSHOT + provided + + + + + com.github.MilkBowl + VaultAPI + 1.7.1 + provided + true + + + org.bukkit + bukkit + + + + + + + com.sk89q.worldedit + worldedit-bukkit + 7.2.9 + provided + + + org.bukkit + bukkit + + + org.sk89q.bukkit + bukkit-classloader-check + + + + + + \ No newline at end of file diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java new file mode 100644 index 0000000..b45cfd7 --- /dev/null +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java @@ -0,0 +1,143 @@ +package fr.pandacube.lib.paper.permissions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.ServerOperator; +import org.bukkit.plugin.java.JavaPlugin; + +import fr.pandacube.lib.permissions.Permissions; +import fr.pandacube.lib.util.Log; + +public class PandalibPaperPermissions implements Listener { + + /* package */ static JavaPlugin plugin; + /* package */ static String serverName; + /* package */ static final Map permissionMap = new HashMap<>(); + + public static void init(JavaPlugin plugin, String serverName) { + PandalibPaperPermissions.plugin = plugin; + PandalibPaperPermissions.serverName = serverName; + PermissionsInjectorBukkit.inject(Bukkit.getConsoleSender()); + PermissionsInjectorVault.inject(); + PermissionsInjectorWEPIF.inject(); + + Bukkit.getPluginManager().registerEvents(new PandalibPaperPermissions(), plugin); + } + + /** + * Add the provided pair of permission into an internal permission map. This is used when a plugin asks the value of + * the sourcePerm, then the permission system actually check for the destPerm. + *

+ * This mapping is useful, for instance, when the bukkit dispatcher force the fake vanilla commands to have a + * permission starting with {@code minecraft.command.} even if we defined a custom permission in the plugin. + * @param sourcePerm the source permission to replace + * @param destPerm the replacement permission + */ + public static void addPermissionMapping(String sourcePerm, String destPerm) { + Objects.requireNonNull(sourcePerm, "sourcePerm"); + if (destPerm == null) { + permissionMap.remove(sourcePerm); + } + else { + permissionMap.put(sourcePerm, destPerm); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent event) { + Permissions.clearPlayerCache(event.getPlayer().getUniqueId()); + Permissions.precachePlayerAsync(event.getPlayer().getUniqueId()); + + PermissionsInjectorBukkit.inject(event.getPlayer()); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + PermissionsInjectorBukkit.uninject(event.getPlayer()); + Permissions.clearPlayerCache(event.getPlayer().getUniqueId()); + } + + + + + /* package */ static Function> SUPERPERMS_PARENT_PERMISSION_GETTER = childPerm -> { + return Bukkit.getPluginManager().getPermissions().stream() + .filter(p -> p.getChildren().containsKey(childPerm)) + .collect(Collectors.toList()); + }; + + /* package */ static ServerOperator dummyOperator(boolean isOp) { + return new ServerOperator() { + @Override public void setOp(boolean op) { } + @Override public boolean isOp() { return isOp; } + }; + } + + /* package */ static Boolean hasSuperPermsPermission(ServerOperator opable, String permission, Predicate parentPermissionChecker) { + if (opable instanceof CommandSender sender) { + Permissible permissible = PermissionsInjectorBukkit.getPermissible(sender); + if (permissible instanceof PermissionsInjectorBukkit.PandaPermissible pPerm) + return hasSuperPermsPermission(opable, permission, parentPermissionChecker, pPerm); + } + return hasSuperPermsPermission(opable, permission, parentPermissionChecker, null); + } + + /* package */ static Boolean hasSuperPermsPermission(ServerOperator opable, String permission, Predicate parentPermissionChecker, PermissionsInjectorBukkit.PandaPermissible pandaPermissible) { + + boolean reversed = permission.startsWith("-"); + if (reversed) { + permission = permission.substring(1); + } + + boolean defined = false; + Permission perm = Bukkit.getPluginManager().getPermission(permission); + if (perm != null) { + if (perm.getDefault().getValue(opable.isOp())) + return !reversed; + defined = true; + } + + try { + List parents = pandaPermissible != null ? pandaPermissible.superPermsPermissionCache.get(permission) : SUPERPERMS_PARENT_PERMISSION_GETTER.apply(permission); + + for (Permission parent : parents) { + Boolean childValue = parent.getChildren().get(permission); + if (childValue == null) + continue; + boolean parentPerm = parentPermissionChecker.test(parent.getName()); + if (parentPerm == childValue) + return !reversed; + defined = true; + } + } catch (ExecutionException e) { + Log.severe("Unable to compute SuperPerms permission", e); + } + + Boolean ret = defined ? reversed : null; + if (Log.isDebugEnabled()) { + String name = (opable instanceof CommandSender cs) ? cs.getName() : "unknown entity"; + String actualPerm = permission; + if (reversed) actualPerm = "-" + permission; + Log.debug("[SuperPerms] For " + name + ", '" + actualPerm + "' is " + ret); + } + return ret; + } + +} diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java new file mode 100644 index 0000000..754fe9b --- /dev/null +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java @@ -0,0 +1,313 @@ +package fr.pandacube.lib.paper.permissions; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import fr.pandacube.lib.permissions.Permissions; +import fr.pandacube.lib.reflect.Reflect; +import fr.pandacube.lib.util.Log; + +public class PermissionsInjectorBukkit +{ + + // to be called : onEnable for console, onPlayerLogin (not Join) (Priority LOWEST) for players + public static void inject(CommandSender sender) { + Permissible oldP = getPermissible(sender); + if (oldP instanceof PandaPermissible) + return; + if (!oldP.getClass().equals(PermissibleBase.class)) { + Log.warning("Another plugin is already injecting permissions into Bukkit for " + sender.getName() + ": " + oldP.getClass().getName()); + Log.warning("Not injecting our own permissions."); + } + PandaPermissible p = new PandaPermissible(sender, getPermissible(sender)); + setPermissible(p.sender, p); + p.recalculatePermissions(); + } + + + + // to be called : onDisable for console, onPlayerQuit () and onDisable for players + public static void uninject(CommandSender sender) + { + Permissible perm = getPermissible(sender); + if (perm instanceof PandaPermissible p) { + setPermissible(sender, p.oldPermissible); + p.oldPermissible.recalculatePermissions(); + } + } + + private static void setPermissible(CommandSender sender, Permissible newpermissible) + { + try { + Field perm = getPermField(sender); + if (perm == null) + return; + perm.setAccessible(true); + perm.set(sender, newpermissible); + } + catch (Exception e) { + Log.severe(e); + } + } + + /* package */ static Permissible getPermissible(CommandSender sender) + { + Field perm = getPermField(sender); + if (perm == null) + return null; + try { + perm.setAccessible(true); + Permissible p = (Permissible) perm.get(sender); + if (p == null) { + Log.warning("Null permissible instance found in provided CommandSender: " + sender, new Throwable()); + } + return p; + } + catch (Exception e) { + Log.severe(e); + } + return null; + } + + private static Field getPermField(CommandSender sender) + { + if (sender == null) { + throw new IllegalArgumentException("sender cannot be null"); + } + try { + if (sender instanceof Player || sender instanceof ConsoleCommandSender) + return Reflect.ofClassOfInstance(sender).field("perm").get(); + else + throw new IllegalArgumentException("Unsupported type for sender: " + sender.getClass()); + } + catch (Exception e) { + Log.severe(e); + } + return null; + } + + /* package */ static class PandaPermissible extends PermissibleBase + { + private final CommandSender sender; + private final Permissible oldPermissible; + + /* package */ LoadingCache> superPermsPermissionCache = CacheBuilder.newBuilder() + .build(CacheLoader.from(PandalibPaperPermissions.SUPERPERMS_PARENT_PERMISSION_GETTER::apply)); + + private boolean init = false; + + private PandaPermissible(CommandSender sender, Permissible oldPermissible) + { + super(sender); + this.sender = sender; + this.oldPermissible = oldPermissible; + init = true; + recalculatePermissions(); + } + + private Boolean hasPermissionOnServerInWorld(String permission) { + if (sender instanceof Player player) { + String world = player.getWorld().getName(); + return Permissions.getPlayer(player.getUniqueId()).hasPermission(permission, PandalibPaperPermissions.serverName, world); + } + return true; + } + + @Override + public boolean hasPermission(String permission) + { + /* + * WARNING: don’t call PermissibleOnlinePlayer#hasPermission(String) here or it will result on a stack overflow + */ + + if (permission.toLowerCase().startsWith("minecraft.command.")) + permission = PandalibPaperPermissions.permissionMap.getOrDefault(permission.toLowerCase(), permission); + + Boolean res = hasPermissionOnServerInWorld(permission); // supports negative permission + if (res != null) + return res; + + res = PandalibPaperPermissions.hasSuperPermsPermission(sender, permission, this::hasPermission, this); // supports negative permission + if (res != null) + return res; + + boolean reversed = permission.startsWith("-"); + if (reversed) { + permission = permission.substring(1); + } + + return oldPermissible.hasPermission(permission) != reversed; + } + + @Override + public boolean hasPermission(Permission permission) + { + if (permission.getName().toLowerCase().startsWith("minecraft.command.") && PandalibPaperPermissions.permissionMap.containsKey(permission.getName().toLowerCase())) { + return hasPermission(PandalibPaperPermissions.permissionMap.get(permission.getName().toLowerCase())); + } + + Boolean res = hasPermissionOnServerInWorld(permission.getName()); // supports negative permission + if (res != null) + return res; + + res = PandalibPaperPermissions.hasSuperPermsPermission(sender, permission.getName(), this::hasPermission, this); // supports negative permission + if (res != null) + return res; + + return oldPermissible.hasPermission(permission); // doesn’t need to manage negative permission (should not happend) + } + + @Override + public void recalculatePermissions() + { + // need this check because super class constructor calls this method, + // thus before oldPermissible has its value assigned + if (!init) + return; + + oldPermissible.recalculatePermissions(); + + superPermsPermissionCache.invalidateAll(); + + effectivePermissionsListCache.invalidateAll(); + } + + + private Map getEffectivePermissionsOnServerInWorld() { + if (sender instanceof Player player) { + String world = player.getWorld().getName(); + return Permissions.getPlayer(player.getUniqueId()).listEffectivePermissions(PandalibPaperPermissions.serverName, world); + } + return new HashMap<>(); + } + + + // key is world + private final Cache> effectivePermissionsListCache = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + @Override + public Set getEffectivePermissions() + { + // PlotSquared uses this method to optimize permission range (plots.limit.10 for example) + // MobArena uses this method when a player leave the arena + // LibsDisguises uses this method (and only this one) to parse all the permissions + + //Log.warning("There is a plugin calling CommandSender#getEffectivePermissions(). See the stacktrace to understand the reason for that.", new Throwable()); + + String world = null; + if (sender instanceof Player player) { + world = player.getWorld().getName(); + } + + try { + return effectivePermissionsListCache.get(world, () -> { + // first get the superperms effective permissions (taht take isOp into accound) + Map perms = oldPermissible.getEffectivePermissions().stream() + .collect(Collectors.toMap(PermissionAttachmentInfo::getPermission, Function.identity())); + + // then override them with the permissions from our permission system (that has priority, and take current world into account) + for (Map.Entry permE : getEffectivePermissionsOnServerInWorld().entrySet()) { + perms.put(permE.getKey(), new PermissionAttachmentInfo(this, permE.getKey(), null, permE.getValue())); + } + + return new LinkedHashSet<>(perms.values()); + }); + } catch (ExecutionException e) { + Log.severe(e); + return oldPermissible.getEffectivePermissions(); + } + + } + + @Override + public boolean isOp() + { + return oldPermissible.isOp(); + } + + @Override + public void setOp(boolean value) + { + oldPermissible.setOp(value); + } + + @Override + public boolean isPermissionSet(String permission) + { + Boolean res = hasPermissionOnServerInWorld(permission); + if (res != null) + return true; + return oldPermissible.isPermissionSet(permission); + } + + @Override + public boolean isPermissionSet(Permission permission) + { + Boolean res = hasPermissionOnServerInWorld(permission.getName()); + if (res != null) + return true; + return oldPermissible.isPermissionSet(permission); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) + { + return oldPermissible.addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) + { + return oldPermissible.addAttachment(plugin, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) + { + return oldPermissible.addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) + { + return oldPermissible.addAttachment(plugin, name, value, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) + { + oldPermissible.removeAttachment(attachment); + } + + @Override + public synchronized void clearPermissions() + { + if (oldPermissible instanceof PermissibleBase) + ((PermissibleBase) oldPermissible).clearPermissions(); + } + } +} diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java new file mode 100644 index 0000000..970d3e4 --- /dev/null +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java @@ -0,0 +1,255 @@ +package fr.pandacube.lib.paper.permissions; + +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.plugin.ServicePriority; + +import fr.pandacube.lib.permissions.PermGroup; +import fr.pandacube.lib.permissions.Permissions; +import fr.pandacube.lib.util.Log; + +public class PermissionsInjectorVault { + + public static PandaVaultPermission permInstance; + + public static void inject() { + try { + permInstance = new PandaVaultPermission(); + PandaVaultChat chat = new PandaVaultChat(permInstance); + Bukkit.getServicesManager().register(net.milkbowl.vault.permission.Permission.class, permInstance, + PandalibPaperPermissions.plugin, ServicePriority.High); + Bukkit.getServicesManager().register(net.milkbowl.vault.chat.Chat.class, chat, + PandalibPaperPermissions.plugin, ServicePriority.High); + Log.info("Providing permissions and chat prefix/suffix through Vault API."); + } catch (NoClassDefFoundError e) { + Log.warning("Vault plugin not detected. Not using it to provide permissions and prefix/suffix." + e.getMessage()); + } + } + + + + + + public static class PandaVaultPermission extends net.milkbowl.vault.permission.Permission { + + private PandaVaultPermission() { } + + @Override + public String getName() { + return "Pandalib"; + } + + @Override + public boolean isEnabled() { + return PandalibPaperPermissions.plugin != null && PandalibPaperPermissions.plugin.isEnabled(); + } + + @Override + public boolean hasSuperPermsCompat() { + return true; + } + + @Deprecated + @Override + public boolean playerHas(String world, String player, String permission) { + return playerHas(world, Bukkit.getOfflinePlayer(player), permission); + } + + @Override + public boolean playerHas(String world, OfflinePlayer player, String permission) { + Boolean res = Permissions.getPlayer(player.getUniqueId()).hasPermission(permission, PandalibPaperPermissions.serverName, world); + if (res != null) + return res; + + res = PandalibPaperPermissions.hasSuperPermsPermission(player, permission, p -> playerHas(world, player, p)); + if (res != null) + return res; + + return permission.startsWith("-"); + } + + @Override + public boolean playerAdd(String world, String player, String permission) { + return false; + } + + @Override + public boolean playerRemove(String world, String player, String permission) { + return false; + } + + @Override + public boolean groupHas(String world, String group, String permission) { + Boolean res = Permissions.getGroup(group).hasPermission(permission, PandalibPaperPermissions.serverName, world); + if (res != null) + return res; + + res = PandalibPaperPermissions.hasSuperPermsPermission(PandalibPaperPermissions.dummyOperator(false), permission, p -> groupHas(world, group, p), null); + if (res != null) + return res; + + return permission.startsWith("-"); + } + + @Override + public boolean groupAdd(String world, String group, String permission) { + return false; + } + + @Override + public boolean groupRemove(String world, String group, String permission) { + return false; + } + + @Deprecated + @Override + public boolean playerInGroup(String world, String player, String group) { + return playerInGroup(world, Bukkit.getOfflinePlayer(player), group); + } + + @Override + public boolean playerInGroup(String world, OfflinePlayer player, String group) { + return Permissions.getPlayer(player.getUniqueId()).isInGroup(group); + } + + @Override + public boolean playerAddGroup(String world, String player, String group) { + return false; + } + + @Override + public boolean playerRemoveGroup(String world, String player, String group) { + return false; + } + + @Deprecated + @Override + public String[] getPlayerGroups(String world, String player) { + return getPlayerGroups(world, Bukkit.getOfflinePlayer(player)); + } + + @Override + public String[] getPlayerGroups(String world, OfflinePlayer player) { + List groups = Permissions.getPlayer(player.getUniqueId()).getGroupsString(); + return groups.toArray(new String[0]); + } + + @Deprecated + @Override + public String getPrimaryGroup(String world, String player) { + return getPrimaryGroup(world, Bukkit.getOfflinePlayer(player)); + } + + @Override + public String getPrimaryGroup(String world, OfflinePlayer player) { + return Permissions.getPlayer(player.getUniqueId()).getGroupsString().stream() + .findFirst().orElse(null); + } + + @Override + public String[] getGroups() { + return Permissions.getGroups().stream() + .map(PermGroup::getName).toArray(String[]::new); + } + + @Override + public boolean hasGroupSupport() { + return true; + } + + } + + + private static class PandaVaultChat extends net.milkbowl.vault.chat.Chat { + + public PandaVaultChat(net.milkbowl.vault.permission.Permission perms) { + super(perms); + } + + @Override + public String getName() { + return "Pandalib"; + } + + @Override + public boolean isEnabled() { + return PandalibPaperPermissions.plugin != null && PandalibPaperPermissions.plugin.isEnabled(); + } + + @Deprecated + @Override + public String getPlayerPrefix(String world, String player) { + return getPlayerPrefix(world, Bukkit.getOfflinePlayer(player)); + } + + @Override + public String getPlayerPrefix(String world, OfflinePlayer player) { + return Permissions.getPlayer(player.getUniqueId()).getPrefix(); + } + + @Deprecated + @Override + public String getPlayerSuffix(String world, String player) { + return getPlayerSuffix(world, Bukkit.getOfflinePlayer(player)); + } + + @Override + public String getPlayerSuffix(String world, OfflinePlayer player) { + return Permissions.getPlayer(player.getUniqueId()).getSuffix(); + } + + @Override + public String getGroupPrefix(String world, String group) { + return Permissions.getGroup(group).getPrefix(); + } + + @Override + public String getGroupSuffix(String world, String group) { + return Permissions.getGroup(group).getSuffix(); + } + + @Override + public void setPlayerPrefix(String world, String player, String prefix) { /* unsupported */ } + @Override + public void setPlayerSuffix(String world, String player, String suffix) { /* unsupported */ } + @Override + public void setGroupPrefix(String world, String group, String prefix) { /* unsupported */ } + @Override + public void setGroupSuffix(String world, String group, String suffix) { /* unsupported */ } + @Override + public int getPlayerInfoInteger(String world, String player, String node, int defaultValue) { return defaultValue; } + @Override + public void setPlayerInfoInteger(String world, String player, String node, int value) { /* unsupported */ } + @Override + public int getGroupInfoInteger(String world, String group, String node, int defaultValue) { return defaultValue; } + @Override + public void setGroupInfoInteger(String world, String group, String node, int value) { /* unsupported */ } + @Override + public double getPlayerInfoDouble(String world, String player, String node, double defaultValue) { return defaultValue; } + @Override + public void setPlayerInfoDouble(String world, String player, String node, double value) { /* unsupported */ } + @Override + public double getGroupInfoDouble(String world, String group, String node, double defaultValue) { return defaultValue; } + @Override + public void setGroupInfoDouble(String world, String group, String node, double value) { /* unsupported */ } + @Override + public boolean getPlayerInfoBoolean(String world, String player, String node, boolean defaultValue) { return defaultValue; } + @Override + public void setPlayerInfoBoolean(String world, String player, String node, boolean value) { /* unsupported */ } + @Override + public boolean getGroupInfoBoolean(String world, String group, String node, boolean defaultValue) { return defaultValue; } + @Override + public void setGroupInfoBoolean(String world, String group, String node, boolean value) { /* unsupported */ } + @Override + public String getPlayerInfoString(String world, String player, String node, String defaultValue) { return defaultValue; } + @Override + public void setPlayerInfoString(String world, String player, String node, String value) { /* unsupported */ } + @Override + public String getGroupInfoString(String world, String group, String node, String defaultValue) { return defaultValue; } + @Override + public void setGroupInfoString(String world, String group, String node, String value) { /* unsupported */ } + } + +} diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java new file mode 100644 index 0000000..afb3af2 --- /dev/null +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java @@ -0,0 +1,93 @@ +package fr.pandacube.lib.paper.permissions; + +import java.util.List; + +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.ServicePriority; + +import fr.pandacube.lib.permissions.PermPlayer; +import fr.pandacube.lib.permissions.Permissions; +import fr.pandacube.lib.util.Log; + +public class PermissionsInjectorWEPIF { + + public static PandaWEPIFPermissionsProvider permInstance; + + public static void inject() { + try { + permInstance = new PandaWEPIFPermissionsProvider(); + Bukkit.getServicesManager().register(com.sk89q.wepif.PermissionsProvider.class, permInstance, + PandalibPaperPermissions.plugin, ServicePriority.Highest); + Log.info("Providing permissions through WEPIF"); + Plugin pl = Bukkit.getPluginManager().getPlugin("WorldEdit"); + if (pl == null || !pl.isEnabled()) + return; + ((WorldEditPlugin) pl).getPermissionsResolver().findResolver(); + } catch (NoClassDefFoundError e) { + Log.warning("WorldEdit plugin not detected. Not using WEPIF to provide permissions and prefix/suffix." + e.getMessage()); + } + } + + + + + + public static class PandaWEPIFPermissionsProvider implements com.sk89q.wepif.PermissionsProvider { + private PandaWEPIFPermissionsProvider() { } + + private PermPlayer getPlayer(OfflinePlayer player) { + return Permissions.getPlayer(player.getUniqueId()); + } + + @Override + public String[] getGroups(OfflinePlayer player) { + List groups = getPlayer(player).getGroupsString(); + return groups.toArray(new String[0]); + } + @Deprecated + @Override + public String[] getGroups(String player) { + return getGroups(Bukkit.getOfflinePlayer(player)); + } + @Override + public boolean hasPermission(OfflinePlayer player, String permission) { + Player p = Bukkit.getPlayer(player.getUniqueId()); + return hasPermission(p != null ? p.getWorld().getName() : null, player, permission); + } + @Deprecated + @Override + public boolean hasPermission(String player, String permission) { + return hasPermission(Bukkit.getOfflinePlayer(player), permission); + } + @Override + public boolean hasPermission(String worldName, OfflinePlayer player, String permission) { + Boolean res = Permissions.getPlayer(player.getUniqueId()).hasPermission(permission, PandalibPaperPermissions.serverName, worldName); + if (res != null) + return res; + + res = PandalibPaperPermissions.hasSuperPermsPermission(player, permission, p -> hasPermission(worldName, player, p)); + if (res != null) + return res; + + return permission.startsWith("-"); + } + @Deprecated + @Override + public boolean hasPermission(String worldName, String player, String permission) { + return hasPermission(worldName, Bukkit.getOfflinePlayer(player), permission); + } + @Override + public boolean inGroup(OfflinePlayer player, String group) { + return getPlayer(player).isInGroup(group); + } + @Deprecated + @Override + public boolean inGroup(String player, String group) { + return inGroup(Bukkit.getOfflinePlayer(player), group); + } + } +} diff --git a/pandalib-paper/pom.xml b/pandalib-paper/pom.xml index 75e4937..652af6c 100644 --- a/pandalib-paper/pom.xml +++ b/pandalib-paper/pom.xml @@ -24,7 +24,7 @@ fr.pandacube.lib - pandalib-core + pandalib-chat ${project.version} @@ -37,17 +37,31 @@ + + + fr.pandacube.lib + pandalib-reflect + ${project.version} + + + + fr.pandacube.lib + pandalib-util + ${project.version} + io.papermc.paper paper-api ${paper.version}-SNAPSHOT + provided io.papermc.paper paper-mojangapi ${paper.version}-SNAPSHOT + provided diff --git a/pandalib-players-permissible/pom.xml b/pandalib-players-permissible/pom.xml index 842c621..89e8c61 100644 --- a/pandalib-players-permissible/pom.xml +++ b/pandalib-players-permissible/pom.xml @@ -23,7 +23,6 @@ fr.pandacube.lib pandalib-players-standalone ${project.version} - provided diff --git a/pom.xml b/pom.xml index 453f81a..6f59487 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,9 @@ pandalib-reflect pandalib-util pandalib-players-permissible + pandalib-bungee-permissions + pandalib-bungee-players + pandalib-paper-permissions