Add support for fetching authTokens from microG

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-09-28 10:59:17 +01:00
committed by Rahul Patel
parent 279be8af68
commit b4d89d0658
5 changed files with 146 additions and 7 deletions

View File

@@ -46,6 +46,10 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />

View File

@@ -1,7 +1,13 @@
package com.aurora.store.data.work
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import androidx.core.os.bundleOf
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
@@ -10,8 +16,16 @@ import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.data.model.AccountType
import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.runBlocking
/**
* Worker to refresh [AuthData] in background
@@ -26,6 +40,8 @@ open class AuthWorker @AssistedInject constructor(
private val TAG = AuthWorker::class.java.simpleName
private val authToken: MutableSharedFlow<String?> = MutableSharedFlow(extraBufferCapacity = 1)
override suspend fun doWork(): Result {
if (!AccountProvider.isLoggedIn(appContext)) {
Log.i(TAG, "User has logged out!")
@@ -42,14 +58,48 @@ open class AuthWorker @AssistedInject constructor(
val accountType = AccountProvider.getAccountType(appContext)
val authData = when (accountType) {
AccountType.GOOGLE -> {
authProvider.buildGoogleAuthData(
AccountProvider.getLoginEmail(appContext)!!,
AccountProvider.getLoginToken(appContext)!!.first,
AccountProvider.getLoginToken(appContext)!!.second
).getOrThrow()
val email = AccountProvider.getLoginEmail(appContext)!!
val token = AccountProvider.getLoginToken(appContext)!!.first
val tokenType = AccountProvider.getLoginToken(appContext)!!.second
if (tokenType == AuthHelper.Token.AAS) {
Log.i(TAG, "Refreshing AuthData for personal account")
authProvider.buildGoogleAuthData(email, token, tokenType).getOrThrow()
} else {
/*
* We are working with AuthToken here. The only scenario when we will have
* AuthToken and Google login is when the user used microG to login into
* Aurora Store. In this case, we use system's AccountManager to request credentials.
*/
Log.i(TAG, "Refreshing AuthData for personal account using AccountManager")
AccountManager.get(appContext)
.getAuthToken(
Account(email, GOOGLE_ACCOUNT_TYPE),
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
bundleOf(
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
"overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
),
true,
{
authToken.tryEmit(it.result.getString(AccountManager.KEY_AUTHTOKEN))
},
Handler(Looper.getMainLooper())
)
runBlocking {
authProvider.buildGoogleAuthData(
email,
authToken.take(1).first()!!,
tokenType
).getOrThrow()
}
}
}
AccountType.ANONYMOUS -> authProvider.buildAnonymousAuthData().getOrThrow()
AccountType.ANONYMOUS -> {
Log.i(TAG, "Refreshing AuthData for anonymous account")
authProvider.buildAnonymousAuthData().getOrThrow()
}
}
require(verifyAndSaveAuth(authData, accountType) != null)

View File

@@ -37,6 +37,11 @@ object CertUtil {
private val TAG = "CertUtil"
const val GOOGLE_ACCOUNT_TYPE = "com.google"
const val GOOGLE_PLAY_AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/googleplay"
const val GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"
const val GOOGLE_PLAY_CERT = "MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK"
fun isFDroidApp(context: Context, packageName: String): Boolean {
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
}

View File

@@ -46,6 +46,10 @@ object PackageUtil {
private const val TAG = "PackageUtil"
private const val PACKAGE_NAME_MICRO_G = "com.google.android.gms"
private const val VERSION_CODE_MICRO_G = 240913402
private const val VERSION_CODE_MICRO_G_HUAWEI = 240913007
fun getAllValidPackages(context: Context): List<PackageInfo> {
val sharedLibs = context.packageManager.systemSharedLibraryNames ?: emptyArray()
return context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
@@ -57,6 +61,14 @@ object PackageUtil {
}
}
fun hasSupportedMicroG(context: Context): Boolean {
return if (isHuawei) {
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G_HUAWEI)
} else {
isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G)
}
}
fun isInstalled(context: Context, packageName: String): Boolean {
return try {
getPackageInfo(context, packageName, PackageManager.GET_META_DATA)

View File

@@ -19,19 +19,34 @@
package com.aurora.store.view.ui.splash
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Intent
import android.net.UrlQuerySanitizer
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log
import android.view.View
import androidx.core.view.isVisible
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.extensions.hide
import com.aurora.extensions.isMAndAbove
import com.aurora.extensions.show
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.R
import com.aurora.store.data.model.AuthState
import com.aurora.store.databinding.FragmentSplashBinding
import com.aurora.store.util.CertUtil.GOOGLE_ACCOUNT_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_AUTH_TOKEN_TYPE
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_CERT
import com.aurora.store.util.CertUtil.GOOGLE_PLAY_PACKAGE_NAME
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_INTRO
@@ -44,8 +59,20 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint
class SplashFragment : BaseFragment<FragmentSplashBinding>() {
private val TAG = SplashFragment::class.java.simpleName
private val viewModel: AuthViewModel by activityViewModels()
private val startForAccount =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val accountName = it.data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
if (!accountName.isNullOrBlank()) {
requestAuthTokenForGoogle(accountName)
} else {
findNavController().navigate(R.id.googleFragment)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -176,7 +203,22 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
binding.btnGoogle.addOnClickListener {
if (viewModel.authState.value != AuthState.Fetching) {
binding.btnGoogle.updateProgress(true)
findNavController().navigate(R.id.googleFragment)
if (isMAndAbove && PackageUtil.hasSupportedMicroG(requireContext())) {
Log.i(TAG, "Found supported microG, trying to request credentials")
val accountIntent = AccountManager.newChooseAccountIntent(
null,
null,
arrayOf(GOOGLE_ACCOUNT_TYPE),
null,
null,
null,
null
)
startForAccount.launch(accountIntent)
} else {
findNavController().navigate(R.id.googleFragment)
}
}
}
}
@@ -221,4 +263,30 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
requireArguments().getString("packageName") ?: ""
}
}
private fun requestAuthTokenForGoogle(accountName: String) {
try {
AccountManager.get(requireContext())
.getAuthToken(
Account(accountName, GOOGLE_ACCOUNT_TYPE),
GOOGLE_PLAY_AUTH_TOKEN_TYPE,
bundleOf(
"overridePackage" to GOOGLE_PLAY_PACKAGE_NAME,
"overrideCertificate" to Base64.decode(GOOGLE_PLAY_CERT, Base64.DEFAULT)
),
requireActivity(),
{
viewModel.buildGoogleAuthData(
accountName,
it.result.getString(AccountManager.KEY_AUTHTOKEN)!!,
AuthHelper.Token.AUTH
)
},
Handler(Looper.getMainLooper())
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to get authToken for Google login")
findNavController().navigate(R.id.googleFragment)
}
}
}