Permissions¶
If you are interested in the permissions that are used in vanilla commands have a look at this page. For customizing the permission levels refer to the server permissions page or your permission plugin’s documentation.
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.
Note
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. PermissionDescription
s 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;
PluginContainer plugin = ...;
Builder builder = permissionService.newDescriptionBuilder(plugin);
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
Tip
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 Subject
s by default, but there are many other types of
Subject
s. 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 Subject
s. 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.
Warning
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 behavior for features that were changed by the plugin.
Note
The default Subject
s’ persistent SubjectData
s take precedence over the transient ones.
For all other Subject
s the transient SubjectData
s 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.
Note
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
Player
s and all off-lineUser
s (at least those with none-default settings).
- Contains all on-line
- Group
- Contains all group
Subject
. Groups are a simple way of structuring aSubject
’s inheritance tree using namedSubject
s. Groups should be used if a specific subset ofSubject
s have additional permission settings such as a team, faction or role.
- Contains all group
- System
- Contains other
Subject
s used by the server such as the console and possible remote consoles.
- Contains other
- Command Block
- Contains all
Subject
s for command blocks. These are useful if you would like to run aCommandBlock
only with the permissions of the creator.
- Contains all
- Role Template
- Contains all role template subjects that are used in
PermissionDescription
s. Useful to lookup all recommended permissions for a user. These should not be used for inheritance.
- Contains all role template subjects that are used in
Note
When SubjectCollection
s 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
PermissionService
s 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 SubjectData
s.
Example¶
The following example could be used to give a player a permission with global context and a true value
import org.spongepowered.api.entity.living.player.Player;
public void setPermission(Player player, String permission) {
if(!player.hasPermission(permission)
player.getSubjectData().setPermission(SubjectData.GLOBAL_CONTEXT, permission, Tristate.TRUE);
}
Note
In the above example Tristate.TRUE
was used but you can also use Tristate.FALSE
for a false permission and
Tristate.UNDEFINED
to unset the permission entirely.
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 Subject
s 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.
Note
Please make sure that your ContextCalculator
responds as fast as possible as it will get called frequently.
Warning
ContextCalculator
implementations must be thread safe, because they may be called from outside of the main
thread, or even called in parallel. For to this reason, all non-name or non-uuid based ContextCalculator
s
(such as location-based ones) have to utilize a cache and must be to be updated using event listeners or
synchronized schedulers.
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, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
if (player.canUseCommand(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.
Note
The SRG name for this method is func_70003_b
.