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

Commit 2bc6d088 authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

refac:3870: simplify fetch AuthData procedures.

parent 6c12f40f
Loading
Loading
Loading
Loading
+0 −69
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019-2025 e Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package foundation.e.apps.data.login

import foundation.e.apps.data.enums.User
import foundation.e.apps.data.preference.AppLoungeDataStore
import foundation.e.apps.data.preference.AppLoungePreference
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Contains common function for first login, logout, get which type of authentication / source
 * to be used etc...
 *
 * https://gitlab.e.foundation/e/backlog/-/issues/5680
 */
@Singleton
class LoginCommon @Inject constructor(
    private val appLoungeDataStore: AppLoungeDataStore,
    private val appLoungePreference: AppLoungePreference,
) {
    suspend fun saveUserType(user: User) {
        appLoungeDataStore.saveUserType(user)
    }

    fun getUserType(): User {
        return appLoungeDataStore.getUser()
    }

    suspend fun saveGoogleLogin(email: String, oauth: String) {
        appLoungeDataStore.saveGoogleLogin(email, oauth)
    }

    suspend fun setNoGoogleMode() {
        appLoungePreference.run {
            disablePlayStore()
            enableOpenSource()
            enablePwa()
        }
        appLoungeDataStore.saveUserType(User.NO_GOOGLE)
    }

    suspend fun logout() {
        appLoungeDataStore.destroyCredentials()
        appLoungeDataStore.saveUserType(null)
        // reset app source preferences on logout.
        appLoungePreference.run {
            enableOpenSource()
            enablePwa()
            enablePlayStore()
        }
    }
}
+0 −162
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019-2025 e Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package foundation.e.apps.data.login

import android.content.Context
import com.aurora.gplayapi.data.models.AuthData
import dagger.hilt.android.qualifiers.ApplicationContext
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.enums.ResultStatus
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.login.api.GoogleLoginManager
import foundation.e.apps.data.login.api.PlayStoreLoginManager
import foundation.e.apps.data.login.api.PlayStoreLoginManagerFactory
import foundation.e.apps.data.login.api.PlayStoreLoginWrapper
import foundation.e.apps.data.preference.AppLoungeDataStore
import foundation.e.apps.data.preference.AppLoungePreference
import foundation.e.apps.data.preference.getSync
import foundation.e.apps.data.retryWithBackoff
import kotlinx.serialization.json.Json
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Class to get GPlay auth data. Call [login] to get an already saved auth data
 * or to fetch a new one for first use. Handles auth validation internally.
 *
 * https://gitlab.e.foundation/e/backlog/-/issues/5680
 */
@Singleton
class PlayStoreAuthenticator @Inject constructor(
    @ApplicationContext private val context: Context,
    private val json: Json,
    private val appLoungeDataStore: AppLoungeDataStore,
) {

    @Inject
    lateinit var loginManagerFactory: PlayStoreLoginManagerFactory

    private val user: User
        get() = appLoungeDataStore.getUser()

    private val loginManager: PlayStoreLoginManager
        get() = loginManagerFactory.createLoginManager(user)

    private val loginWrapper: PlayStoreLoginWrapper
        get() = PlayStoreLoginWrapper(loginManager, user)

    private val locale: Locale
        get() = context.resources.configuration.locales[0]


    /**
     * Get authData stored as JSON and convert to AuthData class.
     * Returns null if nothing is saved.
     */
    private fun getSavedAuthData(): AuthData? {
        val authJson = appLoungeDataStore.authData.getSync()
        return if (authJson.isBlank()) null
        else try {
            json.decodeFromString<AuthData>(authJson)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    private suspend fun saveAuthData(authData: AuthData) {
        appLoungeDataStore.saveAuthData(authData)
    }

    /**
     * Generate new AuthData based on the user type.
     */
    private suspend fun generateAuthData(): ResultSupreme<AuthData?> {
        return when (appLoungeDataStore.getUser()) {
            User.ANONYMOUS -> getAuthDataAnonymously()
            User.GOOGLE -> getAuthDataWithGoogleAccount()
            else -> ResultSupreme.Error("User type not ANONYMOUS or GOOGLE")
        }
    }

    /**
     * Aurora OSS GPlay API complains of missing headers sometimes.
     * Converting [authData] to Json and back to [AuthData] fixed it.
     */
    private fun formatAuthData(authData: AuthData): AuthData {
        val localAuthDataJson = json.encodeToString(authData)
        return json.decodeFromString<AuthData>(localAuthDataJson)
    }

    suspend fun getAuthDataAnonymously(): ResultSupreme<AuthData?> {
        return loginWrapper.login(locale).run {
            if (isSuccess()) ResultSupreme.Success(formatAuthData(this.data!!))
            else this
        }
    }

    suspend fun getAuthDataWithGoogleAccount(): ResultSupreme<AuthData?> {

        val email = appLoungeDataStore.emailData.getSync()
        val oauthToken = appLoungeDataStore.oauthToken.getSync()
        val aasToken = appLoungeDataStore.aasToken.getSync()
        /*
         * If aasToken is not blank, means it was stored successfully from a previous Google login.
         * Use it to fetch auth data.
         */
        if (aasToken.isNotBlank()) {
            return loginWrapper.login(locale)
        }

        /*
         * If aasToken is not yet saved / made, fetch it from email and oauthToken.
         */
        val aasTokenResponse = loginWrapper.getAasToken(
            loginManager as GoogleLoginManager,
            email,
            oauthToken
        )

        /*
         * If fetch was unsuccessful, return blank auth data.
         * We replicate from the response, so that it will carry on any error message if present
         * in the aasTokenResponse.
         */
        if (!aasTokenResponse.isSuccess()) {
            return ResultSupreme.replicate(aasTokenResponse, null)
        }

        val aasTokenFetched = aasTokenResponse.data ?: ""

        if (aasTokenFetched.isBlank()) {
            return ResultSupreme.Error("Fetched AAS Token is blank")
        }

        /*
         * Finally save the aasToken and create auth data.
         */
        appLoungeDataStore.saveAasToken(aasTokenFetched)
        return loginWrapper.login(locale).run {
            if (isSuccess()) ResultSupreme.Success(formatAuthData(this.data!!))
            else this
        }
    }
}
+0 −68
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019-2022  E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps.data.login.api

import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.helpers.AuthHelper
import foundation.e.apps.data.playstore.utils.AC2DMTask
import foundation.e.apps.data.preference.AppLoungeDataStore
import foundation.e.apps.data.preference.getSync
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Properties

class GoogleLoginManager(
    private val nativeDeviceProperty: Properties,
    private val aC2DMTask: AC2DMTask,
    private val appLoungeDataStore: AppLoungeDataStore,
) : PlayStoreLoginManager {

    /**
     * Get PlayResponse for AC2DM Map. This allows us to get an error message too.
     *
     * An aasToken is extracted from this map. This is passed to [login]
     * to generate AuthData. This token is very important as it cannot be regenerated,
     * hence it must be saved for future use.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5680
     */
    suspend fun getAC2DMResponse(email: String, oauthToken: String): PlayResponse {
        var response: PlayResponse
        withContext(Dispatchers.IO) {
            response = aC2DMTask.getAC2DMResponse(email, oauthToken)
        }
        return response
    }

    /**
     * Login
     *
     * @return authData: authentication data
     */
    override suspend fun login(): AuthData? {
        val email = appLoungeDataStore.emailData.getSync()
        val aasToken = appLoungeDataStore.aasToken.getSync()

        var authData: AuthData?
        withContext(Dispatchers.IO) {
            authData = AuthHelper.build(email, aasToken, tokenType = AuthHelper.Token.AAS, properties = nativeDeviceProperty)
        }
        return authData
    }
}
+0 −25
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019-2022  E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps.data.login.api

import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse

interface PlayStoreLoginManager {
    suspend fun login(): AuthData?
}
+0 −44
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019-2022  E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps.data.login.api

import foundation.e.apps.data.enums.User
import foundation.e.apps.data.playstore.utils.AC2DMTask
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import foundation.e.apps.data.preference.AppLoungeDataStore
import kotlinx.serialization.json.Json
import java.util.Properties
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PlayStoreLoginManagerFactory @Inject constructor(
    private val gPlayHttpClient: GPlayHttpClient,
    private val nativeDeviceProperty: Properties,
    private val aC2DMTask: AC2DMTask,
    private val json: Json,
    private val appLoungeDataStore: AppLoungeDataStore,
) {

    fun createLoginManager(user: User): PlayStoreLoginManager {
        return when (user) {
            User.GOOGLE -> GoogleLoginManager(nativeDeviceProperty, aC2DMTask, appLoungeDataStore)
            else -> AnonymousLoginManager(gPlayHttpClient, nativeDeviceProperty, json)
        }
    }
}
Loading