diff --git a/fabric-permission-api-v1/build.gradle b/fabric-permission-api-v1/build.gradle new file mode 100644 index 00000000000..a98a6cc8f74 --- /dev/null +++ b/fabric-permission-api-v1/build.gradle @@ -0,0 +1,14 @@ +version = getSubprojectVersion(project) + +loom { + accessWidenerPath = file('src/main/resources/fabric-permission-api-v1.classtweaker') +} + +moduleDependencies(project, [ + ":fabric-api-base" +]) + +testDependencies(project, [ + ":fabric-command-api-v2", + ":fabric-lifecycle-events-v1" +]) diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/MutablePermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/MutablePermissionContext.java new file mode 100644 index 00000000000..11d80136843 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/MutablePermissionContext.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import org.jspecify.annotations.Nullable; + +/** + * Mutable version of {@link PermissionContext}, intended for creation of custom context conditions. + */ +public interface MutablePermissionContext extends PermissionContext { + MutablePermissionContext set(PermissionContext.Key key, @Nullable T value); +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContext.java new file mode 100644 index 00000000000..4ca3a72794e --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContext.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.permissions.PermissionLevel; +import net.minecraft.server.players.NameAndId; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +import net.fabricmc.fabric.impl.permission.CustomPermissionContext; +import net.fabricmc.fabric.impl.permission.OverriddenPermissionContext; +import net.fabricmc.fabric.impl.permission.PermissionContextKey; + +/** + * Interface representing context object used for permission checks, providing both required + * and additional values. + * + *

Permission checks should be applied by calling methods defined in {@link PermissionContextOwner} + * For command checks, you can use {@link PermissionPredicates}. + */ +public interface PermissionContext extends PermissionContextOwner { + /** + * Represents name attached to the permission context. + * There is no requirement for it to be unique, as it might be changed by external factors. + * Mainly used to help with identifying system-type contexts or context with shared/nil uuid. + * For entities, it defaults to the plain name, based on either custom name or entity type name. + */ + Key NAME = PermissionContextKey.NAME; + + /** + * Represents position current position in which permission check is applied. + */ + Key POSITION = PermissionContextKey.POSITION; + + /** + * Represents position current block position in which permission check is applied. + */ + Key BLOCK_POSITION = PermissionContextKey.BLOCK_POSITION; + + /** + * Represents entity for which permission check is applied. + */ + Key ENTITY = PermissionContextKey.ENTITY; + + /** + * Represents command source stack for which permission check is applied. + */ + Key COMMAND_SOURCE_STACK = PermissionContextKey.COMMAND_SOURCE_STACK; + + /** + * Represents level for which permission check is applied. + */ + Key LEVEL = PermissionContextKey.LEVEL; + + /** + * Represents the server to which this context is attached to. + */ + Key SERVER = PermissionContextKey.SERVER; + + /** + * Creates a custom context, without any optional values. + * + * @param uuid the uuid connected to this context + * @param type type of the context + * @param permissionLevel base permission level + * @return mutable permission context + */ + static MutablePermissionContext create(UUID uuid, Type type, PermissionLevel permissionLevel) { + Objects.requireNonNull(uuid, "uuid cannot be null"); + Objects.requireNonNull(type, "type cannot be null"); + Objects.requireNonNull(permissionLevel, "permissionLevel cannot be null"); + + return new CustomPermissionContext(uuid, type, permissionLevel); + } + + /** + * Creates a context of offline player. + * Do note that depending on the backing implementation, the check for offline players + * might be noticeably slower, so using async check methods or checking them on non-main threads + * is encouraged. + * + * @param uuid player's uuid + * @param server the currently running server instance + * @return mutable permission context + */ + static CompletableFuture offlinePlayer(UUID uuid, MinecraftServer server) { + Objects.requireNonNull(uuid, "uuid cannot be null"); + Objects.requireNonNull(server, "server cannot be null"); + + PermissionLevel permissionLevel = server.getProfilePermissions(new NameAndId(uuid, "")).level(); + var ctx = new CustomPermissionContext(uuid, Type.PLAYER, permissionLevel); + ctx.set(PermissionContext.SERVER, server); + + return PermissionEvents.PREPARE_OFFLINE_PLAYER.invoker().prepareOfflinePlayer(ctx, server).thenApply(consumer -> { + if (consumer != null) { + consumer.accept(ctx); + } + + return ctx; + }); + } + + /** + * Creates a context of offline player. + * Do note that depending on the backing implementation, the check for offline players + * might be noticeably slower, so using async check methods or checking them on non-main threads + * is encouraged. + * + * @param nameAndId player's name and uuid + * @param server the currently running server instance + * @return mutable permission context + */ + static CompletableFuture offlinePlayer(NameAndId nameAndId, MinecraftServer server) { + Objects.requireNonNull(nameAndId, "nameAndId cannot be null"); + Objects.requireNonNull(server, "server cannot be null"); + + PermissionLevel permissionLevel = server.getProfilePermissions(nameAndId).level(); + var ctx = new CustomPermissionContext(nameAndId.id(), Type.PLAYER, permissionLevel); + ctx.set(PermissionContext.NAME, nameAndId.name()); + ctx.set(PermissionContext.SERVER, server); + + return PermissionEvents.PREPARE_OFFLINE_PLAYER.invoker().prepareOfflinePlayer(ctx, server).thenApply(consumer -> { + if (consumer != null) { + consumer.accept(ctx); + } + + return ctx; + }); + } + + /** + * Creates a unique key, intended for attaching additional context data. + * This key/value can't be serialized. + * + * @param identifier unique identifier + * @param type of attached + * @return unique key + */ + static Key key(Identifier identifier) { + Objects.requireNonNull(identifier, "identifier cannot be null"); + + return new PermissionContextKey<>(identifier); + } + + /** + * UUID connected to this context. + */ + UUID uuid(); + + /** + * The type of the context. + */ + Type type(); + + /** + * Returns optional value attached to this context. + * + * @param key unique key + * @param type of value + * @return stored value if it's present, null otherwise + */ + @Nullable + T get(Key key); + + /** + * Returns optional value attached to this context, with a fallback. + * + * @param key unique key + * @param defaultValue fallback value, if it's not present or null + * @param type of value + * @return stored value if it's present, otherwise defaultValue + */ + default T orElse(Key key, T defaultValue) { + T value = get(key); + + return value != null ? value : defaultValue; + } + + /** + * Creates a mutable copy of this context. + * + * @return a new mutable permission context. + */ + default MutablePermissionContext mutable() { + return new OverriddenPermissionContext(this); + } + + /** + * Provides the vanilla permission level of the context. + * + * @return permission level of the context. + */ + PermissionLevel permissionLevel(); + + /** + * Provides a set of defined permission context keys. + * + * @return unmodifiable set of permission keys. + */ + Set> keys(); + + @Override + default PermissionContext getPermissionContext() { + return this; + } + + /** + * Identifies the owner type of the permission context. + */ + enum Type { + PLAYER, + ENTITY, + SYSTEM, + OTHER + } + + /** + * Key used to represent additional permission context. + * + * @param type of the context + */ + @ApiStatus.NonExtendable + interface Key { + /** + * Identifier representing this context. + * + * @return id of this key + */ + Identifier id(); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContextOwner.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContextOwner.java new file mode 100644 index 00000000000..f68a2a809ca --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionContextOwner.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.resources.Identifier; +import net.minecraft.server.permissions.PermissionLevel; +import net.minecraft.world.entity.Entity; + +import net.fabricmc.fabric.api.util.TriState; + +/** + * Utility interface allowing quick access for permission checking methods. + * Implemented by default on {@link Entity}, {@link CommandSourceStack} and {@link PermissionContext}. + * Other mods are allowed to implement this on their own classes as well. + * + *

See {@link PermissionContext} for creation and modification of permission contexts. + * + *

Example usage: + *

{@code
+ * Identifier claimBypassPermission = Identifier.fromNamespaceAndPath("potatoclaims", "bypass_protection");
+ * ServerPlayer player = ...;
+ *
+ * AttackEntityCallback.EVENT.register((playerEntity, _, _, entity, _) -> {
+ *     if (ModChecks.isProtected(entity) && !player.checkPermission(claimBypassPermission, PermissionLevel.GAMEMASTERS)) {
+ *         return InteractionResult.FAIL;
+ *     }
+ *     return InteractionResult.PASS;
+ * });
+ * }
+ */ +public interface PermissionContextOwner { + /** + * Provides the permission context. + * In case of entities, this context will be dynamic. + * + * @return PermissionContext attached to this object + */ + default PermissionContext getPermissionContext() { + throw new IllegalStateException("Implemented via Mixin"); + } + + /** + * Simple permission check. Should be used to check if something is allowed. + * + * @param permission a permission identifier to check against + * @return TriState returning value of the permission (DEFAULT if not changed) + */ + default TriState checkPermission(Identifier permission) { + return TriState.of(this.checkPermission(PermissionNode.of(permission))); + } + + /** + * Simple permission check. Should be used to check if something is allowed. + * Will default to {@param defaultValue} if permission value is not provided. + * + * @param permission a permission identifier to check against + * @param defaultValue fallback value + * @return a boolean representing state of the permission, returns defaultValue if not modified by other mods + */ + default boolean checkPermission(Identifier permission, boolean defaultValue) { + Boolean value = this.checkPermission(PermissionNode.of(permission)); + return value != null ? value : defaultValue; + } + + /** + * Simple permission check. Should be used to check if something is allowed. + * Will check for vanilla permission level, if permission value is not provided. + * + * @param permission a permission identifier to check against + * @param defaultPermissionLevel a fallback permission level to check against + * @return a boolean representing state of the permission + */ + default boolean checkPermission(Identifier permission, PermissionLevel defaultPermissionLevel) { + PermissionLevel permissionLevel = this.getPermissionContext().permissionLevel(); + return this.checkPermission(PermissionNode.of(permission), permissionLevel.isEqualOrHigherThan(defaultPermissionLevel)); + } + + /** + * A dynamic, typed permission check. Should be used to check for more complex permission values, + * like allowed amount and alike. + * + * @param permission a permission node to check against + * @param type of the permission + * @return value of the permission or null if not provided + */ + @Nullable + default T checkPermission(PermissionNode permission) { + return this.checkPermission(permission, null); + } + + /** + * A dynamic, typed permission check. Should be used to check for more complex permission values, + * like allowed amount and alike. + * + * @param permission a permission node to check against + * @param defaultValue fallback value, if not provided + * @param type of the permission + * @return value of the permission or {@param defaultValue} if not provided + */ + @Contract("_, null -> _; _, !null -> !null") + default @Nullable T checkPermission(PermissionNode permission, @Nullable T defaultValue) { + T value = PermissionEvents.ON_REQUEST.invoker().handlePermissionRequest(this.getPermissionContext(), permission); + + return value != null ? value : defaultValue; + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionEvents.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionEvents.java new file mode 100644 index 00000000000..981121d420b --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionEvents.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import org.jspecify.annotations.Nullable; + +import net.minecraft.server.MinecraftServer; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * These events used for handling permission resolution within PermissionContext system. + * Implemented callbacks for these event should be thread safe, as permission methods can be called from any active thread. + * Additionally, the execution should be reasonably fast for non-player and online player cases. + * Offline player checks are allowed to be slower. + * + *

When implementing a permission handler, only {@link PermissionEvents#ON_REQUEST} needs to be implemented. + * + *

To check for permissions, you should use dedicated methods from {@link PermissionContextOwner} interface + * and it's implementations over invoking this event. + */ +public final class PermissionEvents { + private PermissionEvents() { + } + + /** + * Event used for handling permission resolution. + * + *

This event is invoked by methods provided by {@link PermissionContextOwner}. + */ + public static final Event ON_REQUEST = EventFactory.createArrayBacked(OnRequest.class, arr -> new OnRequest() { + @Override + public @Nullable T handlePermissionRequest(PermissionContext context, PermissionNode permission) { + for (OnRequest callback : arr) { + T out = callback.handlePermissionRequest(context, permission); + + if (out != null) { + return out; + } + } + + return null; + } + }); + + /** + * Event for preparing for offline player checks. + * + *

This event is invoked by {@link PermissionContext#offlinePlayer}. + */ + public static final Event PREPARE_OFFLINE_PLAYER = EventFactory.createArrayBacked(PrepareOfflinePlayer.class, + (_, _) -> CompletableFuture.completedFuture(null), arr -> (context, server) -> { + var list = new ArrayList>>(); + + for (PrepareOfflinePlayer callback : arr) { + list.add(callback.prepareOfflinePlayer(context, server)); + } + + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)).thenApply(_ -> { + return mutableContext -> { + for (CompletableFuture<@Nullable Consumer> future : list) { + Consumer consumer = future.getNow(null); + + if (consumer != null) { + consumer.accept(mutableContext); + } + } + }; + }); + }); + + @FunctionalInterface + public interface OnRequest { + /** + * Main permission checking, it can execute on any thread. + * + * @param context context to check for. + * @param permission a permission node representing a permission. + * @param type of permission. + * @return value of type T if present, null to pass to the next handler. + */ + @Nullable + T handlePermissionRequest(PermissionContext context, PermissionNode permission); + } + + @FunctionalInterface + public interface PrepareOfflinePlayer { + /** + * A callback run before providing a {@link PermissionContext} for offline player checks. + * Should be used to preload the relevant permission data if needed. + * + * @param context context to load. + * @param server server for which this player is resolved against. + * @return a completable future indicating that permission context is ready to be checked against, with optional callback to modify the context. + */ + CompletableFuture<@Nullable Consumer> prepareOfflinePlayer(PermissionContext context, MinecraftServer server); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionNode.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionNode.java new file mode 100644 index 00000000000..cc483df1a50 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionNode.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import java.util.Objects; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.resources.Identifier; + +import net.fabricmc.fabric.impl.permission.PermissionNodeImpl; + +/** + * This class represents a permission, which consist of identifier as the key and a codec to dictate + * its type. This class can be instantiated dynamically (just before permission request) or statically + * on mod initialization. + * + *

PermissionNode objects are considered to be equal objects (but not the same instances) + * as long as they were created with the same key and codec pair. + * + * @param type of the permission + */ +@ApiStatus.NonExtendable +public interface PermissionNode { + /** + * Creates a permission node of boolean type. + * Primarily used for simple permission checks (if owner can/can't do something). + * + * @param key a key identifying this permission + * @return permission node for boolean type + */ + static PermissionNode of(Identifier key) { + Objects.requireNonNull(key, "key can't be null!"); + + return new PermissionNodeImpl<>(key, Codec.BOOL); + } + + /** + * Creates a permission node of boolean type. + * Primarily used for simple permission checks (if owner can/can't do something). + * + * @param namespace namespace of the key identifying this permission node + * @param path path of the key identifying this permission node + * @return permission node for boolean type + */ + static PermissionNode of(String namespace, String path) { + Objects.requireNonNull(namespace, "namespace can't be null!"); + Objects.requireNonNull(path, "path can't be null!"); + + return of(Identifier.fromNamespaceAndPath(namespace, path)); + } + + /** + * Creates a permission node of integer type. + * Primarily used for limiting permission checks (if player can do/have X of something). + * + * @param key a key identifying this permission + * @return permission node for integer type + */ + static PermissionNode ofInteger(Identifier key) { + Objects.requireNonNull(key, "key can't be null!"); + + return new PermissionNodeImpl<>(key, Codec.INT); + } + + /** + * Creates a permission node of integer type. + * Primarily used for limiting permission checks (if player can do/have X of something). + * + * @param namespace namespace of the key identifying this permission node + * @param path path of the key identifying this permission node + * @return permission node for integer type + */ + static PermissionNode ofInteger(String namespace, String path) { + Objects.requireNonNull(namespace, "namespace can't be null!"); + Objects.requireNonNull(path, "path can't be null!"); + + return ofInteger(Identifier.fromNamespaceAndPath(namespace, path)); + } + + /** + * Creates a permission node of string type. + * Primarily used for information/logical checks. + * + * @param key a key identifying this permission + * @return permission node for string type + */ + static PermissionNode ofString(Identifier key) { + Objects.requireNonNull(key, "key can't be null!"); + + return new PermissionNodeImpl<>(key, Codec.STRING); + } + + /** + * Creates a permission node of string type. + * Primarily used for information/logical checks. + * + * @param namespace namespace of the key identifying this permission node + * @param path path of the key identifying this permission node + * @return permission node for string type + */ + static PermissionNode ofString(String namespace, String path) { + Objects.requireNonNull(namespace, "namespace can't be null!"); + Objects.requireNonNull(path, "path can't be null!"); + + return ofString(Identifier.fromNamespaceAndPath(namespace, path)); + } + + /** + * Creates a permission node of custom, codec defined type. + * + * @param key a key identifying this permission + * @param codec a codec used to read the permission value + * @param the type of permission + * @return permission node for codec-defined type + */ + static PermissionNode ofCustom(Identifier key, Codec codec) { + Objects.requireNonNull(key, "key can't be null!"); + + return new PermissionNodeImpl<>(key, codec); + } + + /** + * Creates a permission node of custom, codec defined type. + * + * @param namespace namespace of the key identifying this permission node + * @param path path of the key identifying this permission node + * @param codec a codec used to read the permission value + * @param the type of permission + * @return permission node for codec-defined type + */ + static PermissionNode ofCustom(String namespace, String path, Codec codec) { + Objects.requireNonNull(namespace, "namespace can't be null!"); + Objects.requireNonNull(path, "path can't be null!"); + + return ofCustom(Identifier.fromNamespaceAndPath(namespace, path), codec); + } + + /** + * Returns a key that represents this permission. + * + * @return key identifying this permission. + */ + Identifier key(); + + /** + * Returns a codec, which defined type of this permission. + * + * @return codec representing the type of this permission. + */ + Codec codec(); +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionPredicates.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionPredicates.java new file mode 100644 index 00000000000..56b888dc191 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/PermissionPredicates.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.permission.v1; + +import java.util.function.Predicate; + +import net.minecraft.resources.Identifier; +import net.minecraft.server.permissions.PermissionLevel; + +/** + * Utility methods for creating permission predicates, mainly to be used for commands, + * but will work in any context that needs a predicate. + * + *

Example usage: + *

{@code
+ * CommandRegistrationCallback.EVENT.register((dispatcher, _, _) -> {
+ *     dispatcher.register(literal("modcommand")
+ *     	   // By using direct Identitier
+ *         .requires(PermissionPredicates.require(Identifier.fromNamespaceAndPath("mymod", "command/main"), true))
+ *         .executes(ModCommands::executeMainCommand)
+ *         .then(literal("admin")
+ *             // By using boolean permission node
+ *             .requires(PermissionPredicates.require(PermissionNode.of(Identifier.fromNamespaceAndPath("mymod", "command/admin")), PermissionLevel.ADMINS))
+ *             .executes(ModCommands::executeMainCommand)
+ *         )
+ * });
+ * }
+ */ +public final class PermissionPredicates { + private PermissionPredicates() { } + + /** + * Predicate checking if context has a permission, defaults to false. + * + * @param permission permission to check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(Identifier permission) { + return x -> x.checkPermission(permission, false); + } + + /** + * Predicate checking if context has a permission. + * + * @param permission permission to check + * @param defaultValue default result of permission check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(Identifier permission, boolean defaultValue) { + return x -> x.checkPermission(permission, defaultValue); + } + + /** + * Predicate checking if context has a permission. + * + * @param permission permission to check + * @param permissionLevel fallback permission level check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(Identifier permission, PermissionLevel permissionLevel) { + return x -> x.checkPermission(permission, permissionLevel); + } + + /** + * Predicate checking if context has a permission, defaults to false. + * + * @param permission permission to check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(PermissionNode permission) { + return x -> x.checkPermission(permission, false); + } + + /** + * Predicate checking if context has a permission. + * + * @param permission permission to check + * @param defaultValue default result of permission check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(PermissionNode permission, boolean defaultValue) { + return x -> x.checkPermission(permission, defaultValue); + } + + /** + * Predicate checking if context has a permission. + * + * @param permission permission to check + * @param permissionLevel fallback permission level check + * @param type of the owner + * @return predicate checking context's permission + */ + public static Predicate require(PermissionNode permission, PermissionLevel permissionLevel) { + return x -> x.checkPermission(permission, x.getPermissionContext().permissionLevel().isEqualOrHigherThan(permissionLevel)); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/package-info.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/package-info.java new file mode 100644 index 00000000000..4835637550f --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/api/permission/v1/package-info.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Permission API is an api allowing mods to easily interact with each + * other in order to know what is and isn't allowed (aka the titular permissions). + * This applies to actions done by players, entities and anything else that + * might do actions you would want other mods to easily prevent. + * + *

By default, this api provides direct support for checking permissions + * for {@link net.minecraft.world.entity.player.Player}, {@link net.minecraft.world.entity.Entity} + * and {@link net.minecraft.commands.CommandSourceStack}, but also creation of custom contexts. + * To simplify access, these classes now extend the {@link net.fabricmc.fabric.api.permission.v1.PermissionContextOwner} + * interface allowing for access to the attached {@link net.fabricmc.fabric.api.permission.v1.PermissionContext} (which also extends that interface) + * as well as providing additional methods for sync and async permission checks. + * Custom implementations of {@link net.fabricmc.fabric.api.permission.v1.PermissionContextOwner} + * and {@link net.fabricmc.fabric.api.permission.v1.PermissionContext} are encouraged in places that require + * more flexibility or lazy evaluation. + * + *

Permission themselves are typed, which allows to support more types (aside of most common boolean permissions) + * allowing for more flexibility of the interactions. + * To create a typed permission node you can use one of the provided static factory methods from + * the {@link net.fabricmc.fabric.api.permission.v1.PermissionNode} interface. Permission nodes can be + * created at any point in time, either dynamically or statically. You can then use this object + * directly as an argument of {@link net.fabricmc.fabric.api.permission.v1.PermissionContextOwner} permission + * checking methods. + * Boolean permissions can also use {@link net.minecraft.resources.Identifier} directly, + * with addition for some extra utility methods on the owner object. + * + *

To define a provider, you need to register callbacks for events defined in {@link net.fabricmc.fabric.api.permission.v1.PermissionEvents}. + * By default, you only need to implement the + * {@link net.fabricmc.fabric.api.permission.v1.PermissionEvents#ON_REQUEST} event, but other ones might still be good to look into to allow better handling of them. + * + *

Example cases where you might want to use this api: + * - Commands that might not make sense to give to all players, but might be required for helpers/moderators/admins, + * - Dynamic limits for things based on external factors (for example warps, max protected area size), + * - Checking if non-regular in world interaction is allowed within protected area (for example transmuting blocks with a special items, summoning mounts), + * + *

Example code - Checking for command permission: + *

{@code
+ * CommandRegistrationCallback.EVENT.register((dispatcher, _, _) -> {
+ *     dispatcher.register(literal("modcommand")
+ *     	   // By using direct Identitier
+ *         .requires(PermissionPredicates.require(Identifier.fromNamespaceAndPath("mymod", "command/main"), true))
+ *         .executes(ModCommands::executeMainCommand)
+ *         .then(literal("admin")
+ *             // By using boolean permission node
+ *             .requires(PermissionPredicates.require(PermissionNode.of("mymod", "command/admin"), PermissionLevel.ADMINS))
+ *             .executes(ModCommands::executeMainCommand)
+ *         )
+ * });
+ * }
+ * + *

Example code - Validating if special interaction works in claim at select position. + *

{@code
+ * // Check side (...)
+ * var canSummonMountPermission = PermissionNode.of("mymod", "can_summon_mount");
+ * public boolean trySummoningMount(Player player, Vec3 pos) {
+ *     var context = player.getPermissionContext().mutable()
+ *     				.set(PermissionContext.BLOCK_POSITION, BlockPos.containing(pos))
+ *     				.set(PermissionContext.POSITION, pos);
+ *
+ *     	if (!context.checkPermission(canSummonMountPermission, true)) {
+ *     	    player.sendSystemMessage(Component.literal("You can't summon your mount here!"));
+ *     	    return false;
+ *     	}
+ *
+ *     	// Mount summoning logic goes here (...)
+ *     	return true;
+ * }
+ *
+ * // Protection mod / validation side (...)
+ * var checkedPermissions = Set.of(Identifier.fromNamespaceAndPath("mymod", "can_summon_mount"), ...);
+ *
+ * PermissionEvents.register((context, permission) -> {
+ * 		if (context.type() != PermissionContext.Type.PLAYER && context.type() != PermissionContext.Type.ENTITY) return null;
+ *
+ * 		var pos = context.get(PermissionContext.BLOCK_POSITION);
+ * 		if (pos == null) return null;
+ *
+ * 		var claim = ClaimMod.getClaimAt(pos);
+ * 		if (claim == null) return null;
+ *
+ *      if (checkedPermissions.contains(permission.key()) && permission.codec() == Codec.BOOL) {
+ *          return (T) Boolean.valueOf(claim.canModifyClaim(context.uuid()));
+ *      }
+ *      // Any other logic...
+ *      return null;
+ * });
+ * }
+ */ +@ApiStatus.Experimental +@NullMarked +package net.fabricmc.fabric.api.permission.v1; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CommandPermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CommandPermissionContext.java new file mode 100644 index 00000000000..db804e1c4f9 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CommandPermissionContext.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import java.util.Set; +import java.util.UUID; + +import org.jspecify.annotations.Nullable; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.server.permissions.LevelBasedPermissionSet; +import net.minecraft.server.permissions.PermissionLevel; + +import net.fabricmc.fabric.api.permission.v1.PermissionContext; + +public class CommandPermissionContext implements PermissionContext { + private final CommandSourceStack source; + + public CommandPermissionContext(CommandSourceStack source) { + this.source = source; + } + + @SuppressWarnings("unchecked") + @Override + public @Nullable T get(Key key) { + if (key == PermissionContext.NAME) { + return (T) this.source.getTextName(); + } else if (key == PermissionContext.POSITION) { + return (T) this.source.getPosition(); + } else if (key == PermissionContext.BLOCK_POSITION) { + return (T) BlockPos.containing(this.source.getPosition()); + } else if (key == PermissionContext.LEVEL) { + return (T) this.source.getLevel(); + } else if (key == PermissionContext.ENTITY) { + return (T) this.source.getEntity(); + } else if (key == PermissionContext.COMMAND_SOURCE_STACK) { + return (T) this.source; + } else if (key == PermissionContext.SERVER) { + return (T) this.source.getServer(); + } + + return null; + } + + @Override + public PermissionLevel permissionLevel() { + return this.source.permissions() instanceof LevelBasedPermissionSet levelBasedPermissionSet ? levelBasedPermissionSet.level() : PermissionLevel.ALL; + } + + @Override + public Set> keys() { + return this.source.getEntity() != null ? PermissionContextKey.DEFAULT_COMMAND_ENTITY_KEYS : PermissionContextKey.DEFAULT_COMMAND_KEYS; + } + + @Override + public Type type() { + return ((Extension) this.source).fabric_getType(); + } + + @Override + public UUID uuid() { + return ((Extension) this.source).fabric_getUuid(); + } + + public interface Extension { + Type fabric_getType(); + UUID fabric_getUuid(); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CustomPermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CustomPermissionContext.java new file mode 100644 index 00000000000..08c917f9c07 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/CustomPermissionContext.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.jspecify.annotations.Nullable; + +import net.minecraft.server.permissions.PermissionLevel; + +import net.fabricmc.fabric.api.permission.v1.MutablePermissionContext; + +public record CustomPermissionContext(UUID uuid, Type type, PermissionLevel permissionLevel, Map, Object> overrides) implements MutablePermissionContext { + public CustomPermissionContext(UUID uuid, Type type, PermissionLevel permissionLevel) { + this(uuid, type, permissionLevel, new HashMap<>()); + } + + @Override + public MutablePermissionContext set(Key key, @Nullable T value) { + if (value != null) { + this.overrides.put(key, value); + } else { + this.overrides.remove(key); + } + + return this; + } + + @Override + public @Nullable T get(Key key) { + //noinspection unchecked + return (T) this.overrides.get(key); + } + + @Override + public Set> keys() { + return Collections.unmodifiableSet(this.overrides.keySet()); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/EntityPermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/EntityPermissionContext.java new file mode 100644 index 00000000000..27b20a7e5db --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/EntityPermissionContext.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import java.util.Set; +import java.util.UUID; + +import org.jspecify.annotations.Nullable; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.permissions.LevelBasedPermissionSet; +import net.minecraft.server.permissions.PermissionLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; + +import net.fabricmc.fabric.api.permission.v1.PermissionContext; + +public class EntityPermissionContext implements PermissionContext { + private final Entity entity; + private final Type type; + private final Set> keys; + private final @Nullable MinecraftServer server; + + public EntityPermissionContext(Entity entity) { + this.entity = entity; + this.type = entity instanceof Player ? Type.PLAYER : Type.ENTITY; + this.server = entity.level().getServer() != null ? entity.level().getServer() : null; + + if (this.entity instanceof ServerPlayer) { + this.keys = PermissionContextKey.DEFAULT_COMMAND_ENTITY_KEYS; + } else if (this.server != null) { + this.keys = PermissionContextKey.DEFAULT_SERVER_ENTITY_KEYS; + } else { + this.keys = PermissionContextKey.DEFAULT_ENTITY_KEYS; + } + } + + @Override + public UUID uuid() { + return this.entity.getUUID(); + } + + @SuppressWarnings({"unchecked", "resource"}) + @Override + public @Nullable T get(Key key) { + if (key == PermissionContext.NAME) { + return (T) this.entity.getPlainTextName(); + } else if (key == PermissionContext.POSITION) { + return (T) this.entity.position(); + } else if (key == PermissionContext.BLOCK_POSITION) { + return (T) this.entity.blockPosition(); + } else if (key == PermissionContext.LEVEL) { + return (T) this.entity.level(); + } else if (key == PermissionContext.ENTITY) { + return (T) this.entity; + } else if (key == PermissionContext.COMMAND_SOURCE_STACK) { + return (T) this.entity instanceof ServerPlayer player ? (T) player.commandSource() : null; + } else if (key == PermissionContext.SERVER) { + return (T) this.server; + } + + return null; + } + + @Override + public PermissionLevel permissionLevel() { + return entity instanceof Player player && player.permissions() instanceof LevelBasedPermissionSet levelBasedPermissionSet + ? levelBasedPermissionSet.level() + : PermissionLevel.ALL; + } + + @Override + public Set> keys() { + return this.keys; + } + + @Override + public Type type() { + return type; + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/OverriddenPermissionContext.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/OverriddenPermissionContext.java new file mode 100644 index 00000000000..015210a5123 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/OverriddenPermissionContext.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.google.common.collect.Sets; +import org.jspecify.annotations.Nullable; + +import net.minecraft.server.permissions.PermissionLevel; + +import net.fabricmc.fabric.api.permission.v1.MutablePermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionContext; + +public record OverriddenPermissionContext(PermissionContext context, Map, Object> overrides) implements MutablePermissionContext { + public OverriddenPermissionContext(PermissionContext context) { + this(context, new HashMap<>()); + } + + @Override + public MutablePermissionContext set(Key key, @Nullable T value) { + if (value != null) { + this.overrides.put(key, value); + } else { + this.overrides.remove(key); + } + + return this; + } + + @Override + public UUID uuid() { + return this.context.uuid(); + } + + @Override + public Type type() { + return this.context.type(); + } + + @Override + public @Nullable T get(Key key) { + if (this.overrides.containsKey(key)) { + //noinspection unchecked + return (T) this.overrides.get(key); + } + + return this.context.get(key); + } + + @Override + public PermissionLevel permissionLevel() { + return this.context.permissionLevel(); + } + + @Override + public Set> keys() { + return Sets.union(this.overrides.keySet(), this.context.keys()); + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionContextKey.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionContextKey.java new file mode 100644 index 00000000000..e41fda37cff --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionContextKey.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import java.util.Set; + +import com.google.common.collect.Sets; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +import net.fabricmc.fabric.api.permission.v1.PermissionContext; + +public record PermissionContextKey(Identifier id) implements PermissionContext.Key { + public static final PermissionContext.Key NAME = fabricKey("name"); + public static final PermissionContext.Key POSITION = fabricKey("position"); + public static final PermissionContext.Key BLOCK_POSITION = fabricKey("block_position"); + public static final PermissionContext.Key ENTITY = fabricKey("entity"); + public static final PermissionContext.Key COMMAND_SOURCE_STACK = fabricKey("command_source_stack"); + public static final PermissionContext.Key LEVEL = fabricKey("level"); + public static final PermissionContext.Key SERVER = fabricKey("server"); + + public static final Set> DEFAULT_COMMON_KEYS = Set.of(POSITION, BLOCK_POSITION, LEVEL, NAME); + public static final Set> DEFAULT_ENTITY_KEYS = Sets.union(DEFAULT_COMMON_KEYS, Set.of(ENTITY)); + public static final Set> DEFAULT_SERVER_ENTITY_KEYS = Sets.union(DEFAULT_COMMON_KEYS, Set.of(ENTITY, SERVER)); + public static final Set> DEFAULT_COMMAND_KEYS = Sets.union(DEFAULT_COMMON_KEYS, Set.of(COMMAND_SOURCE_STACK, SERVER)); + public static final Set> DEFAULT_COMMAND_ENTITY_KEYS = Sets.union(DEFAULT_COMMON_KEYS, Set.of(ENTITY, COMMAND_SOURCE_STACK, SERVER)); + + private static PermissionContext.Key fabricKey(String path) { + return new PermissionContextKey<>(Identifier.fromNamespaceAndPath("fabric", path)); + } + + @Override + public String toString() { + return "PermissionContext.Key[" + id + "]"; + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionNodeImpl.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionNodeImpl.java new file mode 100644 index 00000000000..d2e0f4d8b6a --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/PermissionNodeImpl.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.permission; + +import com.mojang.serialization.Codec; + +import net.minecraft.resources.Identifier; + +import net.fabricmc.fabric.api.permission.v1.PermissionNode; + +public record PermissionNodeImpl(Identifier key, Codec codec) implements PermissionNode { +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/package-info.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/package-info.java new file mode 100644 index 00000000000..2c36c71769d --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/impl/permission/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NullMarked +package net.fabricmc.fabric.impl.permission; + +import org.jspecify.annotations.NullMarked; diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/CommandSourceStackMixin.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/CommandSourceStackMixin.java new file mode 100644 index 00000000000..2571b60c091 --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/CommandSourceStackMixin.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.permission; + +import java.util.UUID; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.permissions.PermissionSet; +import net.minecraft.util.Util; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import net.fabricmc.fabric.api.permission.v1.PermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionContextOwner; +import net.fabricmc.fabric.impl.permission.CommandPermissionContext; + +@Mixin(CommandSourceStack.class) +public abstract class CommandSourceStackMixin implements PermissionContextOwner, CommandPermissionContext.Extension { + @Unique + private final PermissionContext context = new CommandPermissionContext((CommandSourceStack) ((Object) this)); + @Unique + private PermissionContext.Type sourceType = PermissionContext.Type.SYSTEM; + @Unique + private UUID sourceUuid = Util.NIL_UUID; + + @Inject(method = "(Lnet/minecraft/commands/CommandSource;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec2;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/server/permissions/PermissionSet;Ljava/lang/String;Lnet/minecraft/network/chat/Component;Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/world/entity/Entity;)V", at = @At("TAIL")) + private void storeOriginalSource(CommandSource source, Vec3 position, Vec2 rotation, ServerLevel level, PermissionSet permissions, String textName, Component displayName, MinecraftServer server, Entity entity, CallbackInfo ci) { + this.sourceType = switch (entity) { + case Player _ -> PermissionContext.Type.PLAYER; + case Entity _ -> PermissionContext.Type.ENTITY; + case null -> PermissionContext.Type.SYSTEM; + }; + this.sourceUuid = switch (entity) { + case Entity _ -> entity.getUUID(); + case null -> Util.NIL_UUID; + }; + } + + @SuppressWarnings("DataFlowIssue") + @ModifyReturnValue(method = "/^with/ desc=/CommandSourceStack;$/", at = @At("RETURN")) + private CommandSourceStack copyOriginalOwner(CommandSourceStack result) { + ((CommandSourceStackMixin) (Object) result).sourceUuid = this.sourceUuid; + ((CommandSourceStackMixin) (Object) result).sourceType = this.sourceType; + return result; + } + + @Override + public PermissionContext.Type fabric_getType() { + return this.sourceType; + } + + @Override + public UUID fabric_getUuid() { + return this.sourceUuid; + } + + @Override + public PermissionContext getPermissionContext() { + return this.context; + } +} diff --git a/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/EntityMixin.java b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/EntityMixin.java new file mode 100644 index 00000000000..6d12ef42a6b --- /dev/null +++ b/fabric-permission-api-v1/src/main/java/net/fabricmc/fabric/mixin/permission/EntityMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.permission; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; + +import net.fabricmc.fabric.api.permission.v1.PermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionContextOwner; +import net.fabricmc.fabric.impl.permission.EntityPermissionContext; + +@Mixin(Entity.class) +public abstract class EntityMixin implements PermissionContextOwner { + @Unique + private PermissionContext context; + + @Inject(method = "", at = @At("TAIL")) + private void createPermissionContext(EntityType type, Level level, CallbackInfo ci) { + this.context = new EntityPermissionContext((Entity) ((Object) this)); + } + + @Override + public PermissionContext getPermissionContext() { + return this.context; + } +} diff --git a/fabric-permission-api-v1/src/main/resources/assets/fabric-permission-api-v1/icon.png b/fabric-permission-api-v1/src/main/resources/assets/fabric-permission-api-v1/icon.png new file mode 100644 index 00000000000..12c4531de92 Binary files /dev/null and b/fabric-permission-api-v1/src/main/resources/assets/fabric-permission-api-v1/icon.png differ diff --git a/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.classtweaker b/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.classtweaker new file mode 100644 index 00000000000..f2f7960245a --- /dev/null +++ b/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.classtweaker @@ -0,0 +1,4 @@ +classTweaker v1 official + +transitive-inject-interface net/minecraft/commands/CommandSourceStack net/fabricmc/fabric/api/permission/v1/PermissionContextOwner +transitive-inject-interface net/minecraft/world/entity/Entity net/fabricmc/fabric/api/permission/v1/PermissionContextOwner diff --git a/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.mixins.json b/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.mixins.json new file mode 100644 index 00000000000..14b25e44a1d --- /dev/null +++ b/fabric-permission-api-v1/src/main/resources/fabric-permission-api-v1.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.permission", + "compatibilityLevel": "JAVA_25", + "mixins": [ + "CommandSourceStackMixin", + "EntityMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + } +} diff --git a/fabric-permission-api-v1/src/main/resources/fabric.mod.json b/fabric-permission-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 00000000000..c02fd8fd86f --- /dev/null +++ b/fabric-permission-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "fabric-permission-api-v1", + "name": "Fabric Permission API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-permission-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.18.4" + }, + "entrypoints": { + }, + "description": "General-use permission api", + "mixins": [ + "fabric-permission-api-v1.mixins.json" + ], + "accessWidener": "fabric-permission-api-v1.classtweaker", + "custom": { + "fabric-api:module-lifecycle": "experimental" + } +} diff --git a/fabric-permission-api-v1/src/test/java/net/fabricmc/fabric/test/permission/unit/PermissionContextTests.java b/fabric-permission-api-v1/src/test/java/net/fabricmc/fabric/test/permission/unit/PermissionContextTests.java new file mode 100644 index 00000000000..0f333ea034e --- /dev/null +++ b/fabric-permission-api-v1/src/test/java/net/fabricmc/fabric/test/permission/unit/PermissionContextTests.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.permission.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.minecraft.resources.Identifier; +import net.minecraft.server.permissions.PermissionLevel; +import net.minecraft.util.Util; + +import net.fabricmc.fabric.api.permission.v1.MutablePermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionContext; + +public class PermissionContextTests { + private static final PermissionContext.Key OBJECT_KEY = PermissionContext.key(Identifier.fromNamespaceAndPath("test", "object")); + private MutablePermissionContext context; + + @BeforeEach + void setUp() { + context = PermissionContext.create(Util.NIL_UUID, PermissionContext.Type.OTHER, PermissionLevel.ALL); + } + + // Test storing and retrieving values. + @Test + void storeAndRetrieve() { + var object = new Object(); + + assertNull(context.get(OBJECT_KEY)); + + context.set(OBJECT_KEY, object); + assertEquals(context.get(OBJECT_KEY), object); + + context.set(OBJECT_KEY, null); + assertNull(context.get(OBJECT_KEY)); + + assertNull(context.get(PermissionContext.ENTITY)); + } + + // Test orElse fallback methods. + @Test + void orElse() { + var a = new Object(); + var b = new Object(); + + assertEquals(context.orElse(OBJECT_KEY, b), b); + + context.set(OBJECT_KEY, a); + assertEquals(context.orElse(OBJECT_KEY, b), a); + } +} diff --git a/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/PermissionTestMod.java b/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/PermissionTestMod.java new file mode 100644 index 00000000000..02fdc271aac --- /dev/null +++ b/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/PermissionTestMod.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.permission; + +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; + +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.IdentifierArgument; +import net.minecraft.commands.arguments.NbtTagArgument; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TextComponentTagVisitor; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.permissions.PermissionLevel; +import net.minecraft.server.players.NameAndId; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.permission.v1.MutablePermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionContext; +import net.fabricmc.fabric.api.permission.v1.PermissionEvents; +import net.fabricmc.fabric.api.permission.v1.PermissionNode; +import net.fabricmc.fabric.api.permission.v1.PermissionPredicates; +import net.fabricmc.fabric.test.permission.example.PermissionMap; + +public class PermissionTestMod implements ModInitializer, PermissionEvents.OnRequest, PermissionEvents.PrepareOfflinePlayer { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final PermissionContext.Key OBJECT_KEY = PermissionContext.key(Identifier.fromNamespaceAndPath("test", "object_key")); + + private static final PermissionNode ON_STONE = PermissionNode.of(Identifier.fromNamespaceAndPath("test", "on_stone")); + private static final PermissionNode IS_ENTITY = PermissionNode.of(Identifier.fromNamespaceAndPath("test", "is_entity")); + private static final PermissionNode ABOVE_SEA = PermissionNode.of(Identifier.fromNamespaceAndPath("test", "above_sea")); + private static final PermissionNode MAGIC = PermissionNode.ofInteger(Identifier.fromNamespaceAndPath("test", "magic")); + + private final PermissionMap globalPermissionMap = new PermissionMap(); + + @Override + public void onInitialize() { + CommandRegistrationCallback.EVENT.register(this::registerCommands); + PermissionEvents.ON_REQUEST.register(this); + PermissionEvents.PREPARE_OFFLINE_PLAYER.register(this); + + this.runBasicTest(); + ServerLifecycleEvents.SERVER_STARTED.register(this::runServerTest); + } + + private void runBasicTest() { + int value = RandomSource.createThreadLocalInstance().nextInt(); + + this.globalPermissionMap.set(MAGIC.key(), value); + + PermissionContext context = PermissionContext.create(UUID.randomUUID(), PermissionContext.Type.OTHER, PermissionLevel.ADMINS); + + int valueMainCheck = context.checkPermission(MAGIC, value + 1); + + if (valueMainCheck != value) { + throw new IllegalStateException("Permission check failed! valueMainCheck != value, d=" + (valueMainCheck - value)); + } + } + + private void runServerTest(MinecraftServer server) { + PermissionContext.offlinePlayer(NameAndId.createOffline("TinyPotato"), server).thenAcceptAsync(context -> { + if (context.get(OBJECT_KEY) == null) { + throw new IllegalStateException("Context wasn't modified correctly!"); + } + + int value = RandomSource.createThreadLocalInstance().nextInt(); + this.globalPermissionMap.set(MAGIC.key(), value); + + int valueMainCheck = context.checkPermission(MAGIC, value + 1); + + if (valueMainCheck != value) { + throw new IllegalStateException("Permission check failed! valueMainCheck != value, d=" + (valueMainCheck - value)); + } + }, server); + } + + private void registerCommands(CommandDispatcher dispatcher, CommandBuildContext context, Commands.CommandSelection selection) { + dispatcher.register(literal("permissions") + .then( + literal("set").then(argument("permission", IdentifierArgument.id()).then(argument("value", NbtTagArgument.nbtTag()).executes(this::setPermissionValue))) + ) + .then( + literal("get").then(argument("permission", IdentifierArgument.id()).executes(this::getPermissionValue)) + ) + .then( + literal("check_bool").then(argument("permission", IdentifierArgument.id()).executes(this::checkPermissionValue)) + ) + .then( + literal("on_stone_command").requires(PermissionPredicates.require(ON_STONE, false)).executes(this::onStoneCommand) + ) + ); + } + + private int setPermissionValue(CommandContext context) { + Identifier id = IdentifierArgument.getId(context, "permission"); + Tag value = NbtTagArgument.getNbtTag(context, "value"); + + if (context.getSource().getPlayer() instanceof ServerPlayer player) { + context.getSource().getServer().getPlayerList().sendPlayerPermissionLevel(player); + } + + this.globalPermissionMap.set(id, value); + return 1; + } + + private int getPermissionValue(CommandContext context) { + Identifier id = IdentifierArgument.getId(context, "permission"); + Tag value = this.globalPermissionMap.getRaw(id); + + context.getSource().sendSystemMessage(value != null ? new TextComponentTagVisitor("", TextComponentTagVisitor.RichStyling.INSTANCE).visit(value) : Component.literal("")); + return 1; + } + + private int checkPermissionValue(CommandContext context) { + Identifier id = IdentifierArgument.getId(context, "permission"); + + context.getSource().sendSystemMessage(Component.literal(context.getSource().checkPermission(id).getSerializedName())); + return 1; + } + + private int onStoneCommand(CommandContext context) { + context.getSource().sendSystemMessage(Component.literal("You got the stone permission")); + return 1; + } + + @SuppressWarnings("unchecked") + @Override + public @Nullable T handlePermissionRequest(PermissionContext context, PermissionNode permission) { + Level level = context.get(PermissionContext.LEVEL); + BlockPos blockPos = context.get(PermissionContext.BLOCK_POSITION); + Entity entity = context.get(PermissionContext.ENTITY); + + if (permission.codec() == Codec.BOOL) { + if (permission.equals(ON_STONE) && level != null && blockPos != null) { + return (T) Boolean.valueOf(level.getBlockState(blockPos.below()).is(Blocks.STONE)); + } + + if (permission.equals(IS_ENTITY)) { + return (T) Boolean.valueOf(entity != null); + } + + if (permission.equals(ABOVE_SEA) && blockPos != null && level != null) { + return (T) Boolean.valueOf(level.getSeaLevel() < blockPos.getY()); + } + } + + return this.globalPermissionMap.get(permission.key(), permission.codec()); + } + + @Override + public @NonNull CompletableFuture<@Nullable Consumer> prepareOfflinePlayer(PermissionContext context, MinecraftServer server) { + LOGGER.info("Preparing for offline player check for {} (also known as {})!", context.uuid(), context.get(PermissionContext.NAME)); + return CompletableFuture.completedFuture(mutCtx -> { + mutCtx.set(OBJECT_KEY, new Object()); + LOGGER.info("Modified context for {}!", mutCtx.uuid()); + }); + } +} diff --git a/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/example/PermissionMap.java b/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/example/PermissionMap.java new file mode 100644 index 00000000000..49b0dadc918 --- /dev/null +++ b/fabric-permission-api-v1/src/testmod/java/net/fabricmc/fabric/test/permission/example/PermissionMap.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.permission.example; + +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; + +import com.mojang.serialization.Codec; +import org.jspecify.annotations.Nullable; + +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.Identifier; + +public record PermissionMap(Map permissions) { + public PermissionMap() { + this(new HashMap<>()); + } + + public void set(Identifier identifier, Tag tag) { + this.permissions.put(identifier, new PermissionValue(tag)); + } + + public void set(Identifier identifier, boolean val) { + this.permissions.put(identifier, new PermissionValue(ByteTag.valueOf(val))); + } + + public void set(Identifier identifier, int val) { + this.permissions.put(identifier, new PermissionValue(IntTag.valueOf(val))); + } + + public void set(Identifier identifier, String val) { + this.permissions.put(identifier, new PermissionValue(StringTag.valueOf(val))); + } + + @Nullable + public T get(Identifier identifier, Codec codec) { + PermissionValue value = this.permissions.get(identifier); + return value != null ? value.get(codec) : null; + } + + public Tag getRaw(Identifier identifier) { + PermissionValue value = this.permissions.get(identifier); + return value != null ? value.tag() : null; + } + + public record PermissionValue(Tag tag, Map, Optional> map) { + public PermissionValue(Tag tag) { + this(tag, Collections.synchronizedMap(new IdentityHashMap<>())); + } + + @SuppressWarnings("unchecked") + @Nullable + public T get(Codec codec) { + Optional val = (Optional) this.map.get(codec); + + //noinspection OptionalAssignedToNull + if (val != null) { + return val.orElse(null); + } + + Optional parse = codec.parse(NbtOps.INSTANCE, tag).result(); + this.map.put(codec, (Optional) parse); + return parse.orElse(null); + } + } +} diff --git a/fabric-permission-api-v1/src/testmod/resources/fabric.mod.json b/fabric-permission-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 00000000000..157248335de --- /dev/null +++ b/fabric-permission-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": 1, + "id": "fabric-permission-api-v1-testmod", + "name": "Fabric Permission API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-permission-api-v1": "*" + }, + "entrypoints": { + "main": [ + "net.fabricmc.fabric.test.permission.PermissionTestMod" + ] + }, + "custom": { + "fabric-api:module-lifecycle": "experimental" + } +} diff --git a/gradle.properties b/gradle.properties index c435305d50d..d77871075cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,6 +38,7 @@ fabric-model-loading-api-v1-version=7.0.11 fabric-networking-api-v1-version=6.3.0 fabric-object-builder-api-v1-version=23.0.13 fabric-particles-v1-version=5.0.14 +fabric-permission-api-v1-version=1.0.0 fabric-recipe-api-v1-version=9.0.13 fabric-registry-sync-v0-version=7.0.12 fabric-renderer-api-v1-version=10.1.0 diff --git a/settings.gradle b/settings.gradle index 5b58ec989c1..0d3748dc6b1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -53,6 +53,7 @@ include 'fabric-message-api-v1' include 'fabric-networking-api-v1' include 'fabric-object-builder-api-v1' include 'fabric-particles-v1' +include 'fabric-permission-api-v1' include 'fabric-recipe-api-v1' include 'fabric-registry-sync-v0' //include 'fabric-renderer-api-v1'