Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a905db7 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Merge branch '0000-a15-fix-state-tokens-ai-review' into 'main'

Fix some stale tokens in Google logins

See merge request !759
parents 66fb3596 98c995ed
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -52,8 +52,8 @@ class MicrogLoginManager @Inject constructor(

    override suspend fun login(): AuthData? {
        val oldToken = playStoreAuthStore.awaitOauthToken()
        val shouldRefresh = hasMicrogAccount() && oldToken.startsWith(MICROG_TOKEN_PREFIX)
        val oauthToken = if (shouldRefresh) {
        val shouldRefreshToken = hasMicrogAccount() && oldToken.startsWith(MICROG_TOKEN_PREFIX)
        val oauthToken = if (shouldRefreshToken) {
            fetchRefreshedToken(oldToken)
        } else {
            oldToken
+3 −7
Original line number Diff line number Diff line
@@ -136,15 +136,11 @@ class PlayStoreAuthenticator @Inject constructor(
    }

    private suspend fun loginWithGoogleBackend(): ResultSupreme<AuthData?> {
        val aasToken = playStoreAuthStore.awaitAasToken()
        return if (aasToken.isNotBlank()) {
            loginWithAasToken()
        } else {
            loginWithAasTokenConversion()
        }
        val storedAasToken = playStoreAuthStore.awaitAasToken()
        if (storedAasToken.isNotBlank()) {
            return loginWithAasToken()
        }

    private suspend fun loginWithAasTokenConversion(): ResultSupreme<AuthData?> {
        val email = playStoreAuthStore.awaitEmail()
        val oauthToken = playStoreAuthStore.awaitOauthToken()
        val aasTokenResponse = aasTokenConverter.convert(email, oauthToken)
+25 −20
Original line number Diff line number Diff line
@@ -209,7 +209,7 @@ class PlayStoreRepository @Inject constructor(
                throw exception
            }

            if (!isEmulator() && appDetails.versionCode == 0L && isAnonymousUser()) {
            if (!isEmulator() && appDetails.versionCode == 0L) {
                // Google Play returns limited result ( i.e. version code being 0) with a stale token,
                // so we need to refresh authentication to get a new token.
                Timber.i("Version code is 0 for ${appDetails.packageName}.")
@@ -242,14 +242,18 @@ class PlayStoreRepository @Inject constructor(
    }

    private suspend fun <T> retryOnUnauthorized(block: suspend () -> T): T {
        return try {
        return withContext(gPlayHttpClient.requestResponseCodeContext) {
            try {
                gPlayHttpClient.resetResponseCode()
                block()
            } catch (exception: Exception) {
                val requestResponseCode = gPlayHttpClient.getRequestResponseCode()
                val isUnauthorized: Boolean = when (exception) {
                    is GplayHttpRequestException ->
                    exception.status == HttpURLConnection.HTTP_UNAUTHORIZED
                        exception.status == HttpURLConnection.HTTP_UNAUTHORIZED ||
                            requestResponseCode == HttpURLConnection.HTTP_UNAUTHORIZED
                    is InternalException.AppNotFound ->
                    gPlayHttpClient.responseCode.value == HttpURLConnection.HTTP_UNAUTHORIZED
                        requestResponseCode == HttpURLConnection.HTTP_UNAUTHORIZED
                    else -> false
                }

@@ -266,6 +270,7 @@ class PlayStoreRepository @Inject constructor(
                block()
            }
        }
    }

    suspend fun getAppDetailsWeb(packageName: String): Application? {
        val webAppDetailsHelper = WebAppDetailsHelper().using(gPlayHttpClient)
+21 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import foundation.e.apps.data.event.EventBus
import foundation.e.apps.data.login.core.AuthObject
import foundation.e.apps.data.system.SystemInfoProvider
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.asContextElement
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -46,6 +47,7 @@ import java.net.SocketTimeoutException
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext

class GPlayHttpClient @Inject constructor(
    loggingInterceptor: HttpLoggingInterceptor
@@ -65,6 +67,9 @@ class GPlayHttpClient @Inject constructor(
    }

    private val _responseCode = MutableStateFlow(INITIAL_RESPONSE_CODE)
    private val requestResponseCode = ThreadLocal.withInitial {
        RequestResponseCodeState(INITIAL_RESPONSE_CODE)
    }
    override val responseCode: StateFlow<Int>
        get() = _responseCode.asStateFlow()

@@ -168,9 +173,21 @@ class GPlayHttpClient @Inject constructor(
        return headersWithLocale
    }

    fun resetResponseCode() {
        _responseCode.value = 0
        checkNotNull(requestResponseCode.get()).value = 0
    }

    fun getRequestResponseCode(): Int {
        return checkNotNull(requestResponseCode.get()).value
    }

    val requestResponseCodeContext: CoroutineContext
        get() = requestResponseCode.asContextElement(RequestResponseCodeState(0))

    private fun processRequest(request: Request): PlayResponse {
        // Reset response code as flow doesn't sends the same value twice
        _responseCode.value = 0
        resetResponseCode()
        var response: Response? = null
        return try {
            val call = okHttpClient.newCall(request)
@@ -237,8 +254,11 @@ class GPlayHttpClient @Inject constructor(
            responseBytes = responseBytes,
        ).withErrorString(errorMessage).apply {
            _responseCode.value = response.code
            checkNotNull(requestResponseCode.get()).value = response.code
        }
    }
}

private data class RequestResponseCodeState(var value: Int)

class GplayHttpRequestException(val status: Int, message: String) : Exception(message)
+24 −0
Original line number Diff line number Diff line
@@ -244,6 +244,30 @@ class MicrogLoginManagerTest {
        org.mockito.kotlin.verify(playStoreAuthStore).saveAasToken("")
    }

    @Test
    fun `login uses existing oauth token when microg account exists but token is not a microg token`() = runBlocking {
        val account = Account("user@gmail.com", MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)
        val accountManager = mock<AccountManager>()
        whenever(accountManager.getAccountsByType(eq(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE)))
            .thenReturn(arrayOf(account))
        val oauthAuthDataBuilder = mock<OauthAuthDataBuilder>()
        val playStoreAuthStore = mock<PlayStoreAuthStore>()
        whenever(playStoreAuthStore.awaitOauthToken()).thenReturn("non-ya29-stale-token")
        whenever(playStoreAuthStore.awaitEmail()).thenReturn(account.name)
        val authData = com.aurora.gplayapi.data.models.AuthData(email = account.name)
        whenever(oauthAuthDataBuilder.build("non-ya29-stale-token")).thenReturn(authData)

        val result = buildMicrogLoginManager(accountManager, oauthAuthDataBuilder, playStoreAuthStore).login()

        assertEquals(authData, result)
        org.mockito.kotlin.verify(accountManager, org.mockito.kotlin.never())
            .invalidateAuthToken(any(), any())
        org.mockito.kotlin.verify(playStoreAuthStore, org.mockito.kotlin.never())
            .saveGoogleLogin(any(), any())
        org.mockito.kotlin.verify(playStoreAuthStore, org.mockito.kotlin.never())
            .saveAasToken(any())
    }

    @Test
    fun `login throws when token missing`() {
        val accountManager = mock<AccountManager>()
Loading