Permissions

Permission

A permission is a case-insensitive hierarchical string key that is used to determine whether a Subject can do a specific action or not. The string is split into separate parts using the dot character. Permissions should be structured like this.

<PluginID>.<MainGroup>.<Subgroup1>...

Allowed characters are:

  • «A» - «Z»
  • «a» - «z»
  • «0» - «9»
  • «_»
  • «-«
  • «.»

Inheritance

If a user has the permission myplugin.commands then they automatically have all sub permissions such as myplugin.commands.teleport unless the permissions are explicitly removed.

Obs

There is no such thing as a myplugin.commands.* wildcard permission. Use myplugin.commands for that.

Structure-Example

The following example show one possible way of structuring permissions, but following this structure is not required at all.

  • myplugin
    • Grants full access to all plugin’s permissions.
  • myplugin.commands
    • Grants full access to all plugin’s commands.
  • myplugin.commands.teleport.execute
    • Only grants the user the permission to execute the command. Without this permission he is not able to execute the command even if he has other teleport permissions. (With this permission alone the user would only be able to teleport himself to others in his current world.)
  • myplugin.commands.teleport.all
    • Only grants the user the permission to teleport all players at once.
  • myplugin.commands.teleport.worlds
    • Only grants the user the permission to teleport to all worlds.
  • myplugin.commands.teleport.worlds.mynetherworld
    • Only grants the user the permission to teleport to mynetherworld.

PermissionDescription

The PermissionDescription is an utility that is meant to provide the server owner with details on a certain permission. PermissionDescriptions are an optional feature a PermissionService can provide. The creation of a PermissionDescription does not have any impact on whether a permission exists, who has access or what its default value is.

The description consists of:

  • the target permission id
  • a Text description
  • one or more assigned roles
  • the owning plugin

If you have a dynamic element such as a World or ItemType then you can use <TemplateParts>.

Usage-Example

import org.spongepowered.api.service.permission.PermissionDescription;
import org.spongepowered.api.service.permission.PermissionDescription.Builder;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.text.Text;
import java.util.Optional;

Optional<Builder> optBuilder = permissionService.newDescriptionBuilder(myplugin);
if (optBuilder.isPresent()) {
    Builder builder = optBuilder.get();
    builder.id("myplugin.commands.teleport.execute")
           .description(Text.of("Allows the user to execute the teleport command."))
           .assign(PermissionDescription.ROLE_STAFF, true)
           .register();
}

Simple-Result

myplugin.commands.teleport.execute

Description: Allows the user to execute the teleport command.
Role: user
Owner: MyPlugin v1.2.3

Template-Result

myplugin.commands.teleport.worlds.<World>

Description: Allows the user to teleport to the world <World>.
Role: staff
Owner: MyPlugin v1.2.3

Tips

You might skip writing descriptions for some parent permission groups such as myplugin.commands.teleport.worlds or myplugin.commands as their meaning can be derived from the permission structure and the defined children alone.

Subject

A Subject is a holder of assigned permissions. It can be obtained from the PermissionService via SubjectCollections. CommandSources such as Players are Subjects by default, but there are many other types of Subjects. Anything that has permissions is a Subject even if it just delegates the checks to a contained Subject. Permissions can be granted or denied to a Subject. If a permission is neither granted nor denied its setting will be inherited. See Inheritance. Subjects provide methods to check whether they have a certain permission or not. Plugins that use this method should only query for the specific permission they want to check. It is the PermissionService’s task to respect the permission and subject inheritance.

Example

The following example could be used to check whether the Player is allowed to execute the teleport command.

import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.world.World;

public boolean canTeleport(Player subject, World targetWorld) {
    return subject.hasPermission("myplugin.command.teleport.execute")
            && (subject.getWorld() == targetWorld
                    || subject.hasPermission("myplugin.command.teleport." + targetWorld.getName()));
}

Inheritance

If a Subject has a permission assigned, it will use that value. Otherwise it will be inherited from any parent Subject. It does not matter what kind of parent (e.g. group or player) Subject that might be.

If neither the subject itself nor any parent subjects grant or deny a permission then it will be inherited from the default Subjects. Each SubjectCollection defines its own defaults. The global and weakest default subject can be obtained from the PermissionService. Plugins may define their own permissions to the default’s transient SubjectData during every server start-up. This allows server owners to overwrite the defaults defined by plugins according to their needs using the default’s persistent SubjectData. If you would like to provide a configuration guideline for server owners use PermissionDescription’s role-templates instead.

Advarsel

You should think carefully before granting default permissions to users. By granting the permissions you are assuming that all server owners will want these defaults (at least the first time the plugin runs) and that exceptions will require server owners to explicitly deny the permissions (which can’t even be done without a custom permissions service implementation). This should roughly correspond to a guest on a single player lan world without cheats. For example a chat plugin would allow sending chat messages by default to imitate vanilla game behaviour for features that were changed by the plugin.

Obs

The default Subjects’ persistent SubjectDatas take precedence over the transient ones. For all other Subjects the transient SubjectDatas take precedence over the persistent ones.

If neither the Subject, nor any of its parents, nor the defaults assign a value to a permission, then it is automatically denied.

Obs

Order of precedence in descending order:

  • Subject itself
    • Transient
    • Persistent
  • Parent Subjects
    • Transient
    • Persistent
  • SubjectCollection Defaults
    • Persistent
    • Transient
  • PermissionService Defaults
    • Persistent
    • Transient
  • Deny permission

SubjectCollections

A container for subjects that can be used to obtain a Subject by name. These are the default Subject Collections:

  • User
    • Contains all on-line Players and all off-line Users (at least those with none-default settings).
  • Group
    • Contains all group Subject. Groups are a simple way of structuring a Subject’s inheritance tree using named Subjects. Groups should be used if a specific subset of Subjects have additional permission settings such as a team, faction or role.
  • System
    • Contains other Subjects used by the server such as the the console and possible remote consoles.
  • Command Block
    • Contains all Subjects for command blocks. These are useful if you would like to run a CommandBlock only with the permissions of the creator.
  • Role Template
    • Contains all role template subjects that are used in PermissionDescriptions. Useful to lookup all recommended permissions for a user. These should not be used for inheritance.

Obs

When SubjectCollections are queried for a Subject they will automatically be created, if they do not already exist. However they might not necessarily show up in getAllSubjects() unless none-default values are set.

SubjectData

SubjectData are the actual permission stores connected to the Subject. There are two types of Subject stores:

  • Transient = Only lasts for the duration of the session, it is never saved
  • Regular (persistent) = Might be saved somewhere, and therefore be persisted and exist forever. Its recommended for PermissionServices to implement a persistent store, however it is not a requirement. It might also depend on the subject type. If there is no persistence then the transient store will be returned in both methods.

Plugin authors should consider whether it is necessary to persist a value when choosing between them.

  • If it is only for a short time (e.g. during a minigame) then use the transient one.
  • If it is for a long time or forever (e.g. a promotion to VIP) use the regular (persistent) one.

Please refer to the Inheritance section if want to know more about the inheritance and precedence of the transient and persistent SubjectDatas.

Subject Options

Subjects also provide the possibility to store string options. These are basically key value pairs that can be assigned and inherited. Unlike the permission strings the keys are not hierarchical and don’t provide any inheritance mechanisms themselves, but the key value pairs itself are inherited from parent Subjects in the same way permissions are.

Contexts

If you consider each permission to a privilege or ability to be able to do something, a Context is the circumstances where that privilege is usable.

You might want to give a Subject permission to do something, but only when the Subject is in a certain world, or in a certain region.

Contexts are accumulated by a Subject, and are then used by the PermissionService to decide if the Subject has a privilege or not.

Sponge provides some contexts by default, but it is generally down to other plugins to provide additional contexts to the PermissionService, through a ContextCalculator.

When creating contexts for your own plugin please try to avoid conflicts with other plugins (e.g. by prefixing the context key with your plugin id) unless these contexts are meant to be shared.

Obs

Please make sure that your ContextCalculator responds as fast as possible as it will get called frequently.

Example

Your ContextCalculator may look like this:

import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.context.ContextCalculator;
import org.spongepowered.api.service.permission.Subject;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

public class ExampleCalculator implements ContextCalculator<Subject> {

    private static final Context IN_ANY_ARENA = new Context("myarenaplugin-inAnyArena", "true");
    private static final Context NOT_ANY_ARENA = new Context("myarenaplugin-inAnyArena", "false");
    private static final String ARENA_KEY = "myarenaplugin-arena";

    private final Map<UUID, String> playerArenas = new HashMap<>();

    @Override
    public void accumulateContexts(Subject calculable, Set<Context> accumulator) {
        final Optional<CommandSource> commandSource = calculable.getCommandSource();

        if (commandSource.isPresent() && commandSource.get() instanceof Player) {
            final Player player = (Player) commandSource.get();

            final UUID uuid = player.getUniqueId();
            if (this.playerArenas.containsKey(uuid)) {
                accumulator.add(IN_ANY_ARENA);
                accumulator.add(new Context(ARENA_KEY, this.playerArenas.get(uuid)));
            } else {
                accumulator.add(NOT_ANY_ARENA);
            }
        }
    }

    @Override
    public boolean matches(Context context, Subject subject) {
        if (!context.equals(IN_ANY_ARENA) && !context.equals(NOT_ANY_ARENA) && !context.getKey().equals(ARENA_KEY)) {
            return false;
        }

        final Optional<CommandSource> commandSource = subject.getCommandSource();
        if (!commandSource.isPresent() || !(commandSource.get() instanceof Player)) {
            return false;
        }

        final Player player = (Player) commandSource.get();

        if (context.equals(IN_ANY_ARENA) && !this.playerArenas.containsKey(player.getUniqueId())) {
            return false;
        }

        if (context.equals(NOT_ANY_ARENA) && this.playerArenas.containsKey(player.getUniqueId())) {
            return false;
        }

        if (context.getKey().equals(ARENA_KEY)) {
            if (!this.playerArenas.containsKey(player.getUniqueId())) {
                return false;
            }

            if (!this.playerArenas.get(player.getUniqueId()).equals(context.getValue())) {
                return false;
            }
        }

        return true;
    }
}

The ContextCalculator can be registered via:

permissionService.registerContextCalculator(contextCalculator);

For Forge Mods

If you are the author of a Forge mod and are not using the new Forge PermissionsAPI but are doing OP checks, then you are already on the right path for Sponge to pick up permissions.

The simplest way to create a Sponge permission in a Forge mod without soft-depending on SpongeAPI is to use the method provided by Vanilla Minecraft code in ICommandSender, namely ICommandSender.canCommandSenderUseCommand(int permLevel, String commandName). The String passed into that method has no use at all in a Vanilla Forge environment, but when SpongeForge is added it automatically takes that String and converts it into a working permission.

Example

public class AwesomeBlock extends Block {
    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) {
        if (player.canCommandSenderUseCommand(4, "examplemod.awesomeblock.interact")) {
            // Do cool stuff
            return true;
        }
        return false;
    }
}

As you can see, we simply check for the OP level and pass in an arbitrary String we want to use as a permission when Sponge is used. When Forge is used by itself the player simply requires the OP level, so passing a value of 0 would allow all users to interact with the block, but when SpongeForge is added they require the permission node of examplemod.awesomeblock.interact. It is recommended to follow the permission structure as described above. The permission inheritance does also apply to these checks.

Obs

The SRG name for this method is func_70003_b.