Merge branch 'auth' into 'dev'
Add support for fetching authTokens from microG See merge request AuroraOSS/AuroraStore!414
This commit is contained in:
@@ -46,6 +46,12 @@
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_CREDENTIALS"
|
||||
android:maxSdkVersion="22" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,19 +19,35 @@
|
||||
|
||||
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.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
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.runOnUiThread
|
||||
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 +60,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 {
|
||||
runOnUiThread { resetActions() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -176,7 +204,29 @@ 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())) {
|
||||
val accounts = fetchGoogleAccounts()
|
||||
|
||||
// Do not show selection dialog if there is only one account available
|
||||
if (accounts.isNotEmpty() && accounts.size == 1) {
|
||||
requestAuthTokenForGoogle(accounts.first().name)
|
||||
return@addOnClickListener
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +244,8 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
}
|
||||
|
||||
private fun navigateToDefaultTab() {
|
||||
val defaultDestination = Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
|
||||
val defaultDestination =
|
||||
Preferences.getInteger(requireContext(), PREFERENCE_DEFAULT_SELECTED_TAB)
|
||||
val directions =
|
||||
when (requireArguments().getInt("destinationId", defaultDestination)) {
|
||||
R.id.updatesFragment -> {
|
||||
@@ -221,4 +272,34 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
|
||||
requireArguments().getString("packageName") ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchGoogleAccounts(): Array<Account> {
|
||||
val accountManager = AccountManager.get(requireContext())
|
||||
return accountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user