Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ca75625
Create Fabric Permission API
Patbox Feb 22, 2026
63f2ba3
Improve javadocs, move from interface to a final class, safer Command…
Patbox Feb 26, 2026
4b87796
Merge branch '26.1' into permission-api
Patbox Feb 26, 2026
5f31c28
Merge branch '26.1' into permission-api
Patbox Feb 26, 2026
98e6e08
Async permission check utility methods, mention possible slowness of …
Patbox Feb 27, 2026
07cc1e1
Provide better support for async permission checks on provider side
Patbox Mar 3, 2026
a83a17b
Merge branch '26.1' into permission-api
Patbox Mar 3, 2026
13d5cda
Fix async check (oops)
Patbox Mar 3, 2026
2bf037a
Wrap permission key and codec into a single object, swap from tristat…
Patbox Mar 4, 2026
050baef
Write package-info, test key serialization
Patbox Mar 4, 2026
e002854
Intern keys, don't use supply async
Patbox Mar 6, 2026
ee133e5
Fix interner being initialized too late
Patbox Mar 6, 2026
60cc5eb
a
Patbox Mar 6, 2026
b9d0c87
Mark as experimental
Patbox Mar 12, 2026
77f1229
Merge branch '26.1' into permission-api
Patbox Mar 14, 2026
703a71e
Merge remote-tracking branch 'origin/permission-api' into permission-api
Patbox Mar 14, 2026
1d1cfc3
Drop serializable context keys for now
Patbox Mar 14, 2026
73eab46
Merge branch '26.1' into permission-api
Patbox Mar 21, 2026
9d52cb9
Rework how permissions are resolved, remove async methods, add server…
Patbox Mar 28, 2026
856cf52
Merge branch '26.1' into permission-api
Patbox Mar 28, 2026
49648a4
Checkstyle fix
Patbox Mar 28, 2026
cbeeb1a
Fix not being marked as experimental in fabric.mod.json
Patbox Mar 28, 2026
f301395
Add a callback for context modification
Patbox Mar 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;

import net.fabricmc.fabric.api.event.Event;
Expand Down Expand Up @@ -101,6 +102,15 @@ default void modify(Item item, Consumer<DataComponentMap.Builder> builderConsume
default void modify(Collection<Item> items, BiConsumer<DataComponentMap.Builder, Item> builderConsumer) {
modify(items::contains, builderConsumer);
}

/**
* Modify the default data components of the specified items.
* @param tag The tag with items to modify
* @param builderConsumer A consumer that provides a {@link DataComponentMap.Builder} to modify the item's components.
*/
default void modify(TagKey<Item> tag, BiConsumer<DataComponentMap.Builder, Item> builderConsumer) {
modify(item -> item.builtInRegistryHolder().is(tag), builderConsumer);
}
}

@FunctionalInterface
Expand Down
14 changes: 14 additions & 0 deletions fabric-permission-api-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -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"
])
Original file line number Diff line number Diff line change
@@ -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 {
<T> MutablePermissionContext set(PermissionContext.Key<T> key, @Nullable T value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/*
* 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.
*
* <p>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<String> NAME = PermissionContextKey.NAME;

/**
* Represents position current position in which permission check is applied.
*/
Key<Vec3> POSITION = PermissionContextKey.POSITION;

/**
* Represents position current block position in which permission check is applied.
*/
Key<BlockPos> BLOCK_POSITION = PermissionContextKey.BLOCK_POSITION;

/**
* Represents entity for which permission check is applied.
*/
Key<Entity> ENTITY = PermissionContextKey.ENTITY;

/**
* Represents command source stack for which permission check is applied.
*/
Key<CommandSourceStack> COMMAND_SOURCE_STACK = PermissionContextKey.COMMAND_SOURCE_STACK;

/**
* Represents level for which permission check is applied.
*/
Key<Level> LEVEL = PermissionContextKey.LEVEL;

/**
* Represents the server to which this context is attached to.
*/
Key<MinecraftServer> 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<MutablePermissionContext> 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<MutablePermissionContext> 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, indented for attaching additional context data.
* This key/value can't be serialized.
*
* @param identifier unique identifier
* @param <T> type of attached
* @return unique key
*/
static <T> Key<T> key(Identifier identifier) {
Objects.requireNonNull(identifier, "identifier cannot be null");

return PermissionContextKey.getOrCreateKey(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 <T> type of value
* @return stored value if it's present, null otherwise
*/
@Nullable
<T> T get(Key<T> key);

/**
* Returns optional value attached to this context, with a fallback.
*
* @param key unique key
* @param defaultValue fallback value, if it's not present
* @param <T> type of value
* @return stored value if it's present, otherwise defaultValue
*/
default <T> T orElse(Key<T> 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<Key<?>> 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.
* The context itself might be serializable, however it's not a strict requirement.
*
* @param <T> type of the context
*/
@ApiStatus.NonExtendable
interface Key<T> {
/**
* Identifier representing this context.
*
* @return id of this key
*/
Identifier id();
}
}
Loading
Loading