Add support for fetching authTokens from microG
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
committed by
Rahul Patel
parent
279be8af68
commit
b4d89d0658
@@ -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" />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user