Repository: thunderbird/thunderbird-android
File: mail/protocols/smtp/src/main/java/com/fsck/k9/mail/transport/smtp/SmtpTransport.kt (Kotlin)

Code before:
        if (negativeResponses.isNotEmpty()) {
            Log.w("failed to authenticate with all discovered users.")
            val (user, negativeResponse) = negativeResponses[username]?.let { username to it }
                ?: negativeResponses.entries.first().toPair()

            logger.log("invalidating current token")
            oauthTokenProvider.invalidateToken()

            if (!retryOAuthWithNewToken) {
                handlePermanentOAuthFailure(method, negativeResponse)
            } else {
                handleTemporaryOAuthFailure(
                    method = method,
                    username = user,
                    negativeResponseFromOldToken = negativeResponse,
                )
            }
        }
    }

    private fun handlePermanentOAuthFailure(
        method: OAuthMethod,
        negativeResponse: NegativeSmtpReplyException,
    ): Nothing {
        throw AuthenticationFailedException(
            message = "${method.command} failed",
            throwable = negativeResponse,
            messageFromServer = negativeResponse.replyText,
        )
    }

    private fun handleTemporaryOAuthFailure(
        method: OAuthMethod,
        username: String,
        negativeResponseFromOldToken: NegativeSmtpReplyException,
    ) {
        // Token was invalid. We could avoid this double check if we had a reasonable chance of knowing if a token was
        // invalid before use (e.g. due to expiry). But we don't. This is the intended behaviour per AccountManager.
        Log.v(negativeResponseFromOldToken, "Authentication exception, re-trying with new token")

        try {
            attempOAuth(method, username)
        } catch (negativeResponseFromNewToken: NegativeSmtpReplyException) {
            if (negativeResponseFromNewToken.replyCode != SMTP_AUTHENTICATION_FAILURE_ERROR_CODE) {
                throw negativeResponseFromNewToken
            }

            // Okay, we failed on a new token. Invalidate the token anyway but assume it's permanent.
            Log.v(negativeResponseFromNewToken, "Authentication exception for new token, permanent error assumed")

            oauthTokenProvider!!.invalidateToken()

Review comment (bug):
This could be a separate method:

```suggestion
        val negativeResponses = authenticateUsers(method, users)
```

and

```kotlin
private fun authenticateUsers(
    method: OAuthMethod,
    users: MutableSet<String>
): Map<String, NegativeSmtpReplyException> {
    val negativeResponses = mutableMapOf<String, NegativeSmtpReplyException>()
    val sensitiveLog = "*sensitive*"
    logger.log("users = ${users.takeIf { K9MailLib.isDebugSensitive() } ?: "[${users.size} emails found]"}")


    val iterator = users.iterator()
    while (iterator.hasNext()) {
        val user = iterator.next()
        try {
            logger.log(
                "trying to authenticate with user '${
                    user.takeIf { K9MailLib.isDebugSensitive() } ?: sensitiveLog
                }'",
            )
            attempOAuth(method, user)
            negativeResponses.clear()
            break
        } catch (negativeResponse: NegativeSmtpReplyException) {
            Log.w(negativeResponse, "saslOAuth: failed to authenticate.")
            if (negativeResponse.replyCode != SMTP_AUTHENTICATION_FAILURE_ERROR_CODE) {
                throw negativeResponse
            }
            iterator.remove()
            negativeResponses += user to negativeResponse
        }
    }
    return negativeResponses
}
```

Code after:
    ): MutableMap<String, NegativeSmtpReplyException> {
        val negativeResponses = mutableMapOf<String, NegativeSmtpReplyException>()
        val sensitiveLog = "*sensitive*"
        logger.log("users = ${users.takeIf { K9MailLib.isDebugSensitive() } ?: "[${users.size} emails found]"}")

        val iterator = users.iterator()
        while (iterator.hasNext()) {
            val user = iterator.next()
            try {
                logger.log(
                    "trying to authenticate with user '${
                        user.takeIf { K9MailLib.isDebugSensitive() } ?: sensitiveLog
                    }'",
                )
                attempOAuth(method, user)
                negativeResponses.clear()
                break
            } catch (negativeResponse: NegativeSmtpReplyException) {
                Log.w(negativeResponse, "saslOAuth: failed to authenticate.")
                if (negativeResponse.replyCode != SMTP_AUTHENTICATION_FAILURE_ERROR_CODE) {
                    throw negativeResponse
                }
                iterator.remove()
                negativeResponses += user to negativeResponse
            }
        }
        return negativeResponses
    }

    private fun handlePermanentOAuthFailure(
        method: OAuthMethod,
        negativeResponse: NegativeSmtpReplyException,
    ): Nothing {
        throw AuthenticationFailedException(
            message = "${method.command} failed",
            throwable = negativeResponse,
            messageFromServer = negativeResponse.replyText,
        )
    }

    private fun handleTemporaryOAuthFailure(
        method: OAuthMethod,
        username: String,
        negativeResponseFromOldToken: NegativeSmtpReplyException,
    ) {
        // Token was invalid. We could avoid this double check if we had a reasonable chance of knowing if a token was
        // invalid before use (e.g. due to expiry). But we don't. This is the intended behaviour per AccountManager.
        Log.v(negativeResponseFromOldToken, "Authentication exception, re-trying with new token")

        try {
            attempOAuth(method, username)