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.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" />
<uses-feature <uses-feature
android:name="android.software.leanback" android:name="android.software.leanback"
android:required="false" /> android:required="false" />

View File

@@ -1,7 +1,13 @@
package com.aurora.store.data.work package com.aurora.store.data.work
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.util.Log import android.util.Log
import androidx.core.os.bundleOf
import androidx.hilt.work.HiltWorker import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters 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.model.AccountType
import com.aurora.store.data.providers.AccountProvider import com.aurora.store.data.providers.AccountProvider
import com.aurora.store.data.providers.AuthProvider 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.Assisted
import dagger.assisted.AssistedInject 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 * Worker to refresh [AuthData] in background
@@ -26,6 +40,8 @@ open class AuthWorker @AssistedInject constructor(
private val TAG = AuthWorker::class.java.simpleName private val TAG = AuthWorker::class.java.simpleName
private val authToken: MutableSharedFlow<String?> = MutableSharedFlow(extraBufferCapacity = 1)
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (!AccountProvider.isLoggedIn(appContext)) { if (!AccountProvider.isLoggedIn(appContext)) {
Log.i(TAG, "User has logged out!") Log.i(TAG, "User has logged out!")
@@ -42,14 +58,48 @@ open class AuthWorker @AssistedInject constructor(
val accountType = AccountProvider.getAccountType(appContext) val accountType = AccountProvider.getAccountType(appContext)
val authData = when (accountType) { val authData = when (accountType) {
AccountType.GOOGLE -> { AccountType.GOOGLE -> {
authProvider.buildGoogleAuthData( val email = AccountProvider.getLoginEmail(appContext)!!
AccountProvider.getLoginEmail(appContext)!!, val token = AccountProvider.getLoginToken(appContext)!!.first
AccountProvider.getLoginToken(appContext)!!.first, val tokenType = AccountProvider.getLoginToken(appContext)!!.second
AccountProvider.getLoginToken(appContext)!!.second
).getOrThrow() 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) require(verifyAndSaveAuth(authData, accountType) != null)

View File

@@ -37,6 +37,11 @@ object CertUtil {
private val TAG = "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 { fun isFDroidApp(context: Context, packageName: String): Boolean {
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName) return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
} }

View File

@@ -46,6 +46,10 @@ object PackageUtil {
private const val TAG = "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> { fun getAllValidPackages(context: Context): List<PackageInfo> {
val sharedLibs = context.packageManager.systemSharedLibraryNames ?: emptyArray() val sharedLibs = context.packageManager.systemSharedLibraryNames ?: emptyArray()
return context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA) 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 { fun isInstalled(context: Context, packageName: String): Boolean {
return try { return try {
getPackageInfo(context, packageName, PackageManager.GET_META_DATA) getPackageInfo(context, packageName, PackageManager.GET_META_DATA)

View File

@@ -19,19 +19,34 @@
package com.aurora.store.view.ui.splash package com.aurora.store.view.ui.splash
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Intent import android.content.Intent
import android.net.UrlQuerySanitizer import android.net.UrlQuerySanitizer
import android.os.Bundle 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 android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.aurora.extensions.hide import com.aurora.extensions.hide
import com.aurora.extensions.isMAndAbove
import com.aurora.extensions.show import com.aurora.extensions.show
import com.aurora.gplayapi.helpers.AuthHelper
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.model.AuthState import com.aurora.store.data.model.AuthState
import com.aurora.store.databinding.FragmentSplashBinding 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
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_INTRO import com.aurora.store.util.Preferences.PREFERENCE_INTRO
@@ -44,8 +59,20 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class SplashFragment : BaseFragment<FragmentSplashBinding>() { class SplashFragment : BaseFragment<FragmentSplashBinding>() {
private val TAG = SplashFragment::class.java.simpleName
private val viewModel: AuthViewModel by activityViewModels() 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -176,7 +203,22 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
binding.btnGoogle.addOnClickListener { binding.btnGoogle.addOnClickListener {
if (viewModel.authState.value != AuthState.Fetching) { if (viewModel.authState.value != AuthState.Fetching) {
binding.btnGoogle.updateProgress(true) 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") ?: "" 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)
}
}
} }