From 7b8e9cc1c66dc9b74ab6b2819cc690b7fc57d967 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 12 Sep 2020 18:51:40 +0800 Subject: [PATCH] Review: Permission, PermissionService; Lots of improvements; Rename Permissible to Permittee, rename PermissibleIdentifier to PermitteeId; Add docs for Permission system; Remove ExperimentalPermission annotations on some targets --- .../mirai/console/command/BuiltInCommands.kt | 444 ++---------------- .../mamoe/mirai/console/command/Command.kt | 9 +- .../mirai/console/command/CommandSender.kt | 33 +- .../description/CommandArgumentContext.kt | 4 +- .../CommandArgumentParserBuiltins.kt | 16 +- .../console/extension/ComponentStorage.kt | 2 +- .../MiraiConsoleImplementationBridge.kt | 2 +- .../AbstractConcurrentPermissionService.kt | 23 +- .../permission/BuiltInPermissionServices.kt | 51 +- .../permission/parseFromStringImpl.kt | 80 ++++ .../console/internal/util/CommonUtils.kt | 30 ++ .../permission/PermissibleIdentifier.kt | 186 -------- .../mirai/console/permission/Permission.kt | 80 ++-- .../mirai/console/permission/PermissionId.kt | 35 +- .../permission/PermissionIdNamespace.kt | 2 - .../permission/PermissionImplementation.kt | 23 + .../permission/PermissionNotFoundException.kt | 15 - ...=> PermissionRegistryConflictException.kt} | 12 +- .../console/permission/PermissionService.kt | 130 +++-- .../{Permissible.kt => Permittee.kt} | 12 +- .../mirai/console/permission/PermitteeId.kt | 307 ++++++++++++ .../mamoe/mirai/console/util/ContactUtils.kt | 22 +- 22 files changed, 744 insertions(+), 774 deletions(-) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/{ => internal}/permission/AbstractConcurrentPermissionService.kt (64%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/{ => internal}/permission/BuiltInPermissionServices.kt (72%) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/parseFromStringImpl.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/CommonUtils.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissibleIdentifier.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt delete mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionNotFoundException.kt rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/{DuplicatedPermissionRegistrationException.kt => PermissionRegistryConflictException.kt} (68%) rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/{Permissible.kt => Permittee.kt} (62%) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index ad390ef01..827a216d8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -12,22 +12,20 @@ package net.mamoe.mirai.console.command import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import net.mamoe.mirai.Bot import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.description.* import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands -import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.permission.* import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail -import net.mamoe.mirai.console.permission.PermissionService.Companion.getGrantedPermissions +import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi -import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.secondsToMillis @@ -37,20 +35,26 @@ import kotlin.system.exitProcess @ConsoleExperimentalApi @Suppress("EXPOSED_SUPER_INTERFACE") -public interface BuiltInCommand : Command, BuiltInCommandInternal +public interface BuiltInCommand : Command // for identification -internal interface BuiltInCommandInternal : Command +internal interface BuiltInCommandInternal : Command, BuiltInCommand /** * 内建指令列表 */ -@ConsoleExperimentalApi @Suppress("unused") @OptIn(ExperimentalPermission::class) public object BuiltInCommands { + @ConsoleExperimentalApi + public val rootPermission: Permission by lazy { + PermissionService.INSTANCE.register( + PermissionId("console", "*"), + "The parent of any built-in commands" + ) + } - public val all: Array by lazy { + internal val all: Array by lazy { this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray() } @@ -63,8 +67,8 @@ public object BuiltInCommands { public object HelpCommand : SimpleCommand( ConsoleCommandOwner, "help", description = "Command list", - parentPermission = RootConsoleBuiltInPermission, - ), BuiltInCommand { + parentPermission = rootPermission, + ), BuiltInCommandInternal { @Handler public suspend fun CommandSender.handle() { sendMessage( @@ -83,8 +87,8 @@ public object BuiltInCommands { public object StopCommand : SimpleCommand( ConsoleCommandOwner, "stop", "shutdown", "exit", description = "Stop the whole world.", - parentPermission = RootConsoleBuiltInPermission, - ), BuiltInCommand { + parentPermission = rootPermission, + ), BuiltInCommandInternal { private val closingLock = Mutex() @@ -94,16 +98,16 @@ public object BuiltInCommands { closingLock.withLock { sendMessage("Stopping mirai-console") kotlin.runCatching { - ignoreException { MiraiConsole.job.cancelAndJoin() } + runIgnoreException { MiraiConsole.job.cancelAndJoin() } }.fold( onSuccess = { - ignoreException { sendMessage("mirai-console stopped successfully.") } + runIgnoreException { sendMessage("mirai-console stopped successfully.") } }, onFailure = { if (it is CancellationException) return@fold @OptIn(ConsoleInternalApi::class) MiraiConsole.mainLogger.error("Exception in stop", it) - ignoreException { + runIgnoreException { sendMessage( it.localizedMessage ?: it.message ?: it.toString() ) @@ -119,8 +123,8 @@ public object BuiltInCommands { public object LoginCommand : SimpleCommand( ConsoleCommandOwner, "login", "登录", description = "Log in a bot account.", - parentPermission = RootConsoleBuiltInPermission, - ), BuiltInCommand { + parentPermission = rootPermission, + ), BuiltInCommandInternal { @Handler public suspend fun CommandSender.handle(id: Long, password: String) { kotlin.runCatching { @@ -149,31 +153,37 @@ public object BuiltInCommands { ConsoleCommandOwner, "permission", "权限", "perm", description = "Manage permissions", overrideContext = buildCommandArgumentContext { - PermissibleIdentifier::class with PermissibleIdentifierArgumentParser + PermitteeId::class with PermissibleIdentifierArgumentParser Permission::class with PermissionIdArgumentParser.map { id -> kotlin.runCatching { id.findCorrespondingPermissionOrFail() }.getOrElse { illegalArgument("指令不存在: $id", it) } } }, - parentPermission = RootConsoleBuiltInPermission, - ), BuiltInCommand { + parentPermission = rootPermission, + ), BuiltInCommandInternal { // TODO: 2020/9/10 improve Permission command - @SubCommand - public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: Permission) { + @SubCommand("permit", "grant", "add") + public suspend fun CommandSender.permit(target: PermitteeId, permission: Permission) { target.grantPermission(permission) sendMessage("OK") } - @SubCommand - public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: Permission) { - target.denyPermission(permission) + @SubCommand("cancel", "deny", "remove") + public suspend fun CommandSender.cancel(target: PermitteeId, permission: Permission) { + target.denyPermission(permission, false) sendMessage("OK") } - @SubCommand("grantedPermissions", "gp") - public suspend fun CommandSender.grantedPermissions(target: PermissibleIdentifier) { - val grantedPermissions = target.getGrantedPermissions() + @SubCommand("cancelAll", "denyAll", "removeAll") + public suspend fun CommandSender.cancelAll(target: PermitteeId, permission: Permission) { + target.denyPermission(permission, true) + sendMessage("OK") + } + + @SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp") + public suspend fun CommandSender.permittedPermissions(target: PermitteeId) { + val grantedPermissions = target.getPermittedPermissions() sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() }) } @@ -182,380 +192,4 @@ public object BuiltInCommands { sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() }) } } -} - -internal inline fun ignoreException(block: () -> R): R? { - try { - return block() - } catch (e: Throwable) { - if (e is E) return null - throw e - } -} - -internal inline fun ignoreException(block: () -> Unit): Unit? { - try { - return block() - } catch (e: Throwable) { - if (e is E) return null - throw e - } -} - -internal fun ContactOrBot.render(): String { - return when (this) { - is Bot -> "Bot $nick($id)" - is Group -> "Group $name($id)" - is Friend -> "Friend $nick($id)" - is Member -> "Friend $nameCardOrNick($id)" - else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}") - } -} - -/* - -/** - * Some defaults commands are recommend to be replaced by plugin provided commands - */ -internal object DefaultCommands { - internal val commandPrefix = "mirai.command.prefix".property() ?: "/" - private suspend fun CommandSender.login(account: Long, password: String) { - MiraiConsole.logger("[Bot Login]", 0, "login...") - try { - MiraiConsole.frontEnd.prePushBot(account) - val bot = Bot(account, password) { - fileBasedDeviceInfo(MiraiConsole.path + "/device.json") - this.loginSolver = MiraiConsole.frontEnd.createLoginSolver() - this.botLoggerSupplier = { - SimpleLogger("BOT $account]") { _, message, e -> - MiraiConsole.logger("[BOT $account]", account, message) - if (e != null) { - MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ - } - } - } - this.networkLoggerSupplier = { - SimpleLogger("BOT $account") { _, message, e -> - MiraiConsole.logger("[NETWORK]", account, message)//因为在一页 所以可以不打QQ - if (e != null) { - MiraiConsole.logger("[NETWORK ERROR]", account, e)//因为在一页 所以可以不打QQ - } - } - } - } - bot.login() - MiraiConsole.subscribeMessages { - startsWith(commandPrefix) { message -> - if (this.bot != bot) return@startsWith - - if (bot.checkManager(this.sender.id)) { - val sender = if (this is GroupMessageEvent) { - GroupContactCommandSender(bot,this.sender, this.subject) - } else { - ContactCommandSender(bot,this.subject) - } - CommandManager.runCommand( - sender, message - ) - } - } - } - sendMessage("$account login successes") - MiraiConsole.frontEnd.pushBot(bot) - } catch (e: Exception) { - sendMessage("$account login failed -> " + e.message) - } - } - - private fun String.property(): String? = System.getProperty(this) - - @JvmSynthetic - internal fun tryLoginAuto() { - // For java -Dmirai.account=10086 -Dmirai.password=Password -jar mirai-console-wrapper-X.jar - val account = ("mirai.account".property() ?: return).toLong() - val password = "mirai.password".property() ?: "mirai.passphrase".property() ?: "mirai.passwd".property() - if (password == null) { - MiraiConsole.logger.invoke( - SimpleLogger.LogPriority.ERROR, "[AUTO LOGIN]", account, - "Find the account to be logged in, but no password specified" - ) - return - } - GlobalScope.launch { - ConsoleCommandSender.login(account, password) - } - } - - operator fun invoke() { - registerConsoleCommands { - name = "manager" - description = "Add a manager" - onCommand { it -> - if (this !is ConsoleCommandSender) { - sendMessage("请在后台使用该指令") - return@onCommand false - } - if (it.size < 2) { - MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]") - MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]") - MiraiConsole.logger("[Bot Manager]", 0, "/manager list [bot ID]") - return@onCommand true - } - val botId = try { - it[1].toLong() - } catch (e: Exception) { - MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个Bot的ID") - return@onCommand false - } - val bot = MiraiConsole.getBotOrNull(botId) - if (bot == null) { - MiraiConsole.logger("[Bot Manager]", 0, "$botId 没有在Console中登陆") - return@onCommand false - } - when (it[0]) { - "add" -> { - if (it.size < 3) { - MiraiConsole.logger("[Bot Manager]", 0, "/manager add [bot ID] [Manager ID]") - return@onCommand true - } - val adminID = try { - it[2].toLong() - } catch (e: Exception) { - MiraiConsole.logger("[Bot Manager]", 0, it[2] + " 不是一个ID") - return@onCommand false - } - if (bot.addManager(adminID)) { - MiraiConsole.logger("[Bot Manager]", 0, it[2] + "增加成功") - } else { - MiraiConsole.logger("[Bot Manager]", 0, it[2] + "已经是一个manager了") - } - } - "remove" -> { - if (it.size < 3) { - MiraiConsole.logger("[Bot Manager]", 0, "/manager remove [bot ID] [Manager ID]") - return@onCommand true - } - val adminID = try { - it[2].toLong() - } catch (e: Exception) { - MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个ID") - return@onCommand false - } - if (!bot.checkManager(adminID)) { - MiraiConsole.logger("[Bot Manager]", 0, it[2] + "本身不是一个Manager") - return@onCommand true - } - bot.removeManager(adminID) - MiraiConsole.logger("[Bot Manager]", 0, it[2] + "移除成功") - } - "list" -> { - bot.managers.forEach { - MiraiConsole.logger("[Bot Manager]", 0, " -> $it") - } - } - } - return@onCommand true - } - } - - registerConsoleCommands { - name = "login" - description = "机器人登录" - onCommand { - if (this !is ConsoleCommandSender) { - sendMessage("请在后台使用该指令") - return@onCommand false - } - if (it.size < 2) { - MiraiConsole.logger("\"/login qq password \" to login a bot") - MiraiConsole.logger("\"/login qq号 qq密码 \" 来登录一个BOT") - return@onCommand false - } - val qqNumber = it[0].toLong() - val qqPassword = it[1] - login(qqNumber, qqPassword) - true - } - } - - registerConsoleCommands { - name = "status" - description = "获取状态" - onCommand { args -> - when (args.size) { - 0 -> { - sendMessage("当前有" + botInstances.size + "个BOT在线") - } - 1 -> { - val bot = args[0] - var find = false - botInstances.forEach { - if (it.id.toString().contains(bot)) { - find = true - appendMessage( - "" + it.id + ": 在线中; 好友数量:" + it.friends.size + "; 群组数量:" + it.groups.size - ) - } - } - if (!find) { - sendMessage("没有找到BOT$bot") - } - } - } - true - } - } - - - registerConsoleCommands { - name = "say" - description = "聊天功能演示" - onCommand { - if (it.size < 2) { - MiraiConsole.logger("say [好友qq号或者群号] [测试消息] //将默认使用第一个BOT") - MiraiConsole.logger("say [bot号] [好友qq号或者群号] [测试消息]") - return@onCommand false - } - val bot: Bot? = if (it.size == 2) { - if (botInstances.isEmpty()) { - MiraiConsole.logger("还没有BOT登录") - return@onCommand false - } - botInstances[0] - } else { - MiraiConsole.getBotOrNull(it[0].toLong()) - } - if (bot == null) { - MiraiConsole.logger("没有找到BOT") - return@onCommand false - } - val target = it[it.size - 2].toLong() - val message = it[it.size - 1] - try { - val contact = bot.getFriendOrNull(target) ?: bot.getGroup(target) - contact.sendMessage(message) - MiraiConsole.logger("消息已推送") - } catch (e: NoSuchElementException) { - MiraiConsole.logger("没有找到群或好友 号码为${target}") - return@onCommand false - } - true - } - } - - - registerConsoleCommands { - name = "plugins" - alias = listOf("plugin") - description = "获取插件列表" - onCommand { - PluginManager.getAllPluginDescriptions().let { descriptions -> - descriptions.forEach { - appendMessage("\t" + it.name + " v" + it.version + " by " + it.author + " " + it.info) - } - appendMessage("加载了" + descriptions.size + "个插件") - true - } - } - } - - registerConsoleCommands { - name = "command" - alias = listOf("commands", "help", "helps") - description = "获取指令列表" - onCommand { - CommandManager.commands.toSet().let { commands -> - var size = 0 - appendMessage("")//\n - commands.forEach { - ++size - appendMessage("-> " + it.name + " :" + it.description) - } - appendMessage("""共有${size}条指令""") - } - true - } - } - - registerConsoleCommands { - name = "about" - description = "About Mirai-Console" - onCommand { - appendMessage("v${MiraiConsole.version} ${MiraiConsole.build} is still in testing stage, major features are available") - appendMessage("now running under ${MiraiConsole.path}") - appendMessage("在Github中获取项目最新进展: https://github.com/mamoe/mirai") - appendMessage("Mirai为开源项目,请自觉遵守开源项目协议") - appendMessage("Powered by Mamoe Technologies and contributors") - true - } - } - - registerConsoleCommands { - name = "reload" - alias = listOf("reloadPlugins") - description = "重新加载全部插件" - onCommand { - PluginManager.reloadPlugins() - sendMessage("重新加载完成") - true - } - } - - registerConsoleCommands { - name = "install" - description = "Install plugin from PluginCenter" - usage = "/install [plugin-name] to install plugin or /install [page-num] to show list " - onCommand { args -> - - val center = MiraiConsole.frontEnd.pluginCenter - - suspend fun showPage(num: Int) { - sendMessage("正在连接 " + center.name) - val list = center.fetchPlugin(num) - if (list.isEmpty()) { - sendMessage("页码过大") - return - } - sendMessage("显示插件列表第 $num 页") - appendMessage("\n") - list.values.forEach { - appendMessage("=> " + it.name + " ;作者: " + it.author + " ;介绍: " + it.description) - } - sendMessage("使用 /install ${num + 1} 查看下一页") - } - - suspend fun installPlugin(name: String) { - sendMessage("正在连接 " + center.name) - val plugin = center.findPlugin(name) - if (plugin == null) { - sendMessage("插件未找到, 请注意大小写") - return - } - sendMessage("正在安装 " + plugin.name) - try { - center.downloadPlugin(name) {} - sendMessage("安装 " + plugin.name + " 成功, 请重启服务器以更新") - } catch (e: Exception) { - sendMessage("安装 " + plugin.name + " 失败, " + (e.message ?: "未知原因")) - } - } - - if (args.isEmpty()) { - showPage(1) - } else { - val arg = args[0] - - val id = arg.toIntOrNull() ?: 0 - if (id > 0) { - showPage(id) - } else { - installPlugin(arg) - } - } - true - } - } - } -} - - */ \ No newline at end of file +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 2bbdc769a..5a29b2559 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -54,7 +54,6 @@ public interface Command { /** * 指令权限 */ - @ExperimentalPermission public val permission: Permission /** @@ -85,13 +84,13 @@ public interface Command { @JvmStatic public val Command.primaryName: String get() = names[0] + + @JvmSynthetic + public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = + sender.run { onCommand(args) } } } -@JvmSynthetic -public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit = - sender.run { onCommand(args) } - /** * [Command] 的基础实现 * diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 1aa13de3d..10a1bd6d9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -32,10 +32,10 @@ import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf -import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier +import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.ExperimentalPermission -import net.mamoe.mirai.console.permission.Permissible -import net.mamoe.mirai.console.permission.PermissibleIdentifier +import net.mamoe.mirai.console.permission.Permittee +import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext @@ -137,7 +137,7 @@ import kotlin.internal.LowPriorityInOverloadResolution * @see asCommandSender */ @OptIn(ExperimentalPermission::class) -public interface CommandSender : CoroutineScope, Permissible { +public interface CommandSender : CoroutineScope, Permittee { /** * 与这个 [CommandSender] 相关的 [Bot]. * 当通过控制台执行时为 `null`. @@ -504,6 +504,9 @@ public fun CommandSender.getBotOrNull(): Bot? { /** * 控制台指令执行者. 代表由控制台执行指令 + * + * 控制台拥有一切指令的执行权限. + * * @see INSTANCE */ // 前端实现 @@ -515,7 +518,7 @@ public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constr public final override fun toString(): String = NAME @ExperimentalPermission - public final override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.Console + public final override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope { public const val NAME: String = "ConsoleCommandSender" @@ -607,13 +610,13 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 */ public open class FriendCommandSender internal constructor( - public final override val user: Friend + public final override val user: Friend, ) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") { public override val subject: Contact get() = user public override fun toString(): String = "FriendCommandSender($user)" @ExperimentalPermission - public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactFriend(user.id) + public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactFriend(user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) @@ -627,7 +630,7 @@ public open class FriendCommandSender internal constructor( * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. */ public open class MemberCommandSender internal constructor( - public final override val user: Member + public final override val user: Member, ) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("MemberCommandSender") { @@ -636,7 +639,7 @@ public open class MemberCommandSender internal constructor( public override fun toString(): String = "MemberCommandSender($user)" @ExperimentalPermission - public override val identifier: PermissibleIdentifier = AbstractPermissibleIdentifier.ExactMember(group.id, user.id) + public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactMember(group.id, user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) @@ -650,7 +653,7 @@ public open class MemberCommandSender internal constructor( * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 */ public open class TempCommandSender internal constructor( - public final override val user: Member + public final override val user: Member, ) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("TempCommandSender") { @@ -659,8 +662,8 @@ public open class TempCommandSender internal constructor( public override fun toString(): String = "TempCommandSender($user)" @ExperimentalPermission - public override val identifier: PermissibleIdentifier = - AbstractPermissibleIdentifier.ExactTemp(user.group.id, user.id) + public override val permitteeId: PermitteeId = + AbstractPermitteeId.ExactTemp(user.group.id, user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) @@ -695,7 +698,7 @@ public interface CommandSenderOnMessage : * @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式 */ public class FriendCommandSenderOnMessage internal constructor( - public override val fromEvent: FriendMessageEvent + public override val fromEvent: FriendMessageEvent, ) : FriendCommandSender(fromEvent.sender), CommandSenderOnMessage, MessageEventExtensions by fromEvent { @@ -708,7 +711,7 @@ public class FriendCommandSenderOnMessage internal constructor( * @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式 */ public class MemberCommandSenderOnMessage internal constructor( - public override val fromEvent: GroupMessageEvent + public override val fromEvent: GroupMessageEvent, ) : MemberCommandSender(fromEvent.sender), CommandSenderOnMessage, MessageEventExtensions by fromEvent { @@ -721,7 +724,7 @@ public class MemberCommandSenderOnMessage internal constructor( * @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式 */ public class TempCommandSenderOnMessage internal constructor( - public override val fromEvent: TempMessageEvent + public override val fromEvent: TempMessageEvent, ) : TempCommandSender(fromEvent.sender), CommandSenderOnMessage, MessageEventExtensions by fromEvent { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt index 920b7b857..714bc639a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt @@ -17,8 +17,8 @@ import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair import net.mamoe.mirai.console.permission.ExperimentalPermission -import net.mamoe.mirai.console.permission.PermissibleIdentifier import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* import kotlin.internal.LowPriorityInOverloadResolution @@ -85,7 +85,7 @@ public interface CommandArgumentContext { Bot::class with ExistingBotArgumentParser PermissionId::class with PermissionIdArgumentParser - PermissibleIdentifier::class with PermissibleIdentifierArgumentParser + PermitteeId::class with PermissibleIdentifierArgumentParser }) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt index 3484296f1..91cfffcae 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParserBuiltins.kt @@ -13,10 +13,10 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender import net.mamoe.mirai.console.internal.command.fuzzySearchMember -import net.mamoe.mirai.console.permission.AbstractPermissibleIdentifier +import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.ExperimentalPermission -import net.mamoe.mirai.console.permission.PermissibleIdentifier import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.contact.* import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getGroupOrNull @@ -318,17 +318,17 @@ public object PermissionIdArgumentParser : CommandArgumentParser { } @ExperimentalPermission -public object PermissibleIdentifierArgumentParser : CommandArgumentParser { - override fun parse(raw: String, sender: CommandSender): PermissibleIdentifier { - return if (raw == "~") sender.identifier - else kotlin.runCatching { AbstractPermissibleIdentifier.parseFromString(raw) }.getOrElse { +public object PermissibleIdentifierArgumentParser : CommandArgumentParser { + override fun parse(raw: String, sender: CommandSender): PermitteeId { + return if (raw == "~") sender.permitteeId + else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse { illegalArgument("无法解析 $raw 为 PermissibleIdentifier") } } - override fun parse(raw: MessageContent, sender: CommandSender): PermissibleIdentifier { + override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId { if (raw is At) { - return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).identifier + return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId } return super.parse(raw, sender) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt index e7ffcc587..6886e3da4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt @@ -65,7 +65,7 @@ public class ScopedComponentStorage( } /** - * 注册一个扩展 + * 注册一个 [PermissionService] */ @ExperimentalPermission public fun contributePermissionService( diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 7109b7c0e..f9ebc4e55 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -36,9 +36,9 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector +import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.util.autoHexToBytes -import net.mamoe.mirai.console.permission.BuiltInPermissionService import net.mamoe.mirai.console.permission.ExperimentalPermission import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/AbstractConcurrentPermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/AbstractConcurrentPermissionService.kt similarity index 64% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/AbstractConcurrentPermissionService.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/AbstractConcurrentPermissionService.kt index 21563301a..7bdc4bedf 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/AbstractConcurrentPermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/AbstractConcurrentPermissionService.kt @@ -7,10 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.permission +package net.mamoe.mirai.console.internal.permission import net.mamoe.mirai.console.data.PluginDataExtensions -import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith +import net.mamoe.mirai.console.permission.* +import net.mamoe.mirai.console.permission.PermitteeId.Companion.hasChild /** * @@ -18,7 +19,7 @@ import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grante @ExperimentalPermission internal abstract class AbstractConcurrentPermissionService

: PermissionService

{ protected abstract val permissions: MutableMap - protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap> + protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap> protected abstract fun createPermission( id: PermissionId, @@ -31,26 +32,28 @@ internal abstract class AbstractConcurrentPermissionService

: Pe override fun register(id: PermissionId, description: String, parent: Permission): P { val instance = createPermission(id, description, parent) val old = permissions.putIfAbsent(id, instance) - if (old != null) throw DuplicatedPermissionRegistrationException(instance, old) + if (old != null) throw PermissionRegistryConflictException(instance, old) return instance } - override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) { + override fun permit(permitteeId: PermitteeId, permission: P) { val id = permission.id - grantedPermissionsMap[id].add(permissibleIdentifier) + grantedPermissionsMap[id].add(permitteeId) } - override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) { - grantedPermissionsMap[permission.id].remove(permissibleIdentifier) + override fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) { + if (recursive) { + grantedPermissionsMap[permission.id] + } else grantedPermissionsMap[permission.id].remove(permitteeId) } override fun getRegisteredPermissions(): Sequence

= permissions.values.asSequence() - override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence

= sequence

{ + override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence

= sequence

{ for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) { val granted = if (permissibleIdentifiers.isEmpty()) false - else permissibleIdentifiers.any { permissibleIdentifier.grantedWith(it) } + else permissibleIdentifiers.any { permitteeId.hasChild(it) } if (granted) get(permissionIdentifier)?.let { yield(it) } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt similarity index 72% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt index c1c3918e2..a49fcfdb2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/BuiltInPermissionServices.kt @@ -7,20 +7,33 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.console.permission +package net.mamoe.mirai.console.internal.permission import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.PluginDataExtensions import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.permission.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArraySet import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf +@Suppress("unused") // don't pollute top-level +@OptIn(ExperimentalPermission::class) +internal fun PermissionService<*>.checkType(permissionType: KClass): PermissionService { + require(this.permissionType.isSuperclassOf(permissionType)) { + "Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " + + "Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get" + } + + @Suppress("UNCHECKED_CAST") + return this as PermissionService +} @ExperimentalPermission -internal object AllGrantPermissionService : PermissionService { +internal object AllPermitPermissionService : PermissionService { private val all = ConcurrentHashMap() override val permissionType: KClass get() = PermissionImpl::class override val rootPermission: PermissionImpl get() = RootPermissionImpl.also { all[it.id] = it } @@ -28,26 +41,26 @@ internal object AllGrantPermissionService : PermissionService { override fun register( id: PermissionId, description: String, - parent: Permission + parent: Permission, ): PermissionImpl { val new = PermissionImpl(id, description, parent) val old = all.putIfAbsent(id, new) - if (old != null) throw DuplicatedPermissionRegistrationException(new, old) + if (old != null) throw PermissionRegistryConflictException(new, old) return new } override fun get(id: PermissionId): PermissionImpl? = all[id] override fun getRegisteredPermissions(): Sequence = all.values.asSequence() - override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence = + override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence = all.values.asSequence() - override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) { + override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) { } - override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean = + override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean = true - override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) { + override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) { } } @@ -65,26 +78,26 @@ internal object AllDenyPermissionService : PermissionService { override fun register( id: PermissionId, description: String, - parent: Permission + parent: Permission, ): PermissionImpl { val new = PermissionImpl(id, description, parent) val old = all.putIfAbsent(id, new) - if (old != null) throw DuplicatedPermissionRegistrationException(new, old) + if (old != null) throw PermissionRegistryConflictException(new, old) return new } override fun get(id: PermissionId): PermissionImpl? = all[id] override fun getRegisteredPermissions(): Sequence = all.values.asSequence() - override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence = + override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence = emptySequence() - override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) { + override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) { } - override fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl): Boolean = + override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean = false - override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: PermissionImpl) { + override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) { } } @@ -99,8 +112,8 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService

> - get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap> + override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap> + get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap> override fun createPermission(id: PermissionId, description: String, parent: Permission): PermissionImpl = PermissionImpl(id, description, parent) @@ -112,10 +125,10 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService

> - by value>>(ConcurrentHashMap()) + public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap> + by value>>(ConcurrentHashMap()) .withDefault { CopyOnWriteArraySet() } public companion object { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/parseFromStringImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/parseFromStringImpl.kt new file mode 100644 index 000000000..6cc91ca49 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/parseFromStringImpl.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console.internal.permission + +import net.mamoe.mirai.console.permission.AbstractPermitteeId +import net.mamoe.mirai.console.permission.AbstractPermitteeId.* +import net.mamoe.mirai.console.permission.ExperimentalPermission + +@OptIn(ExperimentalPermission::class) +internal fun parseFromStringImpl(string: String): AbstractPermitteeId { + val str = string.trim { it.isWhitespace() }.toLowerCase() + if (str == "console") return Console + if (str.isNotEmpty()) { + when (str[0]) { + 'g' -> { + val arg = str.substring(1) + if (arg == "*") return AnyGroup + else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it } + } + 'f' -> { + val arg = str.substring(1) + if (arg == "*") return AnyFriend + else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it } + } + 'u' -> { + val arg = str.substring(1) + if (arg == "*") return AnyUser + else arg.toLongOrNull()?.let(::ExactUser)?.let { return it } + } + 'c' -> { + val arg = str.substring(1) + if (arg == "*") return AnyContact + } + 'm' -> kotlin.run { + val arg = str.substring(1) + if (arg == "*") return AnyMemberFromAnyGroup + else { + val components = arg.split('.') + + if (components.size == 2) { + val groupId = components[0].toLongOrNull() ?: return@run + + if (components[1] == "*") return AnyMember(groupId) + else { + val memberId = components[1].toLongOrNull() ?: return@run + return ExactMember(groupId, memberId) + } + } + } + } + 't' -> kotlin.run { + val arg = str.substring(1) + if (arg == "*") return AnyTempFromAnyGroup + else { + val components = arg.split('.') + + if (components.size == 2) { + val groupId = components[0].toLongOrNull() ?: return@run + + if (components[1] == "*") return AnyTemp(groupId) + else { + val memberId = components[1].toLongOrNull() ?: return@run + return ExactTemp(groupId, memberId) + } + } + } + } + } + } + error("Cannot deserialize '$str' as AbstractPermissibleIdentifier") +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/CommonUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/CommonUtils.kt new file mode 100644 index 000000000..516609400 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/CommonUtils.kt @@ -0,0 +1,30 @@ +/* + * 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:JvmName("CommonUtils") + +package net.mamoe.mirai.console.internal.util + +internal inline fun runIgnoreException(block: () -> R): R? { + try { + return block() + } catch (e: Throwable) { + if (e is E) return null + throw e + } +} + +internal inline fun runIgnoreException(block: () -> Unit): Unit? { + try { + return block() + } catch (e: Throwable) { + if (e is E) return null + throw e + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissibleIdentifier.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissibleIdentifier.kt deleted file mode 100644 index 515b59164..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissibleIdentifier.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -@file:Suppress("unused") - -package net.mamoe.mirai.console.permission - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.serializer -import net.mamoe.mirai.console.internal.data.map -import net.mamoe.mirai.console.util.ConsoleExperimentalApi - -/** - */ -@ExperimentalPermission("Classname is subject to change") -public interface PermissibleIdentifier { - public val parents: Array - - public companion object { - @ExperimentalPermission - public fun PermissibleIdentifier.grantedWith(with: PermissibleIdentifier): Boolean { - return allParentsWithSelf().any { it == with } - } - - private fun PermissibleIdentifier.allParentsWithSelf(): Sequence { - return sequence { - yield(this@allParentsWithSelf) - yieldAll(parents.asSequence().flatMap { it.allParentsWithSelf() }) - } - } - } -} - -@Serializable(with = AbstractPermissibleIdentifier.AsStringSerializer::class) -@ExperimentalPermission -public sealed class AbstractPermissibleIdentifier( - public final override vararg val parents: PermissibleIdentifier -) : PermissibleIdentifier { - public companion object { - @JvmStatic - public fun parseFromString(string: String): AbstractPermissibleIdentifier { - val str = string.trim { it.isWhitespace() }.toLowerCase() - if (str == "console") return Console - if (str.isNotEmpty()) { - when (str[0]) { - 'g' -> { - val arg = str.substring(1) - if (arg == "*") return AnyGroup - else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it } - } - 'f' -> { - val arg = str.substring(1) - if (arg == "*") return AnyFriend - else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it } - } - 'u' -> { - val arg = str.substring(1) - if (arg == "*") return AnyUser - else arg.toLongOrNull()?.let(::ExactUser)?.let { return it } - } - 'c' -> { - val arg = str.substring(1) - if (arg == "*") return AnyContact - } - 'm' -> kotlin.run { - val arg = str.substring(1) - if (arg == "*") return AnyMemberFromAnyGroup - else { - val components = arg.split('.') - - if (components.size == 2) { - val groupId = components[0].toLongOrNull() ?: return@run - - if (components[1] == "*") return AnyMember(groupId) - else { - val memberId = components[1].toLongOrNull() ?: return@run - return ExactMember(groupId, memberId) - } - } - } - } - 't' -> kotlin.run { - val arg = str.substring(1) - if (arg == "*") return AnyTempFromAnyGroup - else { - val components = arg.split('.') - - if (components.size == 2) { - val groupId = components[0].toLongOrNull() ?: return@run - - if (components[1] == "*") return AnyTemp(groupId) - else { - val memberId = components[1].toLongOrNull() ?: return@run - return ExactTemp(groupId, memberId) - } - } - } - } - } - } - error("Cannot deserialize '$str' as AbstractPermissibleIdentifier") - } - } - - @ConsoleExperimentalApi - public object AsStringSerializer : KSerializer by String.serializer().map( - serializer = { it.toString() }, - - deserializer = d@{ str -> parseFromString(str) } - ) - - public object AnyGroup : AbstractPermissibleIdentifier(AnyContact) { - override fun toString(): String = "g*" - } - - public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup) { - override fun toString(): String = "g$groupId" - } - - public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup) { - override fun toString(): String = "m$groupId.*" - } - - public object AnyMemberFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) { - override fun toString(): String = "m*" - } - - public object AnyTempFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) { - override fun toString(): String = "t*" - } - - public data class ExactMember( - public val groupId: Long, - public val memberId: Long - ) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId)) { - override fun toString(): String = "m$groupId.$memberId" - } - - public object AnyFriend : AbstractPermissibleIdentifier(AnyUser) { - override fun toString(): String = "f*" - } - - public data class ExactFriend( - public val id: Long - ) : AbstractPermissibleIdentifier(ExactUser(id)) { - override fun toString(): String = "f$id" - } - - public data class AnyTemp( - public val groupId: Long, - ) : AbstractPermissibleIdentifier(AnyUser, AnyMember(groupId)) { - override fun toString(): String = "t$groupId.*" - } - - public data class ExactTemp( - public val groupId: Long, - public val memberId: Long - ) : AbstractPermissibleIdentifier(ExactUser(groupId), ExactMember(groupId, memberId)) { - override fun toString(): String = "t$groupId.$memberId" - } - - public object AnyUser : AbstractPermissibleIdentifier(AnyContact) { - override fun toString(): String = "u*" - } - - public data class ExactUser( - public val id: Long - ) : AbstractPermissibleIdentifier(AnyUser) { - override fun toString(): String = "u$id" - } - - public object AnyContact : AbstractPermissibleIdentifier() { - override fun toString(): String = "*" - } - - public object Console : AbstractPermissibleIdentifier() { - override fun toString(): String = "console" - } -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt index e251b77de..be88bbd34 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permission.kt @@ -9,49 +9,75 @@ package net.mamoe.mirai.console.permission -import net.mamoe.mirai.console.util.ConsoleExperimentalApi - +import net.mamoe.mirai.console.command.BuiltInCommands +import net.mamoe.mirai.console.command.Command /** - * 一个权限节点. + * 一个权限. * * 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id]. * - * 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例. + * **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例. + * + * ### 获取 [Permission] + * + * #### 根权限 + * [RootPermission] 是所有权限的父权限. + * + * #### 指令的权限 + * 每个指令都拥有一个 [Command.permission]. + * + * [BuiltInCommands.rootPermission] 为所有内建指令的权限. + * + * #### 手动申请权限 + * [PermissionService.register] */ -@ExperimentalPermission public interface Permission { + /** + * 唯一识别 ID. 所有权限的 [id] 都互不相同. + * + * @see PermissionService.get 由 [id] 获取已注册的 [Permission] + * @see PermissionId + */ public val id: PermissionId + + /** + * 描述信息. 描述信息在注册权限时强制提供. + */ public val description: String /** + * 父权限. + * * [RootPermission] 的 parent 为自身 */ public val parent: Permission + + public companion object { + /** + * 根权限. 是所有权限的父权限. + * + * 供 Java 用户使用. + * + * @see RootPermission 推荐 Kotlin 用户使用. + */ + @JvmStatic + public fun getRootPermission(): Permission = PermissionService.INSTANCE.rootPermission + + /** + * 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent` ... 直到 [Permission.parent] 为它自己. + */ + @get:JvmStatic + public val Permission.parentsWithSelf: Sequence + get() = generateSequence(this) { p -> + p.parent.takeIf { parent -> parent != p } + } + } } /** - * 所有权限的父权限. + * 根权限. 是所有权限的父权限. */ -@get:JvmName("getRootPermission") -@ExperimentalPermission +@get:JvmSynthetic public val RootPermission: Permission - get() = PermissionService.INSTANCE.rootPermission - -/** - * 所有内建指令的权限 - */ -@ExperimentalPermission -public val RootConsoleBuiltInPermission: Permission by lazy { - PermissionService.INSTANCE.register( - PermissionId("console", "*"), - "The parent of any built-in commands" - ) -} - -@ConsoleExperimentalApi -@ExperimentalPermission -public fun Permission.parentsWithSelfSequence(): Sequence = - generateSequence(this) { p -> - p.parent.takeIf { parent -> parent != p } - } \ No newline at end of file + get() = PermissionService.INSTANCE.rootPermission \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt index d4362a5ae..caabfd017 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionId.kt @@ -11,19 +11,19 @@ package net.mamoe.mirai.console.permission import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.Serializer import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.internal.data.map /** + * 表示一个 [权限][Permission] 的唯一 ID. + * * [PermissionId] 与 [Permission] 唯一对应. */ -@Serializable(with = PermissionId.AsStringSerializer::class) -@ExperimentalPermission +@Serializable(with = PermissionId.PermissionIdAsStringSerializer::class) public data class PermissionId( public val namespace: String, - public val id: String + public val id: String, ) { init { require(!namespace.contains(':')) { @@ -34,21 +34,30 @@ public data class PermissionId( } } - @Serializer(forClass = PermissionId::class) - public object AsClassSerializer - - public object AsStringSerializer : KSerializer by String.serializer().map( + public object PermissionIdAsStringSerializer : KSerializer by String.serializer().map( serializer = { it.namespace + ":" + it.id }, deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } } ) - public override fun toString(): String { - return "$namespace:$id" - } + /** + * 返回 `$namespace:$id` + */ + public override fun toString(): String = "$namespace:$id" public companion object { - public fun parseFromString(string: String): PermissionId = - string.split(':').let { (namespace, id) -> PermissionId(namespace, id) } + /** + * 由 `$namespace:$id` 解析 [PermissionId]. + * + * @throws IllegalArgumentException 在解析失败时抛出. + */ + @JvmStatic + public fun parseFromString(string: String): PermissionId { + return kotlin.runCatching { + string.split(':').let { (namespace, id) -> PermissionId(namespace, id) } + }.getOrElse { + throw IllegalArgumentException("Could not parse PermissionId from '$string'", it) + } + } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt index 6ce0a93a5..305369090 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdNamespace.kt @@ -9,8 +9,6 @@ package net.mamoe.mirai.console.permission -@ExperimentalPermission public interface PermissionIdNamespace { - @ExperimentalPermission public fun permissionId(id: String): PermissionId } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt new file mode 100644 index 000000000..ddedd80d7 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionImplementation.kt @@ -0,0 +1,23 @@ +/* + * 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.permission + +import kotlin.annotation.AnnotationTarget.* + +/** + * 表示一个应该由 [权限服务][PermissionService] 实现的类. + * + * 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段. + * 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们. + */ +@Retention(AnnotationRetention.BINARY) +@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) +@MustBeDocumented +internal annotation class PermissionImplementation \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionNotFoundException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionNotFoundException.kt deleted file mode 100644 index e005fd0aa..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionNotFoundException.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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.permission - -@ExperimentalPermission -public open class PermissionNotFoundException public constructor( - public val id: PermissionId -) : Exception(id.toString()) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/DuplicatedPermissionRegistrationException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionRegistryConflictException.kt similarity index 68% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/DuplicatedPermissionRegistrationException.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionRegistryConflictException.kt index 39cbb2807..de85ebe53 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/DuplicatedPermissionRegistrationException.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionRegistryConflictException.kt @@ -11,8 +11,10 @@ package net.mamoe.mirai.console.permission -@ExperimentalPermission -public class DuplicatedPermissionRegistrationException( - newInstance: Permission, - public val existingInstance: Permission -) : Exception("Duplicated Permission registry. new: $newInstance, existing: $existingInstance") \ No newline at end of file +/** + * @see PermissionService.register + */ +public class PermissionRegistryConflictException( + public val newInstance: Permission, + public val existingInstance: Permission, +) : Exception("Conflicted Permission registry. new: $newInstance, existing: $existingInstance") \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index cef6b07de..787792e09 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -12,13 +12,18 @@ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.extensions.PermissionServiceProvider +import net.mamoe.mirai.console.internal.permission.checkType +import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import kotlin.reflect.KClass -import kotlin.reflect.full.isSuperclassOf /** + * 权限服务. 用于承载 Console 的权限系统. + * + * ### 可扩展 + * 权限服务可由插件扩展并覆盖默认实现. + * * [PermissionServiceProvider] */ -@ExperimentalPermission public interface PermissionService

{ @ExperimentalPermission public val permissionType: KClass

@@ -29,12 +34,25 @@ public interface PermissionService

{ public operator fun get(id: PermissionId): P? public fun getRegisteredPermissions(): Sequence

- public fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence

- public fun testPermission(permissibleIdentifier: PermissibleIdentifier, permission: P): Boolean { + /** + * 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表 + */ + public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence

+ + /** + * 判断 [permission] 对 [permission] 的权限. + * + * 返回 `true` 的意义: + * - 通常意义: [permitteeId] 拥有 [permission] 的 '能力' + * - 实现意义: [permitteeId] 自身或任意父标识 [PermissionService] 被授予高于或等于 [permission] 的权限 + * + * @see Companion.testPermission 接收 [Permittee] 参数的扩展 + */ + public fun testPermission(permitteeId: PermitteeId, permission: P): Boolean { val permissionId = permission.id - val all = this[permissionId]?.parentsWithSelfSequence() ?: return false - return getGrantedPermissions(permissibleIdentifier).any { p -> + val all = this[permissionId]?.parentsWithSelf ?: return false + return getPermittedPermissions(permitteeId).any { p -> all.any { p.id == it.id } } } @@ -42,7 +60,12 @@ public interface PermissionService

{ /////////////////////////////////////////////////////////////////////////// - @Throws(DuplicatedPermissionRegistrationException::class) + /** + * 申请并注册一个权限 [Permission]. + * + * @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出. + */ + @Throws(PermissionRegistryConflictException::class) public fun register( id: PermissionId, description: String, @@ -51,8 +74,28 @@ public interface PermissionService

{ /////////////////////////////////////////////////////////////////////////// - public fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) - public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) + /** + * 授予 [permitteeId] 以 [permission] 权限 + * + * Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持. + * + * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. + */ + public fun permit(permitteeId: PermitteeId, permission: P) + + /** + * 撤销 [permitteeId] 的 [permission] 授权 + * + * Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持. + * + * @param recursive `true` 时递归撤销所有子权限. + * 例如, 若 [permission] 为 "*:*", + * recursive 为 `true` 时撤销全部权限 (因为所有权限都是 "*:*" 的子权限); + * 而为 `false` 时仅撤销 "*:*" 本身, 而不会影响子权限. + * + * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. + */ + public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) public companion object { internal var instanceField: PermissionService<*>? = null @@ -63,7 +106,7 @@ public interface PermissionService

{ get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.") public fun

PermissionService

.getOrFail(id: PermissionId): P = - get(id) ?: throw PermissionNotFoundException(id) + get(id) ?: throw NoSuchElementException("Permission not found: $id") internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) = PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase()) @@ -72,81 +115,68 @@ public interface PermissionService

{ public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this) - public fun PermissibleIdentifier.grantPermission(permission: Permission) { - INSTANCE.checkType(permission::class).grant(this, permission) + public fun PermitteeId.grantPermission(permission: Permission) { + INSTANCE.checkType(permission::class).permit(this, permission) } - public fun PermissibleIdentifier.grantPermission(permissionId: PermissionId) { + public fun PermitteeId.grantPermission(permissionId: PermissionId) { grantPermission(permissionId.findCorrespondingPermissionOrFail()) } - public fun PermissibleIdentifier.denyPermission(permission: Permission) { - INSTANCE.checkType(permission::class).deny(this, permission) + public fun PermitteeId.denyPermission(permission: Permission, recursive: Boolean) { + INSTANCE.checkType(permission::class).cancel(this, permission, recursive) } - public fun PermissibleIdentifier.denyPermission(permissionId: PermissionId) { - denyPermission(permissionId.findCorrespondingPermissionOrFail()) + public fun PermitteeId.denyPermission(permissionId: PermissionId, recursive: Boolean) { + denyPermission(permissionId.findCorrespondingPermissionOrFail(), recursive) } - public fun Permissible.hasPermission(permission: Permission): Boolean = + public fun Permittee.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) - public fun PermissibleIdentifier.hasPermission(permission: Permission): Boolean = + public fun PermitteeId.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) - public fun PermissibleIdentifier.hasPermission(permissionId: PermissionId): Boolean { + public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean { val instance = permissionId.findCorrespondingPermissionOrFail() return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance) } - public fun Permissible.hasPermission(permissionId: PermissionId): Boolean = + public fun Permittee.hasPermission(permissionId: PermissionId): Boolean = permissionId.testPermission(this@hasPermission) - public fun Permissible.getGrantedPermissions(): Sequence = - INSTANCE.getGrantedPermissions(this@getGrantedPermissions.identifier) + public fun Permittee.getPermittedPermissions(): Sequence = + INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId) - public fun Permissible.grantPermission(vararg permissions: Permission) { + public fun Permittee.grantPermission(vararg permissions: Permission) { for (permission in permissions) { - INSTANCE.checkType(permission::class).grant(this.identifier, permission) + INSTANCE.checkType(permission::class).permit(this.permitteeId, permission) } } - public fun Permissible.denyPermission(vararg permissions: Permission) { + public fun Permittee.denyPermission(vararg permissions: Permission, recursive: Boolean) { for (permission in permissions) { - INSTANCE.checkType(permission::class).deny(this.identifier, permission) + INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive) } } - public fun PermissibleIdentifier.getGrantedPermissions(): Sequence = - INSTANCE.getGrantedPermissions(this@getGrantedPermissions) + public fun PermitteeId.getPermittedPermissions(): Sequence = + INSTANCE.getPermittedPermissions(this@getPermittedPermissions) - public fun Permission.testPermission(permissible: Permissible): Boolean = - INSTANCE.checkType(this::class).testPermission(permissible.identifier, this@testPermission) + public fun Permission.testPermission(permittee: Permittee): Boolean = + INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission) - public fun Permission.testPermission(permissibleIdentifier: PermissibleIdentifier): Boolean = - INSTANCE.checkType(this::class).testPermission(permissibleIdentifier, this@testPermission) + public fun Permission.testPermission(permitteeId: PermitteeId): Boolean = + INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission) - public fun PermissionId.testPermission(permissible: Permissible): Boolean { + public fun PermissionId.testPermission(permittee: Permittee): Boolean { val p = INSTANCE[this] ?: return false - return p.testPermission(permissible) + return p.testPermission(permittee) } - public fun PermissionId.testPermission(permissible: PermissibleIdentifier): Boolean { + public fun PermissionId.testPermission(permissible: PermitteeId): Boolean { val p = INSTANCE[this] ?: return false return p.testPermission(permissible) } } -} - -@OptIn(ExperimentalPermission::class) -internal fun PermissionService<*>.checkType(permissionType: KClass): PermissionService { - return PermissionService.INSTANCE.run { - require(this.permissionType.isSuperclassOf(permissionType)) { - "Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " + - "Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get" - } - - @Suppress("UNCHECKED_CAST") - this as PermissionService - } -} +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permissible.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permittee.kt similarity index 62% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permissible.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permittee.kt index b970e50e5..e7235e22a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permissible.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Permittee.kt @@ -14,15 +14,15 @@ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.CommandSender /** - * 可拥有权限的对象. + * 可被赋予权限的对象, 即 '被许可人'. * - * 典型的实例为 [CommandSender] + * 被许可人自身不持有拥有的权限列表, 而是拥有 [PermitteeId], 标识自己的身份, 供 [权限服务][PermissionService] 处理. * - * 注意: 请不要自主实现 [Permissible] + * **注意**: 请不要自主实现 [Permittee] * * @see CommandSender */ -@ExperimentalPermission -public interface Permissible { - public val identifier: PermissibleIdentifier +@PermissionImplementation +public interface Permittee { + public val permitteeId: PermitteeId } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt new file mode 100644 index 000000000..da363b47f --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermitteeId.kt @@ -0,0 +1,307 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console.permission + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.internal.data.map +import net.mamoe.mirai.console.internal.permission.parseFromStringImpl +import net.mamoe.mirai.console.permission.AbstractPermitteeId.AnyMember +import net.mamoe.mirai.console.permission.AbstractPermitteeId.ExactMember +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.User + +/** + * [被许可人][Permittee] 的标识符 + * + * 一个这样的标识符即可代表特定的单个 [Permittee], 也可以表示多个同类 [Permittee]. + * + * ### 获取 [PermitteeId] + * 总是通过 [Permittee.permitteeId]. + */ +@PermissionImplementation +public interface PermitteeId { + /** + * 直接父 [PermitteeId]. 在检查权限时会首先检查自己, 再递归检查父类. + * + * @see allParentsWithSelf + * @see allParents + */ + public val directParents: Array + + /** + * 转换为字符串表示. 用于权限服务识别和指令的解析. + */ + public fun asString(): String + + public companion object { + /** + * 当 [this] 或 [this] 的任意一个直接或间接父 [PermitteeId.asString] 与 `this.asString` 相同时返回 `true` + */ + @JvmStatic + public fun PermitteeId.hasChild(child: PermitteeId): Boolean { + return allParentsWithSelf.any { it.asString() == child.asString() } // asString is for compatibility issue with external implementations + } + + /** + * 获取所有直接或间接父类的 [PermitteeId]. + */ + @get:JvmStatic + public val PermitteeId.allParentsWithSelf: Sequence + get() = sequence { + yield(this@allParentsWithSelf) + yieldAll(allParents) + } + + /** + * 获取所有直接或间接父类的 [PermitteeId], 返回包含 `this` + 这些父类 的 [Sequence] + */ + @get:JvmStatic + public val PermitteeId.allParents: Sequence + get() = directParents.asSequence().flatMap { it.allParentsWithSelf } + } +} + +/** + * 内建的 [PermitteeId]. + * + * - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令. + * + * ``` + * Console AnyContact + * ↑ + * | + * +---------------------------+------------------------+ + * | | + * AnyUser AnyGroup + * ↑ ↑ + * | | + * +--------------+---------------------+ | + * | | | | + * AnyFriend | AnyMemberFromAnyGroup | + * ↑ | ↑ | + * | | | | + * | | +--------+--------------+ | + * | | | | | + * | | | AnyTempFromAnyGroup | + * | | | ↑ | + * | | AnyMember | | + * | | ↑ | | + * | ExactUser | | ExactGroup + * | ↑ ↑ | | + * | | | | | + * +------------+ +----------+ | + * | | | + * ExactFriend ExactMember | + * ↑ | + * | | + * +-----------------------+ + * | + * | + * ExactTemp + * ``` + */ +@Serializable(with = AbstractPermitteeId.AsStringSerializer::class) +public sealed class AbstractPermitteeId( + public final override vararg val directParents: PermitteeId, +) : PermitteeId { + public final override fun toString(): String = asString() + + public companion object { + /** + * 由 [AbstractPermitteeId.asString] 解析 [AbstractPermitteeId] + */ + @JvmStatic + public fun parseFromString(string: String): AbstractPermitteeId = parseFromStringImpl(string) + } + + /** + * 使用 [asString] 序列化 [AbstractPermitteeId] + */ + @ConsoleExperimentalApi + public object AsStringSerializer : KSerializer by String.serializer().map( + serializer = AbstractPermitteeId::asString, + deserializer = ::parseFromString + ) + + /** + * 表示任何群对象. (不是指群成员, 而是指这个 '群') + * + * - **直接父标识符**: [AnyContact] + * - **间接父标识符**: 无 + * - 字符串表示: "g*" + * + * @see AnyMember + */ + public object AnyGroup : AbstractPermitteeId(AnyContact) { + override fun asString(): String = "g*" + } + + /** + * 表示一个群 + * + * - **直接父标识符**: [AnyGroup] + * - **间接父标识符**: [AnyContact] + * - 字符串表示: "g$groupId" + */ + public data class ExactGroup(public val groupId: Long) : AbstractPermitteeId(AnyGroup) { + override fun asString(): String = "g$groupId" + } + + /** + * 表示来自一个群的任意一个成员 + * + * - **直接父标识符**: [AnyMemberFromAnyGroup] + * - **间接父标识符**: [AnyUser], [AnyContact] + * - 字符串表示: "m$groupId.*" + */ + public data class AnyMember(public val groupId: Long) : AbstractPermitteeId(AnyMemberFromAnyGroup) { + override fun asString(): String = "m$groupId.*" + } + + /** + * 表示来自任意群的任意一个成员 + * + * - **直接父标识符**: [AnyUser] + * - **间接父标识符**: [AnyContact] + * - 字符串表示: "m*" + */ + public object AnyMemberFromAnyGroup : AbstractPermitteeId(AnyUser) { + override fun asString(): String = "m*" + } + + /** + * 表示唯一的一个群成员 + * + * - **直接父标识符**: [AnyMember], [ExactUser] + * - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact] + * - 字符串表示: "m$groupId.$memberId" + */ + public data class ExactMember( + public val groupId: Long, + public val memberId: Long, + ) : AbstractPermitteeId(AnyMember(groupId), ExactUser(memberId)) { + override fun asString(): String = "m$groupId.$memberId" + } + + /** + * 表示任何好友 + * + * - **直接父标识符**: [AnyUser] + * - **间接父标识符**: [AnyContact] + * - 字符串表示: "f*" + */ + public object AnyFriend : AbstractPermitteeId(AnyUser) { + override fun asString(): String = "f*" + } + + /** + * 表示唯一的一个好友 + * + * - **直接父标识符**: [ExactUser] + * - **间接父标识符**: [AnyUser], [AnyContact] + * - 字符串表示: "f$id" + */ + public data class ExactFriend( + public val id: Long, + ) : AbstractPermitteeId(ExactUser(id)) { + override fun asString(): String = "f$id" + } + + /** + * 表示任何一个通过一个群 *在临时会话发送消息的* [群成员][Member] + * + * - **直接父标识符**: [AnyMember], [AnyTempFromAnyGroup] + * - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact] + * - 字符串表示: "t$groupId.*" + */ + public data class AnyTemp( + public val groupId: Long, + ) : AbstractPermitteeId(AnyMember(groupId), AnyTempFromAnyGroup) { + override fun asString(): String = "t$groupId.*" + } + + /** + * 表示任何一个 *在临时会话发送消息的* [群成员][Member] + * + * - **直接父标识符**: [AnyUser] + * - **间接父标识符**: [AnyContact] + * - 字符串表示: "t*" + */ + public object AnyTempFromAnyGroup : AbstractPermitteeId(AnyUser) { + override fun asString(): String = "t*" + } + + /** + * 表示唯一的一个 *在临时会话发送消息的* [群成员][Member] + * + * - **直接父标识符**: [ExactMember] + * - **间接父标识符**: [AnyUser], [AnyMember], [ExactUser], [AnyContact] + * - 字符串表示: "t$groupId.$memberId" + */ + public data class ExactTemp( + public val groupId: Long, + public val memberId: Long, + ) : AbstractPermitteeId(ExactMember(groupId, memberId)) { + override fun asString(): String = "t$groupId.$memberId" + } + + + /** + * 表示任何 [用户][User] + * + * - **直接父标识符**: [AnyContact] + * - **间接父标识符**: 无 + * - 字符串表示: "u*" + */ + public object AnyUser : AbstractPermitteeId(AnyContact) { + override fun asString(): String = "u*" + } + + /** + * 表示任何 [用户][User] + * + * - **直接父标识符**: [AnyUser] + * - **间接父标识符**: [AnyContact] + * - 字符串表示: "u$id" + */ + public data class ExactUser( + public val id: Long, + ) : AbstractPermitteeId(AnyUser) { + override fun asString(): String = "u$id" + } + + /** + * 表示任何 [联系对象][Contact] + * + * - **直接父标识符**: 无 + * - **间接父标识符**: 无 + * - 字符串表示: "*" + */ + public object AnyContact : AbstractPermitteeId() { + override fun asString(): String = "*" + } + + /** + * 表示控制台 + * + * - **直接父标识符**: 无 + * - **间接父标识符**: 无 + * - 字符串表示: "console" + */ + public object Console : AbstractPermitteeId() { + override fun asString(): String = "console" + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt index 825d7b89a..9b3aef0ed 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ContactUtils.kt @@ -12,8 +12,8 @@ package net.mamoe.mirai.console.util import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.contact.* /** * 为简化操作提供的一些工具 @@ -32,7 +32,7 @@ public object ContactUtils { @JvmOverloads @JvmStatic @ConsoleExperimentalApi - public fun Bot.getContact(id: Long, includeMembers: Boolean = false): Contact { + public fun Bot.getContact(id: Long, includeMembers: Boolean = true): Contact { return getContactOrNull(id, includeMembers) ?: throw NoSuchElementException("Contact $id not found for bot ${this.id}") } @@ -45,7 +45,7 @@ public object ContactUtils { @JvmOverloads @JvmStatic @ConsoleExperimentalApi - public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = false): Contact? { + public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = true): Contact? { return getFriendOrGroupOrNull(id) ?: kotlin.run { if (includeMembers) { groups.asSequence().flatMap { it.members.asSequence() }.firstOrNull { it.id == id } @@ -77,4 +77,18 @@ public object ContactUtils { return this.friends.getOrNull(id) ?: this.groups.getOrNull(id) } + /** + * 将 [ContactOrBot] 输出为字符串表示. + */ + @JvmName("renderContactOrName") + @JvmStatic + public fun ContactOrBot.render(): String { + return when (this) { + is Bot -> "Bot $nick($id)" + is Group -> "Group $name($id)" + is Friend -> "Friend $nick($id)" + is Member -> "Member $nameCardOrNick(${group.id}.$id)" + else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}") + } + } } \ No newline at end of file