mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-05 23:10:30 +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("mezz.jei")
|
||||
includeModule("com.terraformersmc", "modmenu")
|
||||
includeModule("me.lucko", "fabric-permissions-api")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ slf4j = "1.7.36"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.0.8+1.19.4"
|
||||
fabricPermissions = "0.2.20221016"
|
||||
iris = "1.5.2+1.19.4"
|
||||
jei = "13.1.0.11"
|
||||
modmenu = "6.1.0-rc.1"
|
||||
@ -93,6 +94,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
# Minecraft mods
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
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" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
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-runtime = ["jei-forge"]
|
||||
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"]
|
||||
|
||||
# Testing
|
||||
|
@ -15,6 +15,7 @@ import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.shared.command.UserLevel;
|
||||
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
|
||||
@ -37,6 +38,7 @@ import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.details.BlockDetails;
|
||||
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.PrintoutItem;
|
||||
import dan200.computercraft.shared.media.items.RecordMedia;
|
||||
@ -77,6 +79,7 @@ import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.ImpostorRecipe;
|
||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@ -98,6 +101,7 @@ import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
|
||||
@ -366,6 +370,18 @@ public final class ModRegistry {
|
||||
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.
|
||||
*/
|
||||
@ -379,6 +395,7 @@ public final class ModRegistry {
|
||||
ArgumentTypes.REGISTRY.register();
|
||||
LootItemConditionTypes.REGISTRY.register();
|
||||
RecipeSerializers.REGISTRY.register();
|
||||
Permissions.REGISTRY.register();
|
||||
|
||||
// Register bundled power providers
|
||||
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
|
||||
|
@ -11,6 +11,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
@ -60,7 +61,7 @@ public final class CommandComputerCraft {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(choice("computercraft")
|
||||
.then(literal("dump")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
|
||||
.executes(context -> {
|
||||
var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
|
||||
|
||||
@ -118,7 +119,7 @@ public final class CommandComputerCraft {
|
||||
})))
|
||||
|
||||
.then(command("shutdown")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
|
||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
||||
.executes((context, computerSelectors) -> {
|
||||
var shutdown = 0;
|
||||
@ -132,7 +133,7 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("turn-on")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
|
||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
||||
.executes((context, computerSelectors) -> {
|
||||
var on = 0;
|
||||
@ -146,7 +147,7 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("tp")
|
||||
.requires(UserLevel.OP)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_TP)
|
||||
.arg("computer", oneComputer())
|
||||
.executes(context -> {
|
||||
var computer = getComputerArgument(context, "computer");
|
||||
@ -171,7 +172,7 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("queue")
|
||||
.requires(UserLevel.ANYONE)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
|
||||
.arg(
|
||||
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
||||
.suggests((context, builder) -> Suggestions.empty())
|
||||
@ -193,7 +194,7 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("view")
|
||||
.requires(UserLevel.OP)
|
||||
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
|
||||
.arg("computer", oneComputer())
|
||||
.executes(context -> {
|
||||
var player = context.getSource().getPlayerOrException();
|
||||
@ -213,8 +214,8 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(choice("track")
|
||||
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
|
||||
.then(command("start")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.executes(context -> {
|
||||
getMetricsInstance(context.getSource()).start();
|
||||
|
||||
@ -227,7 +228,6 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("stop")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.executes(context -> {
|
||||
var timings = getMetricsInstance(context.getSource());
|
||||
if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
|
||||
@ -236,7 +236,6 @@ public final class CommandComputerCraft {
|
||||
}))
|
||||
|
||||
.then(command("dump")
|
||||
.requires(UserLevel.OWNER_OP)
|
||||
.argManyValue("fields", metric(), DEFAULT_FIELDS)
|
||||
.executes((context, fields) -> {
|
||||
AggregatedMetric sort;
|
||||
@ -270,23 +269,25 @@ public final class CommandComputerCraft {
|
||||
out.append(" (id " + computerId + ")");
|
||||
|
||||
// And, if we're a player, some useful links
|
||||
if (serverComputer != null && UserLevel.OP.test(source) && isPlayer(source)) {
|
||||
out
|
||||
.append(" ")
|
||||
.append(link(
|
||||
if (serverComputer != null && isPlayer(source)) {
|
||||
if (ModRegistry.Permissions.PERMISSION_TP.test(source)) {
|
||||
out.append(" ").append(link(
|
||||
text("\u261b"),
|
||||
"/computercraft tp " + serverComputer.getInstanceID(),
|
||||
Component.translatable("commands.computercraft.tp.action")
|
||||
))
|
||||
.append(" ")
|
||||
.append(link(
|
||||
));
|
||||
}
|
||||
|
||||
if (ModRegistry.Permissions.PERMISSION_VIEW.test(source)) {
|
||||
out.append(" ").append(link(
|
||||
text("\u20e2"),
|
||||
"/computercraft view " + serverComputer.getInstanceID(),
|
||||
Component.translatable("commands.computercraft.view.action")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (UserLevel.OWNER.test(source) && isPlayer(source)) {
|
||||
if (isPlayer(source) && UserLevel.isOwner(source)) {
|
||||
var linkPath = linkStorage(source, computerId);
|
||||
if (linkPath != null) out.append(" ").append(linkPath);
|
||||
}
|
||||
@ -295,7 +296,7 @@ public final class CommandComputerCraft {
|
||||
}
|
||||
|
||||
private static Component linkPosition(CommandSourceStack context, ServerComputer computer) {
|
||||
if (UserLevel.OP.test(context)) {
|
||||
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
|
||||
return link(
|
||||
position(computer.getPosition()),
|
||||
"/computercraft tp " + computer.getInstanceID(),
|
||||
|
@ -10,7 +10,6 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@ -22,8 +21,8 @@ public final class CommandUtils {
|
||||
}
|
||||
|
||||
public static boolean isPlayer(CommandSourceStack output) {
|
||||
var sender = output.getEntity();
|
||||
return sender instanceof ServerPlayer player && !PlatformHelper.get().isFakePlayer(player);
|
||||
var player = output.getPlayer();
|
||||
return player != null && !PlatformHelper.get().isFakePlayer(player);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@ -13,11 +13,6 @@ import java.util.function.Predicate;
|
||||
* The level a user must be at in order to execute a command.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -35,7 +30,6 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||
|
||||
public int toLevel() {
|
||||
return switch (this) {
|
||||
case OWNER -> 4;
|
||||
case OP, OWNER_OP -> 2;
|
||||
case ANYONE -> 0;
|
||||
};
|
||||
@ -44,39 +38,26 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||
@Override
|
||||
public boolean test(CommandSourceStack source) {
|
||||
if (this == ANYONE) return true;
|
||||
if (this == OWNER) return isOwner(source);
|
||||
if (this == OWNER_OP && isOwner(source)) return true;
|
||||
return source.hasPermission(toLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the union of two {@link UserLevel}s.
|
||||
* <p>
|
||||
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
||||
*
|
||||
* @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;
|
||||
public boolean test(ServerPlayer source) {
|
||||
if (this == ANYONE) return true;
|
||||
if (this == OWNER_OP && isOwner(source)) return true;
|
||||
return source.hasPermissions(toLevel());
|
||||
}
|
||||
|
||||
private static boolean isOwner(CommandSourceStack source) {
|
||||
public static boolean isOwner(CommandSourceStack source) {
|
||||
var server = source.getServer();
|
||||
var sender = source.getEntity();
|
||||
var player = source.getPlayer();
|
||||
return server.isDedicatedServer()
|
||||
? 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 class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import dan200.computercraft.shared.command.UserLevel;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
@ -31,6 +30,7 @@ import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
||||
*/
|
||||
public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<CommandSourceStack> {
|
||||
private final Collection<HelpingArgumentBuilder> children = new ArrayList<>();
|
||||
private @Nullable Predicate<CommandSourceStack> requirement;
|
||||
|
||||
private HelpingArgumentBuilder(String literal) {
|
||||
super(literal);
|
||||
@ -41,26 +41,20 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
|
||||
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
|
||||
public HelpingArgumentBuilder requires(Predicate<CommandSourceStack> requirement) {
|
||||
this.requirement = requirement;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
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(
|
||||
children.stream().map(ArgumentBuilder::getRequirement),
|
||||
getArguments().stream().map(CommandNode::getRequirement)
|
||||
).toList();
|
||||
|
||||
// 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;
|
||||
return x -> requirements.stream().anyMatch(y -> y.test(x));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -181,7 +175,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
||||
.append(Component.translatable("commands." + id + ".desc"));
|
||||
|
||||
for (var child : node.getChildren()) {
|
||||
if (!child.getRequirement().test(context.getSource()) || !(child instanceof LiteralCommandNode)) {
|
||||
if (!child.canUse(context.getSource()) || !(child instanceof LiteralCommandNode)) {
|
||||
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