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")
package net.mamoe.mirai.console.command
@ -78,6 +87,23 @@ sealed class CommandExecuteResult {
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,
/** 没有匹配的指令 */
COMMAND_NOT_FOUND
COMMAND_NOT_FOUND,
/** 权限不足 */
PERMISSION_DENIED
}
}
@ -121,6 +150,18 @@ fun CommandExecuteResult.isExecutionException(): Boolean {
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`
*/

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)
/**
* 解析并执行一个指令
* 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出
*
* @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
/**
@ -224,6 +198,10 @@ internal suspend inline fun CommandSender.executeCommandDetailedInternal(
val command =
InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents().dropToTypedArray(1)
if (!command.testPermission(this)) {
return CommandExecuteResult.PermissionDenied(command, commandName)
}
kotlin.runCatching {
command.onCommand(this, args)
}.fold(

View File

@ -25,6 +25,9 @@ import net.mamoe.mirai.contact.isOwner
interface CommandPermission {
/**
* 判断 [this] 是否拥有这个指令的权限
*
* @see CommandSender.hasPermission
* @see CommandPermission.testPermission
*/
fun CommandSender.hasPermission(): Boolean
@ -32,11 +35,13 @@ interface CommandPermission {
/**
* 满足两个权限其中一个即可使用指令
*/ // no extension for Java
@JvmDefault
infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another)
/**
* 同时拥有两个权限才能使用指令
*/ // no extension for Java
@JvmDefault
infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
@ -48,7 +53,7 @@ interface CommandPermission {
}
/**
* 任何人都不能使用这个指令. 指令只能通过代码在 [execute] 使用
* 任何人都不能使用这个指令. 指令只能通过调用 [Command.onCommand] 执行.
*/
object None : CommandPermission {
override fun CommandSender.hasPermission(): Boolean = false
@ -94,7 +99,7 @@ interface 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)
@ -117,6 +122,8 @@ inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
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(
private val first: 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
): Command? {
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 {
command.onCommand(this, loweredArgs)
command.onCommand(this, messages.flattenCommandComponents().dropToTypedArray(1))
}.fold(
onSuccess = {
return command
},
onFailure = {
throw CommandExecutionException(command, commandName, loweredArgs, it)
throw CommandExecutionException(command, commandName, it)
}
)
}