Implement command permission checking and relevant exceptions

This commit is contained in:
Him188 2020-06-23 18:18:09 +08:00
parent a26b607f35
commit 4bc19a0a9d
6 changed files with 120 additions and 34 deletions

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
@ -78,6 +87,23 @@ sealed class CommandExecuteResult {
override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND
} }
/** 权限不足 */
class PermissionDenied(
/** 尝试执行的指令 */
override val command: Command,
/** 尝试执行的指令名 */
override val commandName: String
) : CommandExecuteResult() {
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
override val args: Nothing? get() = null
/** 指令执行时发生的错误, 总是 `null` */
override val exception: Nothing? get() = null
/** 指令最终执行状态, 总是 [CommandExecuteStatus.PERMISSION_DENIED] */
override val status: CommandExecuteStatus get() = CommandExecuteStatus.PERMISSION_DENIED
}
/** /**
* 指令的执行状态 * 指令的执行状态
*/ */
@ -89,7 +115,10 @@ sealed class CommandExecuteResult {
EXECUTION_EXCEPTION, EXECUTION_EXCEPTION,
/** 没有匹配的指令 */ /** 没有匹配的指令 */
COMMAND_NOT_FOUND COMMAND_NOT_FOUND,
/** 权限不足 */
PERMISSION_DENIED
} }
} }
@ -121,6 +150,18 @@ fun CommandExecuteResult.isExecutionException(): Boolean {
return this is CommandExecuteResult.ExecutionException return this is CommandExecuteResult.ExecutionException
} }
/**
* [this] [CommandExecuteResult.ExecutionException] 时返回 `true`
*/
@JvmSynthetic
fun CommandExecuteResult.isPermissionDenied(): Boolean {
contract {
returns(true) implies (this@isPermissionDenied is CommandExecuteResult.PermissionDenied)
returns(false) implies (this@isPermissionDenied !is CommandExecuteResult.PermissionDenied)
}
return this is CommandExecuteResult.PermissionDenied
}
/** /**
* [this] [CommandExecuteResult.ExecutionException] 时返回 `true` * [this] [CommandExecuteResult.ExecutionException] 时返回 `true`
*/ */

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
/**
* [executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
*/
class CommandExecutionException(
/**
* 执行过程发生异常的指令
*/
val command: Command,
/**
* 匹配到的指令名
*/
val name: String,
cause: Throwable
) : RuntimeException(
"Exception while executing command '${command.primaryName}'",
cause
) {
override fun toString(): String =
"CommandExecutionException(command=$command, name='$name')"
}

View File

@ -131,7 +131,7 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
//// executing without detailed result (faster) //// executing without detailed result (faster)
/** /**
* 解析并执行一个指令 * 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出
* *
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString] * @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
* *
@ -160,32 +160,6 @@ suspend fun CommandSender.executeCommand(message: MessageChain): Command? {
} }
/**
* [executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
*/
class CommandExecutionException(
/**
* 执行过程发生异常的指令
*/
val command: Command,
/**
* 匹配到的指令名
*/
val name: String,
/**
* 基础分割后的实际参数列表, 元素类型可能为 [Message] [String]
*/
val args: Array<out Any>,
cause: Throwable
) : RuntimeException(
"Exception while executing command '${command.primaryName}' with args ${args.joinToString { "'$it'" }}",
cause
) {
override fun toString(): String =
"CommandExecutionException(command=$command, name='$name', args=${args.contentToString()})"
}
//// execution with detailed result //// execution with detailed result
/** /**
@ -224,6 +198,10 @@ internal suspend inline fun CommandSender.executeCommandDetailedInternal(
val command = val command =
InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName) InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents().dropToTypedArray(1) val args = messages.flattenCommandComponents().dropToTypedArray(1)
if (!command.testPermission(this)) {
return CommandExecuteResult.PermissionDenied(command, commandName)
}
kotlin.runCatching { kotlin.runCatching {
command.onCommand(this, args) command.onCommand(this, args)
}.fold( }.fold(

View File

@ -25,6 +25,9 @@ import net.mamoe.mirai.contact.isOwner
interface CommandPermission { interface CommandPermission {
/** /**
* 判断 [this] 是否拥有这个指令的权限 * 判断 [this] 是否拥有这个指令的权限
*
* @see CommandSender.hasPermission
* @see CommandPermission.testPermission
*/ */
fun CommandSender.hasPermission(): Boolean fun CommandSender.hasPermission(): Boolean
@ -32,11 +35,13 @@ interface CommandPermission {
/** /**
* 满足两个权限其中一个即可使用指令 * 满足两个权限其中一个即可使用指令
*/ // no extension for Java */ // no extension for Java
@JvmDefault
infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another) infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another)
/** /**
* 同时拥有两个权限才能使用指令 * 同时拥有两个权限才能使用指令
*/ // no extension for Java */ // no extension for Java
@JvmDefault
infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another) infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
@ -48,7 +53,7 @@ interface CommandPermission {
} }
/** /**
* 任何人都不能使用这个指令. 指令只能通过代码在 [execute] 使用 * 任何人都不能使用这个指令. 指令只能通过调用 [Command.onCommand] 执行.
*/ */
object None : CommandPermission { object None : CommandPermission {
override fun CommandSender.hasPermission(): Boolean = false override fun CommandSender.hasPermission(): Boolean = false
@ -94,7 +99,7 @@ interface CommandPermission {
* 仅控制台能使用和这个指令 * 仅控制台能使用和这个指令
*/ */
object Console : CommandPermission { object Console : CommandPermission {
override fun CommandSender.hasPermission(): Boolean = false override fun CommandSender.hasPermission(): Boolean = this is ConsoleCommandSender
} }
object Default : CommandPermission by (Manager or Console) object Default : CommandPermission by (Manager or Console)
@ -117,6 +122,8 @@ inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() } inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
inline fun Command.testPermission(sender: CommandSender): Boolean = sender.hasPermission(this.permission)
internal class OrCommandPermission( internal class OrCommandPermission(
private val first: CommandPermission, private val first: CommandPermission,
private val second: CommandPermission private val second: CommandPermission

View File

@ -0,0 +1,25 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
/**
* [executeCommand] , [CommandSender] 未拥有 [Command.permission] 所要求的权限时抛出的异常.
*
* 总是作为 [CommandExecutionException.cause].
*/
class CommandPermissionDeniedException(
/**
* 执行过程发生异常的指令
*/
val command: Command
) : RuntimeException("Permission denied while executing command '${command.primaryName}'") {
override fun toString(): String =
"CommandPermissionDeniedException(command=$command)"
}

View File

@ -171,18 +171,19 @@ internal suspend inline fun CommandSender.executeCommandInternal(
commandName: String commandName: String
): Command? { ): Command? {
val command = InternalCommandManager.matchCommand(commandName) ?: return null val command = InternalCommandManager.matchCommand(commandName) ?: return null
val rawInput = messages.flattenCommandComponents()
val loweredArgs = rawInput.dropToTypedArray(1) if (!command.testPermission(this)) {
throw CommandExecutionException(command, commandName, CommandPermissionDeniedException(command))
}
kotlin.runCatching { kotlin.runCatching {
command.onCommand(this, loweredArgs) command.onCommand(this, messages.flattenCommandComponents().dropToTypedArray(1))
}.fold( }.fold(
onSuccess = { onSuccess = {
return command return command
}, },
onFailure = { onFailure = {
throw CommandExecutionException(command, commandName, loweredArgs, it) throw CommandExecutionException(command, commandName, it)
} }
) )
} }