Log verbose checks for the console, commandblocks & entities when running on Bukkit

This commit is contained in:
Luck 2018-01-16 19:20:06 +00:00
parent b800db03fc
commit 5ae90f2a4b
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
8 changed files with 354 additions and 7 deletions

View File

@ -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);

View File

@ -45,7 +45,7 @@ public final class ReflectionUtil {
}
}
public static String getServerVersion() {
private static String getServerVersion() {
return SERVER_VERSION;
}

View File

@ -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; }

View File

@ -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;
}

View File

@ -0,0 +1,143 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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<PermissionAttachmentInfo> getEffectivePermissions() { return this.delegate.getEffectivePermissions(); }
}

View File

@ -0,0 +1,162 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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");
}
}
}

View File

@ -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;
}

View File

@ -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;