权限

如果你想了解原版命令的权限,请移步至这个页面。关于定制权限等级的说明,请参阅权限管理页面,或查阅你使用的权限插件的文档。

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;

Builder builder = permissionService.newDescriptionBuilder(myplugin);

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 的实现必须保证线程安全,因为它们可能会在别的线程上调用,甚至是并发调用。也因此,所有不是基于名称或 UUID 的 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);

兼容Forge Mod

如果你是一个 Forge Mod 的作者,同时目前却没有使用 Forge 的权限 API,而是使用检查 OP 的方式,那么你其实也可以让 Sponge 检查特定的权限。

你可以通过一个方法在 Forge Mod 中检查 Sponge 权限,而不需要依赖 SpongeAPI,这个方法就是 Minecraft 代码中的 ICommandSender 接口,它的全名是 ICommandSender.canCommandSenderUseCommand(int permLevel, String commandName) 。在纯 Forge 中这个方法传入的第二个参数没有任何用途,但是如果添加有 SpongeForge,那么 Sponge 会获取这个字符串,并将其转换为针对权限的检查。

示例

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

正如你所看到的那样,我们首先检查 OP 等级,并传入一个看起来没什么用的字符串,以希望 Sponge 检查对应的权限,如果 Sponge 存在的话。如果只有 Forge 的话,那么玩家只需要拥有特定的 OP 等级,所以如果传入零,那么所有的玩家都可以和方块交互,但如果有 SpongeForge 的话,那么玩家就需要有相应的权限,也就是 examplemod.awesomeblock.interact 的权限。Sponge 建议在检查权限时按照上面的权限的结构的约定。继承在这里的权限检查中同样也是起到作用的。

注解

这个方法的 SRG 名称是 func_70003_b