mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-26 20:20:14 +08:00
[core] Implementation of BotAuthorization
This commit is contained in:
parent
e487ec78a4
commit
6836368f14
@ -11,6 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import net.mamoe.mirai.auth.BotAuthorization
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@ -111,6 +112,17 @@ public interface BotFactory {
|
||||
*/
|
||||
public fun newBot(qq: Long, passwordMd5: ByteArray): Bot = newBot(qq, passwordMd5, BotConfiguration.Default)
|
||||
|
||||
public fun newBot(qq: Long, authorization: BotAuthorization): Bot =
|
||||
newBot(qq, authorization, BotConfiguration.Default)
|
||||
|
||||
public fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot
|
||||
public fun newBot(
|
||||
qq: Long,
|
||||
authorization: BotAuthorization,
|
||||
configuration: BotConfigurationLambda /* = BotConfiguration.() -> Unit */
|
||||
): Bot = newBot(qq, authorization, configuration.run { BotConfiguration().apply { invoke() } })
|
||||
|
||||
|
||||
public companion object INSTANCE : BotFactory {
|
||||
override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot {
|
||||
return Mirai.BotFactory.newBot(qq, password, configuration)
|
||||
@ -160,5 +172,9 @@ public interface BotFactory {
|
||||
passwordMd5: ByteArray,
|
||||
configuration: BotConfiguration.() -> Unit /* = BotConfiguration.() -> Unit */
|
||||
): Bot = newBot(qq, passwordMd5, BotConfiguration().apply(configuration))
|
||||
|
||||
override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot {
|
||||
return Mirai.BotFactory.newBot(qq, authorization, configuration)
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ package net.mamoe.mirai.internal
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.BotFactory
|
||||
import net.mamoe.mirai.auth.BotAuthorization
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.DeprecatedSinceMirai
|
||||
|
||||
@ -39,4 +40,8 @@ internal object BotFactoryImpl : BotFactory {
|
||||
passwordMd5: ByteArray,
|
||||
configuration: BotConfiguration
|
||||
): Bot = QQAndroidBot(BotAccount(qq, passwordMd5), configuration)
|
||||
|
||||
override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot {
|
||||
return QQAndroidBot(BotAccount(qq, authorization), configuration)
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ import net.mamoe.mirai.utils.lateinitMutableProperty
|
||||
internal interface BotClientHolder {
|
||||
var client: QQAndroidClient
|
||||
|
||||
fun refreshClient()
|
||||
|
||||
companion object : ComponentKey<BotClientHolder>
|
||||
}
|
||||
|
||||
@ -27,6 +29,10 @@ internal class BotClientHolderImpl(
|
||||
) : BotClientHolder {
|
||||
override var client: QQAndroidClient by lateinitMutableProperty { createClient(bot) }
|
||||
|
||||
override fun refreshClient() {
|
||||
client = createClient(bot)
|
||||
}
|
||||
|
||||
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
|
||||
val ssoContext = bot.components[SsoProcessorContext]
|
||||
val device = ssoContext.device
|
||||
|
@ -34,11 +34,6 @@ internal interface QRCodeLoginProcessor {
|
||||
internal val NOOP = object : QRCodeLoginProcessor {}
|
||||
|
||||
fun parse(ssoContext: SsoProcessorContext, logger: MiraiLogger): QRCodeLoginProcessor {
|
||||
if (!ssoContext.bot.configuration.doQRCodeLogin) return NOOP
|
||||
check(ssoContext.bot.configuration.protocol.asInternal.canDoQRCodeLogin) {
|
||||
"The login protocol must be ANDROID_WATCH or MACOS while enabling qrcode login." +
|
||||
"Set it by `bot.configuration.protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH`."
|
||||
}
|
||||
return QRCodeLoginProcessorPreLoaded(ssoContext, logger)
|
||||
}
|
||||
}
|
||||
@ -49,6 +44,11 @@ internal class QRCodeLoginProcessorPreLoaded(
|
||||
private val logger: MiraiLogger,
|
||||
) : QRCodeLoginProcessor {
|
||||
override fun prepareProcess(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginProcessor {
|
||||
check(ssoContext.bot.configuration.protocol.asInternal.canDoQRCodeLogin) {
|
||||
"The login protocol must be ANDROID_WATCH or MACOS while enabling qrcode login." +
|
||||
"Set it by `bot.configuration.protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH`."
|
||||
}
|
||||
|
||||
val loginSolver = ssoContext.bot.configuration.loginSolver
|
||||
?: throw IllegalStateException(
|
||||
"No LoginSolver found while enabling qrcode login. " +
|
||||
@ -60,16 +60,13 @@ internal class QRCodeLoginProcessorPreLoaded(
|
||||
val qrCodeLoginListener = loginSolver.createQRCodeLoginListener(client.bot)
|
||||
|
||||
return loginSolver.run {
|
||||
QRCodeLoginProcessorImpl(qrCodeLoginListener, qrCodeSize, qrCodeMargin, qrCodeEcLevel, logger)
|
||||
QRCodeLoginProcessorImpl(qrCodeLoginListener, logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class QRCodeLoginProcessorImpl(
|
||||
private val qrCodeLoginListener: QRCodeLoginListener,
|
||||
private val size: Int,
|
||||
private val margin: Int,
|
||||
private val ecLevel: Int,
|
||||
private val logger: MiraiLogger,
|
||||
) : QRCodeLoginProcessor {
|
||||
|
||||
@ -80,7 +77,15 @@ internal class QRCodeLoginProcessorImpl(
|
||||
client: QQAndroidClient
|
||||
): WtLogin.TransEmp.Response.FetchQRCode {
|
||||
logger.debug { "requesting qrcode." }
|
||||
val resp = handler.sendAndExpect(WtLogin.TransEmp.FetchQRCode(client, size, margin, ecLevel), attempts = 1)
|
||||
val resp = handler.sendAndExpect(
|
||||
WtLogin.TransEmp.FetchQRCode(
|
||||
client,
|
||||
size = qrCodeLoginListener.qrCodeSize,
|
||||
margin = qrCodeLoginListener.qrCodeMargin,
|
||||
ecLevel = qrCodeLoginListener.qrCodeEcLevel,
|
||||
),
|
||||
attempts = 1
|
||||
)
|
||||
check(resp is WtLogin.TransEmp.Response.FetchQRCode) { "Cannot fetch qrcode, resp=$resp" }
|
||||
qrCodeLoginListener.onFetchQRCode(handler.context.bot, resp.imageData)
|
||||
return resp
|
||||
|
@ -11,14 +11,20 @@ package net.mamoe.mirai.internal.network.components
|
||||
|
||||
import kotlinx.atomicfu.AtomicRef
|
||||
import kotlinx.atomicfu.atomic
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.auth.BotAuthInfo
|
||||
import net.mamoe.mirai.auth.BotAuthorization
|
||||
import net.mamoe.mirai.auth.BotAuthorizationResult
|
||||
import net.mamoe.mirai.auth.MiraiInternalBotAuthComponent
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.QRCodeLoginData
|
||||
import net.mamoe.mirai.internal.network.WLoginSigInfo
|
||||
import net.mamoe.mirai.internal.network.component.ComponentKey
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.logger
|
||||
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
|
||||
import net.mamoe.mirai.internal.network.handler.selector.SelectorRequireReconnectException
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.DeviceVerificationResultImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.SmsDeviceVerificationResult
|
||||
@ -31,11 +37,12 @@ import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.RetryLaterException
|
||||
import net.mamoe.mirai.network.UnsupportedSliderCaptchaException
|
||||
import net.mamoe.mirai.network.WrongPasswordException
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
import net.mamoe.mirai.utils.info
|
||||
import net.mamoe.mirai.utils.withExceptionCollector
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.jvm.Volatile
|
||||
|
||||
/**
|
||||
@ -143,44 +150,323 @@ internal class SsoProcessorImpl(
|
||||
override val ssoSession: SsoSession get() = client
|
||||
private val components get() = ssoContext.bot.components
|
||||
|
||||
private val botAuthInfo = object : BotAuthInfo {
|
||||
override val id: Long
|
||||
get() = ssoContext.bot.id
|
||||
override val deviceInfo: DeviceInfo
|
||||
get() = ssoContext.device
|
||||
override val configuration: BotConfiguration
|
||||
get() = ssoContext.bot.configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Do login. Throws [LoginFailedException] if failed
|
||||
*/
|
||||
override suspend fun login(handler: NetworkHandler) = withExceptionCollector {
|
||||
components[CacheValidator].validate()
|
||||
override suspend fun login(handler: NetworkHandler) {
|
||||
|
||||
components[BdhSessionSyncer].loadServerListFromCache()
|
||||
try {
|
||||
fun initAuthControl() {
|
||||
authControl = AuthControl(
|
||||
botAuthInfo,
|
||||
ssoContext.bot.account.authorization,
|
||||
ssoContext.bot.network.logger,
|
||||
ssoContext.bot,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loginSuccess() {
|
||||
components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client))
|
||||
registerClientOnline(handler)
|
||||
ssoContext.bot.logger.info { "Login successful." }
|
||||
}
|
||||
|
||||
if (authControl == null) {
|
||||
ssoContext.bot.account.let { account ->
|
||||
if (account.accountSecretsKeyBuffer == null) {
|
||||
account.accountSecretsKeyBuffer = SecretsProtection.EscapedByteBuffer(
|
||||
account.authorization.calculateSecretsKey(botAuthInfo)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
components[CacheValidator].validate()
|
||||
|
||||
components[BdhSessionSyncer].loadServerListFromCache()
|
||||
|
||||
// try fast login
|
||||
if (client.wLoginSigInfoInitialized) {
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
kotlin.runCatching {
|
||||
FastLoginImpl(handler).doLogin()
|
||||
}.onFailure { e ->
|
||||
collectException(e)
|
||||
initAuthControl()
|
||||
authControl!!.exceptionCollector.collect(e)
|
||||
|
||||
throw SelectorRequireReconnectException()
|
||||
}
|
||||
|
||||
loginSuccess()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (authControl == null) initAuthControl()
|
||||
val authControl0 = authControl!!
|
||||
|
||||
|
||||
var nextAuthMethod: AuthMethod? = null
|
||||
try {
|
||||
ssoContext.bot.components[BotClientHolder].refreshClient()
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
|
||||
ssoContext.bot.account.passwordMd5Buffer = null
|
||||
when (val authw = authControl0.acquireAuth().also { nextAuthMethod = it }) {
|
||||
is AuthMethod.DirectError -> throw authw.exception
|
||||
|
||||
is AuthMethod.Error -> {
|
||||
authControl = null
|
||||
authControl0.exceptionCollector.collectThrow(authw.exception)
|
||||
}
|
||||
AuthMethod.NotAvailable -> {
|
||||
authControl = null
|
||||
authControl0.exceptionCollector.collectThrow(IllegalStateException("No more auth method available"))
|
||||
}
|
||||
|
||||
is AuthMethod.Pwd -> {
|
||||
ssoContext.bot.account.passwordMd5Buffer = authw.passwordMd5
|
||||
|
||||
SlowLoginImpl(handler, LoginType.Password).doLogin()
|
||||
}
|
||||
} else {
|
||||
client = createClient(ssoContext.bot)
|
||||
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
||||
AuthMethod.QRCode -> {
|
||||
val rsp = ssoContext.bot.components[QRCodeLoginProcessor].prepareProcess(
|
||||
handler, client
|
||||
).process(handler, client)
|
||||
|
||||
val qrCodeLoginProcessor = ssoContext.bot.components[QRCodeLoginProcessor]
|
||||
if (qrCodeLoginProcessor !== QRCodeLoginProcessor.NOOP) {
|
||||
val qrcodeLoginData = qrCodeLoginProcessor.prepareProcess(handler, client).process(handler, client)
|
||||
SlowLoginImpl(handler, LoginType.QRCode(qrcodeLoginData)).doLogin()
|
||||
} else {
|
||||
SlowLoginImpl(handler, LoginType.Password).doLogin()
|
||||
SlowLoginImpl(handler, LoginType.QRCode(rsp)).doLogin()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Failed to log in, invalidate secrets.
|
||||
ssoContext.bot.components[AccountSecretsManager].invalidate()
|
||||
throw e
|
||||
|
||||
authControl!!.actComplete()
|
||||
authControl = null
|
||||
} catch (exception: Throwable) {
|
||||
authControl0.exceptionCollector.collectException(exception)
|
||||
|
||||
ssoContext.bot.network.logger.warning({ "Failed with auth method: $nextAuthMethod" }, exception)
|
||||
|
||||
if (nextAuthMethod is AuthMethod.DirectError) { // @TestOnly
|
||||
authControl0.actResume()
|
||||
} else if (nextAuthMethod !is AuthMethod.Error) {
|
||||
if (exception !is SelectorRequireReconnectException) { // login not done
|
||||
authControl0.actFailed(exception)
|
||||
}
|
||||
}
|
||||
|
||||
if (exception is NetworkException) {
|
||||
if (exception.recoverable) throw exception
|
||||
}
|
||||
|
||||
if (nextAuthMethod == null || nextAuthMethod is AuthMethod.NotAvailable || nextAuthMethod is AuthMethod.Error) {
|
||||
throw exception
|
||||
}
|
||||
|
||||
throw SelectorRequireReconnectException()
|
||||
}
|
||||
components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client))
|
||||
registerClientOnline(handler)
|
||||
ssoContext.bot.logger.info { "Login successful." }
|
||||
|
||||
loginSuccess()
|
||||
|
||||
}
|
||||
|
||||
|
||||
sealed class AuthMethod {
|
||||
object NotAvailable : AuthMethod()
|
||||
|
||||
object QRCode : AuthMethod() {
|
||||
override fun toString(): String {
|
||||
return "QRCode"
|
||||
}
|
||||
}
|
||||
|
||||
class Pwd(val passwordMd5: SecretsProtection.EscapedByteBuffer) : AuthMethod() {
|
||||
override fun toString(): String {
|
||||
return "Password@${hashCode()}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception in [BotAuthorization]
|
||||
*/
|
||||
class Error(val exception: Throwable) : AuthMethod() {
|
||||
override fun toString(): String {
|
||||
return "Error[$exception]@${hashCode()}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For mocking a login method throw a exception
|
||||
*/
|
||||
@TestOnly
|
||||
class DirectError(val exception: Throwable) : AuthMethod() {
|
||||
override fun toString(): String {
|
||||
return "DirectError[$exception]@${hashCode()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestOnly
|
||||
internal interface SsoProcessorAuthComponent : MiraiInternalBotAuthComponent {
|
||||
suspend fun emit(method: AuthMethod)
|
||||
suspend fun emitDirectError(error: Throwable) {
|
||||
emit(AuthMethod.DirectError(error))
|
||||
}
|
||||
|
||||
|
||||
val botAuthorizationResult: BotAuthorizationResult
|
||||
}
|
||||
|
||||
internal class AuthControl(
|
||||
private val botAuthInfo: BotAuthInfo,
|
||||
private val authorization: BotAuthorization,
|
||||
private val logger: MiraiLogger,
|
||||
private val scope: CoroutineScope,
|
||||
) {
|
||||
internal val exceptionCollector = ExceptionCollector()
|
||||
|
||||
@Volatile
|
||||
private var authorizationContinuation: Continuation<Unit>? = null
|
||||
|
||||
@Volatile
|
||||
private var authRspFuture = initCompletableDeferred()
|
||||
|
||||
@Volatile
|
||||
private var isCompleted = false
|
||||
|
||||
private val rsp = object : BotAuthorizationResult {}
|
||||
|
||||
@Suppress("RemoveExplicitTypeArguments")
|
||||
@OptIn(TestOnly::class)
|
||||
private val authComponent = object : SsoProcessorAuthComponent {
|
||||
override val botAuthorizationResult: BotAuthorizationResult get() = rsp
|
||||
|
||||
override suspend fun emit(method: AuthMethod) {
|
||||
logger.verbose { "[AuthControl/emit] Trying emit $method" }
|
||||
|
||||
if (isCompleted) {
|
||||
val msg = "[AuthControl/emit] Failed to emit $method because control completed"
|
||||
|
||||
error(msg.also { logger.verbose(it) })
|
||||
}
|
||||
suspendCoroutine<Unit> { next ->
|
||||
val rspTarget = authRspFuture
|
||||
if (!rspTarget.complete(method)) {
|
||||
val msg = "[AuthControl/emit] Failed to emit $method because auth response completed"
|
||||
|
||||
error(msg.also { logger.verbose(it) })
|
||||
}
|
||||
authorizationContinuation = next
|
||||
logger.verbose { "[AuthControl/emit] Emitted $method to $rspTarget" }
|
||||
}
|
||||
logger.verbose { "[AuthControl/emit] Authorization resumed after $method" }
|
||||
}
|
||||
|
||||
override suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthorizationResult {
|
||||
emit(AuthMethod.Pwd(passwordMd5))
|
||||
return rsp
|
||||
}
|
||||
|
||||
override suspend fun authByPassword(password: String): BotAuthorizationResult {
|
||||
return authByPassword(password.md5())
|
||||
}
|
||||
|
||||
override suspend fun authByPassword(passwordMd5: ByteArray): BotAuthorizationResult {
|
||||
return authByPassword(SecretsProtection.EscapedByteBuffer(passwordMd5))
|
||||
}
|
||||
|
||||
override suspend fun authByQRCode(): BotAuthorizationResult {
|
||||
emit(AuthMethod.QRCode)
|
||||
return rsp
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
try {
|
||||
logger.verbose { "[AuthControl/auth] Authorization started" }
|
||||
|
||||
authorization.authorize(authComponent, botAuthInfo)
|
||||
|
||||
logger.verbose { "[AuthControl/auth] Authorization exited" }
|
||||
|
||||
isCompleted = true
|
||||
authRspFuture.complete(AuthMethod.NotAvailable)
|
||||
|
||||
} catch (e: Throwable) {
|
||||
logger.verbose({ "[AuthControl/auth] Authorization failed" }, e)
|
||||
|
||||
isCompleted = true
|
||||
authRspFuture.complete(AuthMethod.Error(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSpinWait() {}
|
||||
suspend fun acquireAuth(): AuthMethod {
|
||||
val authTarget = authRspFuture
|
||||
logger.verbose { "[AuthControl/acquire] Acquiring auth method with $authTarget" }
|
||||
val rsp = authTarget.await()
|
||||
logger.debug { "[AuthControl/acquire] Authorization responded: $authTarget, $rsp" }
|
||||
|
||||
while (authorizationContinuation == null && !isCompleted) {
|
||||
onSpinWait()
|
||||
}
|
||||
logger.verbose { "[AuthControl/acquire] authorizationContinuation setup: $authorizationContinuation, $isCompleted" }
|
||||
|
||||
return rsp
|
||||
}
|
||||
|
||||
fun actFailed(cause: Throwable) {
|
||||
logger.verbose { "[AuthControl/resume] Fire auth failed with cause: $cause" }
|
||||
|
||||
authRspFuture = initCompletableDeferred()
|
||||
authorizationContinuation!!.let { cont ->
|
||||
authorizationContinuation = null
|
||||
cont.resumeWith(Result.failure(cause))
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly // same as act failed
|
||||
fun actResume() {
|
||||
logger.verbose { "[AuthControl/resume] Fire auth resume" }
|
||||
|
||||
authRspFuture = initCompletableDeferred()
|
||||
authorizationContinuation!!.let { cont ->
|
||||
authorizationContinuation = null
|
||||
cont.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun actComplete() {
|
||||
logger.verbose { "[AuthControl/resume] Fire auth completed" }
|
||||
|
||||
isCompleted = true
|
||||
authRspFuture = CompletableDeferred(AuthMethod.NotAvailable)
|
||||
authorizationContinuation!!.let { cont ->
|
||||
authorizationContinuation = null
|
||||
cont.resume(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initCompletableDeferred(): CompletableDeferred<AuthMethod> {
|
||||
return CompletableDeferred<AuthMethod>().also { df ->
|
||||
df.invokeOnCompletion {
|
||||
logger.debug { "[AuthControl/cd] $df completed with $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var authControl: AuthControl? = null
|
||||
|
||||
override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response {
|
||||
return registerClientOnline(handler).also { registerResp = it }
|
||||
}
|
||||
@ -197,17 +483,6 @@ internal class SsoProcessorImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
|
||||
val device = ssoContext.device
|
||||
return QQAndroidClient(
|
||||
ssoContext.account,
|
||||
device = device,
|
||||
accountSecrets = bot.components[AccountSecretsManager].getSecretsOrCreate(ssoContext.account, device)
|
||||
).apply {
|
||||
_bot = bot
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// login
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.component
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.yield
|
||||
import net.mamoe.mirai.auth.BotAuthComponent
|
||||
import net.mamoe.mirai.auth.BotAuthInfo
|
||||
import net.mamoe.mirai.auth.BotAuthorization
|
||||
import net.mamoe.mirai.auth.BotAuthorizationResult
|
||||
import net.mamoe.mirai.internal.network.components.SsoProcessorContext
|
||||
import net.mamoe.mirai.internal.network.components.SsoProcessorImpl
|
||||
import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.DeviceInfo
|
||||
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFails
|
||||
import kotlin.test.fail
|
||||
|
||||
internal class BotAuthControlTest : AbstractCommonNHTest() {
|
||||
val botAuthInfo = object : BotAuthInfo {
|
||||
override val id: Long
|
||||
get() = bot.id
|
||||
override val deviceInfo: DeviceInfo
|
||||
get() = bot.components[SsoProcessorContext].device
|
||||
override val configuration: BotConfiguration
|
||||
get() = bot.configuration
|
||||
}
|
||||
|
||||
private suspend fun SsoProcessorImpl.AuthControl.assertRequire(exceptedType: KClass<*>) {
|
||||
println("Requiring auth method")
|
||||
val nextAuth = acquireAuth()
|
||||
println("Got $nextAuth")
|
||||
yield()
|
||||
|
||||
if (nextAuth is SsoProcessorImpl.AuthMethod.Error) {
|
||||
fail(cause = nextAuth.exception)
|
||||
}
|
||||
if (exceptedType.isInstance(nextAuth)) return
|
||||
fail("Type not match, excepted $exceptedType but got ${nextAuth::class}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auth test`() = runTest {
|
||||
|
||||
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
|
||||
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
|
||||
return authComponent.authByPassword(EMPTY_BYTE_ARRAY)
|
||||
}
|
||||
}, bot.logger, backgroundScope)
|
||||
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
|
||||
control.actComplete()
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test auth failed and reselect`() = runTest {
|
||||
|
||||
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
|
||||
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
|
||||
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY); println("!") }
|
||||
println("114514")
|
||||
return authComponent.authByPassword(EMPTY_BYTE_ARRAY)
|
||||
}
|
||||
}, bot.logger, backgroundScope)
|
||||
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
|
||||
control.actFailed(Throwable())
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
|
||||
control.actComplete()
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed when login complete`() = runTest {
|
||||
|
||||
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
|
||||
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
|
||||
val rsp = authComponent.authByPassword(EMPTY_BYTE_ARRAY)
|
||||
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
|
||||
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
|
||||
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
|
||||
return rsp
|
||||
}
|
||||
}, bot.logger, backgroundScope)
|
||||
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
|
||||
control.actComplete()
|
||||
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
|
||||
|
||||
}
|
||||
}
|
@ -13,6 +13,9 @@ package net.mamoe.mirai.internal.network.framework
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import net.mamoe.mirai.auth.BotAuthInfo
|
||||
import net.mamoe.mirai.auth.BotAuthorizationResult
|
||||
import net.mamoe.mirai.auth.MiraiInternalBotAuthComponent
|
||||
import net.mamoe.mirai.internal.*
|
||||
import net.mamoe.mirai.internal.contact.uin
|
||||
import net.mamoe.mirai.internal.network.KeyWithCreationTime
|
||||
@ -113,6 +116,40 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
|
||||
set(SsoProcessorContext, SsoProcessorContextImpl(bot))
|
||||
set(SsoProcessor, object : TestSsoProcessor(bot) {
|
||||
override suspend fun login(handler: NetworkHandler) {
|
||||
val botAuthInfo = object : BotAuthInfo {
|
||||
override val id: Long get() = bot.id
|
||||
override val deviceInfo: DeviceInfo
|
||||
get() = get(SsoProcessorContext).device
|
||||
override val configuration: BotConfiguration
|
||||
get() = bot.configuration
|
||||
}
|
||||
val rsp = object : BotAuthorizationResult {}
|
||||
|
||||
val botAuthComponents = object : MiraiInternalBotAuthComponent {
|
||||
override suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthorizationResult {
|
||||
bot.account.passwordMd5Buffer = passwordMd5
|
||||
return rsp
|
||||
}
|
||||
|
||||
override suspend fun authByPassword(password: String): BotAuthorizationResult {
|
||||
return authByPassword(password.md5())
|
||||
}
|
||||
|
||||
override suspend fun authByPassword(passwordMd5: ByteArray): BotAuthorizationResult {
|
||||
return authByPassword(SecretsProtection.EscapedByteBuffer(passwordMd5))
|
||||
}
|
||||
|
||||
override suspend fun authByQRCode(): BotAuthorizationResult {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bot.account.authorization.authorize(botAuthComponents, botAuthInfo)
|
||||
bot.account.accountSecretsKeyBuffer = SecretsProtection.EscapedByteBuffer(
|
||||
bot.account.authorization.calculateSecretsKey(botAuthInfo)
|
||||
)
|
||||
|
||||
nhEvents.add(NHEvent.Login)
|
||||
super.login(handler)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user