mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-06-17 02:40:06 +00:00
Use permission APIs for the /computercraft command
- Add a generic PermissionRegistry interface. This behaves similarly to our ShaderMod interface, searching all providers until it finds a compatible one. We could just make this part of the platform code instead, but this allows us to support multiple systems on Fabric, where things are less standardised. This interface behaves like a registry, rather than a straight `getPermission(node, player)` method, as Forge requires us to list our nodes up-front. - Add Forge (using the built-in system) and Fabric (using fabric-permissions-api) implementations of the above interface. - Register permission nodes for our commands, and use those instead. This does mean that the permissions check for the root /computercraft command now requires enumerating all child commands (and so potential does 7 permission lookups), but hopefully this isn't too bad in practice. - Remove UserLevel.OWNER - we never used this anywhere, and I can't imagine we'll want to in the future.
This commit is contained in:
parent
5f8b1dd67f
commit
b3738a7a63
|
@ -66,6 +66,7 @@ repositories {
|
||||||
includeGroup("me.shedaniel")
|
includeGroup("me.shedaniel")
|
||||||
includeGroup("mezz.jei")
|
includeGroup("mezz.jei")
|
||||||
includeModule("com.terraformersmc", "modmenu")
|
includeModule("com.terraformersmc", "modmenu")
|
||||||
|
includeModule("me.lucko", "fabric-permissions-api")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ slf4j = "1.7.36"
|
||||||
|
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
emi = "1.0.8+1.19.4"
|
emi = "1.0.8+1.19.4"
|
||||||
|
fabricPermissions = "0.2.20221016"
|
||||||
iris = "1.5.2+1.19.4"
|
iris = "1.5.2+1.19.4"
|
||||||
jei = "13.1.0.11"
|
jei = "13.1.0.11"
|
||||||
modmenu = "6.1.0-rc.1"
|
modmenu = "6.1.0-rc.1"
|
||||||
|
@ -93,6 +94,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||||
|
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||||
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
||||||
|
@ -154,7 +156,7 @@ externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-forge-compile = ["oculus", "jei-api"]
|
externalMods-forge-compile = ["oculus", "jei-api"]
|
||||||
externalMods-forge-runtime = ["jei-forge"]
|
externalMods-forge-runtime = ["jei-forge"]
|
||||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
|
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
|
||||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||||
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
|
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||||
import dan200.computercraft.shared.details.BlockDetails;
|
import dan200.computercraft.shared.details.BlockDetails;
|
||||||
import dan200.computercraft.shared.details.ItemDetails;
|
import dan200.computercraft.shared.details.ItemDetails;
|
||||||
|
import dan200.computercraft.shared.integration.PermissionRegistry;
|
||||||
import dan200.computercraft.shared.media.items.DiskItem;
|
import dan200.computercraft.shared.media.items.DiskItem;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
import dan200.computercraft.shared.media.items.RecordMedia;
|
import dan200.computercraft.shared.media.items.RecordMedia;
|
||||||
|
@ -77,6 +79,7 @@
|
||||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||||
import dan200.computercraft.shared.util.ImpostorRecipe;
|
import dan200.computercraft.shared.util.ImpostorRecipe;
|
||||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
|
@ -98,6 +101,7 @@
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
|
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
|
||||||
|
@ -366,6 +370,18 @@ private static <T extends CustomRecipe> RegistryEntry<SimpleCraftingRecipeSerial
|
||||||
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
|
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Permissions {
|
||||||
|
static final PermissionRegistry REGISTRY = PermissionRegistry.create();
|
||||||
|
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_DUMP = REGISTRY.registerCommand("dump", UserLevel.OWNER_OP);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_SHUTDOWN = REGISTRY.registerCommand("shutdown", UserLevel.OWNER_OP);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_TURN_ON = REGISTRY.registerCommand("turn_on", UserLevel.OWNER_OP);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_TP = REGISTRY.registerCommand("tp", UserLevel.OP);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_TRACK = REGISTRY.registerCommand("track", UserLevel.OWNER_OP);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_QUEUE = REGISTRY.registerCommand("queue", UserLevel.ANYONE);
|
||||||
|
public static final Predicate<CommandSourceStack> PERMISSION_VIEW = REGISTRY.registerCommand("view", UserLevel.OP);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any objects which don't have to be done on the main thread.
|
* Register any objects which don't have to be done on the main thread.
|
||||||
*/
|
*/
|
||||||
|
@ -379,6 +395,7 @@ public static void register() {
|
||||||
ArgumentTypes.REGISTRY.register();
|
ArgumentTypes.REGISTRY.register();
|
||||||
LootItemConditionTypes.REGISTRY.register();
|
LootItemConditionTypes.REGISTRY.register();
|
||||||
RecipeSerializers.REGISTRY.register();
|
RecipeSerializers.REGISTRY.register();
|
||||||
|
Permissions.REGISTRY.register();
|
||||||
|
|
||||||
// Register bundled power providers
|
// Register bundled power providers
|
||||||
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
|
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import com.mojang.brigadier.suggestion.Suggestions;
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
|
@ -60,7 +61,7 @@ private CommandComputerCraft() {
|
||||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
dispatcher.register(choice("computercraft")
|
dispatcher.register(choice("computercraft")
|
||||||
.then(literal("dump")
|
.then(literal("dump")
|
||||||
.requires(UserLevel.OWNER_OP)
|
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
|
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
|
||||||
|
|
||||||
|
@ -118,7 +119,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
})))
|
})))
|
||||||
|
|
||||||
.then(command("shutdown")
|
.then(command("shutdown")
|
||||||
.requires(UserLevel.OWNER_OP)
|
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
|
||||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
||||||
.executes((context, computerSelectors) -> {
|
.executes((context, computerSelectors) -> {
|
||||||
var shutdown = 0;
|
var shutdown = 0;
|
||||||
|
@ -132,7 +133,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("turn-on")
|
.then(command("turn-on")
|
||||||
.requires(UserLevel.OWNER_OP)
|
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
|
||||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
||||||
.executes((context, computerSelectors) -> {
|
.executes((context, computerSelectors) -> {
|
||||||
var on = 0;
|
var on = 0;
|
||||||
|
@ -146,7 +147,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("tp")
|
.then(command("tp")
|
||||||
.requires(UserLevel.OP)
|
.requires(ModRegistry.Permissions.PERMISSION_TP)
|
||||||
.arg("computer", oneComputer())
|
.arg("computer", oneComputer())
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
var computer = getComputerArgument(context, "computer");
|
var computer = getComputerArgument(context, "computer");
|
||||||
|
@ -171,7 +172,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("queue")
|
.then(command("queue")
|
||||||
.requires(UserLevel.ANYONE)
|
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
|
||||||
.arg(
|
.arg(
|
||||||
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
||||||
.suggests((context, builder) -> Suggestions.empty())
|
.suggests((context, builder) -> Suggestions.empty())
|
||||||
|
@ -193,7 +194,7 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("view")
|
.then(command("view")
|
||||||
.requires(UserLevel.OP)
|
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
|
||||||
.arg("computer", oneComputer())
|
.arg("computer", oneComputer())
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
var player = context.getSource().getPlayerOrException();
|
var player = context.getSource().getPlayerOrException();
|
||||||
|
@ -213,8 +214,8 @@ public AbstractContainerMenu createMenu(int id, Inventory player, Player entity)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(choice("track")
|
.then(choice("track")
|
||||||
|
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
|
||||||
.then(command("start")
|
.then(command("start")
|
||||||
.requires(UserLevel.OWNER_OP)
|
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
getMetricsInstance(context.getSource()).start();
|
getMetricsInstance(context.getSource()).start();
|
||||||
|
|
||||||
|
@ -227,7 +228,6 @@ public AbstractContainerMenu createMenu(int id, Inventory player, Player entity)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("stop")
|
.then(command("stop")
|
||||||
.requires(UserLevel.OWNER_OP)
|
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
var timings = getMetricsInstance(context.getSource());
|
var timings = getMetricsInstance(context.getSource());
|
||||||
if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
|
if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
|
||||||
|
@ -236,7 +236,6 @@ public AbstractContainerMenu createMenu(int id, Inventory player, Player entity)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
.then(command("dump")
|
.then(command("dump")
|
||||||
.requires(UserLevel.OWNER_OP)
|
|
||||||
.argManyValue("fields", metric(), DEFAULT_FIELDS)
|
.argManyValue("fields", metric(), DEFAULT_FIELDS)
|
||||||
.executes((context, fields) -> {
|
.executes((context, fields) -> {
|
||||||
AggregatedMetric sort;
|
AggregatedMetric sort;
|
||||||
|
@ -270,23 +269,25 @@ private static Component linkComputer(CommandSourceStack source, @Nullable Serve
|
||||||
out.append(" (id " + computerId + ")");
|
out.append(" (id " + computerId + ")");
|
||||||
|
|
||||||
// And, if we're a player, some useful links
|
// And, if we're a player, some useful links
|
||||||
if (serverComputer != null && UserLevel.OP.test(source) && isPlayer(source)) {
|
if (serverComputer != null && isPlayer(source)) {
|
||||||
out
|
if (ModRegistry.Permissions.PERMISSION_TP.test(source)) {
|
||||||
.append(" ")
|
out.append(" ").append(link(
|
||||||
.append(link(
|
|
||||||
text("\u261b"),
|
text("\u261b"),
|
||||||
"/computercraft tp " + serverComputer.getInstanceID(),
|
"/computercraft tp " + serverComputer.getInstanceID(),
|
||||||
Component.translatable("commands.computercraft.tp.action")
|
Component.translatable("commands.computercraft.tp.action")
|
||||||
))
|
));
|
||||||
.append(" ")
|
}
|
||||||
.append(link(
|
|
||||||
|
if (ModRegistry.Permissions.PERMISSION_VIEW.test(source)) {
|
||||||
|
out.append(" ").append(link(
|
||||||
text("\u20e2"),
|
text("\u20e2"),
|
||||||
"/computercraft view " + serverComputer.getInstanceID(),
|
"/computercraft view " + serverComputer.getInstanceID(),
|
||||||
Component.translatable("commands.computercraft.view.action")
|
Component.translatable("commands.computercraft.view.action")
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UserLevel.OWNER.test(source) && isPlayer(source)) {
|
if (isPlayer(source) && UserLevel.isOwner(source)) {
|
||||||
var linkPath = linkStorage(source, computerId);
|
var linkPath = linkStorage(source, computerId);
|
||||||
if (linkPath != null) out.append(" ").append(linkPath);
|
if (linkPath != null) out.append(" ").append(linkPath);
|
||||||
}
|
}
|
||||||
|
@ -295,7 +296,7 @@ private static Component linkComputer(CommandSourceStack source, @Nullable Serve
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Component linkPosition(CommandSourceStack context, ServerComputer computer) {
|
private static Component linkPosition(CommandSourceStack context, ServerComputer computer) {
|
||||||
if (UserLevel.OP.test(context)) {
|
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
|
||||||
return link(
|
return link(
|
||||||
position(computer.getPosition()),
|
position(computer.getPosition()),
|
||||||
"/computercraft tp " + computer.getInstanceID(),
|
"/computercraft tp " + computer.getInstanceID(),
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.SharedSuggestionProvider;
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -22,8 +21,8 @@ private CommandUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPlayer(CommandSourceStack output) {
|
public static boolean isPlayer(CommandSourceStack output) {
|
||||||
var sender = output.getEntity();
|
var player = output.getPlayer();
|
||||||
return sender instanceof ServerPlayer player && !PlatformHelper.get().isFakePlayer(player);
|
return player != null && !PlatformHelper.get().isFakePlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
package dan200.computercraft.shared.command;
|
package dan200.computercraft.shared.command;
|
||||||
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@ -13,11 +13,6 @@
|
||||||
* The level a user must be at in order to execute a command.
|
* The level a user must be at in order to execute a command.
|
||||||
*/
|
*/
|
||||||
public enum UserLevel implements Predicate<CommandSourceStack> {
|
public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||||
/**
|
|
||||||
* Only can be used by the owner of the server: namely the server console or the player in SSP.
|
|
||||||
*/
|
|
||||||
OWNER,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can only be used by ops.
|
* Can only be used by ops.
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +30,6 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||||
|
|
||||||
public int toLevel() {
|
public int toLevel() {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
case OWNER -> 4;
|
|
||||||
case OP, OWNER_OP -> 2;
|
case OP, OWNER_OP -> 2;
|
||||||
case ANYONE -> 0;
|
case ANYONE -> 0;
|
||||||
};
|
};
|
||||||
|
@ -44,39 +38,26 @@ public int toLevel() {
|
||||||
@Override
|
@Override
|
||||||
public boolean test(CommandSourceStack source) {
|
public boolean test(CommandSourceStack source) {
|
||||||
if (this == ANYONE) return true;
|
if (this == ANYONE) return true;
|
||||||
if (this == OWNER) return isOwner(source);
|
|
||||||
if (this == OWNER_OP && isOwner(source)) return true;
|
if (this == OWNER_OP && isOwner(source)) return true;
|
||||||
return source.hasPermission(toLevel());
|
return source.hasPermission(toLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean test(ServerPlayer source) {
|
||||||
* Take the union of two {@link UserLevel}s.
|
if (this == ANYONE) return true;
|
||||||
* <p>
|
if (this == OWNER_OP && isOwner(source)) return true;
|
||||||
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
return source.hasPermissions(toLevel());
|
||||||
*
|
|
||||||
* @param left The first user level to take the union of.
|
|
||||||
* @param right The second user level to take the union of.
|
|
||||||
* @return The union of two levels.
|
|
||||||
*/
|
|
||||||
public static UserLevel union(UserLevel left, UserLevel right) {
|
|
||||||
if (left == right) return left;
|
|
||||||
|
|
||||||
// x ∪ ANYONE = ANYONE
|
|
||||||
if (left == ANYONE || right == ANYONE) return ANYONE;
|
|
||||||
|
|
||||||
// x ∪ OWNER = OWNER
|
|
||||||
if (left == OWNER) return right;
|
|
||||||
if (right == OWNER) return left;
|
|
||||||
|
|
||||||
// At this point, we have x != y and x, y ∈ { OP, OWNER_OP }.
|
|
||||||
return OWNER_OP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isOwner(CommandSourceStack source) {
|
public static boolean isOwner(CommandSourceStack source) {
|
||||||
var server = source.getServer();
|
var server = source.getServer();
|
||||||
var sender = source.getEntity();
|
var player = source.getPlayer();
|
||||||
return server.isDedicatedServer()
|
return server.isDedicatedServer()
|
||||||
? source.getEntity() == null && source.hasPermission(4) && source.getTextName().equals("Server")
|
? source.getEntity() == null && source.hasPermission(4) && source.getTextName().equals("Server")
|
||||||
: sender instanceof Player player && server.isSingleplayerOwner(player.getGameProfile());
|
: player != null && server.isSingleplayerOwner(player.getGameProfile());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isOwner(ServerPlayer player) {
|
||||||
|
var server = player.getServer();
|
||||||
|
return server != null && server.isSingleplayerOwner(player.getGameProfile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ public static CommandBuilder<CommandSourceStack> command(String literal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandBuilder<S> requires(Predicate<S> predicate) {
|
public CommandBuilder<S> requires(Predicate<S> predicate) {
|
||||||
requires = requires == null ? predicate : requires.and(predicate);
|
if (requires != null) throw new IllegalStateException("Requires already set");
|
||||||
|
requires = predicate;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import dan200.computercraft.shared.command.UserLevel;
|
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
|
@ -31,6 +30,7 @@
|
||||||
*/
|
*/
|
||||||
public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<CommandSourceStack> {
|
public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<CommandSourceStack> {
|
||||||
private final Collection<HelpingArgumentBuilder> children = new ArrayList<>();
|
private final Collection<HelpingArgumentBuilder> children = new ArrayList<>();
|
||||||
|
private @Nullable Predicate<CommandSourceStack> requirement;
|
||||||
|
|
||||||
private HelpingArgumentBuilder(String literal) {
|
private HelpingArgumentBuilder(String literal) {
|
||||||
super(literal);
|
super(literal);
|
||||||
|
@ -41,26 +41,20 @@ public static HelpingArgumentBuilder choice(String literal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
|
public HelpingArgumentBuilder requires(Predicate<CommandSourceStack> requirement) {
|
||||||
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
|
this.requirement = requirement;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Predicate<CommandSourceStack> getRequirement() {
|
public Predicate<CommandSourceStack> getRequirement() {
|
||||||
// The requirement of this node is the union of all child's requirements.
|
if (requirement != null) return requirement;
|
||||||
|
|
||||||
var requirements = Stream.concat(
|
var requirements = Stream.concat(
|
||||||
children.stream().map(ArgumentBuilder::getRequirement),
|
children.stream().map(ArgumentBuilder::getRequirement),
|
||||||
getArguments().stream().map(CommandNode::getRequirement)
|
getArguments().stream().map(CommandNode::getRequirement)
|
||||||
).toList();
|
).toList();
|
||||||
|
return x -> requirements.stream().anyMatch(y -> y.test(x));
|
||||||
// If all requirements are a UserLevel, take the union of those instead.
|
|
||||||
var userLevel = UserLevel.OWNER;
|
|
||||||
for (var requirement : requirements) {
|
|
||||||
if (!(requirement instanceof UserLevel level)) return x -> requirements.stream().anyMatch(y -> y.test(x));
|
|
||||||
userLevel = UserLevel.union(userLevel, level);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -181,7 +175,7 @@ private static Component getHelp(CommandContext<CommandSourceStack> context, Com
|
||||||
.append(Component.translatable("commands." + id + ".desc"));
|
.append(Component.translatable("commands." + id + ".desc"));
|
||||||
|
|
||||||
for (var child : node.getChildren()) {
|
for (var child : node.getChildren()) {
|
||||||
if (!child.getRequirement().test(context.getSource()) || !(child instanceof LiteralCommandNode)) {
|
if (!child.canUse(context.getSource()) || !(child instanceof LiteralCommandNode)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.integration;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||||
|
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
|
import dan200.computercraft.shared.platform.RegistrationHelper;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
|
||||||
|
import javax.annotation.OverridingMethodsMustInvokeSuper;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry of nodes in a permission system.
|
||||||
|
* <p>
|
||||||
|
* This acts as an abstraction layer over permission systems such Forge's built-in permissions API, or Fabric's
|
||||||
|
* unofficial <a href="https://github.com/lucko/fabric-permissions-api">fabric-permissions-api-v0</a>.
|
||||||
|
* <p>
|
||||||
|
* This behaves similarly to {@link RegistrationHelper} (aka Forge's deferred registry), in that you {@linkplain #create()
|
||||||
|
* create a registry}, {@linkplain #registerCommand(String, UserLevel) add nodes to it} and then finally {@linkplain
|
||||||
|
* #register()} all created nodes.
|
||||||
|
*
|
||||||
|
* @see dan200.computercraft.shared.ModRegistry.Permissions
|
||||||
|
*/
|
||||||
|
public abstract class PermissionRegistry {
|
||||||
|
private boolean frozen = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a permission node for a command. The registered node should be of the form {@code "command." + command}.
|
||||||
|
*
|
||||||
|
* @param command The name of the command. This should be one of the subcommands under the {@code /computercraft}
|
||||||
|
* subcommand, and not something general.
|
||||||
|
* @param fallback The default/fallback permission check.
|
||||||
|
* @return The resulting predicate which should be passed to {@link ArgumentBuilder#requires(Predicate)}.
|
||||||
|
* @see CommandComputerCraft
|
||||||
|
*/
|
||||||
|
public abstract Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the registry has not been frozen (namely {@link #register()} has been called). This should be called
|
||||||
|
* before registering each node.
|
||||||
|
*/
|
||||||
|
protected void checkNotFrozen() {
|
||||||
|
if (frozen) throw new IllegalStateException("Permission registry has been frozen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freeze the permissions registry and register the underlying nodes.
|
||||||
|
*/
|
||||||
|
@OverridingMethodsMustInvokeSuper
|
||||||
|
public void register() {
|
||||||
|
frozen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Provider {
|
||||||
|
Optional<PermissionRegistry> get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PermissionRegistry create() {
|
||||||
|
return ServiceLoader.load(Provider.class)
|
||||||
|
.stream()
|
||||||
|
.flatMap(x -> x.get().get().stream())
|
||||||
|
.findFirst()
|
||||||
|
.orElseGet(DefaultPermissionRegistry::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultPermissionRegistry extends PermissionRegistry {
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
|
||||||
|
checkNotFrozen();
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.integration;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of {@link PermissionRegistry} using Fabric's unofficial {@linkplain Permissions permissions api}.
|
||||||
|
*/
|
||||||
|
public final class FabricPermissionRegistry extends PermissionRegistry {
|
||||||
|
private FabricPermissionRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
|
||||||
|
checkNotFrozen();
|
||||||
|
var name = ComputerCraftAPI.MOD_ID + ".command." + command;
|
||||||
|
return source -> Permissions.getPermissionValue(source, name).orElseGet(() -> fallback.test(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoService(PermissionRegistry.Provider.class)
|
||||||
|
public static final class Provider implements PermissionRegistry.Provider {
|
||||||
|
@Override
|
||||||
|
public Optional<PermissionRegistry> get() {
|
||||||
|
return FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0")
|
||||||
|
? Optional.of(new FabricPermissionRegistry())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.integration;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
|
import net.minecraftforge.server.permission.PermissionAPI;
|
||||||
|
import net.minecraftforge.server.permission.events.PermissionGatherEvent;
|
||||||
|
import net.minecraftforge.server.permission.nodes.PermissionNode;
|
||||||
|
import net.minecraftforge.server.permission.nodes.PermissionType;
|
||||||
|
import net.minecraftforge.server.permission.nodes.PermissionTypes;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of {@link PermissionRegistry} using Forge's {@link PermissionAPI}.
|
||||||
|
*/
|
||||||
|
public final class ForgePermissionRegistry extends PermissionRegistry {
|
||||||
|
private final List<PermissionNode<?>> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
private ForgePermissionRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> PermissionNode<T> registerNode(String nodeName, PermissionType<T> type, PermissionNode.PermissionResolver<T> defaultResolver) {
|
||||||
|
checkNotFrozen();
|
||||||
|
var node = new PermissionNode<>(ComputerCraftAPI.MOD_ID, nodeName, type, defaultResolver);
|
||||||
|
nodes.add(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> registerCommand(String command, UserLevel fallback) {
|
||||||
|
var node = registerNode(
|
||||||
|
"command." + command, PermissionTypes.BOOLEAN,
|
||||||
|
(player, uuid, context) -> player != null && fallback.test(player)
|
||||||
|
);
|
||||||
|
|
||||||
|
return source -> {
|
||||||
|
var player = source.getPlayer();
|
||||||
|
return player == null ? fallback.test(source) : PermissionAPI.getPermission(player, node);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register() {
|
||||||
|
super.register();
|
||||||
|
MinecraftForge.EVENT_BUS.addListener((PermissionGatherEvent.Nodes event) -> event.addNodes(nodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoService(PermissionRegistry.Provider.class)
|
||||||
|
public static final class Provider implements PermissionRegistry.Provider {
|
||||||
|
@Override
|
||||||
|
public Optional<PermissionRegistry> get() {
|
||||||
|
return Optional.of(new ForgePermissionRegistry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user