[core/auth] Wrap exception from BotAuthorization as BotAuthorizationException; add tests

This commit is contained in:
Him188 2023-04-25 12:31:01 +01:00
parent b18e62b3b6
commit bda5d54bd3
10 changed files with 174 additions and 25 deletions

View File

@ -5370,6 +5370,10 @@ public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {
public final class net/mamoe/mirai/message/data/visitor/MessageVisitorKt {
}
public final class net/mamoe/mirai/network/BotAuthorizationException : net/mamoe/mirai/network/LoginFailedException {
public final fun getAuthorization ()Lnet/mamoe/mirai/auth/BotAuthorization;
}
public abstract class net/mamoe/mirai/network/CustomLoginFailedException : net/mamoe/mirai/network/LoginFailedException {
public fun <init> (Z)V
public fun <init> (ZLjava/lang/String;)V

View File

@ -5370,6 +5370,10 @@ public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {
public final class net/mamoe/mirai/message/data/visitor/MessageVisitorKt {
}
public final class net/mamoe/mirai/network/BotAuthorizationException : net/mamoe/mirai/network/LoginFailedException {
public final fun getAuthorization ()Lnet/mamoe/mirai/auth/BotAuthorization;
}
public abstract class net/mamoe/mirai/network/CustomLoginFailedException : net/mamoe/mirai/network/LoginFailedException {
public fun <init> (Z)V
public fun <init> (ZLjava/lang/String;)V

View File

@ -12,6 +12,7 @@
package net.mamoe.mirai.network
import net.mamoe.mirai.Bot
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiInternalApi
@ -75,6 +76,19 @@ public class NoStandardInputForCaptchaException @MiraiInternalApi constructor(
public override val cause: Throwable? = null
) : LoginFailedException(true, "no standard input for captcha")
/**
* 表示在登录过程中, [BotAuthorization] 抛出的异常.
* @since 2.15
*/
public class BotAuthorizationException @MiraiInternalApi constructor(
public val authorization: BotAuthorization,
cause: Throwable?,
) : LoginFailedException(
killBot = true,
"BotAuthorization(${authorization}) threw an exception during authorization process. See cause below.",
cause
)
/**
* 当前 [LoginSolver] 不支持此验证方式
*

View File

@ -11,5 +11,13 @@ package net.mamoe.mirai.utils.channels
public class ProducerFailureException(
override val message: String? = "Producer failed to produce a value, see cause",
override val cause: Throwable?
) : Exception()
override var cause: Throwable?
) : Exception() {
private val unwrapped: Throwable by lazy {
val cause = cause ?: return@lazy this
this.cause = null
cause.also { addSuppressed(this) }
}
public fun unwrap(): Throwable = unwrapped
}

View File

@ -285,7 +285,8 @@ class OnDemandChannelTest {
}
assertTrue { channel.isClosed }
// The exception looks like this, though I don't know why there are two causes.
// The exception looks like this.
// The first cause is stacktrace-recovered by coroutines, and the second is the original one.
//net.mamoe.mirai.utils.channels.ProducerFailureException: Producer failed to produce a value, see cause
// at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel.receiveOrNull(OnDemandChannelImpl.kt:164)

View File

@ -17,8 +17,13 @@ import net.mamoe.mirai.utils.TestOnly
internal class BotAccount(
internal val id: Long,
val authorization: BotAuthorization,
authorization: BotAuthorization,
) {
var authorization: BotAuthorization = authorization
// FIXME: Making this mutable is very bad.
// But I had to do this because the current test framework is bad, and I don't have time to do a major rewrite.
@TestOnly set
@TestOnly // to be compatible with your local tests :)
constructor(
id: Long, pwd: String

View File

@ -58,7 +58,7 @@ internal class AuthControl(
val rsp = try {
userDecisions.receiveOrNull() ?: SsoProcessorImpl.AuthMethod.NotAvailable
} catch (e: ProducerFailureException) {
SsoProcessorImpl.AuthMethod.Error(e)
SsoProcessorImpl.AuthMethod.Error(e.unwrap())
}
logger.debug { "[AuthControl/acquire] Authorization responded: $rsp" }

View File

@ -32,10 +32,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.UrlDeviceVerificat
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.*
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.network.*
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
import kotlin.coroutines.cancellation.CancellationException
@ -117,7 +114,7 @@ internal interface SsoSession {
*
* Used by `NettyNetworkHandler.StateConnecting`.
*/
internal class SsoProcessorImpl(
internal open class SsoProcessorImpl(
val ssoContext: SsoProcessorContext,
) : SsoProcessor {
@ -155,10 +152,22 @@ internal class SsoProcessorImpl(
get() = ssoContext.bot.configuration
}
protected open suspend fun doSlowLogin(
handler: NetworkHandler,
loginType: LoginType
) {
SlowLoginImpl(handler, loginType).doLogin()
}
protected open suspend fun doFastLogin(handler: NetworkHandler) {
FastLoginImpl(handler).doLogin()
}
/**
* Do login. Throws [LoginFailedException] if failed
* Throws [LoginFailedException] if failed. Any other exceptions are considered as internal error.
*/
override suspend fun login(handler: NetworkHandler) {
final override suspend fun login(handler: NetworkHandler) {
fun initAndStartAuthControl() {
authControl = AuthControl(
@ -194,7 +203,7 @@ internal class SsoProcessorImpl(
if (client.wLoginSigInfoInitialized) {
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
kotlin.runCatching {
FastLoginImpl(handler).doLogin()
doFastLogin(handler)
}.onFailure { e ->
initAndStartAuthControl()
authControl!!.exceptionCollector.collect(e)
@ -220,7 +229,7 @@ internal class SsoProcessorImpl(
when (val authw = authControl0.acquireAuth().also { nextAuthMethod = it }) {
is AuthMethod.Error -> {
authControl = null
throw authw.exception
throw BotAuthorizationException(ssoContext.account.authorization, authw.exception)
}
AuthMethod.NotAvailable -> {
@ -229,7 +238,8 @@ internal class SsoProcessorImpl(
}
is AuthMethod.Pwd -> {
SlowLoginImpl(handler, LoginType.Password(authw.passwordMd5)).doLogin()
val loginType = LoginType.Password(authw.passwordMd5)
doSlowLogin(handler, loginType)
}
AuthMethod.QRCode -> {
@ -237,7 +247,8 @@ internal class SsoProcessorImpl(
handler, client
).process(handler, client)
SlowLoginImpl(handler, LoginType.QRCode(rsp)).doLogin()
val loginType = LoginType.QRCode(rsp)
doSlowLogin(handler, loginType)
}
}
@ -271,7 +282,6 @@ internal class SsoProcessorImpl(
}
sealed class AuthMethod {
object NotAvailable : AuthMethod() {
override fun toString(): String = "NotAvailable"
@ -288,7 +298,9 @@ internal class SsoProcessorImpl(
/**
* Exception in [BotAuthorization]
*/
class Error(val exception: Throwable) : AuthMethod() {
class Error(
val exception: Throwable // unwrapped
) : AuthMethod() {
override fun toString(): String = "Error[$exception]@${hashCode()}"
}
}
@ -296,10 +308,6 @@ internal class SsoProcessorImpl(
private var authControl: AuthControl? = null
override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response {
return registerClientOnline(handler).also { registerResp = it }
}
private suspend fun registerClientOnline(handler: NetworkHandler): StatSvc.Register.Response {
return handler.sendAndExpect(StatSvc.Register.online(client)).also {
registerResp = it
}
@ -317,7 +325,7 @@ internal class SsoProcessorImpl(
// we have exactly two methods----slow and fast.
private abstract inner class LoginStrategy(
protected abstract inner class LoginStrategy(
val handler: NetworkHandler,
) {
protected val context get() = handler.context
@ -478,12 +486,12 @@ internal class SsoProcessorImpl(
}
}
private sealed class LoginType {
protected sealed class LoginType {
class Password(val passwordMd5: SecretsProtection.EscapedByteBuffer) : LoginType()
class QRCode(val qrCodeLoginData: QRCodeLoginData) : LoginType()
}
private inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
protected inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
override suspend fun doLogin() {
val login10 = handler.sendAndExpect(WtLogin10(client))
check(login10 is LoginPacketResponse.Success) { "Fast login failed: $login10" }

View File

@ -0,0 +1,47 @@
/*
* 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.auth
import net.mamoe.mirai.auth.BotAuthInfo
import net.mamoe.mirai.auth.BotAuthResult
import net.mamoe.mirai.auth.BotAuthSession
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.components.SsoProcessorContext
import net.mamoe.mirai.internal.network.components.SsoProcessorImpl
import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTestWithSelector
import net.mamoe.mirai.internal.network.framework.PacketReplierDslBuilder
import net.mamoe.mirai.internal.network.framework.buildPacketReplier
internal abstract class AbstractBotAuthTest : AbstractCommonNHTestWithSelector() {
init {
// Use real processor to test login
overrideComponents[SsoProcessor] = SsoProcessorImpl(overrideComponents[SsoProcessorContext])
}
protected fun setAuthorization(authorize: (session: BotAuthSession, info: BotAuthInfo) -> BotAuthResult) {
// Run a real SsoProcessor, just without sending packets
bot.account.authorization = object : BotAuthorization {
override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult {
return authorize(session, info)
}
}
}
// Use the same replier even after reconnection
protected fun usePacketReplierThroughout(builderAction: PacketReplierDslBuilder.() -> Unit) {
val replier = buildPacketReplier {
builderAction()
}
onEachNetworkInstance {
addPacketReplier(replier) // share the decisions
}
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.auth
import kotlinx.coroutines.test.runTest
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.network.BotAuthorizationException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
internal class BotAuthorizationTest : AbstractBotAuthTest() {
@Test
fun `authorization failure throws BotAuthorizationException`() = runTest {
// Run a real SsoProcessor, just without sending packets
setAuthorization { _, _ ->
throw IllegalStateException("Oops")
}
usePacketReplierThroughout {
expect(WtLogin.ExchangeEmp) reply { WtLogin.Login.LoginPacketResponse.Error(bot, 1, "", "", "") }
expect(WtLogin.Login) reply { WtLogin.Login.LoginPacketResponse.Success(bot) }
expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) }
}
assertFailsWith<BotAuthorizationException> {
bot.login()
}.run {
val cause = cause
assertIs<IllegalStateException>(cause)
assertEquals("Oops", cause.message)
}
// Stacktrace like this:
//net.mamoe.mirai.network.BotAuthorizationException: BotAuthorization(net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2@157c6b0f) threw an exception during authorization process. See cause below.
// at net.mamoe.mirai.internal.network.components.SsoProcessorImpl.login(SsoProcessor.kt:232)
//Caused by: java.lang.IllegalStateException: Oops
// at net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2.authorize(AbstractBotAuthTest.kt:44)
// (Coroutine boundary)
// at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel.receiveOrNull(OnDemandChannelImpl.kt:237)
// (Coroutine creation stacktrace)
// at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$StateConnecting.startState(CommonNetworkHandler.kt:244)
//Caused by: java.lang.IllegalStateException: Oops
// at net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2.authorize(AbstractBotAuthTest.kt:44)
}
}