权限

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 对象,用于向服主提供有关于某些权限的详细信息。 PermissionDescriptionPermissionService 可以提供的可选特性。创建权限描述并 会影响已有的权限是否存在,拥有权限的都有谁,以及其默认值是什么。

权限描述包括:

  • 目标 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\ 继承的。

环境

如果你考虑每个权限的特权或能够做某事的能力, :javadoc: 的环境的情况下,特权是可用的。

你可能想给一个 Subject 允许做某事,但只有当 Subject 是在某个特定世界,或在某一特定区域。

环境是积累的 Subject ,然后用 PermissionService 来决定 Subject 是否拥有特权。

Sponge 在默认情况下提供了一些环境,但是一般它通过 ContextCalculator 为其他插件提供额外的环境服务许可。

创建你自己的插件的上下文时请尽量避免与其他插件冲突(如通过加前缀环境关键你的插件 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);