Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PermissionAPI Replacement #96

Open
TheMeinerLP opened this issue Nov 8, 2024 Discussed in #93 · 2 comments
Open

PermissionAPI Replacement #96

TheMeinerLP opened this issue Nov 8, 2024 Discussed in #93 · 2 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@TheMeinerLP
Copy link
Collaborator

Discussed in #93

Originally posted by TheMeinerLP November 7, 2024

Intro

In the past you could check in Minestom with Player#hasPermission(String) if the player or entity has the rights.

Problem

There have been discussions that some like to use enum values for permissions and others want to use int values and the next one a string again.

If you want to combine multiple permission systems, the current system is too static.
As a result, the upstream has decided to remove the Permission API (Minestom#2302)

This is a disadvantage for our community, even if it is small.

Solution

In order to solve this problem more technically, we came up with an idea.
We build a permission registry that can combine several permission systems into one. Each developer has the choice of how the best permission system looks from his definition.

Some want to have enum values, others with strings, or others with whole class structures or instances.

In order to realize this, I have prepared a mock-up, which you can view here:

public class ExampleUsage {

    public static void main(String[] args) {
        PermissionManager instance = PermissionManager.getInstance();
        // Providers
        instance.registerProvider(PermissionProvider.<String, StringPermissionAttachment>providerBuilder()
                .permissionIdentifier(Key.key("example"))
                .attachmentType(StringPermissionAttachment.class::isInstance)
                .permissionChecker(ExampleUsage::hasPermission)
                .senderType(ExampleEntity.class::isInstance)
                .build());
        // Tranformers
        instance.registerTransformer(PermissionTransformerHolder.<EnumExampleAttachment, StringPermissionAttachment>builder()
                .permissionTransformer(ExampleUsage::convertFromEnumToString)
                .from(EnumExampleAttachment.class::isInstance)
                .to(StringPermissionAttachment.class::isInstance)
                .identifier(Key.key("example"))
                .build());



        ExampleEntity boss = new ExampleEntity();
        ExampleEntity2 employee = new ExampleEntity2();
        System.out.println(boss.hasPermission(EnumExampleAttachment.COOKIE_RIGHTS));
        System.out.println(boss.hasPermission(EnumExampleAttachment.SODA_RIGHTS));
        System.out.println(boss.hasPermission(StringPermissionAttachment.of("example")));
        System.out.println(employee.hasPermission(StringPermissionAttachment.of("example")));
        System.out.println("-------------------");
        instance.unregisterProvider(Key.key("example"));
        instance.registerProvider(PermissionProvider.<String, StringPermissionAttachment>providerBuilder()
                .permissionIdentifier(Key.key("example"))
                .attachmentType(StringPermissionAttachment.class::isInstance)
                .permissionChecker(ExampleUsage::hasPermission)
                .senderType(ExampleEntity2.class::isInstance)
                .build());
        System.out.println(boss.hasPermission(EnumExampleAttachment.COOKIE_RIGHTS));
        System.out.println(boss.hasPermission(EnumExampleAttachment.SODA_RIGHTS));
        System.out.println(boss.hasPermission(StringPermissionAttachment.of("example")));
        System.out.println(employee.hasPermission(StringPermissionAttachment.of("example")));
    }

    private static StringPermissionAttachment convertFromEnumToString(EnumExampleAttachment enumExampleAttachment) {
        return StringPermissionAttachment.of(enumExampleAttachment.name());
    }

    static class ExampleEntity implements CommandSender, Permissible {
        @Override
        public @NotNull Identity identity() {
            return null;
        }

        @Override
        public boolean hasPermission(PermissionAttachment<?> permission) {
            return PermissionManager.getInstance().hasPermission(permission, this);
        }

        @Override
        public @NotNull TagHandler tagHandler() {
            return null;
        }

        @Override
        public @NotNull Set<Permission> getAllPermissions() {
            return Set.of();
        }
    }

    static class ExampleEntity2 implements CommandSender, Permissible {
        @Override
        public @NotNull Identity identity() {
            return null;
        }

        @Override
        public boolean hasPermission(PermissionAttachment<?> permission) {
            return PermissionManager.getInstance().hasPermission(permission, this);
        }

        @Override
        public @NotNull TagHandler tagHandler() {
            return null;
        }

        @Override
        public @NotNull Set<Permission> getAllPermissions() {
            return Set.of();
        }
    }

    private static boolean hasPermission(PermissionAttachment<String> permission, CommandSender sender) {
        if (permission.permissionValue().equals(EnumExampleAttachment.SODA_RIGHTS.name())) {
            return true;
        }
        return permission.permissionValue().equals("example");
    }
}

The solution offers every developer the opportunity to integrate their own solution and even address different transmitters. In addition, the API is hardened and yet modularly expandable.

The goal ...

... was to offer a pure API that is intended as a framework in the server for checking permissions, and only the manager instance is addressed internally. The actual checks are then carried out by the developer, and only the internal interface interlocking is then used by the framework.

Current status/draft PR

Soon

Proposal / Future Changes / Missing features

  • Another possibility to map data from system A to system B would be to add a possibility to the system to convert data using transformers, e.g.
  • In the given mockup, for example, the possibility to completely replace a provider and replace it with its own is missing.
  • A standard permission provider is not specified in the mockup, but will be included in the finalization for a basic system, which can optionally be turned on or off with a ServerFlag to decide between RAW (no permission system) or Basic (string-based permission system).

Feedback

This introduction to the system is intended to stimulate discussion and lead to an exchange of opinions!

@TheMeinerLP TheMeinerLP added enhancement New feature or request help wanted Extra attention is needed labels Nov 8, 2024
@BradBot1
Copy link

BradBot1 commented Nov 9, 2024

How about events? Sending like a "EntityPermissionEvent" that is used to check if an entity has permission isn't a large change and also interops with systems developers are used to. This would also allow different permission on different players, a weird but cool usecase.

Something like so:

@AllArgsConstructor
public class EntityPermissionEvent ... {

    @Getter
    @Setter
    private Entity entity;


    @Getter
    @Setter
    private String permission;

    // assume is cancellable or holds boolean for if permitted

}

Then you'd just do:

Player player = ...;
player.eventNode().addListener(EntityPermissionEvent.class, event -> {
    if (event.getPermission().equals("example.permission")) event.cancel(); // no one can have this permission
});

It'd also be need to have a helper method on the player class to automate the event sending and validation so that callers can oneline things.

And as for the permission being represented as a string, that's just for show. Maybe an object could be used and each event handler would check for types it can proccess?

@TheMeinerLP
Copy link
Collaborator Author

How about events? Sending like a "EntityPermissionEvent" that is used to check if an entity has permission isn't a large change and also interops with systems developers are used to. This would also allow different permission on different players, a weird but cool usecase.

Something like so:

@AllArgsConstructor
public class EntityPermissionEvent ... {

    @Getter
    @Setter
    private Entity entity;


    @Getter
    @Setter
    private String permission;

    // assume is cancellable or holds boolean for if permitted

}

Then you'd just do:

Player player = ...;
player.eventNode().addListener(EntityPermissionEvent.class, event -> {
    if (event.getPermission().equals("example.permission")) event.cancel(); // no one can have this permission
});

It'd also be need to have a helper method on the player class to automate the event sending and validation so that callers can oneline things.

And as for the permission being represented as a string, that's just for show. Maybe an object could be used and each event handler would check for types it can proccess?

The approach is interesting, but goes somewhat against where we might want to go with API design. The mockup you showed aims at string, we want a generic solution, the event system cannot handle generic classes, for example, and would not work at all in that case. In addition, we cannot currently estimate how well optimized the EventNode system is from the graph implementation.
Due to the current architecture, events always have to take place via the MinecraftServer god object, which we would avoid in my case and the system would be more relaxed.

For this purpose, the provider makes the permission query on the actual system and covers the event in this case.

A PermissionProvider is the PermissionBackend for the mockup, where LuckPerms or other system data could be delivered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants