权限
Permission
字符串
Permission 字符串对大小写不敏感,其用于决定一个 Subject 可以和不可以做什么。该字符串使用小数点分隔成若干分立的部分。一个 Permission 字符串应该长这样:
<PluginID>.<MainGroup>.<Subgroup1>...
允许使用的字符有:
大写英文字母(A 到 Z)
小写英文字母(a 到 z)
阿拉伯数字(0 到 9)
下划线(_)
短横线(-)
小数点(.)
继承
如果一个用户具有 myPlugin.commands
的权限,那么所有诸如 myPlugin.commands.teleport
等子权限都将自动拥有,除非它被显式地删除。
注解
没有诸如 myPlugin.commands.*
的通配符形式。请使用 myPlugin.commands
解决问题。
结构示例:
下面的示例展示了一个构造 Permission 字符串的通常方法,当然按照该结构来并不是必须的。
myPlugin
给予有关该插件的 所有 权限。
myPlugin.commands
给予有关该插件的命令的 所有 权限。
myPlugin.commands.teleport.execute
仅仅给予用户执行指定命令的权限。如果用户没有该权限,那么即便他拥有其他有关于 teleport 的权限,他也没有办法运行该命令(拥有该权限的用户只能将自己传送到其所在世界的其他人的位置)。
myPlugin.commands.teleport.all
仅仅给予用户一次性传送所有玩家的权限。
myPlugin.commands.teleport.worlds
仅仅给予用户传送所有世界的玩家的权限。
myPlugin.commands.teleport.worlds.mynetherworld
仅仅给予用户传送到 mynetherworld 的权限。
权限描述
一个权限描述是一个 PermissionDescription 对象,用于向服主提供有关于某些权限的详细信息。 PermissionDescription
是 PermissionService 可以提供的可选特性。创建权限描述并 不 会影响已有的权限是否存在,拥有权限的都有谁,以及其默认值是什么。
权限描述包括:
目标 Permission 字符串
一个用于描述的 Text 对象
一个或多个分配规则
提供该权限的插件
如果你有一个诸如 World
或者 ItemType
的动态元素,那么你可以使用 <TemplateParts>
。
用法示例
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();
}
简单的结果
myPlugin.commands.teleport.execute
Description: Allows the user to execute the teleport command.
Role: user
Owner: MyPlugin v1.2.3
带有动态元素的结果
myPlugin.commands.teleport.worlds.<World>
Description: Allows the user to teleport to the world <World>.
Role: staff
Owner: MyPlugin v1.2.3
小技巧
你可能不会为某些父权限组编写描述,例如 myPlugin.commands.teleport.worlds
或者 myPlugin.commands
等,因为相应的权限结构和其对应的子权限已经足够描述该权限的意义了。
权限主体
一个权限主体是一个 Subject 对象,持有着若干权限。通过 SubjectCollection 我们可以从 PermissionService
中获取到权限主体。一些诸如 CommandSource (比如 Player 等)的对象本身就属于 Subject
。不过同样有其他多种多样的 Subject
。任何具有权限的东西都是权限主体,即使它只是将检查委托给一个包含的权限主体。可以向权限主体授予或去除权限。如果权限既不被授予也不被去除,那么其设置将被继承(请参阅后面的内容)。权限主体提供方法来检查他们是否具有某种权限。使用此方法的插件应仅查询其要检查的特定权限,而 PermissionService 的任务才是管理权限及其继承。
示例
下面的示例可以用于检查玩家是否允许执行传送命令。
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()));
}
继承
如果 Subject
被指定了是否拥有给定的权限,那么它将按照指定的方式去做。否则它会继承任何父 Subject
,而不管父 Subject
是什么样子的(如用户组或玩家等)。
如果权限主体本身或任何父权限主体都不授予或去除一个权限,那么它将继承于默认的 Subject
。每个 SubjectCollection
都定义了自己的默认值。全局和最弱的默认 Subject
可以从 PermissionService
获得。插件可以在每个服务器启动期间定义它们自己对默认 SubjectData 的权限。这允许服主根据自己的需要使用默认的 SubjectData
来覆盖插件定义的默认值。如果你想为服主提供一个配置指南,请使用 PermissionDescription
。
警告
在向用户授予权限前,你应该仔细考虑考虑。你对于这些默认权限的设置相当于你默许所有服主(至少在第一次插件运行时)都接受这些默认值,并且在特殊情况下才会去除相应权限(如果没有插件可用于管理权限,自由去除权限的事甚至无法完成)。插件开发者应该考虑在单人用户的无作弊局域网下,一个玩家加入多人游戏时应有的默认权限。比如一个聊天插件将允许默认情况下发送聊天信息以在基于原版游戏允许做的事的基础上加入自己的特性。
注解
Subject
默认持有的持久的(persistent) SubjectData
比临时的(transient)优先级要高。对于其他的 Subject
,临时的 SubjectData
比持久的优先级要高。
如果权限主体本身、其所有的父权限主体、以及默认情况都没有声明授予还是去除一个权限,那么该权限将自动拒绝授予给该权限主体,也就是去除。
注解
优先次序从先到后排序:
- 权限主体本身
暂时
持续
- 父权限主体
暂时
持续
- SubjectCollection 的默认情况
持续
暂时
- PermissionService 的默认情况
持续
暂时
拒绝
SubjectCollection
SubjectCollection 是一个用于根据名称获取权限主体的容器。有一些默认的 SubjectCollection:
- 用户(User)
包含了所有在线的
Player
及不在线的User
(至少是那些非默认的)。
- 组(Group)
包含了所有组合在一起的
Subject
。组通过一种简单的方式把Subject
用命名过的Subject
方式组成继承树。组通常用于有额外附加权限的Subject
如队伍、派别、或职务等。
- 系统(System)
包括了所有服务端用到的
Subject
,如服务端控制台等终端。
- 命令方块(Command Block)
包括了所有命令方块的
Subject
。如果你只想要命令方块拥有相关权限如命令方块创建者的权限,那么该 SubjectCollection 将起到帮助。
- 角色模板(Role Template)
包括了所有用于
PermissionDescription
的角色模板权限主体。这对于查找一个用户的权限来说是很有帮助的,不过不要将其用于继承。
注解
当试图从 SubjectCollection
中查询 Subject
时,如果它们不存在,它们将被自动创建。然而它们并不一定显示在 getAllSubjects()
中,除非设置了非默认值。
SubjectData
SubjectData 是存储权限主体的权限数据的真正地方。我们有两种存储方式:
暂时(Transient):只会在相关时间段中持续,也就是说不会保存
持续(Persistent):可能会存储在什么地方,也就是说会永远保存下去。我们建议
PermissionService
提供关于持续 SubjectData 的实现,尽管这并非必要。当然这也取决于权限主体。如果没有办法以持续的方式存储,那么两个方法将都返回暂时数据。
插件开发者若要在两者之间选择,应仔细考虑是否需要持续地存储数据。
一般情况下往往只有使用很短时间的权限(如小游戏)才需要暂时存储的 SubjectData。
需要使用长时间的权限(如 VIP)应使用持续方式存储。
如果想知道更多关于继承和优先级的顺时和永久 SubjectData
的,请参阅继承部分。
Subject 选项
Subject 还提供存储字符串选项的可能性。它们基本上就是可以赋值并继承的键值对。与 Permission 字符串不同,这些选项没有层次结构,亦无继承机制,但它们能以和 Permission 字符串相同的方式从父级 Subject
继承过来。
Context
若把 Permission 视作是一种特权,或者说是做某事的能力,那么Context 就是用来决定这种特权是否可用的环境对象。
你可能想让一个 Subject
只在特定世界或者特定区域内的时候有某个权限。
Subject
会不断产生并整合 Context,然后 PermissionService
会用这些 Context 来判断该 Subject
是否有对应权限。
Sponge 自身预置了一些 Context,但一般情况下 Context 都是其他插件通过 ContextCalculator 向 PermissionService 提供的。
新建 Context 时,如果不是与其他插件共享的 Context,请通过使用你的插件 ID 作为前缀等方式来避免与其他插件冲突。
注解
请确保你的 ContextCalculator
尽可能快地响应,因他它将频繁地被调用
示例
你的 ContextCalculator
可能看起来就像这样:
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;
}
}
ContextCalculator
可以通过注册:
permissionService.registerContextCalculator(contextCalculator);