From 5ae90f2a4bf3d728d205161fd2b0fd34d5e6782e Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 16 Jan 2018 19:20:06 +0000 Subject: [PATCH] Log verbose checks for the console, commandblocks & entities when running on Bukkit --- .../luckperms/bukkit/LPBukkitPlugin.java | 4 + .../bukkit/compat/ReflectionUtil.java | 2 +- .../bukkit/model/DummyPermissibleBase.java | 38 ++++ .../bukkit/model/LPPermissionAttachment.java | 2 +- .../model/MonitoredPermissibleBase.java | 143 ++++++++++++++++ .../model/PermissibleMonitoringInjector.java | 162 ++++++++++++++++++ .../bukkit/model/SubscriptionMapInjector.java | 2 +- .../common/api/ApiRegistrationUtil.java | 8 +- 8 files changed, 354 insertions(+), 7 deletions(-) create mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/model/MonitoredPermissibleBase.java create mode 100644 bukkit/src/main/java/me/lucko/luckperms/bukkit/model/PermissibleMonitoringInjector.java diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index e001ab6ab..ca752f085 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -36,6 +36,7 @@ import me.lucko.luckperms.bukkit.listeners.BukkitPlatformListener; import me.lucko.luckperms.bukkit.messaging.BukkitMessagingFactory; import me.lucko.luckperms.bukkit.model.LPPermissible; import me.lucko.luckperms.bukkit.model.PermissibleInjector; +import me.lucko.luckperms.bukkit.model.PermissibleMonitoringInjector; import me.lucko.luckperms.bukkit.model.SubscriptionMapInjector; import me.lucko.luckperms.bukkit.processors.BukkitProcessorsSetupTask; import me.lucko.luckperms.bukkit.processors.ChildPermissionProvider; @@ -256,6 +257,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { // is replaced by some plugins :( this.scheduler.asyncLater(new SubscriptionMapInjector(this), 2L); + // inject verbose handlers into internal bukkit objects + new PermissibleMonitoringInjector(this).run(); + // Provide vault support tryVaultHook(false); diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/compat/ReflectionUtil.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/compat/ReflectionUtil.java index 18d0e84eb..862dbec5b 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/compat/ReflectionUtil.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/compat/ReflectionUtil.java @@ -45,7 +45,7 @@ public final class ReflectionUtil { } } - public static String getServerVersion() { + private static String getServerVersion() { return SERVER_VERSION; } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/DummyPermissibleBase.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/DummyPermissibleBase.java index 2a5b6b568..e434bc5aa 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/DummyPermissibleBase.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/DummyPermissibleBase.java @@ -31,14 +31,52 @@ import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; +import java.lang.reflect.Field; import java.util.Collections; import java.util.Set; public class DummyPermissibleBase extends PermissibleBase { + private static final Field ATTACHMENTS_FIELD; + private static final Field PERMISSIONS_FIELD; + + static { + Field attachmentsField; + try { + attachmentsField = PermissibleBase.class.getDeclaredField("attachments"); + attachmentsField.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + ATTACHMENTS_FIELD = attachmentsField; + + Field permissionsField; + try { + permissionsField = PermissibleBase.class.getDeclaredField("permissions"); + permissionsField.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + PERMISSIONS_FIELD = permissionsField; + } + + public static void nullFields(PermissibleBase permissibleBase) { + try { + ATTACHMENTS_FIELD.set(permissibleBase, null); + } catch (Exception e) { + // ignore + } + try { + PERMISSIONS_FIELD.set(permissibleBase, null); + } catch (Exception e) { + // ignore + } + } + public static final DummyPermissibleBase INSTANCE = new DummyPermissibleBase(); private DummyPermissibleBase() { super(null); + nullFields(this); } @Override public boolean isOp() { return false; } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/LPPermissionAttachment.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/LPPermissionAttachment.java index 8540392df..cc987aabc 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/LPPermissionAttachment.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/LPPermissionAttachment.java @@ -64,7 +64,7 @@ public class LPPermissionAttachment extends PermissionAttachment { permissionAttachmentPermissionsField = PermissionAttachment.class.getDeclaredField("permissions"); permissionAttachmentPermissionsField.setAccessible(true); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + throw new ExceptionInInitializerError(e); } PERMISSION_ATTACHMENT_PERMISSIONS_FIELD = permissionAttachmentPermissionsField; } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/MonitoredPermissibleBase.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/MonitoredPermissibleBase.java new file mode 100644 index 000000000..8426cd575 --- /dev/null +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/MonitoredPermissibleBase.java @@ -0,0 +1,143 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.bukkit.model; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.common.verbose.CheckOrigin; +import me.lucko.luckperms.common.verbose.VerboseHandler; + +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 java.util.Set; + +/** + * A PermissibleBase extension which logs permission checks to the + * plugin's {@link VerboseHandler} facility. + * + * Method calls are forwarded to the delegate permissible. + */ +public class MonitoredPermissibleBase extends PermissibleBase { + private final VerboseHandler verboseHandler; + private final PermissibleBase delegate; + private final String name; + + // remains false until the object has been constructed + // necessary to catch the superclass call to #recalculatePermissions on init + @SuppressWarnings("UnusedAssignment") + private boolean initialised = false; + + public MonitoredPermissibleBase(VerboseHandler verboseHandler, PermissibleBase delegate, String name) { + super(null); + DummyPermissibleBase.nullFields(this); + + this.verboseHandler = verboseHandler; + this.delegate = delegate; + this.name = name; + this.initialised = true; + + // since we effectively cancel the execution of this call in the super + // constructor we need to call it again. + recalculatePermissions(); + } + + private void logCheck(CheckOrigin origin, String permission, boolean result) { + this.verboseHandler.offerCheckData(origin, this.name, ContextSet.empty(), permission, Tristate.fromBoolean(result)); + } + + PermissibleBase getDelegate() { + return this.delegate; + } + + @Override + public boolean isPermissionSet(String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + final boolean result = this.delegate.isPermissionSet(permission); + logCheck(CheckOrigin.PLATFORM_LOOKUP_CHECK, permission, result); + return result; + } + + @Override + public boolean isPermissionSet(Permission permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + final boolean result = this.delegate.isPermissionSet(permission); + logCheck(CheckOrigin.PLATFORM_LOOKUP_CHECK, permission.getName(), result); + return result; + } + + @Override + public boolean hasPermission(String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + final boolean result = this.delegate.hasPermission(permission); + logCheck(CheckOrigin.PLATFORM_PERMISSION_CHECK, permission, result); + return result; + } + + @Override + public boolean hasPermission(Permission permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + final boolean result = this.delegate.hasPermission(permission); + logCheck(CheckOrigin.PLATFORM_PERMISSION_CHECK, permission.getName(), result); + return result; + } + + @Override + public void recalculatePermissions() { + if (!this.initialised) { + return; + } + + this.delegate.recalculatePermissions(); + } + + // just forward calls to the delegate permissible + @Override public boolean isOp() { return this.delegate.isOp(); } + @Override public void setOp(boolean value) { this.delegate.setOp(value); } + @Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { return this.delegate.addAttachment(plugin, name, value); } + @Override public PermissionAttachment addAttachment(Plugin plugin) { return this.delegate.addAttachment(plugin); } + @Override public void removeAttachment(PermissionAttachment attachment) { this.delegate.removeAttachment(attachment); } + @Override public void clearPermissions() { this.delegate.clearPermissions(); } + @Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { return this.delegate.addAttachment(plugin, name, value, ticks); } + @Override public PermissionAttachment addAttachment(Plugin plugin, int ticks) { return this.delegate.addAttachment(plugin, ticks); } + @Override public Set getEffectivePermissions() { return this.delegate.getEffectivePermissions(); } + +} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/PermissibleMonitoringInjector.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/PermissibleMonitoringInjector.java new file mode 100644 index 000000000..a65c2098a --- /dev/null +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/PermissibleMonitoringInjector.java @@ -0,0 +1,162 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.bukkit.model; + +import me.lucko.luckperms.bukkit.LPBukkitPlugin; +import me.lucko.luckperms.bukkit.compat.ReflectionUtil; + +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.ServerOperator; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * Injects {@link MonitoredPermissibleBase}s into non-player permissibles on + * the server so their checks can be monitored by the verbose facility. + */ +public class PermissibleMonitoringInjector implements Runnable { + private final LPBukkitPlugin plugin; + + public PermissibleMonitoringInjector(LPBukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + try { + injectConsole(); + } catch (Exception e) { + // ignore + } + + try { + injectCommandBlock(); + } catch (Exception e) { + // ignore + } + + try { + injectEntity(); + } catch (Exception e) { + // ignore + } + } + + private MonitoredPermissibleBase wrap(PermissibleBase permBase, String name) { + Objects.requireNonNull(permBase, "permBase"); + + // unwrap any previous injection + if (permBase instanceof MonitoredPermissibleBase) { + permBase = ((MonitoredPermissibleBase) permBase).getDelegate(); + } + + // create a monitored instance which delegates to the previous PermissibleBase + return new MonitoredPermissibleBase(this.plugin.getVerboseHandler(), permBase, name); + } + + private void injectConsole() throws Exception { + ConsoleCommandSender consoleSender = this.plugin.getServer().getConsoleSender(); + + // get the ServerCommandSender class + Class serverCommandSenderClass = ReflectionUtil.obcClass("command.ServerCommandSender"); + + // get the perm field + Field permField = serverCommandSenderClass.getDeclaredField("perm"); + permField.setAccessible(true); + + // get the PermissibleBase instance + PermissibleBase permBase = (PermissibleBase) permField.get(consoleSender); + + // create a monitored instance which delegates to the previous PermissibleBase + MonitoredPermissibleBase newPermBase = wrap(permBase, "internal/console"); + + // inject the monitored instance + permField.set(consoleSender, newPermBase); + } + + private void injectCommandBlock() throws Exception { + // get the ServerCommandSender class + Class serverCommandSenderClass = ReflectionUtil.obcClass("command.ServerCommandSender"); + + // get the blockPermInst field + Field permField = serverCommandSenderClass.getDeclaredField("blockPermInst"); + permField.setAccessible(true); + + // get the PermissibleBase instance + PermissibleBase permBase = (PermissibleBase) permField.get(null); + + // if no commandblock senders have been made yet, this field will be null + // we can just initialise one anyway + if (permBase == null) { + permBase = new PermissibleBase(new CommandBlockServerOperator()); + } + + // create a monitored instance which delegates to the previous PermissibleBase + MonitoredPermissibleBase newPermBase = wrap(permBase, "internal/commandblock"); + + // inject the monitored instance + permField.set(null, newPermBase); + } + + private void injectEntity() throws Exception { + // get the CraftEntity class + Class entityClass = ReflectionUtil.obcClass("entity.CraftEntity"); + + // get the method used to obtain a PermissibleBase + // this method will initialise a new PB instance if one doesn't yet exist + Method getPermissibleBaseMethod = entityClass.getDeclaredMethod("getPermissibleBase"); + getPermissibleBaseMethod.setAccessible(true); + + // get the PermissibleBase instance + PermissibleBase permBase = (PermissibleBase) getPermissibleBaseMethod.invoke(null); + + // get the perm field on CraftEntity + Field permField = entityClass.getDeclaredField("perm"); + permField.setAccessible(true); + + // create a monitored instance which delegates to the previous PermissibleBase + MonitoredPermissibleBase newPermBase = wrap(permBase, "internal/entity"); + + // inject the monitored instance + permField.set(null, newPermBase); + } + + // behaviour copied from the implementation of obc.command.CraftBlockCommandSender + private static final class CommandBlockServerOperator implements ServerOperator { + @Override + public boolean isOp() { + return true; + } + + @Override + public void setOp(boolean value) { + throw new UnsupportedOperationException("Cannot change operator status of a block"); + } + } +} diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/SubscriptionMapInjector.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/SubscriptionMapInjector.java index ea4e8348d..6dfa7879d 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/SubscriptionMapInjector.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/SubscriptionMapInjector.java @@ -44,7 +44,7 @@ public class SubscriptionMapInjector implements Runnable { permSubsField = SimplePluginManager.class.getDeclaredField("permSubs"); permSubsField.setAccessible(true); } catch (NoSuchFieldException e) { - throw new RuntimeException(e); + throw new ExceptionInInitializerError(e); } PERM_SUBS_FIELD = permSubsField; } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/ApiRegistrationUtil.java b/common/src/main/java/me/lucko/luckperms/common/api/ApiRegistrationUtil.java index 80fe2057b..41df7c8f8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/ApiRegistrationUtil.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/ApiRegistrationUtil.java @@ -34,16 +34,16 @@ public class ApiRegistrationUtil { private static final Method REGISTER; private static final Method UNREGISTER; static { - Method register = null; - Method unregister = null; + Method register; + Method unregister; try { register = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class); register.setAccessible(true); unregister = LuckPerms.class.getDeclaredMethod("unregisterProvider"); unregister.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); } REGISTER = register;