From 31e436868d52e273f654f53025f3ee34ffd3d22d Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 18 Feb 2018 15:10:35 +0000 Subject: [PATCH] Nukkit support (#764) --- .../luckperms/api/platform/PlatformType.java | 3 +- bukkit/pom.xml | 8 + .../bukkit/model/server/LPDefaultsMap.java | 2 +- .../bukkit/model/server/LPPermissionMap.java | 2 +- .../model/server/LPSubscriptionMap.java | 21 +- .../luckperms/common/config/ConfigKeys.java | 15 + nukkit/pom.xml | 156 +++++ .../luckperms/nukkit/LPNukkitPlugin.java | 634 ++++++++++++++++++ .../nukkit/NukkitCommandExecutor.java | 59 ++ .../luckperms/nukkit/NukkitConfigAdapter.java | 109 +++ .../nukkit/NukkitSchedulerAdapter.java | 186 +++++ .../luckperms/nukkit/NukkitSenderFactory.java | 109 +++ .../calculators/NukkitCalculatorFactory.java | 75 +++ .../nukkit/contexts/NukkitContextManager.java | 53 ++ .../nukkit/contexts/WorldCalculator.java | 56 ++ .../listeners/NukkitConnectionListener.java | 205 ++++++ .../listeners/NukkitPlatformListener.java | 97 +++ .../nukkit/model/PermissionDefault.java | 111 +++ .../model/dummy/DummyPermissibleBase.java | 88 +++ .../nukkit/model/dummy/DummyPlugin.java | 78 +++ .../model/permissible/LPPermissible.java | 279 ++++++++ .../permissible/LPPermissionAttachment.java | 432 ++++++++++++ .../permissible/MonitoredPermissibleBase.java | 145 ++++ .../permissible/PermissibleInjector.java | 146 ++++ .../PermissibleMonitoringInjector.java | 84 +++ .../model/server/InjectorDefaultsMap.java | 138 ++++ .../model/server/InjectorPermissionMap.java | 108 +++ .../model/server/InjectorSubscriptionMap.java | 112 ++++ .../nukkit/model/server/LPDefaultsMap.java | 166 +++++ .../nukkit/model/server/LPPermissionMap.java | 155 +++++ .../model/server/LPSubscriptionMap.java | 259 +++++++ .../nukkit/processors/ChildProcessor.java | 64 ++ .../nukkit/processors/DefaultsProcessor.java | 55 ++ nukkit/src/main/resources/config.yml | 459 +++++++++++++ nukkit/src/main/resources/plugin.yml | 14 + pom.xml | 9 +- sponge/pom.xml | 7 + sponge/sponge-service-api6/pom.xml | 7 + sponge/sponge-service-api7/pom.xml | 7 + sponge/sponge-service/pom.xml | 7 + 40 files changed, 4697 insertions(+), 23 deletions(-) create mode 100644 nukkit/pom.xml create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java create mode 100644 nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java create mode 100644 nukkit/src/main/resources/config.yml create mode 100644 nukkit/src/main/resources/plugin.yml diff --git a/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java b/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java index 48d8ef3e..c66ffa5f 100644 --- a/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java +++ b/api/src/main/java/me/lucko/luckperms/api/platform/PlatformType.java @@ -36,7 +36,8 @@ public enum PlatformType { BUKKIT("Bukkit"), BUNGEE("Bungee"), - SPONGE("Sponge"); + SPONGE("Sponge"), + NUKKIT("Nukkit"); private final String friendlyName; diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 9db25c6b..cbe9bc38 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -244,4 +244,12 @@ + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java index c434bb12..8654adba 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPDefaultsMap.java @@ -55,7 +55,7 @@ import javax.annotation.Nonnull; * * Injected by {@link InjectorDefaultsMap}. */ -public class LPDefaultsMap implements Map> { +public final class LPDefaultsMap implements Map> { // keyset for all instances private static final Set KEY_SET = ImmutableSet.of(Boolean.TRUE, Boolean.FALSE); diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java index 3998c94f..f9bb0162 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPPermissionMap.java @@ -56,7 +56,7 @@ import javax.annotation.Nonnull; * * Injected by {@link InjectorPermissionMap}. */ -public class LPPermissionMap extends ForwardingMap { +public final class LPPermissionMap extends ForwardingMap { // Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key private final Map delegate = new ConcurrentHashMap<>(); diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java index 64366f16..69499f21 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/model/server/LPSubscriptionMap.java @@ -33,7 +33,6 @@ import me.lucko.luckperms.common.utils.ImmutableCollectors; import org.bukkit.entity.Player; import org.bukkit.permissions.Permissible; -import org.bukkit.permissions.Permission; import org.bukkit.plugin.PluginManager; import java.util.Collection; @@ -56,7 +55,7 @@ import javax.annotation.Nonnull; * * Bukkit for some reason sometimes uses subscription status to determine whether * a permissible has a given node, instead of checking directly with - * {@link Permissible#hasPermission(Permission)}. + * {@link Permissible#hasPermission(String)}. * * {@link org.bukkit.Server#broadcast(String, String)} is a good example of this. * @@ -68,7 +67,7 @@ import javax.annotation.Nonnull; * * Injected by {@link InjectorSubscriptionMap}. */ -public class LPSubscriptionMap extends HashMap> { +public final class LPSubscriptionMap extends HashMap> { // the plugin instance final LPBukkitPlugin plugin; @@ -151,7 +150,7 @@ public class LPSubscriptionMap extends HashMap /** * Value map extension which includes LP objects in Permissible related queries. */ - public class LPSubscriptionValueMap implements Map { + public final class LPSubscriptionValueMap implements Map { // the permission being mapped to this value map private final String permission; @@ -159,7 +158,7 @@ public class LPSubscriptionMap extends HashMap // the backing map private final Map backing; - public LPSubscriptionValueMap(String permission, Map backing) { + private LPSubscriptionValueMap(String permission, Map backing) { this.permission = permission; this.backing = backing; } @@ -235,6 +234,11 @@ public class LPSubscriptionMap extends HashMap return false; } + @Override + public int size() { + return Math.max(1, this.backing.size()); + } + // just delegate to the backing map @Override @@ -247,13 +251,6 @@ public class LPSubscriptionMap extends HashMap return this.backing.remove(key); } - // the following methods are not used in the current impls of PluginManager, but just delegate them for now - - @Override - public int size() { - return this.backing.size(); - } - @Override public boolean containsValue(Object value) { return this.backing.containsValue(value); diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java index 6a6b58fa..f3c85a19 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java @@ -208,6 +208,21 @@ public class ConfigKeys { */ public static final ConfigKey APPLY_BUKKIT_ATTACHMENT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-bukkit-attachment-permissions", true)); + /** + * If Nukkit child permissions are being applied. This setting is ignored on other platforms. + */ + public static final ConfigKey APPLY_NUKKIT_CHILD_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-child-permissions", true)); + + /** + * If Nukkit default permissions are being applied. This setting is ignored on other platforms. + */ + public static final ConfigKey APPLY_NUKKIT_DEFAULT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-default-permissions", true)); + + /** + * If Nukkit attachment permissions are being applied. This setting is ignored on other platforms. + */ + public static final ConfigKey APPLY_NUKKIT_ATTACHMENT_PERMISSIONS = EnduringKey.wrap(BooleanKey.of("apply-nukkit-attachment-permissions", true)); + /** * If BungeeCord configured permissions are being applied. This setting is ignored on other platforms. */ diff --git a/nukkit/pom.xml b/nukkit/pom.xml new file mode 100644 index 00000000..ad5f4839 --- /dev/null +++ b/nukkit/pom.xml @@ -0,0 +1,156 @@ + + + + luckperms + me.lucko.luckperms + 4.0-SNAPSHOT + + 4.0.0 + + luckperms-nukkit + jar + + + clean package + LuckPerms-Nukkit-${full.version} + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler.version} + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + ${shade.version} + + + package + + shade + + + false + false + + + + net.kyori.text + me.lucko.luckperms.lib.text + + + + + com.github.benmanes.caffeine + me.lucko.luckperms.lib.caffeine + + + okio + me.lucko.luckperms.lib.okio + + + okhttp3 + me.lucko.luckperms.lib.okhttp3 + + + org.mariadb.jdbc + me.lucko.luckperms.lib.mariadb + + + com.mysql + me.lucko.luckperms.lib.mysql + + + org.postgresql + me.lucko.luckperms.lib.postgresql + + + com.zaxxer.hikari + me.lucko.luckperms.lib.hikari + + + com.mongodb + me.lucko.luckperms.lib.mongodb + + + org.bson + me.lucko.luckperms.lib.bson + + + redis.clients.jedis + me.lucko.luckperms.lib.jedis + + + org.apache.commons.pool2 + me.lucko.luckperms.lib.commonspool2 + + + ninja.leaping.configurate + me.lucko.luckperms.lib.configurate + + + com.typesafe.config + me.lucko.luckperms.lib.hocon + + + + + + + + + + + + + me.lucko.luckperms + luckperms-common + ${project.version} + compile + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + + + + com.github.ben-manes.caffeine + caffeine + 2.6.1 + provided + + + + + cn.nukkit + nukkit + 1.0-SNAPSHOT + provided + + + + + + nukkit-repo + https://repo.potestas.xyz/main/ + + + + diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java new file mode 100644 index 00000000..60fbfeb7 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java @@ -0,0 +1,634 @@ +/* + * 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.nukkit; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.LuckPermsApi; +import me.lucko.luckperms.api.platform.PlatformType; +import me.lucko.luckperms.common.actionlog.LogDispatcher; +import me.lucko.luckperms.common.api.ApiRegistrationUtil; +import me.lucko.luckperms.common.api.LuckPermsApiProvider; +import me.lucko.luckperms.common.buffers.BufferedRequest; +import me.lucko.luckperms.common.buffers.UpdateTaskBuffer; +import me.lucko.luckperms.common.caching.handlers.CachedStateManager; +import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.commands.CommandPermission; +import me.lucko.luckperms.common.commands.sender.Sender; +import me.lucko.luckperms.common.config.AbstractConfiguration; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.config.LuckPermsConfiguration; +import me.lucko.luckperms.common.contexts.ContextManager; +import me.lucko.luckperms.common.contexts.LuckPermsCalculator; +import me.lucko.luckperms.common.dependencies.DependencyManager; +import me.lucko.luckperms.common.dependencies.DependencyRegistry; +import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader; +import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader; +import me.lucko.luckperms.common.event.EventFactory; +import me.lucko.luckperms.common.inheritance.InheritanceHandler; +import me.lucko.luckperms.common.locale.LocaleManager; +import me.lucko.luckperms.common.locale.NoopLocaleManager; +import me.lucko.luckperms.common.locale.SimpleLocaleManager; +import me.lucko.luckperms.common.logging.Logger; +import me.lucko.luckperms.common.logging.SenderLogger; +import me.lucko.luckperms.common.managers.group.StandardGroupManager; +import me.lucko.luckperms.common.managers.track.StandardTrackManager; +import me.lucko.luckperms.common.managers.user.StandardUserManager; +import me.lucko.luckperms.common.messaging.InternalMessagingService; +import me.lucko.luckperms.common.messaging.MessagingFactory; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.storage.Storage; +import me.lucko.luckperms.common.storage.StorageFactory; +import me.lucko.luckperms.common.storage.StorageType; +import me.lucko.luckperms.common.storage.dao.file.FileWatcher; +import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; +import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; +import me.lucko.luckperms.common.tasks.UpdateTask; +import me.lucko.luckperms.common.treeview.PermissionVault; +import me.lucko.luckperms.common.verbose.VerboseHandler; +import me.lucko.luckperms.nukkit.calculators.NukkitCalculatorFactory; +import me.lucko.luckperms.nukkit.contexts.NukkitContextManager; +import me.lucko.luckperms.nukkit.contexts.WorldCalculator; +import me.lucko.luckperms.nukkit.listeners.NukkitConnectionListener; +import me.lucko.luckperms.nukkit.listeners.NukkitPlatformListener; +import me.lucko.luckperms.nukkit.model.PermissionDefault; +import me.lucko.luckperms.nukkit.model.permissible.LPPermissible; +import me.lucko.luckperms.nukkit.model.permissible.PermissibleInjector; +import me.lucko.luckperms.nukkit.model.permissible.PermissibleMonitoringInjector; +import me.lucko.luckperms.nukkit.model.server.InjectorDefaultsMap; +import me.lucko.luckperms.nukkit.model.server.InjectorPermissionMap; +import me.lucko.luckperms.nukkit.model.server.InjectorSubscriptionMap; +import me.lucko.luckperms.nukkit.model.server.LPDefaultsMap; +import me.lucko.luckperms.nukkit.model.server.LPPermissionMap; +import me.lucko.luckperms.nukkit.model.server.LPSubscriptionMap; + +import cn.nukkit.Player; +import cn.nukkit.command.PluginCommand; +import cn.nukkit.event.HandlerList; +import cn.nukkit.permission.Permission; +import cn.nukkit.plugin.PluginBase; +import cn.nukkit.plugin.PluginManager; +import cn.nukkit.plugin.service.ServicePriority; +import cn.nukkit.utils.Config; + +import java.io.File; +import java.io.InputStream; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +/** + * LuckPerms implementation for the Nukkit API. + */ +public class LPNukkitPlugin extends PluginBase implements LuckPermsPlugin { + + private long startTime; + private NukkitSchedulerAdapter scheduler; + private NukkitCommandExecutor commandManager; + private LuckPermsConfiguration configuration; + private StandardUserManager userManager; + private StandardGroupManager groupManager; + private StandardTrackManager trackManager; + private Storage storage; + private FileWatcher fileWatcher = null; + private InternalMessagingService messagingService = null; + private LuckPermsApiProvider apiProvider; + private EventFactory eventFactory; + private Logger log; + private LPSubscriptionMap subscriptionMap; + private LPPermissionMap permissionMap; + private LPDefaultsMap defaultPermissionMap; + private LocaleManager localeManager; + private PluginClassLoader pluginClassLoader; + private DependencyManager dependencyManager; + private InheritanceHandler inheritanceHandler; + private CachedStateManager cachedStateManager; + private ContextManager contextManager; + private CalculatorFactory calculatorFactory; + private BufferedRequest updateTaskBuffer; + private VerboseHandler verboseHandler; + private NukkitSenderFactory senderFactory; + private PermissionVault permissionVault; + private LogDispatcher logDispatcher; + private Set uniqueConnections = ConcurrentHashMap.newKeySet(); + + @Override + public void onLoad() { + // setup minimal functionality in order to load initial dependencies + this.scheduler = new NukkitSchedulerAdapter(this); + this.localeManager = new NoopLocaleManager(); + this.senderFactory = new NukkitSenderFactory(this); + this.log = new SenderLogger(this, getConsoleSender()); + + this.pluginClassLoader = new ReflectionClassLoader(this); + this.dependencyManager = new DependencyManager(this); + this.dependencyManager.loadDependencies(DependencyRegistry.GLOBAL_DEPENDENCIES); + } + + @Override + public void onEnable() { + this.startTime = System.currentTimeMillis(); + sendStartupBanner(getConsoleSender()); + this.verboseHandler = new VerboseHandler(this.scheduler.asyncNukkit(), getVersion()); + this.permissionVault = new PermissionVault(this.scheduler.asyncNukkit()); + this.logDispatcher = new LogDispatcher(this); + + getLog().info("Loading configuration..."); + this.configuration = new AbstractConfiguration(this, new NukkitConfigAdapter(this, resolveConfig("config.yml"))); + this.configuration.loadAll(); + + StorageFactory storageFactory = new StorageFactory(this); + Set storageTypes = storageFactory.getRequiredTypes(StorageType.H2); + this.dependencyManager.loadStorageDependencies(storageTypes); + + // register events + NukkitConnectionListener connectionListener = new NukkitConnectionListener(this); + getServer().getPluginManager().registerEvents(connectionListener, this); + getServer().getPluginManager().registerEvents(new NukkitPlatformListener(this), this); + + if (getConfiguration().get(ConfigKeys.WATCH_FILES)) { + this.fileWatcher = new FileWatcher(this); + getScheduler().asyncRepeating(this.fileWatcher, 30L); + } + + // initialise datastore + this.storage = storageFactory.getInstance(StorageType.H2); + + // initialise messaging + this.messagingService = new MessagingFactory<>(this).getInstance(); + + // setup the update task buffer + this.updateTaskBuffer = new UpdateTaskBuffer(this); + + // load locale + this.localeManager = new SimpleLocaleManager(); + this.localeManager.tryLoad(this, new File(getDataFolder(), "lang.yml")); + + // register commands + this.commandManager = new NukkitCommandExecutor(this); + PluginCommand main = (PluginCommand) getServer().getPluginCommand("luckperms"); + main.setExecutor(this.commandManager); + + // load internal managers + getLog().info("Loading internal permission managers..."); + this.inheritanceHandler = new InheritanceHandler(this); + this.userManager = new StandardUserManager(this); + this.groupManager = new StandardGroupManager(this); + this.trackManager = new StandardTrackManager(this); + this.calculatorFactory = new NukkitCalculatorFactory(this); + this.cachedStateManager = new CachedStateManager(); + + // setup context manager + this.contextManager = new NukkitContextManager(this); + this.contextManager.registerCalculator(new WorldCalculator(this)); + this.contextManager.registerStaticCalculator(new LuckPermsCalculator(getConfiguration())); + + // inject our own custom permission maps + Runnable[] injectors = new Runnable[]{ + new InjectorSubscriptionMap(this), + new InjectorPermissionMap(this), + new InjectorDefaultsMap(this) + }; + + for (Runnable injector : injectors) { + injector.run(); + + // schedule another injection after all plugins have loaded + // the entire pluginmanager instance is replaced by some plugins :( + this.scheduler.asyncLater(injector, 1L); + } + + // inject verbose handlers into internal nukkit objects + new PermissibleMonitoringInjector(this).run(); + + // register with the LP API + this.apiProvider = new LuckPermsApiProvider(this); + + // setup event factory + this.eventFactory = new EventFactory(this, this.apiProvider); + + ApiRegistrationUtil.registerProvider(this.apiProvider); + getServer().getServiceManager().register(LuckPermsApi.class, this.apiProvider, this, ServicePriority.NORMAL); + + + // schedule update tasks + int mins = getConfiguration().get(ConfigKeys.SYNC_TIME); + if (mins > 0) { + long ticks = mins * 60 * 20; + this.scheduler.asyncRepeating(() -> this.updateTaskBuffer.request(), ticks); + } + this.scheduler.asyncLater(() -> this.updateTaskBuffer.request(), 40L); + + // run an update instantly. + getLog().info("Performing initial data load..."); + try { + new UpdateTask(this, true).run(); + } catch (Exception e) { + e.printStackTrace(); + } + + + // register tasks + this.scheduler.asyncRepeating(new ExpireTemporaryTask(this), 60L); + this.scheduler.asyncRepeating(new CacheHousekeepingTask(this), 2400L); + + // register permissions + try { + PluginManager pm = getServer().getPluginManager(); + PermissionDefault permDefault = getConfiguration().get(ConfigKeys.COMMANDS_ALLOW_OP) ? PermissionDefault.OP : PermissionDefault.FALSE; + + for (CommandPermission p : CommandPermission.values()) { + pm.addPermission(new Permission(p.getPermission(), null, permDefault.toString())); + } + } catch (Exception e) { + // this throws an exception if the plugin is /reloaded, grr + } + + if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) { + Config ops = getServer().getOps(); + ops.getKeys(false).forEach(ops::remove); + } + + // replace the temporary executor when the Nukkit one starts + getServer().getScheduler().scheduleTask(this, () -> this.scheduler.setUseFallback(false), true); + + // Load any online users (in the case of a reload) + for (Player player : getServer().getOnlinePlayers().values()) { + this.scheduler.doAsync(() -> { + try { + User user = connectionListener.loadUser(player.getUniqueId(), player.getName()); + if (user != null) { + this.scheduler.doSync(() -> { + try { + LPPermissible lpPermissible = new LPPermissible(player, user, this); + PermissibleInjector.inject(player, lpPermissible); + } catch (Throwable t) { + t.printStackTrace(); + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + getLog().info("Successfully enabled. (took " + (System.currentTimeMillis() - this.startTime) + "ms)"); + } + + @Override + public void onDisable() { + // Switch back to the fallback executor, the nukkit one won't allow new tasks + this.scheduler.setUseFallback(true); + + this.permissionVault.shutdown(); + this.verboseHandler.shutdown(); + + // uninject from players + for (Player player : getServer().getOnlinePlayers().values()) { + try { + PermissibleInjector.unInject(player, false); + } catch (Exception e) { + e.printStackTrace(); + } + + if (getConfiguration().get(ConfigKeys.AUTO_OP)) { + player.setOp(false); + } + + final User user = getUserManager().getIfLoaded(player.getUniqueId()); + if (user != null) { + user.getCachedData().invalidateCaches(); + getUserManager().unload(user); + } + } + + // uninject custom maps + InjectorSubscriptionMap.uninject(); + InjectorPermissionMap.uninject(); + InjectorDefaultsMap.uninject(); + + getLog().info("Closing storage..."); + this.storage.shutdown(); + + if (this.fileWatcher != null) { + this.fileWatcher.close(); + } + + if (this.messagingService != null) { + getLog().info("Closing messaging service..."); + this.messagingService.close(); + } + + ApiRegistrationUtil.unregisterProvider(); + getServer().getServiceManager().cancel(this); + + getLog().info("Shutting down internal scheduler..."); + this.scheduler.shutdown(); + + // Nukkit will do this again when #onDisable completes, but we do it early to prevent NPEs elsewhere. + getServer().getScheduler().cancelTask(this); + HandlerList.unregisterAll(this); + getLog().info("Goodbye!"); + } + + public void refreshAutoOp(User user, Player player) { + if (user == null) { + return; + } + + if (getConfiguration().get(ConfigKeys.AUTO_OP)) { + Map backing = user.getCachedData().getPermissionData(this.contextManager.getApplicableContexts(player)).getImmutableBacking(); + boolean op = Optional.ofNullable(backing.get("luckperms.autoop")).orElse(false); + player.setOp(op); + } + } + + private File resolveConfig(String file) { + File configFile = new File(getDataFolder(), file); + + if (!configFile.exists()) { + getDataFolder().mkdirs(); + saveResource("config.yml", false); + } + + return configFile; + } + + public LPSubscriptionMap getSubscriptionMap() { + return this.subscriptionMap; + } + + public void setSubscriptionMap(LPSubscriptionMap subscriptionMap) { + this.subscriptionMap = subscriptionMap; + } + + public LPPermissionMap getPermissionMap() { + return this.permissionMap; + } + + public void setPermissionMap(LPPermissionMap permissionMap) { + this.permissionMap = permissionMap; + } + + public LPDefaultsMap getDefaultPermissionMap() { + return this.defaultPermissionMap; + } + + public void setDefaultPermissionMap(LPDefaultsMap defaultPermissionMap) { + this.defaultPermissionMap = defaultPermissionMap; + } + + @Override + public Optional getMessagingService() { + return Optional.ofNullable(this.messagingService); + } + + @Override + public void setMessagingService(InternalMessagingService messagingService) { + if (this.messagingService == null) { + this.messagingService = messagingService; + } + } + + @Override + public Optional getFileWatcher() { + return Optional.ofNullable(this.fileWatcher); + } + + @Override + public String getVersion() { + return getDescription().getVersion(); + } + + @Override + public PlatformType getServerType() { + return PlatformType.NUKKIT; + } + + @Override + public String getServerBrand() { + return getServer().getName(); + } + + @Override + public String getServerVersion() { + return getServer().getVersion() + " - " + getServer().getNukkitVersion(); + } + + @Override + public String getServerName() { + return getServer().getServerUniqueId().toString(); + } + + @Override + public File getDataDirectory() { + return super.getDataFolder(); + } + + @Override + public InputStream getResourceStream(String path) { + return getResource(path); + } + + @Override + public Player getPlayer(User user) { + return getServer().getOnlinePlayers().get(user.getUuid()); + } + + @Override + public Optional lookupUuid(String username) { + return Optional.empty(); + } + + @Nullable + @Override + public Contexts getContextForUser(User user) { + Player player = getPlayer(user); + if (player == null) { + return null; + } + return this.contextManager.getApplicableContexts(player); + } + + @Override + public int getPlayerCount() { + return getServer().getOnlinePlayers().size(); + } + + @Override + public Stream getPlayerList() { + return getServer().getOnlinePlayers().values().stream().map(Player::getName); + } + + @Override + public Stream getOnlinePlayers() { + return getServer().getOnlinePlayers().values().stream().map(Player::getUniqueId); + } + + @Override + public boolean isPlayerOnline(UUID external) { + Player player = getServer().getOnlinePlayers().get(external); + return player != null && player.isOnline(); + } + + @Override + public Stream getOnlineSenders() { + return Stream.concat( + Stream.of(getConsoleSender()), + getServer().getOnlinePlayers().values().stream().map(p -> getSenderFactory().wrap(p)) + ); + } + + @Override + public Sender getConsoleSender() { + return getSenderFactory().wrap(getServer().getConsoleSender()); + } + + @Override + public long getStartTime() { + return this.startTime; + } + + @Override + public NukkitSchedulerAdapter getScheduler() { + return this.scheduler; + } + + @Override + public NukkitCommandExecutor getCommandManager() { + return this.commandManager; + } + + @Override + public LuckPermsConfiguration getConfiguration() { + return this.configuration; + } + + @Override + public StandardUserManager getUserManager() { + return this.userManager; + } + + @Override + public StandardGroupManager getGroupManager() { + return this.groupManager; + } + + @Override + public StandardTrackManager getTrackManager() { + return this.trackManager; + } + + @Override + public Storage getStorage() { + return this.storage; + } + + @Override + public LuckPermsApiProvider getApiProvider() { + return this.apiProvider; + } + + @Override + public EventFactory getEventFactory() { + return this.eventFactory; + } + + @Override + public Logger getLog() { + return this.log; + } + + @Override + public LocaleManager getLocaleManager() { + return this.localeManager; + } + + @Override + public PluginClassLoader getPluginClassLoader() { + return this.pluginClassLoader; + } + + @Override + public DependencyManager getDependencyManager() { + return this.dependencyManager; + } + + @Override + public CachedStateManager getCachedStateManager() { + return this.cachedStateManager; + } + + @Override + public ContextManager getContextManager() { + return this.contextManager; + } + + @Override + public InheritanceHandler getInheritanceHandler() { + return this.inheritanceHandler; + } + + @Override + public CalculatorFactory getCalculatorFactory() { + return this.calculatorFactory; + } + + @Override + public BufferedRequest getUpdateTaskBuffer() { + return this.updateTaskBuffer; + } + + @Override + public VerboseHandler getVerboseHandler() { + return this.verboseHandler; + } + + public NukkitSenderFactory getSenderFactory() { + return this.senderFactory; + } + + @Override + public PermissionVault getPermissionVault() { + return this.permissionVault; + } + + @Override + public LogDispatcher getLogDispatcher() { + return this.logDispatcher; + } + + @Override + public Set getUniqueConnections() { + return this.uniqueConnections; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java new file mode 100644 index 00000000..f592c9fe --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitCommandExecutor.java @@ -0,0 +1,59 @@ +/* + * 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.nukkit; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; + +import me.lucko.luckperms.common.commands.CommandManager; +import me.lucko.luckperms.common.commands.sender.Sender; + +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandExecutor; +import cn.nukkit.command.CommandSender; + +import java.util.List; + +public class NukkitCommandExecutor extends CommandManager implements CommandExecutor { + private static final Splitter ARGUMENT_SPLITTER = Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings(); + private static final Joiner ARGUMENT_JOINER = Joiner.on(' '); + + private final LPNukkitPlugin plugin; + + NukkitCommandExecutor(LPNukkitPlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + Sender lpSender = this.plugin.getSenderFactory().wrap(sender); + List arguments = stripQuotes(ARGUMENT_SPLITTER.splitToList(ARGUMENT_JOINER.join(args))); + + onCommand(lpSender, label, arguments); + return true; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java new file mode 100644 index 00000000..1b815860 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitConfigAdapter.java @@ -0,0 +1,109 @@ +/* + * 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.nukkit; + +import me.lucko.luckperms.common.config.adapter.AbstractConfigurationAdapter; +import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import cn.nukkit.utils.Config; +import cn.nukkit.utils.ConfigSection; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class NukkitConfigAdapter extends AbstractConfigurationAdapter implements ConfigurationAdapter { + + private final File file; + private Config configuration; + + public NukkitConfigAdapter(LuckPermsPlugin plugin, File file) { + super(plugin); + this.file = file; + reload(); + } + + @Override + public void reload() { + this.configuration = new Config(this.file, Config.YAML); + } + + @Override + public boolean contains(String path) { + return this.configuration.exists(path); + } + + @Override + public String getString(String path, String def) { + return this.configuration.getString(path, def); + } + + @Override + public int getInt(String path, int def) { + return this.configuration.getInt(path, def); + } + + @Override + public boolean getBoolean(String path, boolean def) { + return this.configuration.getBoolean(path, def); + } + + @Override + public List getList(String path, List def) { + List ret = this.configuration.getStringList(path); + return ret == null ? def : ret; + } + + @Override + public List getObjectList(String path, List def) { + ConfigSection section = this.configuration.getSection(path); + if (section == null) { + return def; + } + + Set keys = section.getKeys(false); + return keys == null ? def : new ArrayList<>(keys); + } + + @Override + public Map getMap(String path, Map def) { + Map map = new HashMap<>(); + ConfigSection section = this.configuration.getSection(path); + if (section == null) { + return def; + } + + for (String key : section.getKeys(false)) { + map.put(key, section.getString(key)); + } + + return map; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java new file mode 100644 index 00000000..0d852fbf --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSchedulerAdapter.java @@ -0,0 +1,186 @@ +/* + * 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.nukkit; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import me.lucko.luckperms.common.plugin.SchedulerAdapter; +import me.lucko.luckperms.common.plugin.SchedulerTask; +import me.lucko.luckperms.common.utils.SafeIteration; + +import cn.nukkit.scheduler.ServerScheduler; +import cn.nukkit.scheduler.TaskHandler; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +public class NukkitSchedulerAdapter implements SchedulerAdapter { + private final LPNukkitPlugin plugin; + + private final ExecutorService asyncFallback; + private final Executor asyncNukkit; + private final Executor sync; + private final Executor async; + + private boolean useFallback = true; + + private final Set tasks = ConcurrentHashMap.newKeySet(); + + public NukkitSchedulerAdapter(LPNukkitPlugin plugin) { + this.plugin = plugin; + + this.sync = new SyncExecutor(); + this.asyncFallback = new FallbackAsyncExecutor(); + this.asyncNukkit = new NukkitAsyncExecutor(); + this.async = new AsyncExecutor(); + } + + private ServerScheduler scheduler() { + return this.plugin.getServer().getScheduler(); + } + + @Override + public void doAsync(Runnable runnable) { + async().execute(runnable); + } + + @Override + public void doSync(Runnable runnable) { + sync().execute(runnable); + } + + @Override + public SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks) { + SchedulerTask task = new NukkitSchedulerTask(scheduler().scheduleDelayedRepeatingTask(this.plugin, runnable, (int) intervalTicks, (int) intervalTicks, true)); + this.tasks.add(task); + return task; + } + + @Override + public SchedulerTask syncRepeating(Runnable runnable, long intervalTicks) { + SchedulerTask task = new NukkitSchedulerTask(scheduler().scheduleDelayedRepeatingTask(this.plugin, runnable, (int) intervalTicks, (int) intervalTicks, false)); + this.tasks.add(task); + return task; + } + + @Override + public SchedulerTask asyncLater(Runnable runnable, long delayTicks) { + return new NukkitSchedulerTask(scheduler().scheduleDelayedTask(this.plugin, runnable, (int) delayTicks, true)); + } + + @Override + public SchedulerTask syncLater(Runnable runnable, long delayTicks) { + return new NukkitSchedulerTask(scheduler().scheduleDelayedTask(this.plugin, runnable, (int) delayTicks, false)); + } + + @Override + public void shutdown() { + SafeIteration.iterate(this.tasks, SchedulerTask::cancel); + + // wait for executor + this.asyncFallback.shutdown(); + try { + this.asyncFallback.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + public ExecutorService asyncFallback() { + return this.asyncFallback; + } + + public Executor asyncNukkit() { + return this.asyncNukkit; + } + + @Override + public Executor sync() { + return this.sync; + } + + @Override + public Executor async() { + return this.async; + } + + public void setUseFallback(boolean useFallback) { + this.useFallback = useFallback; + } + + private final class SyncExecutor implements Executor { + @Override + public void execute(@Nonnull Runnable runnable) { + NukkitSchedulerAdapter.this.plugin.getServer().getScheduler().scheduleTask(NukkitSchedulerAdapter.this.plugin, runnable, false); + } + } + + private final class AsyncExecutor implements Executor { + @Override + public void execute(@Nonnull Runnable runnable) { + if (NukkitSchedulerAdapter.this.useFallback || !NukkitSchedulerAdapter.this.plugin.isEnabled()) { + NukkitSchedulerAdapter.this.asyncFallback.execute(runnable); + } else { + NukkitSchedulerAdapter.this.asyncNukkit.execute(runnable); + } + } + } + + private final class NukkitAsyncExecutor implements Executor { + @Override + public void execute(@Nonnull Runnable runnable) { + NukkitSchedulerAdapter.this.plugin.getServer().getScheduler().scheduleTask(NukkitSchedulerAdapter.this.plugin, runnable, true); + } + } + + private static final class FallbackAsyncExecutor extends ThreadPoolExecutor { + private FallbackAsyncExecutor() { + super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryBuilder().setNameFormat("luckperms-fallback-%d").build()); + } + } + + private static final class NukkitSchedulerTask implements SchedulerTask { + private final TaskHandler task; + + private NukkitSchedulerTask(TaskHandler task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java new file mode 100644 index 00000000..d1af4eb7 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/NukkitSenderFactory.java @@ -0,0 +1,109 @@ +/* + * 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.nukkit; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.common.commands.CommandManager; +import me.lucko.luckperms.common.commands.sender.SenderFactory; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.utils.TextUtils; + +import net.kyori.text.Component; + +import cn.nukkit.Player; +import cn.nukkit.command.CommandSender; +import cn.nukkit.command.ConsoleCommandSender; + +import java.util.UUID; + +public class NukkitSenderFactory extends SenderFactory { + public NukkitSenderFactory(LuckPermsPlugin plugin) { + super(plugin); + } + + @Override + protected String getName(CommandSender sender) { + if (sender instanceof Player) { + return sender.getName(); + } + return CommandManager.CONSOLE_NAME; + } + + @Override + protected UUID getUuid(CommandSender sender) { + if (sender instanceof Player) { + return ((Player) sender).getUniqueId(); + } + return CommandManager.CONSOLE_UUID; + } + + @Override + protected void sendMessage(CommandSender sender, String s) { + // we can safely send async for players and the console + if (sender instanceof Player || sender instanceof ConsoleCommandSender) { + sender.sendMessage(s); + return; + } + + // otherwise, send the message sync + getPlugin().getScheduler().doSync(new SyncMessengerAgent(sender, s)); + } + + @Override + protected void sendMessage(CommandSender sender, Component message) { + // Fallback to legacy format + sendMessage(sender, TextUtils.toLegacy(message)); + } + + @Override + protected Tristate getPermissionValue(CommandSender sender, String node) { + boolean isSet = sender.isPermissionSet(node); + boolean val = sender.hasPermission(node); + + return !isSet ? val ? Tristate.TRUE : Tristate.UNDEFINED : Tristate.fromBoolean(val); + } + + @Override + protected boolean hasPermission(CommandSender sender, String node) { + return sender.hasPermission(node); + } + + private static final class SyncMessengerAgent implements Runnable { + private final CommandSender sender; + private final String message; + + private SyncMessengerAgent(CommandSender sender, String message) { + this.sender = sender; + this.message = message; + } + + @Override + public void run() { + this.sender.sendMessage(this.message); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java new file mode 100644 index 00000000..e53ddddd --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/calculators/NukkitCalculatorFactory.java @@ -0,0 +1,75 @@ +/* + * 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.nukkit.calculators; + +import com.google.common.collect.ImmutableList; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.common.calculators.AbstractCalculatorFactory; +import me.lucko.luckperms.common.calculators.PermissionCalculator; +import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.processors.MapProcessor; +import me.lucko.luckperms.common.processors.PermissionProcessor; +import me.lucko.luckperms.common.processors.RegexProcessor; +import me.lucko.luckperms.common.processors.WildcardProcessor; +import me.lucko.luckperms.common.references.HolderType; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; +import me.lucko.luckperms.nukkit.processors.ChildProcessor; +import me.lucko.luckperms.nukkit.processors.DefaultsProcessor; + +public class NukkitCalculatorFactory extends AbstractCalculatorFactory { + private final LPNukkitPlugin plugin; + + public NukkitCalculatorFactory(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public PermissionCalculator build(Contexts contexts, PermissionCalculatorMetadata metadata) { + ImmutableList.Builder processors = ImmutableList.builder(); + + processors.add(new MapProcessor()); + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_CHILD_PERMISSIONS)) { + processors.add(new ChildProcessor(this.plugin)); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) { + processors.add(new RegexProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLYING_WILDCARDS)) { + processors.add(new WildcardProcessor()); + } + + if (this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS) && metadata.getHolderType() == HolderType.USER) { + processors.add(new DefaultsProcessor(this.plugin, contexts.isOp())); + } + + return registerCalculator(new PermissionCalculator(this.plugin, metadata, processors.build())); + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java new file mode 100644 index 00000000..2e07d116 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/NukkitContextManager.java @@ -0,0 +1,53 @@ +/* + * 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.nukkit.contexts; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.contexts.AbstractContextManager; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.Player; + +public class NukkitContextManager extends AbstractContextManager { + public NukkitContextManager(LPNukkitPlugin plugin) { + super(plugin, Player.class); + } + + @Override + public Contexts formContexts(Player subject, ImmutableContextSet contextSet) { + return new Contexts( + contextSet, + this.plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS), + this.plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS), + true, + this.plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS), + this.plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS), + subject.isOp() + ); + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java new file mode 100644 index 00000000..73b9d151 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/contexts/WorldCalculator.java @@ -0,0 +1,56 @@ +/* + * 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.nukkit.contexts; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import cn.nukkit.Player; + +import javax.annotation.Nonnull; + +public class WorldCalculator implements ContextCalculator { + private final LuckPermsPlugin plugin; + + public WorldCalculator(LuckPermsPlugin plugin) { + this.plugin = plugin; + } + + @Nonnull + @Override + public MutableContextSet giveApplicableContext(@Nonnull Player subject, @Nonnull MutableContextSet accumulator) { + String world = subject.getLevel().getName().toLowerCase(); + while (!accumulator.has(Contexts.WORLD_KEY, world)) { + accumulator.add(Contexts.WORLD_KEY, world); + world = this.plugin.getConfiguration().get(ConfigKeys.WORLD_REWRITES).getOrDefault(world, world).toLowerCase(); + } + + return accumulator; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java new file mode 100644 index 00000000..0de1fcbf --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitConnectionListener.java @@ -0,0 +1,205 @@ +/* + * 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.nukkit.listeners; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.utils.AbstractLoginListener; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; +import me.lucko.luckperms.nukkit.model.permissible.LPPermissible; +import me.lucko.luckperms.nukkit.model.permissible.PermissibleInjector; + +import cn.nukkit.Player; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.EventPriority; +import cn.nukkit.event.Listener; +import cn.nukkit.event.player.PlayerAsyncPreLoginEvent; +import cn.nukkit.event.player.PlayerLoginEvent; +import cn.nukkit.event.player.PlayerQuitEvent; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class NukkitConnectionListener extends AbstractLoginListener implements Listener { + private final LPNukkitPlugin plugin; + + private final Set deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>()); + private final Set deniedLogin = Collections.synchronizedSet(new HashSet<>()); + + public NukkitConnectionListener(LPNukkitPlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerPreLogin(PlayerAsyncPreLoginEvent e) { + /* Called when the player first attempts a connection with the server. + Listening on LOW priority to allow plugins to modify username / UUID data here. (auth plugins) */ + + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLog().info("Processing pre-login for " + e.getUuid() + " - " + e.getName()); + } + + this.plugin.getUniqueConnections().add(e.getUuid()); + + /* Actually process the login for the connection. + We do this here to delay the login until the data is ready. + If the login gets cancelled later on, then this will be cleaned up. + + This includes: + - loading uuid data + - loading permissions + - creating a user instance in the UserManager for this connection. + - setting up cached data. */ + try { + User user = loadUser(e.getUuid(), e.getName()); + this.plugin.getEventFactory().handleUserLoginProcess(e.getUuid(), e.getName(), user); + } catch (Exception ex) { + this.plugin.getLog().severe("Exception occurred whilst loading data for " + e.getUuid() + " - " + e.getName()); + ex.printStackTrace(); + + // deny the connection + this.deniedAsyncLogin.add(e.getUuid()); + e.disAllow(Message.LOADING_ERROR.asString(this.plugin.getLocaleManager())); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerPreLoginMonitor(PlayerAsyncPreLoginEvent e) { + /* Listen to see if the event was cancelled after we initially handled the connection + If the connection was cancelled here, we need to do something to clean up the data that was loaded. */ + + // Check to see if this connection was denied at LOW. + if (this.deniedAsyncLogin.remove(e.getUuid())) { + // their data was never loaded at LOW priority, now check to see if they have been magically allowed since then. + + // This is a problem, as they were denied at low priority, but are now being allowed. + if (e.getLoginResult() == PlayerAsyncPreLoginEvent.LoginResult.SUCCESS) { + this.plugin.getLog().severe("Player connection was re-allowed for " + e.getUuid()); + e.disAllow(""); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent e) { + /* Called when the player starts logging into the server. + At this point, the users data should be present and loaded. */ + + final Player player = e.getPlayer(); + + if (this.plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + this.plugin.getLog().info("Processing login for " + player.getUniqueId() + " - " + player.getName()); + } + + final User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId()); + + /* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */ + if (user == null) { + this.deniedLogin.add(e.getPlayer().getUniqueId()); + + this.plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login."); + e.setCancelled(); + e.setKickMessage(Message.LOADING_ERROR.asString(this.plugin.getLocaleManager())); + return; + } + + // User instance is there, now we can inject our custom Permissible into the player. + // Care should be taken at this stage to ensure that async tasks which manipulate nukkit data check that the player is still online. + try { + // Make a new permissible for the user + LPPermissible lpPermissible = new LPPermissible(player, user, this.plugin); + + // Inject into the player + PermissibleInjector.inject(player, lpPermissible); + + } catch (Throwable t) { + t.printStackTrace(); + } + + this.plugin.refreshAutoOp(user, player); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerLoginMonitor(PlayerLoginEvent e) { + /* Listen to see if the event was cancelled after we initially handled the login + If the connection was cancelled here, we need to do something to clean up the data that was loaded. */ + + // Check to see if this connection was denied at LOW. Even if it was denied at LOW, their data will still be present. + boolean denied = false; + if (this.deniedLogin.remove(e.getPlayer().getUniqueId())) { + denied = true; + + // This is a problem, as they were denied at low priority, but are now being allowed. + if (!e.isCancelled()) { + this.plugin.getLog().severe("Player connection was re-allowed for " + e.getPlayer().getUniqueId()); + e.setCancelled(); + } + } + + // Login event was cancelled by another plugin since we first loaded their data + if (denied || e.isCancelled()) { + return; + } + + // everything is going well. login was processed ok, this is just to refresh auto-op status. + this.plugin.refreshAutoOp(this.plugin.getUserManager().getIfLoaded(e.getPlayer().getUniqueId()), e.getPlayer()); + } + + // Wait until the last priority to unload, so plugins can still perform permission checks on this event + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent e) { + final Player player = e.getPlayer(); + + // Remove the custom permissible + try { + PermissibleInjector.unInject(player, true); + } catch (Exception ex) { + ex.printStackTrace(); + } + + // Handle auto op + if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) { + player.setOp(false); + } + + // Register with the housekeeper, so the User's instance will stick + // around for a bit after they disconnect + this.plugin.getUserManager().getHouseKeeper().registerUsage(player.getUniqueId()); + + // force a clear of transient nodes + this.plugin.getScheduler().doAsync(() -> { + User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId()); + if (user != null) { + user.clearTransientNodes(); + } + }); + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java new file mode 100644 index 00000000..d15e49bd --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/listeners/NukkitPlatformListener.java @@ -0,0 +1,97 @@ +/* + * 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.nukkit.listeners; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.Player; +import cn.nukkit.command.CommandSender; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.EventPriority; +import cn.nukkit.event.Listener; +import cn.nukkit.event.entity.EntityLevelChangeEvent; +import cn.nukkit.event.player.PlayerCommandPreprocessEvent; +import cn.nukkit.event.server.RemoteServerCommandEvent; +import cn.nukkit.event.server.ServerCommandEvent; + +public class NukkitPlatformListener implements Listener { + private final LPNukkitPlugin plugin; + + public NukkitPlatformListener(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerCommand(PlayerCommandPreprocessEvent e) { + handleCommand(e.getPlayer(), e.getMessage().toLowerCase(), e); + } + + @EventHandler + public void onServerCommand(ServerCommandEvent e) { + handleCommand(e.getSender(), e.getCommand().toLowerCase(), e); + } + + @EventHandler + public void onRemoteServerCommand(RemoteServerCommandEvent e) { + handleCommand(e.getSender(), e.getCommand().toLowerCase(), e); + } + + private void handleCommand(CommandSender sender, String s, Cancellable event) { + if (s.isEmpty()) { + return; + } + + if (this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) { + return; + } + + if (s.charAt(0) == '/') { + s = s.substring(1); + } + + if (s.startsWith("minecraft:")) { + s = s.substring("minecraft:".length()); + } + + if (s.equals("op") || s.startsWith("op ") || s.equals("deop") || s.startsWith("deop ")) { + event.setCancelled(true); + sender.sendMessage(Message.OP_DISABLED.asString(this.plugin.getLocaleManager())); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onWorldChange(EntityLevelChangeEvent e) { + if (e.getEntity() instanceof Player) { + Player player = (Player) e.getEntity(); + this.plugin.getContextManager().invalidateCache(player); + this.plugin.refreshAutoOp(this.plugin.getUserManager().getIfLoaded(player.getUniqueId()), player); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java new file mode 100644 index 00000000..9cd38e25 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/PermissionDefault.java @@ -0,0 +1,111 @@ +/* + * 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.nukkit.model; + +import cn.nukkit.permission.Permission; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Represents the possible default values for permissions + */ +public enum PermissionDefault { + TRUE("true") { + @Override + public boolean getValue(boolean op) { + return true; + } + }, + FALSE("false") { + @Override + public boolean getValue(boolean op) { + return false; + } + }, + OP("op", "isop", "operator", "isoperator", "admin", "isadmin") { + @Override + public boolean getValue(boolean op) { + return op; + } + }, + NOT_OP("!op", "notop", "!operator", "notoperator", "!admin", "notadmin") { + @Override + public boolean getValue(boolean op) { + return !op; + } + }; + + private final String[] names; + private static final Map LOOKUP = new HashMap<>(); + + PermissionDefault(String... names) { + this.names = names; + } + + /** + * Calculates the value of this PermissionDefault for the given operator + * value + * + * @param op If the target is op + * @return True if the default should be true, or false + */ + public abstract boolean getValue(boolean op); + + /** + * Looks up a PermissionDefault by name + * + * @param name Name of the default + * @return Specified value, or null if not found + */ + @Nullable + public static PermissionDefault getByName(String name) { + return LOOKUP.get(name.toLowerCase().replaceAll("[^a-z!]", "")); + } + + @Nullable + public static PermissionDefault fromPermission(@Nullable Permission permission) { + if (permission == null) { + return null; + } + return getByName(permission.getDefault()); + } + + @Override + public String toString() { + return this.names[0]; + } + + static { + for (PermissionDefault value : values()) { + for (String name : value.names) { + LOOKUP.put(name, value); + } + } + } +} \ No newline at end of file diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java new file mode 100644 index 00000000..15f7b54e --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPermissibleBase.java @@ -0,0 +1,88 @@ +/* + * 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.nukkit.model.dummy; + +import cn.nukkit.permission.PermissibleBase; +import cn.nukkit.permission.Permission; +import cn.nukkit.permission.PermissionAttachment; +import cn.nukkit.permission.PermissionAttachmentInfo; +import cn.nukkit.plugin.Plugin; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; + +public class DummyPermissibleBase extends PermissibleBase { + private static final Field ATTACHMENTS_FIELD; + private static final Field PERMISSIONS_FIELD; + + static { + try { + ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments"); + ATTACHMENTS_FIELD.setAccessible(true); + + PERMISSIONS_FIELD = PermissibleBase.class.getDeclaredField("permissions"); + PERMISSIONS_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + 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; } + @Override public void setOp(boolean value) {} + @Override public boolean isPermissionSet(String name) { return false; } + @Override public boolean isPermissionSet(Permission perm) { return false; } + @Override public boolean hasPermission(String inName) { return false; } + @Override public boolean hasPermission(Permission perm) { return false; } + @Override public PermissionAttachment addAttachment(Plugin plugin) { return null; } + @Override public PermissionAttachment addAttachment(Plugin plugin, String name) { return null; } + @Override public PermissionAttachment addAttachment(Plugin plugin, String name, Boolean value) { return null; } + @Override public void removeAttachment(PermissionAttachment attachment) {} + @Override public void recalculatePermissions() {} + @Override public void clearPermissions() {} + @Override public Map getEffectivePermissions() { return Collections.emptyMap(); } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java new file mode 100644 index 00000000..e5e1fc97 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/dummy/DummyPlugin.java @@ -0,0 +1,78 @@ +/* + * 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.nukkit.model.dummy; +import cn.nukkit.Server; +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandSender; +import cn.nukkit.plugin.Plugin; +import cn.nukkit.plugin.PluginDescription; +import cn.nukkit.plugin.PluginLoader; +import cn.nukkit.plugin.PluginLogger; +import cn.nukkit.utils.Config; + +import java.io.File; +import java.io.InputStream; + +/** + * Dummy plugin instance + */ +public class DummyPlugin implements Plugin { + public static final DummyPlugin INSTANCE = new DummyPlugin(); + + private DummyPlugin() { + + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isDisabled() { + return false; + } + + @Override public File getDataFolder() { return null; } + @Override public PluginDescription getDescription() { return null; } + @Override public Config getConfig() { return null; } + @Override public InputStream getResource(String s) { return null; } + @Override public boolean saveResource(String s) { return false; } + @Override public void saveConfig() {} + @Override public void saveDefaultConfig() {} + @Override public boolean saveResource(String s, boolean b) { return false; } + @Override public boolean saveResource(String s, String s1, boolean b) { return false; } + @Override public void reloadConfig() {} + @Override public PluginLoader getPluginLoader() { return null; } + @Override public Server getServer() { return null; } + @Override public void onDisable() {} + @Override public void onLoad() {} + @Override public void onEnable() {} + @Override public PluginLogger getLogger() { return null; } + @Override public String getName() { return null; } + @Override public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) { return false; } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java new file mode 100644 index 00000000..cb9ad391 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissible.java @@ -0,0 +1,279 @@ +/* + * 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.nukkit.model.permissible; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.verbose.CheckOrigin; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; +import me.lucko.luckperms.nukkit.model.PermissionDefault; + +import cn.nukkit.Player; +import cn.nukkit.permission.PermissibleBase; +import cn.nukkit.permission.Permission; +import cn.nukkit.permission.PermissionAttachment; +import cn.nukkit.permission.PermissionAttachmentInfo; +import cn.nukkit.plugin.Plugin; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * PermissibleBase for LuckPerms. + * + * This class overrides all methods defined in PermissibleBase, and provides custom handling + * from LuckPerms. + * + * This means that all permission checks made for a player are handled directly by the plugin. + * Method behaviour is retained, but alternate implementation is used. + * + * "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation. + * + * This class is **thread safe**. This means that when LuckPerms is installed on the server, + * is is safe to call Player#hasPermission asynchronously. + */ +public class LPPermissible extends PermissibleBase { + + // the LuckPerms user this permissible references. + private final User user; + + // the player this permissible is injected into. + private final Player player; + + // the luckperms plugin instance + private final LPNukkitPlugin plugin; + + // the players previous permissible. (the one they had before this one was injected) + private PermissibleBase oldPermissible = null; + + // if the permissible is currently active. + private final AtomicBoolean active = new AtomicBoolean(false); + + // the attachments hooked onto the permissible. + // this collection is only modified by the attachments themselves + final Set attachments = ConcurrentHashMap.newKeySet(); + + public LPPermissible(Player player, User user, LPNukkitPlugin plugin) { + super(player); + this.user = Objects.requireNonNull(user, "user"); + this.player = Objects.requireNonNull(player, "player"); + this.plugin = Objects.requireNonNull(plugin, "plugin"); + } + + @Override + public boolean isPermissionSet(String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK); + return ts != Tristate.UNDEFINED || PermissionDefault.OP.getValue(isOp()); + } + + @Override + public boolean isPermissionSet(Permission permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_LOOKUP_CHECK); + if (ts != Tristate.UNDEFINED) { + return true; + } + + if (!this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS)) { + return PermissionDefault.OP.getValue(isOp()); + } else { + PermissionDefault def = PermissionDefault.fromPermission(permission); + return def != null && def.getValue(isOp()); + } + } + + @Override + public boolean hasPermission(String permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission, CheckOrigin.PLATFORM_PERMISSION_CHECK); + return ts != Tristate.UNDEFINED ? ts.asBoolean() : PermissionDefault.OP.getValue(isOp()); + } + + @Override + public boolean hasPermission(Permission permission) { + if (permission == null) { + throw new NullPointerException("permission"); + } + + Tristate ts = this.user.getCachedData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName(), CheckOrigin.PLATFORM_PERMISSION_CHECK); + if (ts != Tristate.UNDEFINED) { + return ts.asBoolean(); + } + + if (!this.plugin.getConfiguration().get(ConfigKeys.APPLY_NUKKIT_DEFAULT_PERMISSIONS)) { + return PermissionDefault.OP.getValue(isOp()); + } else { + PermissionDefault def = PermissionDefault.fromPermission(permission); + return def != null && def.getValue(isOp()); + } + } + + /** + * Adds attachments to this permissible. + * + * @param attachments the attachments to add + */ + public void convertAndAddAttachments(Collection attachments) { + for (PermissionAttachment attachment : attachments) { + new LPPermissionAttachment(this, attachment).hook(); + } + } + + /** + * Obtains a {@link Contexts} instance for the player. + * Values are determined using the plugins ContextManager. + * + * @return the calculated contexts for the player. + */ + private Contexts calculateContexts() { + return this.plugin.getContextManager().getApplicableContexts(this.player); + } + + @Override + public void setOp(boolean value) { + this.player.setOp(value); + } + + @Override + public Map getEffectivePermissions() { + Set> permissions = this.user.getCachedData().getPermissionData(calculateContexts()).getImmutableBacking().entrySet(); + Map ret = new HashMap<>(permissions.size()); + + for (Map.Entry entry : permissions) { + ret.put(entry.getKey(), new PermissionAttachmentInfo(this.player, entry.getKey(), null, entry.getValue())); + } + + return ret; + } + + @Override + public LPPermissionAttachment addAttachment(Plugin plugin) { + if (plugin == null) { + throw new NullPointerException("plugin"); + } + + LPPermissionAttachment ret = new LPPermissionAttachment(this, plugin); + ret.hook(); + return ret; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String permission) { + if (plugin == null) { + throw new NullPointerException("plugin"); + } + if (permission == null) { + throw new NullPointerException("permission"); + } + + PermissionAttachment ret = addAttachment(plugin); + ret.setPermission(permission, true); + return ret; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String permission, Boolean value) { + if (plugin == null) { + throw new NullPointerException("plugin"); + } + if (permission == null) { + throw new NullPointerException("permission"); + } + + PermissionAttachment ret = addAttachment(plugin); + ret.setPermission(permission, value); + return ret; + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + if (attachment == null) { + throw new NullPointerException("attachment"); + } + + if (!(attachment instanceof LPPermissionAttachment)) { + throw new IllegalArgumentException("Given attachment is not a LPPermissionAttachment."); + } + + LPPermissionAttachment a = ((LPPermissionAttachment) attachment); + if (a.getPermissible() != this) { + throw new IllegalArgumentException("Attachment does not belong to this permissible."); + } + + a.remove(); + } + + @Override + public void recalculatePermissions() { + // do nothing + } + + @Override + public void clearPermissions() { + this.attachments.forEach(LPPermissionAttachment::remove); + } + + public User getUser() { + return this.user; + } + + public Player getPlayer() { + return this.player; + } + + public LPNukkitPlugin getPlugin() { + return this.plugin; + } + + public PermissibleBase getOldPermissible() { + return this.oldPermissible; + } + + public AtomicBoolean getActive() { + return this.active; + } + + public void setOldPermissible(PermissibleBase oldPermissible) { + this.oldPermissible = oldPermissible; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java new file mode 100644 index 00000000..fa111dde --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/LPPermissionAttachment.java @@ -0,0 +1,432 @@ +/* + * 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.nukkit.model.permissible; + +import com.google.common.base.Preconditions; + +import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.node.ImmutableTransientNode; +import me.lucko.luckperms.common.node.NodeFactory; +import me.lucko.luckperms.nukkit.model.dummy.DummyPlugin; + +import cn.nukkit.permission.Permission; +import cn.nukkit.permission.PermissionAttachment; +import cn.nukkit.permission.PermissionRemovedExecutor; +import cn.nukkit.plugin.Plugin; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * PermissionAttachment for LuckPerms. + * + * Applies all permissions directly to the backing user instance via transient nodes. + */ +public class LPPermissionAttachment extends PermissionAttachment { + + /** + * The field in PermissionAttachment where the attachments applied permissions + * are *usually* held. + */ + private static final Field PERMISSION_ATTACHMENT_PERMISSIONS_FIELD; + + static { + try { + PERMISSION_ATTACHMENT_PERMISSIONS_FIELD = PermissionAttachment.class.getDeclaredField("permissions"); + PERMISSION_ATTACHMENT_PERMISSIONS_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * The parent LPPermissible + */ + private final LPPermissible permissible; + + /** + * The plugin which "owns" this attachment, may be null + */ + private final Plugin owner; + + /** + * The permissions being applied by this attachment + */ + private final Map perms = Collections.synchronizedMap(new HashMap<>()); + + /** + * If the attachment has been applied to the user + */ + private boolean hooked = false; + + /** + * Callback to run when the attachment is removed + */ + private PermissionRemovedExecutor removalCallback = null; + + public LPPermissionAttachment(LPPermissible permissible, Plugin owner) { + super(DummyPlugin.INSTANCE, null); + this.permissible = permissible; + this.owner = owner; + + injectFakeMap(); + } + + public LPPermissionAttachment(LPPermissible permissible, PermissionAttachment nukkit) { + super(DummyPlugin.INSTANCE, null); + this.permissible = permissible; + this.owner = null; + + // copy + this.perms.putAll(nukkit.getPermissions()); + + injectFakeMap(); + } + + /** + * Injects a fake 'permissions' map into the superclass, for (clever/dumb??) plugins + * which attempt to modify attachment permissions using reflection to get around the slow nukkit + * behaviour in the base PermissionAttachment implementation. + * + * The fake map proxies calls back to the methods on this attachment + */ + private void injectFakeMap() { + // inner class - this proxies calls back to us + FakeBackingMap fakeMap = new FakeBackingMap(); + + try { + // the field we need to modify is in the superclass - it has private + // and final modifiers so we have to use reflection to modify it. + PERMISSION_ATTACHMENT_PERMISSIONS_FIELD.set(this, fakeMap); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public LPPermissible getPermissible() { + return this.permissible; + } + + @Override + public PermissionRemovedExecutor getRemovalCallback() { + return this.removalCallback; + } + + @Override + public void setRemovalCallback(PermissionRemovedExecutor removalCallback) { + this.removalCallback = removalCallback; + } + + /** + * Hooks this attachment with the parent {@link User} instance. + */ + public void hook() { + this.hooked = true; + this.permissible.attachments.add(this); + for (Map.Entry entry : this.perms.entrySet()) { + if (entry.getKey() == null || entry.getKey().isEmpty()) { + continue; + } + setPermissionInternal(entry.getKey(), entry.getValue()); + } + } + + private void setPermissionInternal(String name, boolean value) { + if (!this.permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_NUKKIT_ATTACHMENT_PERMISSIONS)) { + return; + } + + // construct a node for the permission being set + // we use the servers static context to *try* to ensure that the node will apply + Node node = NodeFactory.builder(name) + .setValue(value) + .withExtraContext(this.permissible.getPlugin().getContextManager().getStaticContext()) + .build(); + + // convert the constructed node to a transient node instance to refer back to this attachment + ImmutableTransientNode transientNode = ImmutableTransientNode.of(node, this); + + // set the transient node + User user = this.permissible.getUser(); + if (user.setTransientPermission(transientNode).asBoolean()) { + user.reloadCachedData(); + } + } + + private void unsetPermissionInternal(String name) { + if (!this.permissible.getPlugin().getConfiguration().get(ConfigKeys.APPLY_NUKKIT_ATTACHMENT_PERMISSIONS)) { + return; + } + + // remove transient permissions from the holder which were added by this attachment & equal the permission + User user = this.permissible.getUser(); + if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this && n.getPermission().equals(name))) { + user.reloadCachedData(); + } + } + + private void clearInternal() { + // remove all transient permissions added by this attachment + User user = this.permissible.getUser(); + if (user.removeIfTransient(n -> n instanceof ImmutableTransientNode && ((ImmutableTransientNode) n).getOwner() == this)) { + user.reloadCachedData(); + } + } + + @Override + public void remove() { + if (!this.hooked) { + return; + } + + // clear the internal permissions + clearInternal(); + + // run the callback + if (this.removalCallback != null) { + this.removalCallback.attachmentRemoved(this); + } + + // unhook from the permissible + this.hooked = false; + this.permissible.attachments.remove(this); + } + + @Override + public void setPermission(String name, boolean value) { + Objects.requireNonNull(name, "name is null"); + Preconditions.checkArgument(!name.isEmpty(), "name is empty"); + + String permission = name.toLowerCase(); + + Boolean previous = this.perms.put(permission, value); + if (previous != null && previous == value) { + return; + } + + // if we're not hooked, then don't actually apply the change + // it will get applied on hook - if that ever happens + if (!this.hooked) { + return; + } + + if (previous != null) { + unsetPermissionInternal(permission); + } + + setPermissionInternal(permission, value); + } + + @Override + public void setPermission(Permission permission, boolean value) { + setPermission(permission.getName(), value); + } + + @Override + public void setPermissions(Map permissions) { + for (Map.Entry entry : permissions.entrySet()) { + setPermission(entry.getKey(), entry.getValue()); + } + } + + @Override + public void unsetPermission(String name, boolean value) { + Objects.requireNonNull(name, "name is null"); + Preconditions.checkArgument(!name.isEmpty(), "name is empty"); + + String permission = name.toLowerCase(); + + Boolean previous = this.perms.remove(permission); + if (previous == null) { + return; + } + + // if we're not hooked, then don't actually apply the change + // it will get applied on hook - if that ever happens + if (!this.hooked) { + return; + } + + unsetPermissionInternal(permission); + } + + @Override + public void unsetPermission(Permission permission, boolean value) { + unsetPermission(permission.getName(), value); + } + + @Override + public void unsetPermissions(List permissions) { + for (String perm : permissions) { + unsetPermission(perm, true); + } + } + + @Override + public void clearPermissions() { + this.perms.clear(); + clearInternal(); + } + + @Override + public Map getPermissions() { + return this.perms; + } + + @Override + public Plugin getPlugin() { + return this.owner != null ? this.owner : this.permissible.getPlugin(); + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + /** + * A fake map to be injected into the superclass. This implementation simply + * proxies calls back to this attachment instance. + * + * Some (clever/dumb??) plugins attempt to modify attachment permissions using reflection + * to get around the slow nukkit behaviour in the base PermissionAttachment implementation. + * + * An instance of this map is injected into the super instance so these plugins continue + * to work with LuckPerms. + */ + private final class FakeBackingMap implements Map { + + @Override + public Boolean put(String key, Boolean value) { + // grab the previous result, so we can still satisfy the method signature of Map + Boolean previous = LPPermissionAttachment.this.perms.get(key); + + // proxy the call back through the PermissionAttachment instance + setPermission(key, value); + + // return the previous value + return previous; + } + + @Override + public Boolean remove(Object key) { + // we only accept string keys + if (!(key instanceof String)) { + return null; + } + + String permission = ((String) key); + + // grab the previous result, so we can still satisfy the method signature of Map + Boolean previous = LPPermissionAttachment.this.perms.get(permission); + + // proxy the call back through the PermissionAttachment instance + unsetPermission(permission, true); + + // return the previous value + return previous; + } + + @Override + public void putAll(Map m) { + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + // remove the permissions which have already been applied + if (LPPermissionAttachment.this.hooked) { + clearInternal(); + } + + // clear the backing map + LPPermissionAttachment.this.perms.clear(); + } + + @Override + public int size() { + // return the size of the permissions map - probably the most accurate value we have + return LPPermissionAttachment.this.perms.size(); + } + + @Override + public boolean isEmpty() { + // return if the permissions map is empty - again probably the most accurate thing + // we can return + return LPPermissionAttachment.this.perms.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + // just proxy + return LPPermissionAttachment.this.perms.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + // just proxy + return LPPermissionAttachment.this.perms.containsValue(value); + } + + @Override + public Boolean get(Object key) { + // just proxy + return LPPermissionAttachment.this.perms.get(key); + } + + @Override + public Set keySet() { + // just proxy + return LPPermissionAttachment.this.perms.keySet(); + } + + @Override + public Collection values() { + // just proxy + return LPPermissionAttachment.this.perms.values(); + } + + @Override + public Set> entrySet() { + // just proxy + return LPPermissionAttachment.this.perms.entrySet(); + } + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java new file mode 100644 index 00000000..9a506902 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/MonitoredPermissibleBase.java @@ -0,0 +1,145 @@ +/* + * 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.nukkit.model.permissible; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.verbose.CheckOrigin; +import me.lucko.luckperms.common.verbose.VerboseHandler; +import me.lucko.luckperms.nukkit.model.dummy.DummyPermissibleBase; + +import cn.nukkit.permission.PermissibleBase; +import cn.nukkit.permission.Permission; +import cn.nukkit.permission.PermissionAttachment; +import cn.nukkit.permission.PermissionAttachmentInfo; +import cn.nukkit.plugin.Plugin; + +import java.util.Map; + +/** + * 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 LuckPermsPlugin plugin; + 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(LuckPermsPlugin plugin, PermissibleBase delegate, String name) { + super(null); + DummyPermissibleBase.nullFields(this); + + this.plugin = plugin; + 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.plugin.getVerboseHandler().offerCheckData(origin, this.name, ContextSet.empty(), permission, Tristate.fromBoolean(result)); + this.plugin.getPermissionVault().offer(permission); + } + + 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, String name) { return this.delegate.addAttachment(plugin, name); } + @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 Map getEffectivePermissions() { return this.delegate.getEffectivePermissions(); } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java new file mode 100644 index 00000000..27df5a6b --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleInjector.java @@ -0,0 +1,146 @@ +/* + * 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.nukkit.model.permissible; + +import me.lucko.luckperms.nukkit.model.dummy.DummyPermissibleBase; + +import cn.nukkit.Player; +import cn.nukkit.permission.PermissibleBase; +import cn.nukkit.permission.PermissionAttachment; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * Injects a {@link LPPermissible} into a {@link Player}. + * + * This allows LuckPerms to directly intercept permission checks and take over all handling of + * checks made by plugins. + */ +public final class PermissibleInjector { + + /** + * All permission checks made on standard Nukkit objects are effectively proxied to a + * {@link PermissibleBase} object, held as a variable on the object. + * + * This field is where the permissible is stored on a Player. + */ + private static final Field PLAYER_PERMISSIBLE_FIELD; + + /** + * The field where attachments are stored on a permissible base. + */ + private static final Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD; + + static { + try { + // Try to load the permissible field. + PLAYER_PERMISSIBLE_FIELD = Player.class.getDeclaredField("perm"); + PLAYER_PERMISSIBLE_FIELD.setAccessible(true); + + // Try to load the attachments field. + PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments"); + PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Injects a {@link LPPermissible} into a {@link Player}. + * + * @param player the player to inject into + * @param newPermissible the permissible to inject + * @throws Exception propagates any exceptions which were thrown during injection + */ + public static void inject(Player player, LPPermissible newPermissible) throws Exception { + + // get the existing PermissibleBase held by the player + PermissibleBase oldPermissible = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player); + + // seems we have already injected into this player. + if (oldPermissible instanceof LPPermissible) { + throw new IllegalStateException("LPPermissible already injected into player " + player.toString()); + } + + // Move attachments over from the old permissible + + //noinspection unchecked + Set attachments = (Set) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible); + + newPermissible.convertAndAddAttachments(attachments); + attachments.clear(); + oldPermissible.clearPermissions(); + + // Setup the new permissible + newPermissible.getActive().set(true); + newPermissible.setOldPermissible(oldPermissible); + + // inject the new instance + PLAYER_PERMISSIBLE_FIELD.set(player, newPermissible); + } + + /** + * Uninjects a {@link LPPermissible} from a {@link Player}. + * + * @param player the player to uninject from + * @param dummy if the replacement permissible should be a dummy. + * @throws Exception propagates any exceptions which were thrown during uninjection + */ + public static void unInject(Player player, boolean dummy) throws Exception { + + // gets the players current permissible. + PermissibleBase permissible = (PermissibleBase) PLAYER_PERMISSIBLE_FIELD.get(player); + + // only uninject if the permissible was a luckperms one. + if (permissible instanceof LPPermissible) { + LPPermissible lpPermissible = ((LPPermissible) permissible); + + // clear all permissions + lpPermissible.clearPermissions(); + + // set to inactive + lpPermissible.getActive().set(false); + + // handle the replacement permissible. + if (dummy) { + // just inject a dummy class. this is used when we know the player is about to quit the server. + PLAYER_PERMISSIBLE_FIELD.set(player, DummyPermissibleBase.INSTANCE); + + } else { + PermissibleBase newPb = lpPermissible.getOldPermissible(); + if (newPb == null) { + newPb = new PermissibleBase(player); + } + + PLAYER_PERMISSIBLE_FIELD.set(player, newPb); + } + } + } + + private PermissibleInjector() {} + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java new file mode 100644 index 00000000..ab8fe7b8 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/permissible/PermissibleMonitoringInjector.java @@ -0,0 +1,84 @@ +/* + * 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.nukkit.model.permissible; + +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.command.ConsoleCommandSender; +import cn.nukkit.permission.PermissibleBase; + +import java.lang.reflect.Field; +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 LPNukkitPlugin plugin; + + public PermissibleMonitoringInjector(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + try { + injectConsole(); + } 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, permBase, name); + } + + private void injectConsole() throws Exception { + ConsoleCommandSender consoleSender = this.plugin.getServer().getConsoleSender(); + + // get the perm field + Field permField = ConsoleCommandSender.class.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); + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java new file mode 100644 index 00000000..a22c4488 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorDefaultsMap.java @@ -0,0 +1,138 @@ +/* + * 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.nukkit.model.server; + +import com.google.common.collect.ImmutableMap; + +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.Server; +import cn.nukkit.permission.Permission; +import cn.nukkit.plugin.PluginManager; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Injects a {@link LPDefaultsMap} info the {@link PluginManager}. + */ +public class InjectorDefaultsMap implements Runnable { + private static final Field OP_DEFAULT_PERMISSIONS_FIELD; + private static final Field NON_OP_DEFAULT_PERMISSIONS_FIELD; + + static { + Field opPermissionsField = null; + try { + opPermissionsField = PluginManager.class.getDeclaredField("defaultPermsOp"); + opPermissionsField.setAccessible(true); + } catch (Exception e) { + // ignore + } + OP_DEFAULT_PERMISSIONS_FIELD = opPermissionsField; + + Field nonOpPermissionsField = null; + try { + nonOpPermissionsField = PluginManager.class.getDeclaredField("defaultPerms"); + nonOpPermissionsField.setAccessible(true); + } catch (Exception e) { + // ignore + } + NON_OP_DEFAULT_PERMISSIONS_FIELD = nonOpPermissionsField; + } + + private final LPNukkitPlugin plugin; + + public InjectorDefaultsMap(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + try { + LPDefaultsMap ret = inject(); + if (ret != null) { + this.plugin.setDefaultPermissionMap(ret); + } + } catch (Exception e) { + this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Default Permission map."); + e.printStackTrace(); + } + } + + private LPDefaultsMap inject() throws Exception { + Objects.requireNonNull(OP_DEFAULT_PERMISSIONS_FIELD, "OP_DEFAULT_PERMISSIONS_FIELD"); + Objects.requireNonNull(NON_OP_DEFAULT_PERMISSIONS_FIELD, "NON_OP_DEFAULT_PERMISSIONS_FIELD"); + + PluginManager pluginManager = this.plugin.getServer().getPluginManager(); + + Object opMap = OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager); + Object nonOpMap = NON_OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager); + + if (opMap instanceof LPDefaultsMap.DefaultPermissionSet && ((LPDefaultsMap.DefaultPermissionSet) opMap).parent.plugin == this.plugin) { + if (nonOpMap instanceof LPDefaultsMap.DefaultPermissionSet && ((LPDefaultsMap.DefaultPermissionSet) nonOpMap).parent.plugin == this.plugin) { + return null; + } + } + + //noinspection unchecked + Map castedOpMap = (Map) opMap; + + //noinspection unchecked + Map castedNonOpMap = (Map) nonOpMap; + + // make a new map & inject it + LPDefaultsMap newMap = new LPDefaultsMap(this.plugin, ImmutableMap.of(true, castedOpMap, false, castedNonOpMap)); + OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, newMap.getOpPermissions()); + NON_OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, newMap.getNonOpPermissions()); + return newMap; + } + + public static void uninject() { + try { + Objects.requireNonNull(OP_DEFAULT_PERMISSIONS_FIELD, "OP_DEFAULT_PERMISSIONS_FIELD"); + Objects.requireNonNull(NON_OP_DEFAULT_PERMISSIONS_FIELD, "NON_OP_DEFAULT_PERMISSIONS_FIELD"); + PluginManager pluginManager = Server.getInstance().getPluginManager(); + { + Object map = OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager); + if (map instanceof LPDefaultsMap.DefaultPermissionSet) { + LPDefaultsMap.DefaultPermissionSet lpMap = (LPDefaultsMap.DefaultPermissionSet) map; + OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap)); + } + } + { + Object map = NON_OP_DEFAULT_PERMISSIONS_FIELD.get(pluginManager); + if (map instanceof LPDefaultsMap.DefaultPermissionSet) { + LPDefaultsMap.DefaultPermissionSet lpMap = (LPDefaultsMap.DefaultPermissionSet) map; + NON_OP_DEFAULT_PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java new file mode 100644 index 00000000..7b040b9f --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorPermissionMap.java @@ -0,0 +1,108 @@ +/* + * 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.nukkit.model.server; + +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.Server; +import cn.nukkit.permission.Permission; +import cn.nukkit.plugin.PluginManager; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Injects a {@link LPPermissionMap} into the {@link PluginManager}. + */ +public class InjectorPermissionMap implements Runnable { + private static final Field PERMISSIONS_FIELD; + + static { + Field permissionsField = null; + try { + permissionsField = PluginManager.class.getDeclaredField("permissions"); + permissionsField.setAccessible(true); + } catch (Exception e) { + // ignore + } + PERMISSIONS_FIELD = permissionsField; + } + + private final LPNukkitPlugin plugin; + + public InjectorPermissionMap(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + try { + LPPermissionMap ret = inject(); + if (ret != null) { + this.plugin.setPermissionMap(ret); + } + } catch (Exception e) { + this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Permission map."); + e.printStackTrace(); + } + } + + private LPPermissionMap inject() throws Exception { + Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD"); + PluginManager pluginManager = this.plugin.getServer().getPluginManager(); + + Object map = PERMISSIONS_FIELD.get(pluginManager); + if (map instanceof LPPermissionMap && ((LPPermissionMap) map).plugin == this.plugin) { + return null; + } + + //noinspection unchecked + Map castedMap = (Map) map; + + // make a new map & inject it + LPPermissionMap newMap = new LPPermissionMap(this.plugin, castedMap); + PERMISSIONS_FIELD.set(pluginManager, newMap); + return newMap; + } + + public static void uninject() { + try { + Objects.requireNonNull(PERMISSIONS_FIELD, "PERMISSIONS_FIELD"); + PluginManager pluginManager = Server.getInstance().getPluginManager(); + + Object map = PERMISSIONS_FIELD.get(pluginManager); + if (map instanceof LPPermissionMap) { + LPPermissionMap lpMap = (LPPermissionMap) map; + PERMISSIONS_FIELD.set(pluginManager, new HashMap<>(lpMap)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java new file mode 100644 index 00000000..a6e698f0 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/InjectorSubscriptionMap.java @@ -0,0 +1,112 @@ +/* + * 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.nukkit.model.server; + +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.Server; +import cn.nukkit.permission.Permissible; +import cn.nukkit.plugin.PluginManager; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Injects a {@link LPSubscriptionMap} into the {@link PluginManager}. + */ +public class InjectorSubscriptionMap implements Runnable { + private static final Field PERM_SUBS_FIELD; + + static { + Field permSubsField = null; + try { + permSubsField = PluginManager.class.getDeclaredField("permSubs"); + permSubsField.setAccessible(true); + } catch (Exception e) { + // ignore + } + PERM_SUBS_FIELD = permSubsField; + } + + private final LPNukkitPlugin plugin; + + public InjectorSubscriptionMap(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + try { + LPSubscriptionMap ret = inject(); + if (ret != null) { + this.plugin.setSubscriptionMap(ret); + } + } catch (Exception e) { + this.plugin.getLog().severe("Exception occurred whilst injecting LuckPerms Permission Subscription map."); + e.printStackTrace(); + } + } + + private LPSubscriptionMap inject() throws Exception { + Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD"); + PluginManager pluginManager = this.plugin.getServer().getPluginManager(); + + Object map = PERM_SUBS_FIELD.get(pluginManager); + if (map instanceof LPSubscriptionMap) { + if (((LPSubscriptionMap) map).plugin == this.plugin) { + return null; + } + + map = ((LPSubscriptionMap) map).detach(); + } + + //noinspection unchecked + Map> castedMap = (Map>) map; + + // make a new subscription map & inject it + LPSubscriptionMap newMap = new LPSubscriptionMap(this.plugin, castedMap); + PERM_SUBS_FIELD.set(pluginManager, newMap); + return newMap; + } + + public static void uninject() { + try { + Objects.requireNonNull(PERM_SUBS_FIELD, "PERM_SUBS_FIELD"); + PluginManager pluginManager = Server.getInstance().getPluginManager(); + + Object map = PERM_SUBS_FIELD.get(pluginManager); + if (map instanceof LPSubscriptionMap) { + LPSubscriptionMap lpMap = (LPSubscriptionMap) map; + PERM_SUBS_FIELD.set(pluginManager, lpMap.detach()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java new file mode 100644 index 00000000..218cc887 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPDefaultsMap.java @@ -0,0 +1,166 @@ +/* + * 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.nukkit.model.server; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.permission.Permission; +import cn.nukkit.plugin.PluginManager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A replacement map for the 'defaultPerms' instance in Nukkit's SimplePluginManager. + * + * This instance allows LuckPerms to intercept calls to + * {@link PluginManager#addPermission(Permission)}, specifically regarding + * the default nature of the permission. + * + * Injected by {@link InjectorDefaultsMap}. + */ +public final class LPDefaultsMap { + + // the plugin + final LPNukkitPlugin plugin; + + // the two values in the map + private final Map opSet = new DefaultPermissionSet(true); + private final Map nonOpSet = new DefaultPermissionSet(false); + + // fully resolved defaults (accounts for child permissions too) + private Map resolvedOpDefaults = ImmutableMap.of(); + private Map resolvedNonOpDefaults = ImmutableMap.of(); + + public LPDefaultsMap(LPNukkitPlugin plugin, Map> existingData) { + this.plugin = plugin; + this.opSet.putAll(existingData.getOrDefault(Boolean.TRUE, Collections.emptyMap())); + this.nonOpSet.putAll(existingData.getOrDefault(Boolean.FALSE, Collections.emptyMap())); + refreshOp(); + refreshNonOp(); + } + + public Map getOpPermissions() { + return this.opSet; + } + + public Map getNonOpPermissions() { + return this.nonOpSet; + } + + /** + * Queries whether a given permission should be granted by default. + * + * @param permission the permission to query + * @param isOp if the player is op + * @return a tristate result + */ + public Tristate lookupDefaultPermission(String permission, boolean isOp) { + Map map = isOp ? this.resolvedOpDefaults : this.resolvedNonOpDefaults; + return Tristate.fromNullableBoolean(map.get(permission)); + } + + private void refresh(boolean op) { + if (op) { + refreshOp(); + } else { + refreshNonOp(); + } + } + + /** + * Refreshes the op data in this provider. + */ + private void refreshOp() { + Map builder = new HashMap<>(); + for (Permission perm : getOpPermissions().values()) { + String name = perm.getName().toLowerCase(); + builder.put(name, true); + for (Map.Entry child : this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) { + builder.putIfAbsent(child.getKey(), child.getValue()); + } + } + this.resolvedOpDefaults = ImmutableMap.copyOf(builder); + } + + /** + * Refreshes the non op data in this provider. + */ + private void refreshNonOp() { + Map builder = new HashMap<>(); + for (Permission perm : getNonOpPermissions().values()) { + String name = perm.getName().toLowerCase(); + builder.put(name, true); + for (Map.Entry child : this.plugin.getPermissionMap().getChildPermissions(name, true).entrySet()) { + builder.putIfAbsent(child.getKey(), child.getValue()); + } + } + this.resolvedNonOpDefaults = ImmutableMap.copyOf(builder); + } + + final class DefaultPermissionSet extends ForwardingMap { + final LPDefaultsMap parent = LPDefaultsMap.this; + + private final Map delegate = new ConcurrentHashMap<>(); + private final boolean op; + + private DefaultPermissionSet(boolean op) { + this.op = op; + } + + @Override + protected Map delegate() { + return this.delegate; + } + + @Override + public Permission put(String key, Permission value) { + Permission ret = super.put(key, value); + refresh(this.op); + return ret; + } + + @Override + public Permission putIfAbsent(String key, Permission value) { + Permission ret = super.putIfAbsent(key, value); + refresh(this.op); + return ret; + } + + @Override + public void putAll(Map map) { + super.putAll(map); + refresh(this.op); + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java new file mode 100644 index 00000000..65c013be --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPPermissionMap.java @@ -0,0 +1,155 @@ +/* + * 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.nukkit.model.server; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.treeview.PermissionVault; + +import cn.nukkit.permission.Permission; +import cn.nukkit.plugin.PluginManager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * A replacement map for the 'permissions' instance in Nukkit's SimplePluginManager. + * + * This instance allows LuckPerms to intercept calls to + * {@link PluginManager#addPermission(Permission)} and record permissions in the + * {@link PermissionVault}. + * + * It also allows us to pre-determine child permission relationships. + * + * Injected by {@link InjectorPermissionMap}. + */ +public final class LPPermissionMap extends ForwardingMap { + + // Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key + private final Map delegate = new ConcurrentHashMap<>(); + + // cache from permission --> children + private final LoadingCache> trueChildPermissions = Caffeine.newBuilder() + .build(new ChildPermissionResolver(true)); + + private final LoadingCache> falseChildPermissions = Caffeine.newBuilder() + .build(new ChildPermissionResolver(false)); + + /** + * The plugin instance + */ + final LuckPermsPlugin plugin; + + public LPPermissionMap(LuckPermsPlugin plugin, Map existingData) { + this.plugin = plugin; + putAll(existingData); + } + + public Map getChildPermissions(String permission, boolean value) { + return value ? this.trueChildPermissions.get(permission) : this.falseChildPermissions.get(permission); + } + + private void update() { + this.trueChildPermissions.invalidateAll(); + this.falseChildPermissions.invalidateAll(); + } + + @Override + protected Map delegate() { + return this.delegate; + } + + @Override + public Permission put(@Nonnull String key, @Nonnull Permission value) { + this.plugin.getPermissionVault().offer(key); + Permission ret = super.put(key, value); + update(); + return ret; + } + + @Override + public void putAll(@Nonnull Map m) { + this.plugin.getPermissionVault().offerAll(m.keySet()); + super.putAll(m); + update(); + } + + @Override + public Permission putIfAbsent(String key, Permission value) { + this.plugin.getPermissionVault().offer(key); + Permission ret = super.putIfAbsent(key, value); + update(); + return ret; + } + + private final class ChildPermissionResolver implements CacheLoader> { + private final boolean value; + + private ChildPermissionResolver(boolean value) { + this.value = value; + } + + @CheckForNull + @Override + public Map load(@Nonnull String key) { + Map children = new HashMap<>(); + resolveChildren(children, Collections.singletonMap(key, this.value), false); + children.remove(key, this.value); + return ImmutableMap.copyOf(children); + } + } + + private void resolveChildren(Map accumulator, Map children, boolean invert) { + // iterate through the current known children. + // the first time this method is called for a given permission, the children map will contain only the permission itself. + for (Map.Entry e : children.entrySet()) { + if (accumulator.containsKey(e.getKey())) { + continue; // Prevent infinite loops + } + + // xor the value using the parent (nukkit logic, not mine) + boolean value = e.getValue() ^ invert; + accumulator.put(e.getKey().toLowerCase(), value); + + // lookup any deeper children & resolve if present + Permission perm = this.delegate.get(e.getKey()); + if (perm != null) { + resolveChildren(accumulator, perm.getChildren(), !value); + } + } + } + +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java new file mode 100644 index 00000000..199cb70c --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/model/server/LPSubscriptionMap.java @@ -0,0 +1,259 @@ +/* + * 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.nukkit.model.server; + +import com.google.common.collect.Sets; + +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import cn.nukkit.permission.Permissible; +import cn.nukkit.plugin.PluginManager; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +/** + * A replacement map for the 'permSubs' instance in Nukkit's SimplePluginManager. + * + * This instance allows LuckPerms to intercept calls to + * {@link PluginManager#subscribeToPermission(String, Permissible)}, + * {@link PluginManager#unsubscribeFromPermission(String, Permissible)} and + * {@link PluginManager#getPermissionSubscriptions(String)}. + * + * Nukkit for some reason sometimes uses subscription status to determine whether + * a permissible has a given node, instead of checking directly with + * {@link Permissible#hasPermission(String)}. + * + * In order to implement predicable Nukkit behaviour, LP has two options: + * 1) register subscriptions for all players as normal, or + * 2) inject it's own map instance to proxy calls to {@link PluginManager#getPermissionSubscriptions(String)} back to LuckPerms. + * + * This class implements option 2 above. It is preferred because it is faster & uses less memory + * + * Injected by {@link InjectorSubscriptionMap}. + */ +public final class LPSubscriptionMap extends HashMap> { + + // the plugin instance + final LPNukkitPlugin plugin; + + public LPSubscriptionMap(LPNukkitPlugin plugin, Map> existingData) { + super(existingData); + this.plugin = plugin; + } + + /* + * The get method is the only one which is actually used by SimplePluginManager + * we override it to always return a value - which means the null check in + * subscribeToDefaultPerms always fails - soo, we don't have to worry too much + * about implementing #put. + * + * we also ensure all returns are LPSubscriptionValueMaps. this extension + * will also delegate checks to online players - meaning we don't ever + * have to register their subscriptions with the plugin manager. + */ + @Override + public Set get(Object key) { + if (key == null || !(key instanceof String)) { + return null; + } + + String permission = ((String) key); + + Set result = super.get(key); + + if (result == null) { + // calculate a new map - always! + result = new LPSubscriptionValueSet(permission); + super.put(permission, result); + } else if (!(result instanceof LPSubscriptionValueSet)) { + // ensure return type is a LPSubscriptionMap + result = new LPSubscriptionValueSet(permission, result); + super.put(permission, result); + } + return result; + } + + @Override + public Set put(String key, Set value) { + if (value == null) { + throw new NullPointerException("Map value cannot be null"); + } + + // ensure values are LP subscription maps + if (!(value instanceof LPSubscriptionValueSet)) { + value = new LPSubscriptionValueSet(key, value); + } + return super.put(key, value); + } + + // if the key isn't null and is a string, #get will always return a value for it + @Override + public boolean containsKey(Object key) { + return key != null && key instanceof String; + } + + /** + * Converts this map back to a standard HashMap + * + * @return a standard representation of this map + */ + public Map> detach() { + Map> ret = new HashMap<>(); + + for (Map.Entry> ent : entrySet()) { + if (ent.getValue() instanceof LPSubscriptionValueSet) { + Set backing = ((LPSubscriptionValueSet) ent.getValue()).backing; + Set copy; (copy = Collections.newSetFromMap(new WeakHashMap<>(backing.size()))).addAll(backing); + ret.put(ent.getKey(), copy); + } else { + ret.put(ent.getKey(), ent.getValue()); + } + } + + return ret; + } + + /** + * Value map extension which includes LP objects in Permissible related queries. + */ + public final class LPSubscriptionValueSet implements Set { + + // the permission being mapped to this value map + private final String permission; + + // the backing map + private final Set backing; + + private LPSubscriptionValueSet(String permission, Set content) { + this.permission = permission; + this.backing = Collections.newSetFromMap(new WeakHashMap<>()); + + if (content != null) { + this.backing.addAll(content); + } + } + + private LPSubscriptionValueSet(String permission) { + this(permission, null); + } + + private Sets.SetView getContentView() { + // gather players (LPPermissibles) + Set players = LPSubscriptionMap.this.plugin.getServer().getOnlinePlayers().values().stream() + .filter(player -> player.isPermissionSet(this.permission)) + .collect(Collectors.toSet()); + + return Sets.union(players, this.backing); + } + + @Override + public boolean contains(Object key) { + // try the backing map + if (this.backing.contains(key)) { + return true; + } + + // then try the permissible + if (key instanceof Permissible) { + Permissible p = (Permissible) key; + return p.isPermissionSet(this.permission); + } + + // no result + return false; + } + + + @Override + public boolean isEmpty() { + // we never want to remove this map from the parent - since it just gets recreated + // on subsequent calls + return false; + } + + @Override + public int size() { + return Math.max(1, this.backing.size()); + } + + @Override + public Iterator iterator() { + return getContentView().iterator(); + } + + @Override + public Object[] toArray() { + return getContentView().toArray(); + } + + @Override + public T[] toArray(T[] a) { + return getContentView().toArray(a); + } + + @Override + public boolean add(Permissible permissible) { + return this.backing.add(permissible); + } + + @Override + public boolean addAll(Collection c) { + return this.backing.addAll(c); + } + + @Override + public boolean remove(Object o) { + return this.backing.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return getContentView().containsAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return this.backing.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return this.backing.removeAll(c); + } + + @Override + public void clear() { + this.backing.clear(); + } + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java new file mode 100644 index 00000000..9e2a658a --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/ChildProcessor.java @@ -0,0 +1,64 @@ +/* + * 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.nukkit.processors; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.common.processors.AbstractPermissionProcessor; +import me.lucko.luckperms.common.processors.PermissionProcessor; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Permission Processor for Nukkits "child" permission system. + */ +public class ChildProcessor extends AbstractPermissionProcessor implements PermissionProcessor { + private final LPNukkitPlugin plugin; + private Map childPermissions = Collections.emptyMap(); + + public ChildProcessor(LPNukkitPlugin plugin) { + this.plugin = plugin; + } + + @Override + public Tristate hasPermission(String permission) { + return Tristate.fromNullableBoolean(this.childPermissions.get(permission)); + } + + @Override + public void refresh() { + Map builder = new ConcurrentHashMap<>(); + for (Map.Entry e : this.sourceMap.entrySet()) { + Map children = this.plugin.getPermissionMap().getChildPermissions(e.getKey(), e.getValue()); + if (children != null) { + builder.putAll(children); + } + } + this.childPermissions = builder; + } +} diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java new file mode 100644 index 00000000..423191c5 --- /dev/null +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/processors/DefaultsProcessor.java @@ -0,0 +1,55 @@ +/* + * 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.nukkit.processors; + +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.common.processors.PermissionProcessor; +import me.lucko.luckperms.nukkit.LPNukkitPlugin; +import me.lucko.luckperms.nukkit.model.PermissionDefault; + +/** + * Permission Processor for Nukkits "default" permission system. + */ +public class DefaultsProcessor implements PermissionProcessor { + private final LPNukkitPlugin plugin; + private final boolean isOp; + + public DefaultsProcessor(LPNukkitPlugin plugin, boolean isOp) { + this.plugin = plugin; + this.isOp = isOp; + } + + @Override + public Tristate hasPermission(String permission) { + Tristate t = this.plugin.getDefaultPermissionMap().lookupDefaultPermission(permission, this.isOp); + if (t != Tristate.UNDEFINED) { + return t; + } + + PermissionDefault def = PermissionDefault.fromPermission(this.plugin.getPermissionMap().get(permission)); + return def == null ? Tristate.UNDEFINED : Tristate.fromBoolean(def.getValue(this.isOp)); + } +} diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml new file mode 100644 index 00000000..06884474 --- /dev/null +++ b/nukkit/src/main/resources/config.yml @@ -0,0 +1,459 @@ +#################################################################################################### +# +----------------------------------------------------------------------------------------------+ # +# | __ __ ___ __ __ | # +# | | | | / ` |__/ |__) |__ |__) |\/| /__` | # +# | |___ \__/ \__, | \ | |___ | \ | | .__/ | # +# | | # +# | | # +# | SOURCE CODE: https://github.com/lucko/LuckPerms | # +# | WIKI: https://github.com/lucko/LuckPerms/wiki | # +# | BUG REPORTS: https://github.com/lucko/LuckPerms/issues | # +# | | # +# | Each option in this file is documented and explained here: | # +# | ==> https://github.com/lucko/LuckPerms/wiki/Configuration | # +# | | # +# | New options are not added to this file automatically. Default values are used if an | # +# | option cannot be found. The latest config versions can be obtained at the link above. | # +# +----------------------------------------------------------------------------------------------+ # +#################################################################################################### + +# +----------------------------------------------------------------------------------------------+ # +# | General | # +# +----------------------------------------------------------------------------------------------+ # + +# The name of the server, used for server specific permissions. Set to 'global' to disable. +server: global + +# If users on this server should have their global permissions applied. +# If set to false, only server specific permissions will apply for users on this server +include-global: true + +# If users on this server should have their global world permissions applied. +# If set to false, only world specific permissions will apply for users on this server +include-global-world: true + +# If users on this server should have global (non-server specific) groups applied +apply-global-groups: true + +# If users on this server should have global (non-world specific) groups applied +apply-global-world-groups: true + +# If the servers own UUID cache/lookup facility should be used when there is no record for a player +# in the LuckPerms cache. +use-server-uuid-cache: false + +# If LuckPerms should use the "server-name" property from the "server.properties" +# file as the "server" option within LuckPerms. +use-server-properties-name: false + +# If set to true, LuckPerms will allow usernames with non alphanumeric characters. +# +# Note that due to the design of the storage implementation, usernames must still be +# 16 characters or less. +allow-invalid-usernames: false + +# If LuckPerms should produce extra logging output when it handles logins. +# Useful if you're having issues with UUID forwarding or data not being loaded. +debug-logins: false + +# If the plugin should send log notifications to users whenever permissions are modified. +log-notify: true + +# Mirrors world names. Whenever LuckPerms checks what world a user is in, if the world name is in +# this list, the value assigned will be sent forward for permission calculation instead. +world-rewrite: +# world_nether: world +# world_the_end: world + +# Controls how temporary permissions/parents/meta should be accumulated +# +# The default behaviour is "deny" +# If "accumulate": durations will be added to the existing expiry time +# If "replace": durations will be replaced if the new duration is later than the current expiration +# If "deny": the command will just fail if you try to add another node with the same expiry +temporary-add-behaviour: deny + +# How should LuckPerms determine a users "primary" group. +# +# Available Options: +# -> stored use the value stored against the users record in the file/database +# -> parents-by-weight just use the users most highly weighted parent +# -> all-parents-by-weight same as above, but calculates based upon all parents inherited from both +# directly and indirectly +primary-group-calculation: parents-by-weight + +# If set to false, the plugin will allow a Users primary group to be removed with the +# 'parent remove' command, and will set their primary group back to default. +prevent-primary-group-removal: false + +# If the plugin should check for "extra" permissions with users run LP commands. +# +# These extra permissions allow finer control over what users can do with each command, and +# who they have access to edit. +# +# The permissions are *not* static, unlike the 'base' permisssions, and will depend upon the +# arguments given within the command. +argument-based-command-permissions: false + + + + +# +----------------------------------------------------------------------------------------------+ # +# | Permission Calculation | # +# +----------------------------------------------------------------------------------------------+ # + +# If the plugin should apply wildcard permissions. +# If set to true, LuckPerms will detect wildcard permissions, and resolve & apply all registered +# permissions matching the wildcard. +apply-wildcards: true + +# If the plugin should parse regex permissions. +# If set to true, LuckPerms will detect regex permissions, marked with "r=" at the start of the +# node, and resolve & apply all registered permissions matching the regex. +apply-regex: true + +# If the plugin should complete and apply shorthand permissions. +# If set to true, LuckPerms will detect and expand shorthand node patterns. +apply-shorthand: true + +# If the plugin should apply Nukkit child permissions. +# Plugin authors can define custom permissions structures for their plugin, which will be resolved +# and used by LuckPerms if this setting is enabled. +apply-nukkit-child-permissions: true + +# If the plugin should apply Nukkit default permissions. +# Plugin authors can define permissions which should be given to all users by default, or setup +# permissions which should/shouldn't be given to opped players. +# If this option is set to false, LuckPerms will ignore these defaults. +apply-nukkit-default-permissions: true + +# If the plugin should apply attachment permissions. +# Other plugins on the server are able to add their own "permission attachments" to players. This +# allows them to grant players additional permissions which last until the end of the session, or +# until they're removed. If this option is set to false, LuckPerms will not include these attachment +# permissions when considering if a player should have access to a certain permission. +apply-nukkit-attachment-permissions: true + +# The algorithm LuckPerms should use when traversing the "inheritance tree". +# +# The valid options are: +# - breadth-first +# - depth-first-pre-order +# - depth-first-post-order +# +# See here for information about the differences between each algorithm. +# - https://en.wikipedia.org/wiki/Breadth-first_search +# - https://en.wikipedia.org/wiki/Depth-first_search +inheritance-traversal-algorithm: depth-first-pre-order + +# Define special group weights for this server. +# Default is just 0. +group-weight: +# admin: 10 + + + + +# +----------------------------------------------------------------------------------------------+ # +# | Meta Formatting & Stacking | # +# +----------------------------------------------------------------------------------------------+ # + +# Allows you to setup prefix/suffix stacking options. +# +# Available formats: +# - highest +# - lowest +# - highest_own +# - lowest_own +# - highest_inherited +# - lowest_inherited +# - highest_on_track_ +# - lowest_on_track_ +# - highest_not_on_track_ +# - lowest_not_on_track_ +# +# Each element is added in the order listed. +meta-formatting: + prefix: + format: + - "highest" + start-spacer: "" + middle-spacer: " " + end-spacer: "" + suffix: + format: + - "highest" + start-spacer: "" + middle-spacer: " " + end-spacer: "" + + + + +# +----------------------------------------------------------------------------------------------+ # +# | OP (Server Operator) Settings | # +# +----------------------------------------------------------------------------------------------+ # + +# If the vanilla OP system is enabled. If set to false, all users will be de-opped, and the op/deop +# commands will be disabled. +enable-ops: true + +# If set to true, any user with the permission "luckperms.autoop" will automatically be granted +# server operator status. This permission can be inherited, or set on specific servers/worlds, +# temporarily, etc. +# +# Additionally, setting this to true will force the "enable-ops" option above to false. All users +# will be de-opped unless they have the permission node, and the op/deop commands will be disabled. +# +# It is important to note that this setting is only checked when a player first joins the server, +# and when they switch worlds. Therefore, simply removing this permission from a user will not +# automatically de-op them. A player needs to relog to have the change take effect. +# +# It is recommended that you use this option instead of assigning a single '*' permission. +auto-op: false + +# If opped players should be allowed to use LuckPerms commands. Set to false to only allow users who +# have the permissions access to the commands +commands-allow-op: true + + + + +# +----------------------------------------------------------------------------------------------+ # +# | Vault | # +# +----------------------------------------------------------------------------------------------+ # + +# If the vault-server option below should be used. +# When this option is set to false, the server value defined above under "server" is used. +use-vault-server: false + +# The name of the server used within Vault operations. If you don't want Vault operations to be +# server specific, set this to "global". +# +# Will only take effect if use-vault-server is set to true above. +vault-server: global + +# If global permissions should be considered when retrieving meta or player groups +vault-include-global: true + +# If Vault operations should ignore any world arguments if supplied. +vault-ignore-world: false + +# If LuckPerms should print debugging info to console when a plugin uses a Vault function +vault-debug: false + + + + +# +----------------------------------------------------------------------------------------------+ # +# | Storage | # +# +----------------------------------------------------------------------------------------------+ # + +# Which storage method the plugin should use. +# +# See: https://github.com/lucko/LuckPerms/wiki/Choosing-a-Storage-type +# Currently supported: mysql, mariadb, postgresql, sqlite, h2, json, yaml, hocon, mongodb +# +# Fill out connection info below if you're using MySQL, MariaDB, PostgreSQL or MongoDB +# If your MySQL server supports it, the "mariadb" option is preferred over "mysql". +storage-method: h2 + +# When using a file-based storage type, LuckPerms can monitor the data files for changes, and then +# schedule automatic updates when changes are detected. +# +# If you don't want this to happen, set this option to false. +watch-files: true + +# This block enables support for split datastores. +split-storage: + enabled: false + methods: + user: h2 + group: h2 + track: h2 + uuid: h2 + log: h2 + +data: + # Uses standard DB engine port by default + # MySQL: 3306, PostgreSQL: 5432, MongoDB: 27017 + # Specify as "host:port" if differs + address: localhost + + database: minecraft + username: root + password: '' + + # These settings apply to the MySQL connection pool. + # The default values will be suitable for the majority of users. + # Do not change these settings unless you know what you're doing! + pool-settings: + + # Sets the maximum size of the MySQL connection pool. + # Basically this value will determine the maximum number of actual + # connections to the database backend. + # + # More information about determining the size of connection pools can be found here: + # https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing + maximum-pool-size: 10 + + # Sets the minimum number of idle connections that the pool will try to maintain. + # + # For maximum performance and responsiveness to spike demands, it is recommended to not set + # this value and instead allow the pool to act as a fixed size connection pool. + # (set this value to the same as 'maximum-pool-size') + minimum-idle: 10 + + # This setting controls the maximum lifetime of a connection in the pool in milliseconds. + # The value should be at least 30 seconds less than any database or infrastructure imposed + # connection time limit. + maximum-lifetime: 1800000 # 30 minutes + + # This setting controls the maximum number of milliseconds that the plugin will wait for a + # connection from the pool, before timing out. + connection-timeout: 5000 # 5 seconds + + # This setting allows you to define extra properties for connections. + properties: + useUnicode: true + characterEncoding: utf8 + + # The prefix for all LuckPerms tables. Change this is you want to use different tables for + # different servers. + # + # This should *not* be set to "lp_" if you have previously ran LuckPerms v2.16.81 or earlier with + # this database. + table_prefix: 'luckperms_' + + # The prefix to use for all LuckPerms collections. Change this if you want to use different + # collections for different servers. The default is no prefix. + mongodb_collection_prefix: '' + + # This option controls how frequently LuckPerms will perform a sync task. + # A sync task will refresh all data from the storage, and ensure that the most up-to-date data is + # being used by the plugin. + # + # This is disabled by default, as most users will not need it. However, if you're using a remote + # storage type without a messaging service setup, you may wish to set this value to something like + # 3. + # + # Set to -1 to disable the task completely. + sync-minutes: -1 + +# Settings for the messaging service +# +# If enabled and configured, LuckPerms will use the messaging system to inform other +# connected servers of changes. Use the command "/luckperms networksync" to push changes. +# Data is NOT stored using this service. It is only used as a messaging platform. +# +# If you decide to enable this feature, you should set "sync-minutes" to -1, as there is no need for +# LuckPerms to poll the database for changes. +# +# Available options: +# -> bungee uses the plugin messaging channels. Must be enabled on all connected servers to work. +# -> lilypad uses lilypad pub sub to push changes. You need to have the LilyPad-Connect plugin +# installed. +# -> redis uses redis pub sub to push changes. Your redis server must be configured below. +# -> none nothing +messaging-service: none + +# If LuckPerms should automatically push updates after a change has been made with a command. +auto-push-updates: true + +# If LuckPerms should push logging entries to connected servers via the messaging service. +push-log-entries: true + +# If LuckPerms should broadcast received logging entries to players on this platform. +# +# If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you should +# set this option to false on either your backends or your proxies, to avoid players being messaged +# twice about log entries. +broadcast-received-log-entries: true + +# Settings for Redis. +# Port 6379 is used by default; set address to "host:port" if differs +redis: + enabled: false + address: localhost + password: '' + + + + +# +----------------------------------------------------------------------------------------------+ # +# | Default Assignments | # +# +----------------------------------------------------------------------------------------------+ # + +# This section allows you to define defaults to give users whenever they connect to the server. +# The default assignments are highly configurable and conditional. +# +# There is one default assignment built into LuckPerms, which will add all users to the "default" +# group if they are not a member of any other group. This setting cannot be disabled. However, you +# can use this section to add more of your own. +# +# IMPORTANT: +# In order to save storage space, LuckPerms does not store users who have no permissions defined, +# and are only a member of the default group. Adding default assignments to this section will negate +# this effect. It is HIGHLY RECCOMENDED that instead of assigning defaults here, you add permissions +# to the "default" group, or set the "default" group to inherit other groups, and then use the +# group-name-rewrite rule above. +# +# It is also important to note that these rules are considered every time a player logs into the +# server, and are applied directly to the user's data. Simply removing a rule here will not reverse +# the effect of that rule on any users who have already had it applied to them. +# +# The "has" and "lacks" conditions below support standard boolean logic, using the 'and' & 'or' +# characters used in Java. +# e.g. "(some.other.permission | some.permission.other) & some.thing.else" == a user has +# 'some.other.permission', or 'some.permission.other', and they also have 'some.thing.else' +# +# Groups are represented by the permission node: group. +# Per server and per world nodes are represented by "server-world/permission" or "server/permission" +# +# Within conditions, permission nodes MUST be escaped using "<" and ">". See the example below. +# +# Explanation of the examples below: (they're just to demonstrate the features & use cases) +# +# rule1: +# If a user is either in the vip or vip+ group, and they have the "titles.titlecollector" permission +# set to true, and the "some.random.permission" set to false... if they're not in the group +# "prison_titlepack" on the "prison" server, then give add them to the "prison_titlepack" group on +# the "prison" server, and remove "some.random.permission". +# +# rule2: +# If the user isn't in any of the following groups on the skyblock server: sb_level1, sb_level2, +# sb_level3, then add them to sb_level1 on the skyblock server. +# +# rule3: +# If the user is a member of the default group, remove them from default, add them to member, and +# set their primary group to member. +# +# WARNING: Unlike internal commands, this system does not ensure that a group exists before adding +# a user to it. It also does not unsure that a user is a member of a group before making that group +# their primary group. +# +# Before you use "give: group." or "set-primary-group", make sure that the group exists, and +# that the user is a member of the group. +default-assignments: +# rule1: +# if: +# has-true: ( | ) & +# has-false: +# lacks: +# give: +# - prison/group.prison_titlepack +# take: +# - some.random.permission +# rule2: +# if: +# lacks: & & +# give: +# - skyblock/group.sb_level1 +# rule3: +# if: +# has-true: +# take: +# - group.default +# give: +# - group.member +# set-primary-group: member \ No newline at end of file diff --git a/nukkit/src/main/resources/plugin.yml b/nukkit/src/main/resources/plugin.yml new file mode 100644 index 00000000..0b84a5cd --- /dev/null +++ b/nukkit/src/main/resources/plugin.yml @@ -0,0 +1,14 @@ +name: LuckPerms +version: ${full.version} +api: ["1.0.5"] +description: A permissions plugin +author: Luck +website: https://github.com/lucko/LuckPerms + +main: me.lucko.luckperms.nukkit.LPNukkitPlugin +load: STARTUP + +commands: + luckperms: + description: Manage permissions + aliases: [lp, perm, perms, permission, permissions] diff --git a/pom.xml b/pom.xml index a1721b84..9ffdde9a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ sponge/sponge-service-api6 sponge/sponge-service-api7 sponge + nukkit LuckPerms @@ -152,14 +153,6 @@ luck-repo https://repo.lucko.me/ - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - sponge-repo - https://repo.spongepowered.org/maven - diff --git a/sponge/pom.xml b/sponge/pom.xml index c6d786bb..0e9e33cb 100644 --- a/sponge/pom.xml +++ b/sponge/pom.xml @@ -157,4 +157,11 @@ + + + sponge-repo + https://repo.spongepowered.org/maven + + + diff --git a/sponge/sponge-service-api6/pom.xml b/sponge/sponge-service-api6/pom.xml index 7a035024..a2cd2460 100644 --- a/sponge/sponge-service-api6/pom.xml +++ b/sponge/sponge-service-api6/pom.xml @@ -54,4 +54,11 @@ + + + sponge-repo + https://repo.spongepowered.org/maven + + + diff --git a/sponge/sponge-service-api7/pom.xml b/sponge/sponge-service-api7/pom.xml index 7b60fbae..6576f466 100644 --- a/sponge/sponge-service-api7/pom.xml +++ b/sponge/sponge-service-api7/pom.xml @@ -54,4 +54,11 @@ + + + sponge-repo + https://repo.spongepowered.org/maven + + + diff --git a/sponge/sponge-service/pom.xml b/sponge/sponge-service/pom.xml index 8cea30fb..ba12e90d 100644 --- a/sponge/sponge-service/pom.xml +++ b/sponge/sponge-service/pom.xml @@ -55,4 +55,11 @@ + + + sponge-repo + https://repo.spongepowered.org/maven + + +