AuthProvider: Extract authData generation logic

* Partially revert 51c4beba8e
* Extract the AuthData generation logic to Authprovider and AccountProvider classes

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-09-25 18:43:58 +01:00
parent d5aab4b3cb
commit 9e8dd03cee
7 changed files with 198 additions and 198 deletions

View File

@@ -47,10 +47,10 @@ object Constants {
//ACCOUNTS
const val ACCOUNT_SIGNED_IN = "ACCOUNT_SIGNED_IN"
const val ACCOUNT_SIGNED_TIMESTAMP = "ACCOUNT_SIGNED_TIMESTAMP"
const val ACCOUNT_TYPE = "ACCOUNT_TYPE"
const val ACCOUNT_EMAIL_PLAIN = "ACCOUNT_EMAIL_PLAIN"
const val ACCOUNT_AAS_PLAIN = "ACCOUNT_AAS_PLAIN"
const val ACCOUNT_AUTH_PLAIN = "ACCOUNT_AUTH_PLAIN"
const val PAGE_TYPE = "PAGE_TYPE"
const val TOP_CHART_TYPE = "TOP_CHART_TYPE"

View File

@@ -21,6 +21,7 @@ package com.aurora.store.data.providers
import android.content.Context
import com.aurora.Constants
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.data.model.AccountType
import com.aurora.store.util.Preferences
@@ -34,9 +35,49 @@ object AccountProvider {
}
}
fun logout(context: Context) {
Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, false)
Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, "")
Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, "")
fun isLoggedIn(context: Context): Boolean {
return Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN, false)
}
}
fun getLoginEmail(context: Context): String? {
val email = Preferences.getString(context, Constants.ACCOUNT_EMAIL_PLAIN)
return email.ifBlank { null }
}
fun getLoginToken(context: Context): Pair<String, AuthHelper.Token>? {
val email = Preferences.getString(context, Constants.ACCOUNT_EMAIL_PLAIN)
val aasToken = Preferences.getString(context, Constants.ACCOUNT_AAS_PLAIN)
val authToken = Preferences.getString(context, Constants.ACCOUNT_AUTH_PLAIN)
if (email.isBlank() && (aasToken.isBlank() || authToken.isBlank())) return null
val tokenType = if (aasToken.isBlank()) AuthHelper.Token.AUTH else AuthHelper.Token.AAS
return Pair(aasToken.ifBlank { authToken }, tokenType)
}
fun login(
context: Context,
email: String,
token: String,
tokenType: AuthHelper.Token,
accountType: AccountType
) {
Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, true)
Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, email)
Preferences.putString(context, Constants.ACCOUNT_TYPE, accountType.name)
if (tokenType == AuthHelper.Token.AAS) {
Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, token)
} else {
Preferences.putString(context, Constants.ACCOUNT_AUTH_PLAIN, token)
}
}
fun logout(context: Context) {
Preferences.remove(context, Constants.ACCOUNT_SIGNED_IN)
Preferences.remove(context, Constants.ACCOUNT_TYPE)
Preferences.remove(context, Constants.ACCOUNT_EMAIL_PLAIN)
Preferences.remove(context, Constants.ACCOUNT_AAS_PLAIN)
Preferences.remove(context, Constants.ACCOUNT_AUTH_PLAIN)
}
}

View File

@@ -21,9 +21,8 @@ package com.aurora.store.data.providers
import android.content.Context
import android.util.Log
import com.aurora.Constants
import com.aurora.Constants.ACCOUNT_SIGNED_IN
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.gplayapi.helpers.AuthValidator
import com.aurora.gplayapi.network.IHttpClient
@@ -53,7 +52,27 @@ class AuthProvider @Inject constructor(
private val TAG = AuthProvider::class.java.simpleName
val properties: Properties
val dispenserURL: String?
get() {
val dispensers = Preferences.getStringSet(context, PREFERENCE_DISPENSER_URLS)
return if (dispensers.isNotEmpty()) dispensers.random() else null
}
val authData: AuthData?
get() {
Log.i(TAG, "Loading saved AuthData")
val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA)
return if (rawAuth.isNotBlank()) {
gson.fromJson(rawAuth, AuthData::class.java)
} else {
null
}
}
val isAnonymous: Boolean
get() = AccountProvider.getAccountType(context) == AccountType.ANONYMOUS
private val properties: Properties
get() {
val currentProperties = if (spoofProvider.isDeviceSpoofEnabled()) {
spoofProvider.getSpoofDeviceProperties()
@@ -63,41 +82,14 @@ class AuthProvider @Inject constructor(
setVendingVersion(currentProperties)
return currentProperties
}
val locale: Locale
private val locale: Locale
get() = if (spoofProvider.isLocaleSpoofEnabled()) {
spoofProvider.getSpoofLocale()
} else {
Locale.getDefault()
}
val dispenserURL: String?
get() {
val dispensers = Preferences.getStringSet(context, PREFERENCE_DISPENSER_URLS)
return if (dispensers.isNotEmpty()) dispensers.random() else null
}
val authData: AuthData? get() = getSavedAuthData()
val isAnonymous: Boolean
get() {
val name = Preferences.getString(context, Constants.ACCOUNT_TYPE, AccountType.GOOGLE.name)
return AccountType.valueOf(name) == AccountType.ANONYMOUS
}
private val signedIn: Boolean
get() = Preferences.getBoolean(context, ACCOUNT_SIGNED_IN)
/**
* Builds and returns AuthData based on signed-in account type
*/
suspend fun getTmpAuthData(): AuthData? {
return when {
!signedIn -> null
!isAnonymous -> buildGoogleAuthData()
else -> buildAnonymousAuthData()
}
}
/**
* Checks whether saved AuthData is valid or not
*/
@@ -113,56 +105,79 @@ class AuthProvider @Inject constructor(
}
}
private fun getSavedAuthData(): AuthData? {
Log.i(TAG, "Loading saved AuthData")
val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA)
return if (rawAuth.isNotEmpty()) {
gson.fromJson(rawAuth, AuthData::class.java)
} else {
null
}
}
private suspend fun buildGoogleAuthData(): AuthData? {
/**
* Builds [AuthData] for login using personal Google account
* @param email E-mail ID
* @param token AAS or Auth token
* @param tokenType Type of the token, one from [AuthHelper.Token]
* @return Result encapsulating [AuthData] or exception
*/
suspend fun buildGoogleAuthData(
email: String,
token: String,
tokenType: AuthHelper.Token
): Result<AuthData> {
return withContext(Dispatchers.IO) {
try {
val email = Preferences.getString(context, Constants.ACCOUNT_EMAIL_PLAIN)
val aasToken = Preferences.getString(context, Constants.ACCOUNT_AAS_PLAIN)
return@withContext AuthHelper.build(
email = email,
token = aasToken,
tokenType = AuthHelper.Token.AAS,
properties = properties,
locale = locale
return@withContext Result.success(
AuthHelper.build(
email = email,
token = token,
tokenType = tokenType,
properties = properties,
locale = locale,
)
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to generate Session", exception)
return@withContext null
return@withContext Result.failure(exception)
}
}
}
private suspend fun buildAnonymousAuthData(): AuthData? {
/**
* Builds [AuthData] for login using one of the dispensers
* @return Result encapsulating [AuthData] or exception
*/
suspend fun buildAnonymousAuthData(): Result<AuthData> {
return withContext(Dispatchers.IO) {
try {
val playResponse = httpClient.getAuth(dispenserURL!!)
val playResponse = httpClient.getAuth(dispenserURL!!).also {
if (!it.isSuccessful) throwError(it, context)
}
val auth = gson.fromJson(String(playResponse.responseBytes), Auth::class.java)
return@withContext AuthHelper.build(
email = auth.email,
token = auth.auth,
tokenType = AuthHelper.Token.AUTH,
isAnonymous = true,
properties = properties,
locale = locale
return@withContext Result.success(
AuthHelper.build(
email = auth.email,
token = auth.auth,
tokenType = AuthHelper.Token.AUTH,
isAnonymous = true,
properties = properties,
locale = locale
)
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to generate AuthData", exception)
return@withContext null
return@withContext Result.failure(exception)
}
}
}
/**
* Saves given [AuthData]
*/
fun saveAuthData(authData: AuthData) {
Preferences.putString(context, PREFERENCE_AUTH_DATA, gson.toJson(authData))
}
/**
* Removes saved [AuthData]
*/
fun removeAuthData(context: Context) {
Preferences.remove(context, PREFERENCE_AUTH_DATA)
}
private fun setVendingVersion(currentProperties: Properties) {
val vendingVersionIndex = Preferences.getInteger(context, PREFERENCE_VENDING_VERSION)
if (vendingVersionIndex > 0) {
@@ -174,4 +189,27 @@ class AuthProvider @Inject constructor(
currentProperties.setProperty("Vending.versionString", versionStrings[vendingVersionIndex])
}
}
@Throws(Exception::class)
private fun throwError(playResponse: PlayResponse, context: Context) {
when (playResponse.code) {
400 -> throw Exception(context.getString(R.string.bad_request))
403 -> throw Exception(context.getString(R.string.access_denied_using_vpn))
404 -> throw Exception(context.getString(R.string.server_unreachable))
429 -> throw Exception(context.getString(R.string.login_rate_limited))
503 -> throw Exception(context.getString(R.string.server_maintenance))
else -> {
if (playResponse.errorString.isNotBlank()) {
throw Exception(playResponse.errorString)
} else {
throw Exception(
context.getString(
R.string.failed_generating_session,
playResponse.code
)
)
}
}
}
}
}

View File

@@ -121,19 +121,13 @@ class UpdateWorker @AssistedInject constructor(
}
withContext(Dispatchers.IO) {
val authData = if (authProvider.isSavedAuthDataValid()) {
authProvider.authData
} else {
authProvider.getTmpAuthData()
}
if (authData == null) {
if (!authProvider.isSavedAuthDataValid()) {
Log.i(TAG, "AuthData is not valid, retrying later!")
return@withContext Result.retry()
}
try {
val updatesList = appUtil.checkUpdates(authData)
val updatesList = appUtil.checkUpdates()
.filter { it.hasValidCert }
.filterNot { it.isSelfUpdate() }

View File

@@ -6,7 +6,6 @@ import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.AuroraApp
@@ -58,10 +57,10 @@ class AppUtil @Inject constructor(
}
}
suspend fun checkUpdates(tmpAuthData: AuthData? = null): List<Update> {
suspend fun checkUpdates(): List<Update> {
Log.i(TAG, "Checking for updates")
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
val appUpdatesList = getFilteredInstalledApps(tmpAuthData, packageInfoMap)
val appUpdatesList = getFilteredInstalledApps(packageInfoMap)
.filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
@@ -86,11 +85,10 @@ class AppUtil @Inject constructor(
}
suspend fun getFilteredInstalledApps(
tmpAuthData: AuthData? = null,
packageInfoMap: MutableMap<String, PackageInfo>? = null
): List<App> {
return withContext(Dispatchers.IO) {
val appDetailsHelper = AppDetailsHelper(tmpAuthData?: authProvider.authData!!)
val appDetailsHelper = AppDetailsHelper(authProvider.authData!!)
.using(httpClient)
(packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages ->

View File

@@ -32,6 +32,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.AuroraApp
import com.aurora.store.R
import com.aurora.store.data.event.AuthEvent
@@ -117,7 +118,7 @@ class GoogleFragment : BaseFragment<FragmentGoogleBinding>() {
when (event) {
is AuthEvent.GoogleLogin -> {
if (event.success) {
viewModel.buildGoogleAuthData(event.email, event.token)
viewModel.buildGoogleAuthData(event.email, event.token, AuthHelper.Token.AAS)
} else {
Toast.makeText(
requireContext(),

View File

@@ -27,26 +27,20 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.Constants
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.AuroraApp
import com.aurora.store.R
import com.aurora.store.data.event.AuthEvent
import com.aurora.store.data.model.AccountType
import com.aurora.store.data.model.Auth
import com.aurora.store.data.model.AuthState
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.AC2DMTask
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import java.net.ConnectException
import java.net.UnknownHostException
import javax.inject.Inject
@@ -56,8 +50,6 @@ import javax.inject.Inject
class AuthViewModel @Inject constructor(
val authProvider: AuthProvider,
@ApplicationContext private val context: Context,
private val gson: Gson,
private val httpClient: IHttpClient,
private val aC2DMTask: AC2DMTask
) : ViewModel() {
@@ -67,8 +59,7 @@ class AuthViewModel @Inject constructor(
fun observe() {
if (liveData.value != AuthState.Fetching) {
val signedIn = Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN)
if (signedIn) {
if (AccountProvider.isLoggedIn(context)) {
liveData.postValue(AuthState.Available)
buildSavedAuthData()
} else {
@@ -77,18 +68,14 @@ class AuthViewModel @Inject constructor(
}
}
fun buildGoogleAuthData(email: String, aasToken: String) {
fun buildGoogleAuthData(email: String, token: String, tokenType: AuthHelper.Token) {
liveData.postValue(AuthState.Fetching)
viewModelScope.launch(Dispatchers.IO) {
try {
val authData = AuthHelper.build(
email = email,
token = aasToken,
tokenType = AuthHelper.Token.AAS,
properties = authProvider.properties,
locale = authProvider.locale
verifyAndSaveAuth(
authProvider.buildGoogleAuthData(email, token, tokenType).getOrThrow(),
AccountType.GOOGLE
)
verifyAndSaveAuth(authData, AccountType.GOOGLE)
} catch (exception: Exception) {
liveData.postValue(
AuthState.Failed(context.getString(R.string.failed_to_generate_session))
@@ -102,28 +89,13 @@ class AuthViewModel @Inject constructor(
liveData.postValue(AuthState.Fetching)
viewModelScope.launch(Dispatchers.IO) {
try {
val playResponse = httpClient.getAuth(authProvider.dispenserURL!!)
if (playResponse.isSuccessful) {
val tmpAuthData = gson.fromJson(
String(playResponse.responseBytes),
Auth::class.java
)
val authData = AuthHelper.build(
email = tmpAuthData.email,
token = tmpAuthData.auth,
tokenType = AuthHelper.Token.AUTH,
isAnonymous = true,
properties = authProvider.properties,
locale = authProvider.locale
)
verifyAndSaveAuth(authData, AccountType.ANONYMOUS)
} else {
throwError(playResponse, context)
}
verifyAndSaveAuth(
authProvider.buildAnonymousAuthData().getOrThrow(),
AccountType.ANONYMOUS
)
} catch (exception: Exception) {
liveData.postValue(AuthState.Failed(exception.message.toString()))
Log.e(TAG, "Failed to generate Session", exception)
liveData.postValue(AuthState.Failed(exception.message.toString()))
}
}
}
@@ -155,98 +127,54 @@ class AuthViewModel @Inject constructor(
private fun buildSavedAuthData() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
if (authProvider.isSavedAuthDataValid()) {
liveData.postValue(AuthState.Valid)
} else {
//Generate and validate new auth
when (AccountProvider.getAccountType(context)) {
AccountType.GOOGLE -> {
val email = Preferences.getString(
context,
Constants.ACCOUNT_EMAIL_PLAIN
)
val aasToken = Preferences.getString(
context,
Constants.ACCOUNT_AAS_PLAIN
)
buildGoogleAuthData(email, aasToken)
}
try {
if (authProvider.isSavedAuthDataValid()) {
liveData.postValue(AuthState.Valid)
} else {
// Generate and validate new auth
when (AccountProvider.getAccountType(context)) {
AccountType.GOOGLE -> {
buildGoogleAuthData(
AccountProvider.getLoginEmail(context)!!,
AccountProvider.getLoginToken(context)!!.first,
AccountProvider.getLoginToken(context)!!.second
)
}
AccountType.ANONYMOUS -> {
buildAnonymousAuthData()
}
AccountType.ANONYMOUS -> {
buildAnonymousAuthData()
}
}
} catch (e: Exception) {
val error = when (e) {
is UnknownHostException -> {
context.getString(R.string.title_no_network)
}
is ConnectException -> {
context.getString(R.string.server_unreachable)
}
else -> {
context.getString(R.string.bad_request)
}
}
liveData.postValue(AuthState.Failed(error))
}
} catch (exception: Exception) {
val error = when (exception) {
is UnknownHostException -> context.getString(R.string.title_no_network)
is ConnectException -> context.getString(R.string.server_unreachable)
else -> context.getString(R.string.bad_request)
}
liveData.postValue(AuthState.Failed(error))
}
}
}
private fun verifyAndSaveAuth(authData: AuthData, type: AccountType) {
private fun verifyAndSaveAuth(authData: AuthData, accountType: AccountType) {
liveData.postValue(AuthState.Verifying)
if (authData.authToken.isNotEmpty() && authData.deviceConfigToken.isNotEmpty()) {
configAuthPref(authData, type, true)
authProvider.saveAuthData(authData)
AccountProvider.login(
context,
authData.email,
authData.aasToken.ifBlank { authData.authToken },
if (authData.aasToken.isBlank()) AuthHelper.Token.AUTH else AuthHelper.Token.AAS,
accountType
)
liveData.postValue(AuthState.SignedIn)
} else {
configAuthPref(authData, type, false)
liveData.postValue(AuthState.SignedOut)
authProvider.removeAuthData(context)
AccountProvider.logout(context)
liveData.postValue(
AuthState.Failed(context.getString(R.string.failed_to_generate_session))
)
}
}
private fun configAuthPref(authData: AuthData, type: AccountType, signedIn: Boolean) {
if (signedIn) {
//Save Auth Data
Preferences.putString(context, PREFERENCE_AUTH_DATA, gson.toJson(authData))
}
//Save Auth Type
Preferences.putString(context, Constants.ACCOUNT_TYPE, type.name)
//Save Auth Status
Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, signedIn)
}
@Throws(Exception::class)
private fun throwError(playResponse: PlayResponse, context: Context) {
when (playResponse.code) {
400 -> throw Exception(context.getString(R.string.bad_request))
403 -> throw Exception(context.getString(R.string.access_denied_using_vpn))
404 -> throw Exception(context.getString(R.string.server_unreachable))
429 -> throw Exception(context.getString(R.string.login_rate_limited))
503 -> throw Exception(context.getString(R.string.server_maintenance))
else -> {
if (playResponse.errorString.isNotBlank()) {
throw Exception(playResponse.errorString)
} else {
throw Exception(
context.getString(
R.string.failed_generating_session,
playResponse.code
)
)
}
}
}
}
}