|
@@ -1,5 +1,5 @@
|
|
|
/*
|
|
|
- * Copyright 2019-2022 Mamoe Technologies and contributors.
|
|
|
+ * 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.
|
|
@@ -11,13 +11,18 @@ package net.mamoe.mirai.internal.network.components
|
|
|
|
|
|
import kotlinx.atomicfu.AtomicRef
|
|
|
import kotlinx.atomicfu.atomic
|
|
|
-import net.mamoe.mirai.internal.QQAndroidBot
|
|
|
+import net.mamoe.mirai.auth.*
|
|
|
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.auth.AuthControl
|
|
|
+import net.mamoe.mirai.internal.network.auth.BotAuthorizationWithSecretsProtection
|
|
|
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
|
|
@@ -30,10 +35,8 @@ 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.cancellation.CancellationException
|
|
|
import kotlin.jvm.Volatile
|
|
|
|
|
@@ -142,37 +145,155 @@ 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) {
|
|
|
+
|
|
|
+ fun initAuthControl() {
|
|
|
+ authControl = AuthControl(
|
|
|
+ botAuthInfo,
|
|
|
+ ssoContext.bot.account.authorization,
|
|
|
+ ssoContext.bot.network.logger,
|
|
|
+ ssoContext.bot.coroutineContext, // do not use network context because network may restart whilst auth control should keep alive
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- components[BdhSessionSyncer].loadServerListFromCache()
|
|
|
- try {
|
|
|
+ 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 = when (val authorization = account.authorization) {
|
|
|
+ is BotAuthorizationWithSecretsProtection -> authorization.calculateSecretsKeyImpl(botAuthInfo)
|
|
|
+ else -> SecretsProtection.EscapedByteBuffer(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)
|
|
|
- SlowLoginImpl(handler).doLogin()
|
|
|
+ initAuthControl()
|
|
|
+ authControl!!.exceptionCollector.collect(e)
|
|
|
+
|
|
|
+ throw SelectorRequireReconnectException()
|
|
|
}
|
|
|
- } else {
|
|
|
- client = createClient(ssoContext.bot)
|
|
|
- ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
|
|
- SlowLoginImpl(handler).doLogin()
|
|
|
+
|
|
|
+ loginSuccess()
|
|
|
+
|
|
|
+ return
|
|
|
}
|
|
|
- } catch (e: Exception) {
|
|
|
- // Failed to log in, invalidate secrets.
|
|
|
- ssoContext.bot.components[AccountSecretsManager].invalidate()
|
|
|
- throw e
|
|
|
}
|
|
|
- components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client))
|
|
|
- registerClientOnline(handler)
|
|
|
- ssoContext.bot.logger.info { "Login successful." }
|
|
|
+
|
|
|
+ if (authControl == null) initAuthControl()
|
|
|
+ val authControl0 = authControl!!
|
|
|
+
|
|
|
+
|
|
|
+ var nextAuthMethod: AuthMethod? = null
|
|
|
+ try {
|
|
|
+ ssoContext.bot.components[BotClientHolder].refreshClient()
|
|
|
+ ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
|
|
|
+
|
|
|
+ when (val authw = authControl0.acquireAuth().also { nextAuthMethod = it }) {
|
|
|
+ is AuthMethod.Error -> {
|
|
|
+ authControl = null
|
|
|
+ throw authw.exception
|
|
|
+ }
|
|
|
+
|
|
|
+ AuthMethod.NotAvailable -> {
|
|
|
+ authControl = null
|
|
|
+ error("No more auth method available")
|
|
|
+ }
|
|
|
+
|
|
|
+ is AuthMethod.Pwd -> {
|
|
|
+ SlowLoginImpl(handler, LoginType.Password(authw.passwordMd5)).doLogin()
|
|
|
+ }
|
|
|
+
|
|
|
+ AuthMethod.QRCode -> {
|
|
|
+ val rsp = ssoContext.bot.components[QRCodeLoginProcessor].prepareProcess(
|
|
|
+ handler, client
|
|
|
+ ).process(handler, client)
|
|
|
+
|
|
|
+ SlowLoginImpl(handler, LoginType.QRCode(rsp)).doLogin()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ authControl!!.actComplete()
|
|
|
+ authControl = null
|
|
|
+ } catch (exception: Throwable) {
|
|
|
+ if (exception is SelectorRequireReconnectException) {
|
|
|
+ throw exception
|
|
|
+ }
|
|
|
+
|
|
|
+ ssoContext.bot.network.logger.warning({ "Failed with auth method: $nextAuthMethod" }, exception)
|
|
|
+ authControl0.exceptionCollector.collectException(exception)
|
|
|
+
|
|
|
+ if (nextAuthMethod !is AuthMethod.Error && nextAuthMethod != null) {
|
|
|
+ authControl0.actMethodFailed(exception)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exception is NetworkException) {
|
|
|
+ if (exception.recoverable) throw exception
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nextAuthMethod == null || nextAuthMethod is AuthMethod.NotAvailable || nextAuthMethod is AuthMethod.Error) {
|
|
|
+ authControl = null
|
|
|
+ authControl0.exceptionCollector.throwLast()
|
|
|
+ }
|
|
|
+
|
|
|
+ throw SelectorRequireReconnectException()
|
|
|
+ }
|
|
|
+
|
|
|
+ loginSuccess()
|
|
|
+
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ sealed class AuthMethod {
|
|
|
+ object NotAvailable : AuthMethod() {
|
|
|
+ override fun toString(): String = "NotAvailable"
|
|
|
+ }
|
|
|
+
|
|
|
+ object QRCode : AuthMethod() {
|
|
|
+ override fun toString(): String = "QRCode"
|
|
|
+ }
|
|
|
+
|
|
|
+ class Pwd(val passwordMd5: SecretsProtection.EscapedByteBuffer) : AuthMethod() {
|
|
|
+ override fun toString(): String = "Password@${hashCode()}"
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Exception in [BotAuthorization]
|
|
|
+ */
|
|
|
+ class Error(val exception: Throwable) : AuthMethod() {
|
|
|
+ override fun toString(): String = "Error[$exception]@${hashCode()}"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var authControl: AuthControl? = null
|
|
|
+
|
|
|
override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response {
|
|
|
return registerClientOnline(handler).also { registerResp = it }
|
|
|
}
|
|
@@ -189,17 +310,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
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -219,7 +329,10 @@ internal class SsoProcessorImpl(
|
|
|
abstract suspend fun doLogin()
|
|
|
}
|
|
|
|
|
|
- private inner class SlowLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
|
|
|
+ private inner class SlowLoginImpl(
|
|
|
+ handler: NetworkHandler,
|
|
|
+ private val loginType: LoginType
|
|
|
+ ) : LoginStrategy(handler) {
|
|
|
|
|
|
private fun loginSolverNotNull(): LoginSolver {
|
|
|
fun LoginSolver?.notnull(): LoginSolver {
|
|
@@ -259,9 +372,15 @@ internal class SsoProcessorImpl(
|
|
|
|
|
|
override suspend fun doLogin() = withExceptionCollector {
|
|
|
|
|
|
+ @Suppress("FunctionName")
|
|
|
+ fun SSOWtLogin9(allowSlider: Boolean) = when (loginType) {
|
|
|
+ is LoginType.Password -> WtLogin9.Password(client, loginType.passwordMd5.asByteArray, allowSlider)
|
|
|
+ is LoginType.QRCode -> WtLogin9.QRCode(client, loginType.qrCodeLoginData)
|
|
|
+ }
|
|
|
+
|
|
|
var allowSlider = sliderSupported || bot.configuration.protocol == MiraiProtocol.ANDROID_PHONE
|
|
|
|
|
|
- var response: LoginPacketResponse = WtLogin9(client, allowSlider).sendAndExpect()
|
|
|
+ var response: LoginPacketResponse = SSOWtLogin9(allowSlider).sendAndExpect()
|
|
|
|
|
|
mainloop@ while (true) {
|
|
|
when (response) {
|
|
@@ -281,7 +400,7 @@ internal class SsoProcessorImpl(
|
|
|
check(result is DeviceVerificationResultImpl)
|
|
|
response = when (result) {
|
|
|
is UrlDeviceVerificationResult -> {
|
|
|
- WtLogin9(client, allowSlider).sendAndExpect()
|
|
|
+ SSOWtLogin9(allowSlider).sendAndExpect()
|
|
|
}
|
|
|
|
|
|
is SmsDeviceVerificationResult -> {
|
|
@@ -308,7 +427,7 @@ internal class SsoProcessorImpl(
|
|
|
collectThrow(error)
|
|
|
}
|
|
|
response = if (ticket == null) {
|
|
|
- WtLogin9(client, allowSlider).sendAndExpect()
|
|
|
+ SSOWtLogin9(allowSlider).sendAndExpect()
|
|
|
} else {
|
|
|
WtLogin2.SubmitSliderCaptcha(client, ticket).sendAndExpect()
|
|
|
}
|
|
@@ -358,6 +477,11 @@ internal class SsoProcessorImpl(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private sealed class LoginType {
|
|
|
+ class Password(val passwordMd5: SecretsProtection.EscapedByteBuffer) : LoginType()
|
|
|
+ class QRCode(val qrCodeLoginData: QRCodeLoginData) : LoginType()
|
|
|
+ }
|
|
|
+
|
|
|
private inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
|
|
|
override suspend fun doLogin() {
|
|
|
val login10 = handler.sendAndExpect(WtLogin10(client))
|