From 8e3e3286728a5ca4b87725e5ea9cc6d4bbec8e6d Mon Sep 17 00:00:00 2001 From: Him188 Date: Mon, 7 Sep 2020 12:12:27 +0800 Subject: [PATCH] PermissionService fundamental --- .../mirai/console/command/CommandSender.kt | 4 - .../MiraiConsoleImplementationBridge.kt | 10 +- .../permission/PermissionServiceImpl.kt | 101 +++++++++++++ .../permission/BuiltInPermissionServices.kt | 58 +++++++ .../mirai/console/permission/Exceptions.kt | 27 ++++ .../HotDeploymentSupportPermissionService.kt | 17 +++ .../mirai/console/permission/Permissible.kt | 8 +- .../mirai/console/permission/Permission.kt | 10 ++ .../console/permission/PermissionGroup.kt | 2 +- .../permission/PermissionIdentifier.kt | 25 ++- .../console/permission/PermissionService.kt | 142 ++++++++---------- buildSrc/src/main/kotlin/Versions.kt | 2 +- 12 files changed, 310 insertions(+), 96 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/PermissionServiceImpl.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Exceptions.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/HotDeploymentSupportPermissionService.kt 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 e8ea527ac..c797a967f 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 @@ -161,10 +161,6 @@ public interface CommandSender : CoroutineScope, Permissible { */ public val name: String - @ExperimentalPermission - override val identifier: String - get() = user?.id?.toString() ?: bot?.id?.toString() ?: error("Internal error: bot user and bot are null") - /** * 立刻发送一条消息. * 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 7f7176866..c92678565 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 @@ -33,6 +33,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.plugin.CuiPluginCenter import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl +import net.mamoe.mirai.console.permission.ExperimentalPermission +import net.mamoe.mirai.console.permission.HotDeploymentSupportPermissionService +import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter @@ -80,7 +83,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity) - @OptIn(ConsoleExperimentalAPI::class) + @OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class) internal fun doStart() { val buildDateFormatted = buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) @@ -102,6 +105,11 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI mainLogger.info { "Reloading configurations..." } ConsoleDataScope.reloadAll() + PermissionService // init + if (PermissionService.INSTANCE is HotDeploymentSupportPermissionService<*>) { + + } + BuiltInCommands.registerAll() mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } CommandManager diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/PermissionServiceImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/PermissionServiceImpl.kt new file mode 100644 index 000000000..1155d9fbe --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/permission/PermissionServiceImpl.kt @@ -0,0 +1,101 @@ +package net.mamoe.mirai.console.internal.permission + +import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.UserCommandSender +import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault +import net.mamoe.mirai.console.data.Value +import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.extensions.PermissionServiceProvider +import net.mamoe.mirai.console.permission.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass + +/** + * [PermissionServiceProvider] + */ +@Suppress("RedundantVisibilityModifier") +@ExperimentalPermission +internal abstract class AbstractPermissionService : + PermissionService { + protected abstract val Permissible.identifier: TPermissibleIdentifier + + @JvmField + protected val permissions: MutableMap = ConcurrentHashMap() + + @JvmField + protected val grantedPermissionMap: MutableMap> = + ConcurrentHashMap() + + public override fun getGrantedPermissions(permissible: Permissible): Sequence = + grantedPermissionMap[permissible.identifier]?.asSequence()?.mapNotNull { get(it) }.orEmpty() + + public override operator fun get(identifier: PermissionIdentifier): TPermission? = permissions[identifier] + + public override fun testPermission(permissible: Permissible, permission: TPermission): Boolean = + permissible.getGrantedPermissions().any { it == permission } +} + +/** + * [PermissionServiceProvider] + */ +@Suppress("RedundantVisibilityModifier") +@ExperimentalPermission +internal abstract class AbstractHotDeploymentSupportPermissionService : + PermissionService, + HotDeploymentSupportPermissionService, AutoSavePluginConfig() { + + protected abstract val Permissible.identifier: TPermissibleIdentifier + + @JvmField + protected val permissions: MutableMap = ConcurrentHashMap() + + @JvmField + protected val grantedPermissionMap: Value>> = + value>>().withEmptyDefault() + + public override fun getGrantedPermissions(permissible: Permissible): Sequence = + grantedPermissionMap.value[permissible.identifier]?.asSequence()?.mapNotNull { get(it) }.orEmpty() + + public override operator fun get(identifier: PermissionIdentifier): TPermission? = permissions[identifier] + + public override fun testPermission(permissible: Permissible, permission: TPermission): Boolean = + permissible.getGrantedPermissions().any { it == permission } +} + + +internal data class LiteralPermissibleIdentifier( + val context: String, + val value: String +) + +@OptIn(ExperimentalPermission::class) +private object PermissionServiceImpl : + AbstractHotDeploymentSupportPermissionService() { + + override fun register( + identifier: PermissionIdentifier, + description: String, + base: PermissionIdentifier? + ): PermissionImpl = PermissionImpl(identifier, description, base) + + override fun grant(permissible: Permissible, permission: PermissionImpl) { + grantedPermissionMap.value[permissible.identifier]!!.add(permission.identifier) + } + + override fun deny(permissible: Permissible, permission: PermissionImpl) { + grantedPermissionMap.value[permissible.identifier]!!.remove(permission.identifier) + } + + override val permissionType: KClass + get() = PermissionImpl::class + override val Permissible.identifier: LiteralPermissibleIdentifier + get() = LiteralPermissibleIdentifier( + "", + when (this) { + is ConsoleCommandSender -> "CONSOLE" + is UserCommandSender -> this.user.id.toString() + else -> "" + } + ) +} \ No newline at end of file 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/permission/BuiltInPermissionServices.kt new file mode 100644 index 000000000..464e7a4f6 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/BuiltInPermissionServices.kt @@ -0,0 +1,58 @@ +/* + * 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 java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass + + +@ExperimentalPermission +public object AllGrantPermissionService : PermissionService { + private val all = ConcurrentHashMap() + override val permissionType: KClass + get() = PermissionImpl::class + + override fun register( + identifier: PermissionIdentifier, + description: String, + base: PermissionIdentifier? + ): PermissionImpl { + val new = PermissionImpl(identifier, description, base) + if (all.putIfAbsent(identifier, new) != null) { + throw DuplicatedRegistrationException("Duplicated Permission registry: ${all[identifier]}") + } + return new + } + + override fun get(identifier: PermissionIdentifier): PermissionImpl? = all[identifier] + override fun getGrantedPermissions(permissible: Permissible): Sequence = all.values.asSequence() +} + +@ExperimentalPermission +public object AllDenyPermissionService : PermissionService { + private val all = ConcurrentHashMap() + override val permissionType: KClass + get() = PermissionImpl::class + + override fun register( + identifier: PermissionIdentifier, + description: String, + base: PermissionIdentifier? + ): PermissionImpl { + val new = PermissionImpl(identifier, description, base) + if (all.putIfAbsent(identifier, new) != null) { + throw DuplicatedRegistrationException("Duplicated Permission registry: ${all[identifier]}") + } + return new + } + + override fun get(identifier: PermissionIdentifier): PermissionImpl? = all[identifier] + override fun getGrantedPermissions(permissible: Permissible): Sequence = emptySequence() +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Exceptions.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Exceptions.kt new file mode 100644 index 000000000..c8b358936 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/Exceptions.kt @@ -0,0 +1,27 @@ +/* + * 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 HotDeploymentNotSupportedException : Exception { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} + +@ExperimentalPermission +public open class DuplicatedRegistrationException : Exception { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/HotDeploymentSupportPermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/HotDeploymentSupportPermissionService.kt new file mode 100644 index 000000000..607989ece --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/HotDeploymentSupportPermissionService.kt @@ -0,0 +1,17 @@ +/* + * 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 interface HotDeploymentSupportPermissionService

: PermissionService

{ + public fun grant(permissible: Permissible, permission: P) + public fun deny(permissible: Permissible, permission: P) +} \ 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/Permissible.kt index bc12c3706..f0c1f6383 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/Permissible.kt @@ -11,10 +11,12 @@ package net.mamoe.mirai.console.permission +/** + * + * 注意: 请不要自主实现 [Permissible] + */ @ExperimentalPermission -public interface Permissible { - public val identifier: String -} +public interface Permissible @ExperimentalPermission public inline fun Permissible.hasPermission(permission: Permission): Boolean = 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 790a1f73f..4d1a3ff88 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 @@ -19,3 +19,13 @@ public interface Permission { public val description: String public val base: PermissionIdentifier? } + +/** + * [Permission] 的简单实现 + */ +@ExperimentalPermission +public open class PermissionImpl( + override val identifier: PermissionIdentifier, + override val description: String, + override val base: PermissionIdentifier? +) : Permission \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionGroup.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionGroup.kt index 4e5006494..e898d0c07 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionGroup.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionGroup.kt @@ -44,7 +44,7 @@ public abstract class PermissionGroup( apply { this.permissionChecker = permissionChecker } public fun build(property: KProperty<*>): Permission { - return PermissionService.register( + return PermissionService.INSTANCE.register( identifierNamespace.permissionIdentifier(property.name), description, basePermission diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdentifier.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdentifier.kt index ea773f0ed..b9d7b8d25 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdentifier.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionIdentifier.kt @@ -9,15 +9,34 @@ package net.mamoe.mirai.console.permission -import kotlinx.serialization.Serializable +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializer +import kotlinx.serialization.builtins.serializer +import net.mamoe.mirai.console.internal.data.map -@Serializable @ExperimentalPermission public data class PermissionIdentifier( public val namespace: String, public val id: String -) +) { + init { + require(!namespace.contains(':')) { + "':' is not allowed in namespace" + } + require(!id.contains(':')) { + "':' is not allowed in id" + } + } + + @Serializer(forClass = PermissionIdentifier::class) + public object AsClassSerializer + + public object AsStringSerializer : KSerializer by String.serializer().map( + serializer = { it.namespace + ":" + it.id }, + deserializer = { it.split(':').let { (namespace, id) -> PermissionIdentifier(namespace, id) } } + ) +} @ExperimentalPermission public interface PermissionIdentifierNamespace { 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 938c73b9e..645a50797 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 @@ -11,17 +11,19 @@ package net.mamoe.mirai.console.permission -import net.mamoe.mirai.console.data.AutoSavePluginConfig -import net.mamoe.mirai.console.data.Value -import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.extensions.PermissionServiceProvider -import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KClass /** * [PermissionServiceProvider] */ @ExperimentalPermission public interface PermissionService

{ + @ExperimentalPermission + public val permissionType: KClass

+ + @Throws(DuplicatedRegistrationException::class) public fun register( identifier: PermissionIdentifier, description: String, @@ -30,99 +32,73 @@ public interface PermissionService

{ public operator fun get(identifier: PermissionIdentifier): P? - - public fun getGrantedPermissions(permissible: Permissible): List + public fun getGrantedPermissions(permissible: Permissible): Sequence

public fun testPermission(permissible: Permissible, permission: P): Boolean = - permissible.getGrantedPermissions().any { it == permission.identifier } + permissible.getGrantedPermissions().any { it == permission } + public companion object { + private val builtIn: PermissionService get() = AllGrantPermissionService - public companion object INSTANCE : PermissionService { - private val builtIn: PermissionService get() = TODO("PS IMPL") - - @Suppress("UNCHECKED_CAST") - private val instance by lazy { - PermissionServiceProvider.getExtensions().singleOrNull()?.extension?.instance - ?: builtIn // TODO: 2020/9/4 ask for further choice - as PermissionService + @get:JvmName("getInstance") + @JvmStatic + public val INSTANCE: PermissionService by lazy { + PermissionServiceProvider.getExtensions().singleOrNull()?.extension?.instance ?: builtIn + // TODO: 2020/9/4 ExtensionSelector } - - override fun register( - identifier: PermissionIdentifier, - description: String, - base: PermissionIdentifier? - ): Permission = instance.register(identifier, description, base) - - override fun get(identifier: PermissionIdentifier): Permission? = instance[identifier] - override fun getGrantedPermissions(permissible: Permissible): List = - instance.getGrantedPermissions(permissible) } } @ExperimentalPermission -public interface HotDeploymentSupportPermissionService

: PermissionService

{ - public fun grant(permissible: Permissible, permission: P) - public fun deny(permissible: Permissible, permission: P) -} +public abstract class AbstractPermissionService

: PermissionService

{ + protected val all: MutableMap = ConcurrentHashMap() -@ExperimentalPermission -public open class HotDeploymentNotSupportedException : Exception { - public constructor() : super() - public constructor(message: String?) : super(message) - public constructor(message: String?, cause: Throwable?) : super(message, cause) - public constructor(cause: Throwable?) : super(cause) -} - - -/** - * [PermissionServiceProvider] - */ -@ExperimentalPermission -public abstract class AbstractPermissionService

: AutoSavePluginConfig(), PermissionService

{ - @JvmField - protected val permissions: ConcurrentLinkedQueue

= ConcurrentLinkedQueue() - - @JvmField - protected val grantedPermissionMap: Value>> = value() - - public override fun getGrantedPermissions(permissible: Permissible): List = - grantedPermissionMap.value[permissible.identifier].orEmpty() - - public override operator fun get(identifier: PermissionIdentifier): P? = - permissions.find { it.identifier == identifier } - - public override fun testPermission(permissible: Permissible, permission: P): Boolean = - permissible.getGrantedPermissions().any { it == permission.identifier } -} - -@ExperimentalPermission -public inline fun Permissible.getGrantedPermissions(): List = - PermissionService.run { this.getGrantedPermissions(this@getGrantedPermissions) } - -@ExperimentalPermission -public inline fun Permission.testPermission(permissible: Permissible): Boolean = - PermissionService.run { testPermission(permissible, this@testPermission) } - -@ExperimentalPermission -public inline fun PermissionIdentifier.testPermission(permissible: Permissible): Boolean { - val p = PermissionService[this] ?: return false - return p.testPermission(permissible) -} - -@OptIn(ExperimentalPermission::class) -private class PermissionServiceImpl : AbstractPermissionService() { - private val instances: ConcurrentLinkedQueue = ConcurrentLinkedQueue() - - private class PermissionImpl( - override val identifier: PermissionIdentifier, - override val description: String, - override val base: PermissionIdentifier? - ) : Permission + protected abstract fun createPermission( + identifier: PermissionIdentifier, + description: String, + base: PermissionIdentifier? + ): P override fun register( identifier: PermissionIdentifier, description: String, base: PermissionIdentifier? - ): PermissionImpl = PermissionImpl(identifier, description, base) + ): P { + val new = createPermission(identifier, description, base) + if (all.putIfAbsent(identifier, new) != null) { + throw DuplicatedRegistrationException("Duplicated Permission registry: ${all[identifier]}") + } + return new + } + + override fun get(identifier: PermissionIdentifier): P? = all[identifier] + override fun getGrantedPermissions(permissible: Permissible): Sequence

= all.values.asSequence() +} + +@ExperimentalPermission +public inline fun Permissible.getGrantedPermissions(): Sequence = + PermissionService.INSTANCE.run { + getGrantedPermissions(this@getGrantedPermissions) + } + + +@ExperimentalPermission +public inline fun Permission.testPermission(permissible: Permissible): Boolean = + PermissionService.INSTANCE.run { + require(permissionType.isInstance(this@testPermission)) { + "Custom-constructed Permission instance is not allowed. " + + "Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get" + } + + @Suppress("UNCHECKED_CAST") + this as PermissionService + + testPermission(permissible, this@testPermission) + } + +@ExperimentalPermission +public inline fun PermissionIdentifier.testPermission(permissible: Permissible): Boolean { + val p = PermissionService.INSTANCE[this] ?: return false + return p.testPermission(permissible) } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 3970cc2a4..a93801e38 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -18,7 +18,7 @@ object Versions { const val core = "1.2.2" - const val console = "1.0-M4-dev-4" + const val console = "1.0-M4-dev-5" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = console