權限

權限

Permission 字符串对大小写不敏感,其用于决定一个 Subject 可以和不可以做什么。该字符串使用小数点分隔成若干分立的部分。一个 Permission 字符串应该长这样:

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

允許的字元為:

  • 「A」 - 「Z」

  • 「a」 - 「z」

  • 「0」 - 「9」

  • 「_」

  • 「-」

  • 「.」

繼承(Inheritance)

如果用戶具有 「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()));
}

繼承(Inheritance)

如果 Subject 被指定了是否拥有给定的权限,那么它将按照指定的方式去做。否则它会继承任何父 Subject ,而不管父 Subject 是什么样子的(如用户组或玩家等)。

如果权限主体本身或任何父权限主体都不授予或去除一个权限,那么它将继承于默认的 Subject 。每个 SubjectCollection 都定义了自己的默认值。全局和最弱的默认 Subject 可以从 PermissionService 获得。插件可以在每个服务器启动期间定义它们自己对默认 SubjectData 的权限。这允许服主根据自己的需要使用默认的 SubjectData 来覆盖插件定义的默认值。如果你想为服主提供一个配置指南,请使用 PermissionDescription

警告

在向用户授予权限前,你应该仔细考虑考虑。你对于这些默认权限的设置相当于你默许所有服主(至少在第一次插件运行时)都接受这些默认值,并且在特殊情况下才会去除相应权限(如果没有插件可用于管理权限,自由去除权限的事甚至无法完成)。插件开发者应该考虑在单人用户的无作弊局域网下,一个玩家加入多人游戏时应有的默认权限。比如一个聊天插件将允许默认情况下发送聊天信息以在基于原版游戏允许做的事的基础上加入自己的特性。

備註

Subject 默认持有的持久的(persistent) SubjectData 比临时的(transient)优先级要高。对于其他的 Subject,临时的 SubjectData 比持久的优先级要高。

如果权限主体本身、其所有的父权限主体、以及默认情况都没有声明授予还是去除一个权限,那么该权限将自动拒绝授予给该权限主体,也就是去除。

備註

优先次序从先到后排序:

  • 权限主体本身
    • 暂时

    • 持续

  • 父权限主体
    • 暂时

    • 持续

  • SubjectCollection 的默认情况
    • 持续

    • 暂时

  • PermissionService 的默认情况
    • 持续

    • 暂时

  • 拒絕權限

SubjectCollection

SubjectCollection 是一个用于根据名称获取权限主体的容器。有一些默认的 SubjectCollection:

  • 使用者
    • 包含了所有在线的 Player 及不在线的 User (至少是那些非默认的)。

  • 组(Group)
    • 包含了所有组合在一起的 Subject 。组通过一种简单的方式把 Subject 用命名过的 Subject 方式组成继承树。组通常用于有额外附加权限的 Subject 如队伍、派别、或职务等。

  • 系統
    • 包括了所有服务端用到的 Subject ,如服务端控制台等终端。

  • 命令方塊
    • 包括了所有命令方块的 Subject 。如果你只想要命令方块拥有相关权限如命令方块创建者的权限,那么该 SubjectCollection 将起到帮助。

  • 角色模板(Role Template)
    • 包括了所有用于 PermissionDescription 的角色模板权限主体。这对于查找一个用户的权限来说是很有帮助的,不过不要将其用于继承。

備註

当试图从 SubjectCollection 中查询 Subject 时,如果它们不存在,它们将被自动创建。然而它们并不一定显示在 getAllSubjects() 中,除非设置了非默认值。

主要資訊

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);