diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java index 384c8fe69..e70a1b9b5 100644 --- a/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/LPFabricPlugin.java @@ -42,18 +42,23 @@ import me.lucko.luckperms.common.sender.DummyConsoleSender; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.fabric.context.FabricContextManager; import me.lucko.luckperms.fabric.context.FabricPlayerCalculator; -import me.lucko.luckperms.fabric.listeners.FabricConnectionListener; -import me.lucko.luckperms.fabric.listeners.PermissionCheckListener; +import me.lucko.luckperms.fabric.listeners.FabricAutoOpListener; import me.lucko.luckperms.fabric.listeners.FabricCommandListUpdater; +import me.lucko.luckperms.fabric.listeners.FabricConnectionListener; +import me.lucko.luckperms.fabric.listeners.FabricOtherListeners; +import me.lucko.luckperms.fabric.listeners.PermissionCheckListener; import me.lucko.luckperms.fabric.messaging.FabricMessagingFactory; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.ModContainer; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.luckperms.api.LuckPerms; import net.luckperms.api.query.QueryOptions; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.OperatorList; +import java.io.IOException; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; @@ -88,6 +93,8 @@ public class LPFabricPlugin extends AbstractLuckPermsPlugin { // Command registration also need to occur early, and will persist across game states as well. this.commandManager = new FabricCommandExecutor(this); this.commandManager.register(); + + new FabricOtherListeners(this).registerListeners(); } @Override @@ -160,6 +167,24 @@ public class LPFabricPlugin extends AbstractLuckPermsPlugin { @Override protected void performFinalSetup() { + // remove all operators on startup if they're disabled + if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) { + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + OperatorList operatorList = server.getPlayerManager().getOpList(); + operatorList.values().clear(); + try { + operatorList.save(); + } catch (IOException exception) { + exception.printStackTrace(); + } + }); + } + + // register autoop listener + if (getConfiguration().get(ConfigKeys.AUTO_OP)) { + getApiProvider().getEventBus().subscribe(new FabricAutoOpListener(this)); + } + // register fabric command list updater if (getConfiguration().get(ConfigKeys.UPDATE_CLIENT_COMMAND_LIST)) { getApiProvider().getEventBus().subscribe(new FabricCommandListUpdater(this)); diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/event/PreExecuteCommandCallback.java b/fabric/src/main/java/me/lucko/luckperms/fabric/event/PreExecuteCommandCallback.java new file mode 100644 index 000000000..ccb516d84 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/event/PreExecuteCommandCallback.java @@ -0,0 +1,43 @@ +/* + * 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.fabric.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.command.ServerCommandSource; + +public interface PreExecuteCommandCallback { + Event EVENT = EventFactory.createArrayBacked(PreExecuteCommandCallback.class, listeners -> (source, input) -> { + for (PreExecuteCommandCallback listener : listeners) { + if (!listener.onPreExecuteCommand(source, input)) { + return false; + } + } + return true; + }); + + boolean onPreExecuteCommand(ServerCommandSource source, String input); +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricAutoOpListener.java b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricAutoOpListener.java new file mode 100644 index 000000000..90ff6ffdf --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricAutoOpListener.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.fabric.listeners; + +import me.lucko.luckperms.common.api.implementation.ApiUser; +import me.lucko.luckperms.common.event.LuckPermsEventListener; +import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.fabric.LPFabricPlugin; + +import net.luckperms.api.event.EventBus; +import net.luckperms.api.event.context.ContextUpdateEvent; +import net.luckperms.api.event.user.UserDataRecalculateEvent; +import net.luckperms.api.query.QueryOptions; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.Map; + +public class FabricAutoOpListener implements LuckPermsEventListener { + private static final String NODE = "luckperms.autoop"; + + private final LPFabricPlugin plugin; + + public FabricAutoOpListener(LPFabricPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void bind(EventBus bus) { + bus.subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate); + bus.subscribe(ContextUpdateEvent.class, this::onContextUpdate); + } + + private void onUserDataRecalculate(UserDataRecalculateEvent e) { + User user = ApiUser.cast(e.getUser()); + this.plugin.getBootstrap().getPlayer(user.getUniqueId()).ifPresent(p -> refreshAutoOp(p, false)); + } + + private void onContextUpdate(ContextUpdateEvent e) { + e.getSubject(ServerPlayerEntity.class).ifPresent(p -> refreshAutoOp(p, true)); + } + + private void refreshAutoOp(ServerPlayerEntity player, boolean callerIsSync) { + if (!callerIsSync && !this.plugin.getBootstrap().getServer().isPresent()) { + return; + } + + User user = this.plugin.getUserManager().getIfLoaded(player.getUuid()); + + boolean value; + if (user != null) { + QueryOptions queryOptions = this.plugin.getContextManager().getQueryOptions(player); + Map permData = user.getCachedData().getPermissionData(queryOptions).getPermissionMap(); + value = permData.getOrDefault(NODE, false); + } else { + value = false; + } + + if (callerIsSync) { + setOp(player, value); + } else { + this.plugin.getBootstrap().getScheduler().executeSync(() -> setOp(player, value)); + } + } + + private void setOp(ServerPlayerEntity player, boolean value) { + this.plugin.getBootstrap().getServer().ifPresent(server -> { + if (value) { + server.getPlayerManager().addToOperators(player.getGameProfile()); + } else { + server.getPlayerManager().removeFromOperators(player.getGameProfile()); + } + }); + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricOtherListeners.java b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricOtherListeners.java new file mode 100644 index 000000000..96f854bae --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/listeners/FabricOtherListeners.java @@ -0,0 +1,66 @@ +/* + * 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.fabric.listeners; + +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.Message; +import me.lucko.luckperms.fabric.LPFabricPlugin; +import me.lucko.luckperms.fabric.event.PreExecuteCommandCallback; + +import net.minecraft.server.command.ServerCommandSource; + +import java.util.regex.Pattern; + +public class FabricOtherListeners { + private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(deop|op)( .*)?$"); + + private LPFabricPlugin plugin; + + public FabricOtherListeners(LPFabricPlugin plugin) { + this.plugin = plugin; + } + + public void registerListeners() { + PreExecuteCommandCallback.EVENT.register(this::onPreExecuteCommand); + } + + private boolean onPreExecuteCommand(ServerCommandSource source, String input) { + if (input.isEmpty()) { + return true; + } + + if (this.plugin.getConfiguration().get(ConfigKeys.OPS_ENABLED)) { + return true; + } + + if (OP_COMMAND_PATTERN.matcher(input).matches()) { + Message.OP_DISABLED.send(this.plugin.getSenderFactory().wrap(source)); + return false; + } + + return true; + } +} diff --git a/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/CommandManagerMixin.java b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/CommandManagerMixin.java new file mode 100644 index 000000000..4ff389685 --- /dev/null +++ b/fabric/src/main/java/me/lucko/luckperms/fabric/mixin/CommandManagerMixin.java @@ -0,0 +1,45 @@ +/* + * 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.fabric.mixin; + +import me.lucko.luckperms.fabric.event.PreExecuteCommandCallback; + +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CommandManager.class) +public class CommandManagerMixin { + @Inject(at = @At("HEAD"), method = "execute", cancellable = true) + private void commandExecuteCallback(ServerCommandSource source, String input, CallbackInfoReturnable info) { + if (!PreExecuteCommandCallback.EVENT.invoker().onPreExecuteCommand(source, input)) { + info.setReturnValue(0); + } + } +} diff --git a/fabric/src/main/resources/luckperms.conf b/fabric/src/main/resources/luckperms.conf index 3b03682a7..f8769ebc8 100644 --- a/fabric/src/main/resources/luckperms.conf +++ b/fabric/src/main/resources/luckperms.conf @@ -513,7 +513,7 @@ apply-wildcards = true # - That being: If a user has been granted "example", then the player should have also be # automatically granted "example.function", "example.another", "example.deeper.nesting", # and so on. -apply-sponge-implicit-wildcards=true +apply-sponge-implicit-wildcards = true # If the plugin should parse regex permissions. # @@ -565,6 +565,29 @@ group-weight { # | | # # +----------------------------------------------------------------------------------------------+ # +# +----------------------------------------------------------------------------------------------+ # +# | Server Operator (OP) settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Controls whether server operators should exist at all. +# +# - When set to 'false', all players will be de-opped, and the /op and /deop commands will be +# disabled. +enable-ops = true + +# Enables or disables a special permission based system in LuckPerms for controlling OP status. +# +# - 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 recommended that you use this option instead of assigning a single '*' permission. +# - However, on Fabric this setting can be used as a "pseudo" root wildcard, as many mods support +# the operator system over permissions. +auto-op = false + # +----------------------------------------------------------------------------------------------+ # # | Miscellaneous (and rarely used) settings | # # +----------------------------------------------------------------------------------------------+ # @@ -593,6 +616,9 @@ skip-bulkupdate-confirmation = false # - When this happens, the plugin will set their primary group back to default. prevent-primary-group-removal = false +# If LuckPerms should update the list of commands sent to the client when permissions are changed. +update-client-command-list = true + # If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. # See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors resolve-command-selectors = false diff --git a/fabric/src/main/resources/luckperms.mixins.json b/fabric/src/main/resources/luckperms.mixins.json index b83dfbca7..392d4dac7 100644 --- a/fabric/src/main/resources/luckperms.mixins.json +++ b/fabric/src/main/resources/luckperms.mixins.json @@ -5,11 +5,12 @@ "mixins": [ "ClientSettingsC2SPacketAccessor", "ServerLoginNetworkHandlerAccessor", - "ServerPlayerEntityMixin" + "ServerPlayerEntityMixin", + "CommandManagerMixin" ], "client": [ ], "injectors": { "defaultRequire": 1 } -} \ No newline at end of file +}