From 4aa9f5ac5f0944ba96d2507865a895c6d0a7413d Mon Sep 17 00:00:00 2001 From: Nailik <13292441+Nailik@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:01:36 +0200 Subject: [PATCH] GetPasswordCredentialRequest handling --- .../password/di/PasswordProviderModule.kt | 6 +-- .../model/PasswordGetCredentialsRequest.kt | 1 + .../model/PasswordGetCredentialsResult.kt | 9 +++- .../PasswordProviderProcessorImpl.kt | 42 ++++++++----------- .../password/util/PasswordIntentUtils.kt | 20 ++++++--- .../manager/PasswordCompletionManagerImpl.kt | 39 +++++++++++++++-- .../platform/manager/intent/IntentManager.kt | 2 +- .../manager/intent/IntentManagerImpl.kt | 11 ++++- .../itemlisting/VaultItemListingScreen.kt | 7 ++++ .../itemlisting/VaultItemListingViewModel.kt | 41 +++++++++++++++++- 10 files changed, 135 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/di/PasswordProviderModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/di/PasswordProviderModule.kt index 825da00ad2c..711861b9110 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/di/PasswordProviderModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/di/PasswordProviderModule.kt @@ -6,8 +6,8 @@ import androidx.annotation.RequiresApi import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.autofill.password.processor.PasswordProviderProcessor import com.x8bit.bitwarden.data.autofill.password.processor.PasswordProviderProcessorImpl +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager -import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import dagger.Module import dagger.Provides @@ -30,15 +30,15 @@ object PasswordProviderModule { fun providePasswordCredentialProviderProcessor( @ApplicationContext context: Context, authRepository: AuthRepository, - vaultRepository: VaultRepository, dispatcherManager: DispatcherManager, + autofillCipherProvider: AutofillCipherProvider, intentManager: IntentManager, clock: Clock, ): PasswordProviderProcessor = PasswordProviderProcessorImpl( context, authRepository, - vaultRepository, + autofillCipherProvider, intentManager, clock, dispatcherManager, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsRequest.kt index b8b96612fdb..52c813a8034 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsRequest.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsRequest.kt @@ -15,6 +15,7 @@ data class PasswordGetCredentialsRequest( val candidateQueryData: Bundle, val id: String, val userId: String, + val cipherId: String, val allowedUserIds: Set, val packageName: String, val signingInfo: SigningInfo, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsResult.kt index 441c4bce082..576a6d4a8c5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsResult.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/model/PasswordGetCredentialsResult.kt @@ -1,14 +1,19 @@ package com.x8bit.bitwarden.data.autofill.password.model +import androidx.credentials.provider.BeginGetPasswordOption +import com.bitwarden.vault.LoginView + /** - * Represents the result of a FIDO 2 Get Credentials request. + * Represents the result of a Password Get Credentials request. */ sealed class PasswordGetCredentialsResult { /** * Indicates credentials were successfully queried. */ data class Success( - val data: String + val userId: String, + val option: BeginGetPasswordOption, + val credential: LoginView, ) : PasswordGetCredentialsResult() /** diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/processor/PasswordProviderProcessorImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/processor/PasswordProviderProcessorImpl.kt index f73e05eca58..cf60f1e9267 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/processor/PasswordProviderProcessorImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/processor/PasswordProviderProcessorImpl.kt @@ -24,13 +24,13 @@ import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.CredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.ProviderClearCredentialStateRequest -import com.bitwarden.core.Uuid -import com.bitwarden.fido.Fido2CredentialAutofillView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.UserState +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager -import com.x8bit.bitwarden.data.vault.repository.VaultRepository +import com.x8bit.bitwarden.ui.platform.base.util.toAndroidAppUriString import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -39,7 +39,7 @@ import java.util.concurrent.atomic.AtomicInteger private const val CREATE_PASSWORD_INTENT = "com.x8bit.bitwarden.data.autofill.password.ACTION_CREATE_PASSWORD" const val GET_PASSWORD_INTENT = "com.x8bit.bitwarden.data.autofill.password.ACTION_GET_PASSWORD" -const val UNLOCK_ACCOUNT_INTENT= "com.x8bit.bitwarden.data.autofill.password.ACTION_UNLOCK_ACCOUNT" +const val UNLOCK_ACCOUNT_INTENT = "com.x8bit.bitwarden.data.autofill.password.ACTION_UNLOCK_ACCOUNT" /** * The default implementation of [PasswordProviderProcessor]. Its purpose is to handle Password related @@ -49,7 +49,7 @@ const val UNLOCK_ACCOUNT_INTENT= "com.x8bit.bitwarden.data.autofill.password.ACT class PasswordProviderProcessorImpl( private val context: Context, private val authRepository: AuthRepository, - private val vaultRepository: VaultRepository, + private val autofillCipherProvider: AutofillCipherProvider, private val intentManager: IntentManager, private val clock: Clock, dispatcherManager: DispatcherManager, @@ -187,7 +187,7 @@ class PasswordProviderProcessorImpl( } @Throws(GetCredentialUnsupportedException::class) - private fun getMatchingPasswordCredentialEntries( + private suspend fun getMatchingPasswordCredentialEntries( userId: String, request: BeginGetCredentialRequest, ): List = @@ -198,7 +198,9 @@ class PasswordProviderProcessorImpl( if (option.allowedUserIds.isEmpty() || option.allowedUserIds.contains(userId)) { buildCredentialEntries( userId = userId, - callingPackage = request.callingAppInfo?.packageName, + matchUri = request.callingAppInfo?.origin + ?: request.callingAppInfo?.packageName + ?.toAndroidAppUriString(), option = option, ) } else { @@ -210,43 +212,35 @@ class PasswordProviderProcessorImpl( } } - private fun buildCredentialEntries( + private suspend fun buildCredentialEntries( userId: String, - callingPackage: String?, + matchUri: String?, option: BeginGetPasswordOption, ): List { - //TODO get data and map correctly - return listOf( - Fido2CredentialAutofillView( - credentialId = ByteArray(0), - cipherId = Uuid(), - rpId = "", - userNameForUi = "userNameForUi", - userHandle = ByteArray(0) - ) + return autofillCipherProvider.getLoginAutofillCiphers( + uri = matchUri ?: return emptyList(), ).toCredentialEntries( userId = userId, option = option, ) } - - private fun List.toCredentialEntries( + private fun List.toCredentialEntries( userId: String, option: BeginGetPasswordOption, ): List = this - .map { + .mapNotNull { PasswordCredentialEntry .Builder( context = context, - username = it.userNameForUi ?: context.getString(R.string.no_username), + username = it.username, pendingIntent = intentManager .createPasswordGetCredentialPendingIntent( action = GET_PASSWORD_INTENT, + id = option.id, userId = userId, - credentialId = "", - cipherId = it.cipherId, + cipherId = it.cipherId ?: return@mapNotNull null, requestCode = requestCode.getAndIncrement(), ), beginGetPasswordOption = option, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/util/PasswordIntentUtils.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/util/PasswordIntentUtils.kt index bfe987dc7fe..fc8eae75ae8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/util/PasswordIntentUtils.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/password/util/PasswordIntentUtils.kt @@ -3,11 +3,13 @@ package com.x8bit.bitwarden.data.autofill.password.util import android.content.Intent import android.os.Build import androidx.credentials.CreatePasswordRequest -import androidx.credentials.provider.BeginGetPasswordOption +import androidx.credentials.GetPasswordOption import androidx.credentials.provider.PendingIntentHandler import com.x8bit.bitwarden.data.autofill.password.model.PasswordCredentialRequest import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsRequest import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow +import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CIPHER_ID +import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_PASSWORD_CREDENTIAL_ID import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID /** @@ -47,25 +49,31 @@ fun Intent.getPasswordGetCredentialsRequestOrNull(): PasswordGetCredentialsReque if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null val systemRequest = PendingIntentHandler - .retrieveBeginGetCredentialRequest(this) + .retrieveProviderGetCredentialRequest(this) ?: return null - val option: BeginGetPasswordOption = systemRequest - .beginGetCredentialOptions - .firstNotNullOfOrNull { it as? BeginGetPasswordOption } + val option: GetPasswordOption = systemRequest + .credentialOptions + .firstNotNullOfOrNull { it as? GetPasswordOption } ?: return null val callingAppInfo = systemRequest .callingAppInfo + + val cipherId = getStringExtra(EXTRA_KEY_CIPHER_ID) ?: return null val userId: String = getStringExtra(EXTRA_KEY_USER_ID) ?: return null + val id: String = getStringExtra(EXTRA_KEY_PASSWORD_CREDENTIAL_ID) + ?: return null + return PasswordGetCredentialsRequest( candidateQueryData = option.candidateQueryData, - id = option.id, + id = id, userId = userId, + cipherId = cipherId, allowedUserIds = option.allowedUserIds, packageName = callingAppInfo.packageName, signingInfo = callingAppInfo.signingInfo, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/password/manager/PasswordCompletionManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/password/manager/PasswordCompletionManagerImpl.kt index 153558e1d4d..28dc58434cf 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/autofill/password/manager/PasswordCompletionManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/autofill/password/manager/PasswordCompletionManagerImpl.kt @@ -5,11 +5,13 @@ import android.content.Intent import android.os.Build import androidx.annotation.RequiresApi import androidx.credentials.CreatePasswordResponse +import androidx.credentials.GetCredentialResponse +import androidx.credentials.PasswordCredential import androidx.credentials.exceptions.CreateCredentialUnknownException +import androidx.credentials.exceptions.GetCredentialUnknownException import androidx.credentials.provider.PendingIntentHandler import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsResult import com.x8bit.bitwarden.data.autofill.password.model.PasswordRegisterCredentialResult -import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager /** * Primary implementation of [PasswordCompletionManager] when the build version is @@ -18,7 +20,6 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) class PasswordCompletionManagerImpl( private val activity: Activity, - private val intentManager: IntentManager, ) : PasswordCompletionManager { override fun completePasswordRegistration(result: PasswordRegisterCredentialResult) { @@ -47,6 +48,38 @@ class PasswordCompletionManagerImpl( } override fun completePasswordGetCredentialRequest(result: PasswordGetCredentialsResult) { - TODO() + val resultIntent = Intent() + when (result) { + is PasswordGetCredentialsResult.Success -> { + val userName = result.credential.username + val password = result.credential.password + + if (userName != null && password != null) { + PendingIntentHandler + .setGetCredentialResponse( + resultIntent, + response = GetCredentialResponse( + credential = PasswordCredential( + id = userName, + password = password, + ), + ), + ) + } else { + PendingIntentHandler.setGetCredentialException( + resultIntent, + GetCredentialUnknownException(), + ) + } + } + PasswordGetCredentialsResult.Error -> { + PendingIntentHandler.setGetCredentialException( + resultIntent, + GetCredentialUnknownException(), + ) + } } + activity.setResult(Activity.RESULT_OK, resultIntent) + activity.finish() +} } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt index 9bf19e45937..1263bdb60b4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt @@ -151,8 +151,8 @@ interface IntentManager { */ fun createPasswordGetCredentialPendingIntent( action: String, + id: String, userId: String, - credentialId: String, cipherId: String, requestCode: Int, ): PendingIntent diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt index 1124cdb13cf..77213f784e6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManagerImpl.kt @@ -69,6 +69,13 @@ const val EXTRA_KEY_CREDENTIAL_ID: String = "credential_id" */ const val EXTRA_KEY_CIPHER_ID: String = "cipher_id" +/** + * Key for the credential id included in Password provider "get entries". + * + * @see IntentManager.createPasswordGetCredentialPendingIntent + */ +const val EXTRA_KEY_PASSWORD_CREDENTIAL_ID: String = "password_credential_id" + /** * The default implementation of the [IntentManager] for simplifying the handling of Android * Intents within a given context. @@ -313,15 +320,15 @@ class IntentManagerImpl( override fun createPasswordGetCredentialPendingIntent( action: String, + id: String, userId: String, - credentialId: String, cipherId: String, requestCode: Int ): PendingIntent { val intent = Intent(action) .setPackage(context.packageName) .putExtra(EXTRA_KEY_USER_ID, userId) - .putExtra(EXTRA_KEY_CREDENTIAL_ID, credentialId) + .putExtra(EXTRA_KEY_PASSWORD_CREDENTIAL_ID, id) .putExtra(EXTRA_KEY_CIPHER_ID, cipherId) return PendingIntent.getActivity( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index 3285eb877a2..769206f6355 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -23,6 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.autofill.fido2.manager.Fido2CompletionManager +import com.x8bit.bitwarden.ui.autofill.password.manager.PasswordCompletionManager import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountActionItem import com.x8bit.bitwarden.ui.platform.components.account.BitwardenAccountSwitcher @@ -49,6 +50,7 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalBiometricsManager import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager import com.x8bit.bitwarden.ui.platform.composition.LocalFido2CompletionManager import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager +import com.x8bit.bitwarden.ui.platform.composition.LocalPasswordCompletionManager import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.PinInputDialog import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager @@ -79,6 +81,7 @@ fun VaultItemListingScreen( intentManager: IntentManager = LocalIntentManager.current, exitManager: ExitManager = LocalExitManager.current, fido2CompletionManager: Fido2CompletionManager = LocalFido2CompletionManager.current, + passwordCompletionManager: PasswordCompletionManager = LocalPasswordCompletionManager.current, biometricsManager: BiometricsManager = LocalBiometricsManager.current, viewModel: VaultItemListingViewModel = hiltViewModel(), ) { @@ -173,6 +176,10 @@ fun VaultItemListingScreen( fido2CompletionManager.completeFido2GetCredentialRequest(event.result) } + is VaultItemListingEvent.CompletePasswordGetCredentialsRequest -> { + passwordCompletionManager.completePasswordGetCredentialRequest(event.result) + } + VaultItemListingEvent.ExitApp -> exitManager.exitApplication() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 5230f874e4d..c6a0ed6f255 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -25,6 +25,8 @@ import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.autofill.password.model.PasswordCredentialRequest import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsRequest +import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsResult +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager @@ -36,8 +38,8 @@ import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrN import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull import com.x8bit.bitwarden.data.platform.manager.util.toFido2GetCredentialsRequestOrNull import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull -import com.x8bit.bitwarden.data.platform.manager.util.toPasswordGetCredentialsRequestOrNull import com.x8bit.bitwarden.data.platform.manager.util.toPasswordCredentialsRequestOrNull +import com.x8bit.bitwarden.data.platform.manager.util.toPasswordGetCredentialsRequestOrNull import com.x8bit.bitwarden.data.platform.manager.util.toTotpDataOrNull import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository @@ -1317,8 +1319,33 @@ class VaultItemListingViewModel @Inject constructor( ), ), ) + } ?: state.passwordGetCredentialRequest + ?.let { passwordGetCredentialRequest -> + handlePasswordGetCredentialRequest( + vaultData = vaultData, + request = passwordGetCredentialRequest, + ) } - ?: mutableStateFlow.update { it.copy(isRefreshing = false) } + ?: mutableStateFlow.update { it.copy(isRefreshing = false) } + } + + private fun handlePasswordGetCredentialRequest( + vaultData: DataState.Loaded, + request: PasswordGetCredentialsRequest + ) { + + sendEvent(VaultItemListingEvent.CompletePasswordGetCredentialsRequest( + vaultData.data.cipherViewList + .firstOrNull { it.id == request.cipherId } + ?.let { + PasswordGetCredentialsResult.Success( + userId = request.userId, + option = request.option, + credential = it.login ?: return@let null, + ) + } ?: PasswordGetCredentialsResult.Error + ) + ) } private fun vaultLoadingReceive() { @@ -2286,6 +2313,16 @@ sealed class VaultItemListingEvent { data class CompleteFido2GetCredentialsRequest( val result: Fido2GetCredentialsResult, ) : BackgroundEvent, VaultItemListingEvent() + + /** + * Password credential lookup result has been received and the process is ready to be completed. + * + * @property result The result of querying for matching Password credentials. + */ + data class CompletePasswordGetCredentialsRequest( + val result: PasswordGetCredentialsResult, + ) : BackgroundEvent, VaultItemListingEvent() + } /**