Skip to content

feat: fine-grained selector permission#31

Merged
DrexHD merged 20 commits intoDrexHD:mainfrom
qwertycxz:feat-selector
Aug 5, 2025
Merged

feat: fine-grained selector permission#31
DrexHD merged 20 commits intoDrexHD:mainfrom
qwertycxz:feat-selector

Conversation

@qwertycxz
Copy link
Contributor

@qwertycxz qwertycxz commented Jun 26, 2025

  1. Mixin EntityArgument to implement fine-grained selector permission.
  2. Make detailed introduction in README.md.
  3. Update README.md and CHANGELOG.md.
Details, also available in README.md

Permissions

Permission Description
... ...
minecraft.selector.entity.<command>.<selector> Allow selecting non-player entities using the selector within command
minecraft.selector.player.<command>.<selector> Allow selecting nonself players using the selector within command
minecraft.selector.self.<command>.<selector> Allow selecting self using the selector within command

Meta

Also sometimes referred to as "options" or "variables".

Incorrect types are considered undefined values.

Meta Type Description
minecraft.selector.limit.<command>.<selector> Integer Limit the maximum number of entities that can be selected using the selector within command
minecraft.selector.weight.<command>.<selector> Integer Selector weight, see selection weight for more infomation

Selectors

Command blocks and datapacks bypass all selector permission checks.

By default, granting minecraft.selector allows players to use any selector in commands they have access to.

Fine-grained permission control operates as follows. Note that this mod restricts based on selection results, not
raw selector syntax. Using player names, UUIDs, or selectors like @e are equivalent if they produce identical
results.

Value of selector

Defined in the Arguments section of each command's Minecraft Wiki page.

For example, the /teleport command uses
<targets> and <destination> as selectors.

Scope Control

Use these permissions to define selector scope:

  • minecraft.selector.entity.<command>.<selector>
  • minecraft.selector.player.<command>.<selector>
  • minecraft.selector.self.<command>.<selector>

Commands fail if a player attempts to select unauthorized entities.

Example

- Allow:
  minecraft.command.teleport
  minecraft.selector
  minecraft.selector.self.teleport.targets
  minecraft.selector.entity.teleport.targets
  minecraft.selector.self.teleport.destination
  minecraft.selector.player.teleport.destination
- Deny:
  minecraft.selector.*

Players may teleport themselves (self for targets) or non-player entities (entity for targets) to themselves
(self for destination) or nonself players (player for destination).

Entity Limit

Set meta minecraft.selector.limit.<command>.<selector> to restrict the maximum number of entities selectable via a
given selector.

No limit is applied if this meta is unset.

Selection Weight

Controlled by meta minecraft.selector.weight.<command>.<selector>.

Entities without weight settings can always select any target and be selected by any selector. When both entities have
weight values, a selector can only select targets whose weight is less than or equal to its own.

Example

# Global permissions
- Allow:
  minecraft.command.gamemode
  minecraft.selector
  minecraft.selector.self.gamemode.target
  minecraft.selector.player.gamemode.target
- Deny:
  minecraft.selector.*
# Player-specific metadata
Player1: minecraft.selector.weight.gamemode.target = 7
Player2: minecraft.selector.weight.gamemode.target = -1
Player3: minecraft.selector.weight.gamemode.target = 7
Player4: (no weight set)
Player Can modify gamemode of Reason
Player1 All players Weight ($7$) ≥ all others' weights
Player2 Only Player2 and Player4 Weight ($-1$) < Player1/Player3 ($7$)
No weight restriction for Player4
Player3 All players Weight ($7$) ≥ all others' weights
Player4 All players No weight restriction → unrestricted access

BTW, I updated #30 stuff in CHANGELOG.md, as you only updated it in README.md.

1. Control one can or cannot select self, nonself players and non-player entities
2. Selecting amount limit
3. Selecting weight: entity with no weight can always select or be selected. If both have weight, then one cannot select th other who has bigger weight.

The above permission settings could be applied to specified command and selectors.
For Example, `/teleport` has two selectors: `targets` and `destination`. You could separated control them.
1. Update & format README.md
2. Update & format CHANGELOG.md
@qwertycxz qwertycxz marked this pull request as draft June 27, 2025 09:33
@qwertycxz

This comment was marked as resolved.

@DrexHD
Copy link
Owner

DrexHD commented Jun 28, 2025

Thank you very much, looks like you have some cool/useful ideas.
I am a bit busy currently, so it may take a couple days until I review this.
One thing I wanted to note is: that I don't think you should check selectedWeight for non player entites, because you are already (reasonably) only checking the sourceWeight for players. So this would be a bit inconsistent imo. And as you mentioned, no common implementation of the permission api even supports this.

1. Add support for `GameProfileArgument`, `ScoreHolderArgument` and `WaypointArgument`
2. Refactor directory structure
3. Improve performance by early return and async functions
4. Check `selectedWeight` only for players.
@qwertycxz

This comment was marked as resolved.

@qwertycxz

This comment was marked as resolved.

@DrexHD
Copy link
Owner

DrexHD commented Jun 29, 2025

I have already PRed a fix to fabric-permission-api to fix the NPE. I have also just pushed a commit to fix/improve the command name resolution.

Some more "problems" I see from a second quick look:

  • weightChecks[weightCheckIndex] is not set in some code paths causing a different NPE
  • Currently this system requires very specific meta keys. I had the idea to check parent keys if there is none set for the current node, eg: minecraft.selector.weight.gamemode.target, then minecraft.selector.weight.gamemode, then minecraft.selector.weight. So you can specify a general weight, which can be overwritten by a more specific one. However this would have to be done for source and all targets, which would increase the amount of meta requests even further, so I am not yet sure if that is the way to go!

@qwertycxz

This comment was marked as off-topic.

This commit has no functional changes.
1. Use static imports as many as possible
2. Rename `ArgumentPermission.check` to `ArgumentPermission.validate`
3. `Iterables.getLast(context.getNodes())` -> `context.getNodes().getLast()`
4. Use parallelStream instead of for-loop. Fix potential `NullPointerError` as well as improve performance
5. fix logger format bug
6. fabric_permissions_version for 1.21.6: 0.4.0 -> 0.4.1
This commit has no functional changes.
Copy link
Contributor Author

@qwertycxz qwertycxz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About some refactors.


String[] parts;
if (context.getRootNode() instanceof RootCommandNode) {
parts = context.getNodes().parallelStream().map(node -> node.getNode().getName()).toArray(String[]::new);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the root node is THE root node (i.e. not alias), the infomation stored in context is enough to get name from it.


var sourcePlayer = source.getPlayer().getGameProfile();
try {
allOf(selected.parallelStream().mapMulti((object, consumer) -> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parallelStream instead of for-loop. No more NPE and more efficient!

@DrexHD
Copy link
Owner

DrexHD commented Jun 30, 2025

Do you have some examples of use cases (of the specific selector permissions) that you have in mind with this implementation and what permission setup would be required to "implement them".

I am still a bit skeptical that this will be a rather complex system that barely anyone will ever use, because it may be quite complicated and (maybe?) isn't very practical for real use cases. (I am not sure, that's why I am asking if you could maybe explain more of your vision on that)

For example: In the readme teleport example you deny minecraft.selector.*, but wont that mean users have to manually enable selectors for all other commands players may get, which seems quite annoying if you only want to limit the teleport selector, but not others.

I'm strongly against the idea that this mod should look up minecraft.selector.weight.gamemode.target, then minecraft.selector.weight.gamemode, then minecraft.selector.weight. As I think we could make a separate mod to do so.

I am not sure about using a separate mod for that, because it would lack the context. Because I don't think this approach of recursively looking up meta data will be correct for all meta keys from other mods, which wont expect this behavior.
Or am I misinterpreting your separate mod idea?

@qwertycxz

This comment was marked as resolved.

@qwertycxz

This comment was marked as resolved.

@qwertycxz
Copy link
Contributor Author

qwertycxz commented Jul 16, 2025

A new naming idea

Our idea could coexist.

Your idea: <command_name>.<rest>.<of>.<the>.<command>

My idea: <command_name>.<selector_argument>

Just let them coexist, everything would work great.

  • For commands having no argument, we never validate them.

  • For commands having one argument:

    • if this argument is not a selector, we never validate them.
    • if this argument is a selector, like /teleport <destination>. Your idea and my idea share the same name teleport.destination as well as the same meaning.
  • For commands having more than one argument, out ideas never conflict.

I had a try in here. It was a mess.

So I turn to a new idea. A compromised idea.

The compromised idea: <command_name>.<selector_argument>.<rest>.<of>.<the>.<command>

Command Required selector permission
/waypoint modify @s color reset minecraft.selector.self.waypoint.waypoint.modify.waypoint.color.reset
/tp @s @s minecraft.selector.self.teleport.targets.targets.destination
minecraft.selector.self.teleport.destination.targets.destination

It can cover 100% usage. Everyone will be happy. :)

Although the name is very long, MW4fpa could help.

@DrexHD
Copy link
Owner

DrexHD commented Jul 16, 2025

Which approach do you prefer? It’s your call! :)

I want to keep the maintenance burden for this feature minimal, because I don't think this (super fine selector control) is something that many server admins need.
The best way to achieve this is probably to keep it as a suggested dependency.

For example: In the readme teleport example you deny minecraft.selector.*, but wont that mean users have to manually enable selectors for all other commands players may get, which seems quite annoying if you only want to limit the teleport selector, but not others.

Did you have an answer to this? This is part of my skepticism about the usefulness of this PR. Permissions just aren't a great configuration system for something complex like this.

@qwertycxz
Copy link
Contributor Author

qwertycxz commented Jul 17, 2025

minecraft.selector.* is granted by default. The whole fine selector permission is off if user doesn't need it.

For example: In the readme teleport example you deny minecraft.selector.*, but wont that mean users have to manually_ enable selectors for all other commands players may get, which seems quite annoying if you only want to limit the teleport selector, but not others.

Just don't deny minecraft.selector.*, but deny minecraft.selector.self.teleport.*, minecraft.selector.player.teleport.* or minecraft.selector.entity.teleport.*.

Is that means users have to deny all the three permissions? No, only need to deny one or two of them. If you want to deny all selectors in /teleport, just deny minecraft.command.teleport.destination and minecraft.command.teleport.targets.*


The best way to achieve this is probably to keep it as a suggested dependency.

Great minds think alike! 😀

@qwertycxz

This comment was marked as resolved.

@qwertycxz

This comment was marked as resolved.

@DrexHD
Copy link
Owner

DrexHD commented Jul 18, 2025

I am pretty sure that entity.getCommandSource(world); doesn't exist before 1.21.2 (?) or something around that time.

@DrexHD
Copy link
Owner

DrexHD commented Jul 18, 2025

You shouldn't use #number to reference things, because github will interpret that as an issue / pr reference.
imo the examples should be slightly more clear about what a user actually needs to allow/deny and what is set implicitly. (Implicitly Allow being worded differently than was implicitly allowed and being at the top, before it was actually allowed)

@qwertycxz
Copy link
Contributor Author

How about this?

README.md

Implicitly Allow by default:
  minecraft.selector.* #0
Allow:
  minecraft.command.teleport # /teleport
  minecraft.selector # All selectors
  minecraft.selector.player.teleport.destination.destination #1
  minecraft.selector.entity.teleport.destination.destination #2
  minecraft.selector.entity.teleport.targets.targets.destination #3
  minecraft.selector.player.teleport.facingEntity.* #4
Deny:
  minecraft.selector.player.teleport.* #6
  minecraft.selector.entity.* #5
  minecraft.selector.self.teleport.facingEntity.* #7

Command Behavior:

Command Self Nonself Players Non-player Entities Resulting Behavior
/teleport <destination> <destination> allowed by #𝟎 <destination> allowed by #𝟏 <destination> allowed by #𝟐 Teleport to any entity
/teleport <targets> <destination> <targets> allowed by #𝟎
<destination> allowed by #𝟎
<targets> denied by #𝟓
<destination> denied by #𝟓
<targets> allowed by #𝟑
<destination> denied by #𝟔
Only teleport non-player entities to self
/teleport <location> (No selectors) - - Unrestricted position teleport
/teleport <targets> <location> <targets> allowed by #𝟎 <targets> denied by #𝟓 <targets> denied by #𝟔 Only teleport self to positions
/teleport <targets> <location> facing entity <facingEntity> <targets> allowed by #𝟎
<facingEntity> denied by #𝟕
<targets> denied by #𝟓
<facingEntity> allowed by #𝟒
<targets> denied by #𝟔
<facingEntity> denied by #𝟔
Teleport self to positions while facing nonself players

@DrexHD
Copy link
Owner

DrexHD commented Jul 18, 2025

Maybe I don't quite understand what you want to say with the Implicitly Allow by default.
eg.: If someone wants to copy this example, do they have to also allow this or does another permission listed in Allow "include" this, or something else.

Another thing that I just thought about is that luckperms apply-sponge-implicit-wildcards config option should be mentioned somewhere. Because your examples probably require that to be disabled, but the default is enabled!

@qwertycxz
Copy link
Contributor Author

Maybe I don't quite understand what you want to say with the Implicitly Allow by default. eg.: If someone wants to copy this example, do they have to also allow this or does another permission listed in Allow "include" this, or something else.

Mainly because I want to highlight the default. I have already moved it.

Another thing that I just thought about is that luckperms apply-sponge-implicit-wildcards config option should be mentioned somewhere. Because your examples probably require that to be disabled, but the default is enabled!

Actually, no. The example works whether apply-sponge-implicit-wildcards is enabled.

But you remind me. The readme of MW4fpa should have mentioned that there is no apply-sponge-implicit-wildcards.

@qwertycxz
Copy link
Contributor Author

MW4fpa is finally published 😀. Now there is just one more thing before merge this PR.

The new fabric-permissions-api without NPE bug only supports 1.21.6+. We'd rather backport fabric-permissions-api, or drop the support for offline players in 1.20.1, 1.21.1, 1.21.4, 1.21.5.

@DrexHD What do you think?

@DrexHD
Copy link
Owner

DrexHD commented Jul 23, 2025

It should be reasonably easy to manually patch the broken versions with a mixin in VanillaPermission. I don't think lucko is interested in a backport all the way to 1.20.1!

@qwertycxz
Copy link
Contributor Author

qwertycxz commented Jul 28, 2025

Hey @DrexHD. I have some bad news:

As far as I know, The latest version of LuckPerms in 1.20.1 does not support offline metadata. That means we have to drop support for Selection Weight for offline players in 1.20.1.

The mixin of farbic-permissions-api is somehow tricky, though. I can do it. However, since we are dropping the support of 1.20.1, perhaps it is not that bad to drop the support of 1.21.1, 1.21.4 and 1.21.5.

And I forgot to say that, this NPE glitch only affects offline players' /scoreboard Selection Weight. (Only this command affects offline players). So dropping the support is not a big deal. Just let me point out that in README.md and make a small patch, nothing else will be affected.

What do you think?

@DrexHD
Copy link
Owner

DrexHD commented Jul 28, 2025

In that case drop it and clarify it in the README.

@qwertycxz
Copy link
Contributor Author

qwertycxz commented Jul 29, 2025

Sorry, I made a mistake. Here's a correction:

Status

The following list shows which selectors can use fine-grained permissions:


The above text has already added to the README.md. A relative patch is finished as well. I'd say that this PR is ready. 😃

@qwertycxz qwertycxz marked this pull request as ready for review July 29, 2025 08:59
/*if (object instanceof Player onlinePlayer) {
var selectedWeight = get(onlinePlayer, weight, Integer::parseInt);
if (selectedWeight.isPresent() && selectedWeight.get() > sourceWeightValue) {
consumer.accept(failedFuture(new CompletionException(ERROR_SELECTORS_NOT_ALLOWED.create())));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For < 1.21.6, we only check online player's selected weight.

@DrexHD
Copy link
Owner

DrexHD commented Jul 29, 2025

Thanks, I should be able to take a look and fully review this next week!

@DrexHD
Copy link
Owner

DrexHD commented Aug 5, 2025

Thanks! I have now reviewed this and pushed some code style changes. The only actual code changes were:

  1. Always use the old Iterables.getLast, instead of having to maintain two code paths.
  2. I changed parallelStream to stream, because the computations inside the streams are very lightweight and I am pretty sure the context switches harm the performance more than they could provide any gains.

Let me know if these seem reasonable to me.

I installed Metadata Wildcard for fabric-permissions-api and tried to use meta wild keys, but it didn't work as expected.
I used /lp user player1 meta set minecraft.selector.weight 10 and /lp user player2 meta set minecraft.selector.weight 5 and expected player2 to not be able to run /gamemode creative player1, but it worked fine. Doing the same with minecraft.selector.weight.gamemode.target.creative.target worked fine though!

If the meta problem is resolved (explained what I did wrong or fixed) I think this should be ready to merged and released as a beta 0.3 version!

@qwertycxz
Copy link
Contributor Author

Thank you for your review!

I installed Metadata Wildcard for fabric-permissions-api and tried to use meta wild keys, but it didn't work as expected.
I used /lp user player1 meta set minecraft.selector.weight 10 and /lp user player2 meta set minecraft.selector.weight 5 and expected player2 to not be able to run /gamemode creative player1, but it worked fine. Doing the same with minecraft.selector.weight.gamemode.target.creative.target worked fine though!

This is because MW4fpa works like when apply-sponge-implicit-wildcards= false. You need to /lp user player1 meta set minecraft.selector.weight.* 10.

I have already mentioned that in README of MW4fpa. But if you want, I could change the behavior from apply-sponge-implicit-wildcards=false-like to apply-sponge-implicit-wildcards=true-like.

@DrexHD DrexHD merged commit a57834f into DrexHD:main Aug 5, 2025
1 check passed
@DrexHD
Copy link
Owner

DrexHD commented Aug 5, 2025

Thank you for your work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants