Skip to content

Instantly share code, notes, and snippets.

@ItsDoot
Last active November 21, 2019 01:38
Show Gist options
  • Select an option

  • Save ItsDoot/42fb304544b85f84b157929051549a95 to your computer and use it in GitHub Desktop.

Select an option

Save ItsDoot/42fb304544b85f84b157929051549a95 to your computer and use it in GitHub Desktop.
Sponge Permission Presets API

WIP

Summary

Standardize on a mechanism for plugins to define permission group presets, like griefprevention:user or nucleus:admin, that minimalizes plugin boilerplate and improves discoverability through the usage of a permission plugin.

Motivation and Goals

There is not currently a standard, idiomatic way for plugins to specify permission presets that server owners can then apply for simplified plugin setup. Every plugin (especially large ones) should be able to provide sane defaults for a minimal setup, without needing to build their own system for it.

The end product should meet the following goals:

  • Minimal boilerplate from the plugin developer perspective
  • Low feature support cost for permission plugins
  • Discoverable through the usage of the permission plugin

Solution: Expand the PermissionDescription API

From what I can tell over the course of PermissionDescription's life, it is seldom used, because it doesn't add enough value for plugin developers, to make them want to add descriptions for their permissions (server admins can look at the plugin's project page for this information, anyways). By extending the PermissionDescription API to support permission presets, plugin developers may be more willing to document their permissions, especially if they can setup easy defaults for server admins to use.

How are these defaults then applied?

I see two paths, depending on the server admin's preference, using the following examples:

  • Admins can make their user group directly inherit from the griefprevention:user preset.
  • Admins can use a copy function provided by the permission plugin to copy all current nodes from the griefprevention:user preset into the user group.

Introducing: PermissionPreset

Example Usage:

@Listener
public void onInit(GameInitializationEvent event) {
    PermissionService service = Sponge.getServiceManager().provideUnchecked(PermissionService.class);
    service.newPresetBuilder(this)
        .role("user") // or use PermissionPreset.ROLE_USER
        .add(service.newDescriptionBuilder().id("nucleus.afk.base").build(), true, false)
        .add(service.newDescriptionBuilder().id("nucleus.helpop.base").build(), true, false)
        // or alternatively(?):
        .add("nucleus.home.base", null, true, false)
        .register()
}

Why have a firstRunOnly option?

  • For whenever a plugin wants to send out an update, without giving players new permissions without the server admin knowing. Only clean installs will add these new permissions.

Notable changes:

  • PermissionPreset has been added
    • PermissionDescriptions are registered here, with an additional option to only register them during the first run after plugin installation (i.e. only register them when the preset is not yet known to the permissions plugin).
    • Role Identifiers are namespaced by the supplied plugin; before there was user, now there is griefprevention:user, for example.
      • Useful when a server owner doesn't want to apply all plugins' user presets.
  • PermissionDescription is now a simple data aggregate of a permission and its (optional) description.
    • Descriptions are no longer registered directly to the service. Instead they are added to a preset which is then registerd to the service.
    • PermissionDescription#findAssignedSubjects(String) and PermissionDescription#getAssignedSubjects(String) were removed, as they seem unnecessary (just fetch directly from the PermissionService).
  • PermissionService#SUBJECTS_ROLE_TEMPLATE has been replaced with PermissionService#SUBJECTS_PRESET. This naming more accurately describes what the subject collection holds, given the expanded API.

Other Alternatives

The Bukkit route

Allow permissions that themselves grant other permissions. This is a nasty solution because it's hidden and implicit.

The Nucleus route

Have plugins add their own command to grant a given preset type to the specified group. This is a suboptimal solution because it isn't discoverable, and requires a lot of boilerplate.

The GriefPrevention route

Have plugins, during permission checks, also check for a general permission representing the preset. This is a suboptimal solution because it's not discoverable.

package pw.dotdash.msh.test;
import org.spongepowered.api.text.Text;
import javax.annotation.Nullable;
import java.util.Optional;
/**
* A description object for permissions.
*
* <p>The description is meant to provide human readable descriptions and meta
* data for a permission.</p>
*
* <p>Descriptions <strong>DO NOT</strong> have any impact on permission check,
* and are only provided and registered for an informational purpose.</p>
* TODO: Keep ^?
*
* <p>Instances can be built using
* {@link PermissionService#newDescriptionBuilder()}.</p>
*/
public interface PermissionDescription {
/**
* Gets the permission id this description belongs to.
*
* <p>The permission id must be of the specified format as specified using
* EBNF:
* <ul>
* <li>CHARACTER = "A" - "Z" | "a" - "z" | "0" - "9" | "_" | "-"</li>
* <li>NAME = CHARACTER , { CHARACTER }</li>
* <li>TEMPLATE = "&lt" , NAME , "&gt"</li>
* <li>PART = NAME | TEMPLATE</li>
* <li>PERMISSION = NAME , { "." , PART }</li>
* </ul>
* </p>
*
* <p>The following examples shall help you to structure your permissions
* well:
* <ul>
* <li>"myplugin" - Grants everything in myPlugin</li>
* <li>"myplugin.give" - Grants everything related to give including
* all ItemTypes and Enchantments</li>
* <li>"myplugin.give.execute" - Allows the execution of give</li>
* <li>"myplugin.give.type" - Grants all ItemTypes</li>
* <li>"myplugin.give.type.&ltItemType&gt" - A template should not be
* granted to anybody</li>
* <li>"myplugin.give.type.minecraft.diamond" - Only
* grants minecraft:diamond</li>
* <li>"myplugin.give.enchantment" - Grants all Enchantments</li>
* <li>"myplugin.give.others" - Allow giving to other players</li>
* </ul>
* The addition of the "execute" permission instead of just "myPlugin.give"
* permission is useful to prevent unauthorized access to sub-permissions
* that are not documented or have been added lately.
* </p>
*
* <p>
* So if you want to allow someone to give themself only DIAMONDs, you would
* assign them the following permissions:
* <ul>
* <li>"myPlugin.give.execute"</li>
* <li>"myPlugin.give.type.DIAMOND"</li>
* </ul>
* </p>
*
* <p><b>Note:</b> Permission ids are case insensitive! Permission ids
* should start with the owning plugin's id.</p>
*
* @return The permission id
*/
String getId();
/**
* Gets a short description of the linked permission.
*
* <p>May include a link to a more detailed description on the plugin's
* web page.</p>
*
* <p>Will return an empty optional for descriptions which have been
* automatically generated, or where a description was omitted when the
* {@link org.spongepowered.api.service.permission.PermissionDescription} was created.</p>
*
* @return A short description of the linked permission
*/
Optional<Text> getDescription();
interface Builder {
/**
* Sets the permission id for the description this builder creates.
*
* <p>See {@link org.spongepowered.api.service.permission.PermissionDescription#getId()} for format
* specifications.</p>
*
* @param permissionId The permission id
* @return This builder for chaining
*/
Builder id(String permissionId);
/**
* Sets the short description to use.
*
* <p>May include a link to a more detailed description on the plugin's
* web page.</p>
*
* <p>Can be null if the permission does not have a description.</p>
*
* @param description The short description to use
* @return This builder for chaining
*/
Builder description(@Nullable Text description);
/**
* Creates a new {@link PermissionDescription} instance with the given
* settings.
*
* @return The newly created permission description instance
*/
PermissionDescription build();
}
}
/**
* A preset object for permissions.
*
* <p>A preset provides a sane default list of allowed or denied permissions by
* role to enable a decent starting point after plugin installation.</p>
*/
public interface PermissionPreset {
/**
* The standard role for users.
*
* <p>The user role should be assigned to permissions where everyone should
* have basic access. For example: joining in an arena.</p>
*/
String ROLE_USER = "user";
/**
* The standard role for staff.
*
* <p>The staff role should be assigned to permissions only meant for users
* with elevated access permissions. For example: force start an arena.</p>
*/
String ROLE_STAFF = "staff";
/**
* The standard role for admins.
*
* <p>The admin role should be assigned to permissions only meant for users
* with full access to administrate the server. For example: setup an
* arena.</p>
*/
String ROLE_ADMIN = "admin";
/**
* Gets the unique identifier of this {@link PermissionPreset}. The identifier is
* case insensitive, thus there cannot be another instance with a different
* character case. The id of this instance must remain the same for the
* entire duration of its existence. The identifier can be formatted however
* needed.
*
* <p>A typical id format follows the pattern of <code>`modId:name`</code>.</p>
*
* @return The unique identifier of this preset
*/
String getId();
/**
* Gets the role this preset belongs to.
*
* <p>
* Examples include {@link PermissionPreset#ROLE_USER},
* {@link PermissionPreset#ROLE_STAFF}, or
* {@link PermissionPreset#ROLE_ADMIN}.
* </p>
*
* @return The role
*/
String getRole();
/**
* Gets a immutable collection containing all added
* {@link PermissionDescription}s.
*
* @return An immutable collection contain all added descriptions
*/
Collection<PermissionDescription> getDescriptions();
/**
* A builder for permission presets.
*/
interface Builder {
/**
* Sets the role this preset corresponds to.
*
* <p>
* Examples include {@link PermissionPreset#ROLE_USER},
* {@link PermissionPreset#ROLE_STAFF}, or
* {@link PermissionPreset#ROLE_ADMIN}.
* </p>
*
* @param role The role
* @return This builder for chaining
*/
Builder role(String role);
/**
* Assigns the given permission and value to this preset.
*
* <p>If {@link firstRunOnly} is set to true, the permission
* will only be added when the preset doesn't exist yet.
* </p>
*
* @param description The permission to add
* @param value Whether to allow or deny the permission for this preset
* @param firstRunOnly Whether to only add the permission on first run
* @return This builder for chaining
* @throws IllegalStateException If a description with the given permission
* id was already registered and the {@link PermissionService} does
* not support overwriting descriptions
*/
Builder add(PermissionDescription description, boolean value, boolean firstRunOnly);
/**
* Creates and registers a new {@link PermissionPreset} instance
* with the given settings.
*
* @return The newly created permission preset instance
* @throws IllegalStateException If there are any settings left unset
*/
PermissionPreset register();
}
}
public interface PermissionService {
/**
* The standard identifier for the collection which stores permission preset.
*
* <p>Presets are registered with {@link PermissionPreset}s,
* via {@link PermissionPreset.Builder#register()}</p>
*/
String SUBJECTS_PRESET = "preset";
/**
* Creates a new preset builder for the one of the given plugin's
* permission presets.
*
* @return The newly created permission preset builder builder
* @throws IllegalArgumentException if plugin is not a plugin instance
*/
PermissionPreset.Builder newPresetBuilder(Object plugin);
/**
* Creates a new description builder for the given plugin's permission.
*
* @return The newly created permission description builder
*/
PermissionDescription.Builder newDescriptionBuilder();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment