Skip to content

Commit

Permalink
finish Save PasswordCredential via Credential Manager Api
Browse files Browse the repository at this point in the history
  • Loading branch information
Nailik committed Oct 19, 2024
1 parent cab85a9 commit c74bc86
Show file tree
Hide file tree
Showing 18 changed files with 324 additions and 91 deletions.
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@
<data android:pathPattern="/redirect-connector.*" />
</intent-filter>
<intent-filter>
<action android:name="com.x8bit.bitwarden.data.autofill.password.ACTION_CREATE_PASSWORD" />
<action android:name="com.x8bit.bitwarden.data.autofill.password.ACTION_GET_PASSWORD" />
<action android:name="com.x8bit.bitwarden.data.autofill.password.ACTION_UNLOCK_ACCOUNT" />

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>

<action android:name="com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY" />
<action android:name="com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY" />
<action android:name="com.x8bit.bitwarden.fido2.ACTION_UNLOCK_ACCOUNT" />
Expand Down
10 changes: 0 additions & 10 deletions app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNu
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.password.util.getPasswordAssertionRequestOrNull
import com.x8bit.bitwarden.data.autofill.password.util.getPasswordCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.password.util.getPasswordGetCredentialsRequestOrNull
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
Expand Down Expand Up @@ -246,7 +245,6 @@ class MainViewModel @Inject constructor(
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
val passwordCredentialRequestData = intent.getPasswordCredentialRequestOrNull()
val passwordCredentialAssertionRequest = intent.getPasswordAssertionRequestOrNull()
val passwordGetCredentialsRequest = intent.getPasswordGetCredentialsRequestOrNull()
when {
passwordlessRequestData != null -> {
Expand Down Expand Up @@ -334,7 +332,6 @@ class MainViewModel @Inject constructor(
// Set the user's verification status when a new FIDO 2 request is received to force
// explicit verification if the user's vault is unlocked when the request is
// received.
fido2CredentialManager.isUserVerified = false
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.PasswordSave(
passwordCredentialRequest = passwordCredentialRequestData,
Expand All @@ -348,13 +345,6 @@ class MainViewModel @Inject constructor(
}
}

passwordCredentialAssertionRequest != null -> {
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.PasswordAssertion(
passwordAssertionRequest = passwordCredentialAssertionRequest,
)
}

passwordGetCredentialsRequest != null -> {
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.PasswordGetCredentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import kotlinx.parcelize.Parcelize
* Represents raw data from the a user deciding to create a password in their vault via the
* credential manager framework.
*
* @property userId The user under which the passkey should be saved.
* @property userId The user under which the password should be saved.
* @property userName containing the id of the request representing the userName for the login.
* @property password containing the Password request.
* @property callingAppInfo Information about the application that initiated the request.
*/
@Parcelize
data class PasswordCredentialRequest(
val userId: String,
val userName: String,
val password: String,
val packageName: String,
val signingInfo: SigningInfo,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.x8bit.bitwarden.data.autofill.password.model

/**
* Represents the result of a FIDO 2 Get Credentials request.
*/
sealed class PasswordGetCredentialsResult {
/**
* Indicates credentials were successfully queried.
*/
data class Success(
val data: String
) : PasswordGetCredentialsResult()

/**
* Indicates an error was encountered when querying for matching credentials.
*/
data object Error : PasswordGetCredentialsResult()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.x8bit.bitwarden.data.autofill.password.model

/**
* Models the data returned from creating a FIDO 2 credential.
*/
sealed class PasswordRegisterCredentialResult {

/**
* Indicates the credential has been successfully registered.
*/
data object Success : PasswordRegisterCredentialResult()

/**
* Indicates there was an error and the credential was not registered.
*/
data object Error : PasswordRegisterCredentialResult()

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ 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.fido2.processor.UNLOCK_ACCOUNT_INTENT
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
Expand All @@ -40,6 +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"

/**
* The default implementation of [PasswordProviderProcessor]. Its purpose is to handle Password related
Expand Down Expand Up @@ -91,27 +91,14 @@ class PasswordProviderProcessorImpl(
): BeginCreateCredentialResponse? {
return when (request) {
is BeginCreatePasswordCredentialRequest -> {
handleCreatePassword(request)
handleCreatePassword()
}

else -> null
}
}

private fun handleCreatePassword(
request: BeginCreatePasswordCredentialRequest,
): BeginCreateCredentialResponse? {
println(request)
println(
request
.candidateQueryData
)
val requestJson = request
.candidateQueryData
.getString("androidx.credentials.BUNDLE_KEY_REQUEST_JSON")

if (requestJson.isNullOrEmpty()) return null

private fun handleCreatePassword(): BeginCreateCredentialResponse? {
val userState = authRepository.userStateFlow.value ?: return null

return BeginCreateCredentialResponse.Builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@ package com.x8bit.bitwarden.data.autofill.password.util
import android.content.Intent
import android.os.Build
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.GetPasswordOption
import androidx.credentials.provider.BeginGetPasswordOption
import androidx.credentials.provider.PendingIntentHandler
import com.x8bit.bitwarden.data.autofill.password.model.PasswordCredentialAssertionRequest
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_CREDENTIAL_ID
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID

/**
* Checks if this [Intent] contains a [PasswordCredentialRequest] related to an ongoing FIDO 2
* Checks if this [Intent] contains a [PasswordCredentialRequest] related to an ongoing Password
* credential creation process.
*/
fun Intent.getPasswordCredentialRequestOrNull(): PasswordCredentialRequest? {
Expand All @@ -35,6 +31,7 @@ fun Intent.getPasswordCredentialRequestOrNull(): PasswordCredentialRequest? {

return PasswordCredentialRequest(
userId = userId,
userName = createPublicKeyRequest.id,
password = createPublicKeyRequest.password,
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
Expand All @@ -43,44 +40,7 @@ fun Intent.getPasswordCredentialRequestOrNull(): PasswordCredentialRequest? {
}

/**
* Checks if this [Intent] contains a [PasswordCredentialAssertionRequest] related to an ongoing Password
* credential authentication process.
*/
fun Intent.getPasswordAssertionRequestOrNull(): PasswordCredentialAssertionRequest? {
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null

val systemRequest = PendingIntentHandler
.retrieveProviderGetCredentialRequest(this)
?: return null

val option: GetPasswordOption = systemRequest
.credentialOptions
.firstNotNullOfOrNull { it as? GetPasswordOption }
?: return null

val credentialId = getStringExtra(EXTRA_KEY_CREDENTIAL_ID)
?: return null

val cipherId = getStringExtra(EXTRA_KEY_CIPHER_ID)
?: return null

val userId: String = getStringExtra(EXTRA_KEY_USER_ID)
?: return null

return PasswordCredentialAssertionRequest(
userId = userId,
cipherId = cipherId,
credentialId = credentialId,
allowedUserIds = option.allowedUserIds,
isAutoSelectAllowed = option.isAutoSelectAllowed,
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
origin = systemRequest.callingAppInfo.origin,
)
}

/**
* Checks if this [Intent] contains a [PasswordGetCredentialsRequest] related to an ongoing FIDO 2
* Checks if this [Intent] contains a [PasswordGetCredentialsRequest] related to an ongoing Password
* credential lookup process.
*/
fun Intent.getPasswordGetCredentialsRequestOrNull(): PasswordGetCredentialsRequest? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,6 @@ sealed class SpecialCircumstance : Parcelable {
val passwordCredentialRequest: PasswordCredentialRequest,
) : SpecialCircumstance()

/**
* The app was launched via the credential manager framework in order to authenticate a Password
* credential saved to the user's vault.
*/
@Parcelize
data class PasswordAssertion(
val passwordCredentialAssertionRequest: PasswordCredentialAssertionRequest,
) : SpecialCircumstance()

/**
* The app was launched via the credential manager framework request to retrieve passwords
* associated with the requesting entity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
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.platform.manager.model.SpecialCircumstance
import com.x8bit.bitwarden.ui.vault.model.TotpData

Expand Down Expand Up @@ -53,6 +55,25 @@ fun SpecialCircumstance.toFido2GetCredentialsRequestOrNull(): Fido2GetCredential
else -> null
}


/**
* Returns [PasswordCredentialRequest] when contained in the given [SpecialCircumstance].
*/
fun SpecialCircumstance.toPasswordCredentialsRequestOrNull(): PasswordCredentialRequest? =
when (this) {
is SpecialCircumstance.PasswordSave -> this.passwordCredentialRequest
else -> null
}

/**
* Returns [PasswordGetCredentialsRequest] when contained in the given [SpecialCircumstance].
*/
fun SpecialCircumstance.toPasswordGetCredentialsRequestOrNull(): PasswordGetCredentialsRequest? =
when (this) {
is SpecialCircumstance.PasswordGetCredentials -> this.passwordGetCredentialsRequest
else -> null
}

/**
* Returns the [TotpData] when contained in the given [SpecialCircumstance].
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.x8bit.bitwarden.ui.autofill.password.manager

import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsResult
import com.x8bit.bitwarden.data.autofill.password.model.PasswordRegisterCredentialResult

/**
* A manager for completing the Password creation process.
*/
interface PasswordCompletionManager {

/**
* Completes the Password registration process with the provided [result].
*/
fun completePasswordRegistration(result: PasswordRegisterCredentialResult)

/**
* Complete the Password "Get credentials" process with the provided [result].
*/
fun completePasswordGetCredentialRequest(result: PasswordGetCredentialsResult)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.x8bit.bitwarden.ui.autofill.password.manager

import android.app.Activity
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.credentials.CreatePasswordResponse
import androidx.credentials.exceptions.CreateCredentialUnknownException
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
* UPSIDE_DOWN_CAKE (34) or above.
*/
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class PasswordCompletionManagerImpl(
private val activity: Activity,
private val intentManager: IntentManager,
) : PasswordCompletionManager {

override fun completePasswordRegistration(result: PasswordRegisterCredentialResult) {
activity.also {
val intent = Intent()
when (result) {
is PasswordRegisterCredentialResult.Error -> {
PendingIntentHandler
.setCreateCredentialException(
intent = intent,
exception = CreateCredentialUnknownException(),
)
}

is PasswordRegisterCredentialResult.Success -> {
PendingIntentHandler
.setCreateCredentialResponse(
intent = intent,
response = CreatePasswordResponse(),
)
}
}
it.setResult(Activity.RESULT_OK, intent)
it.finish()
}
}

override fun completePasswordGetCredentialRequest(result: PasswordGetCredentialsResult) {
TODO()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.x8bit.bitwarden.ui.autofill.password.manager

import com.x8bit.bitwarden.data.autofill.password.model.PasswordGetCredentialsResult
import com.x8bit.bitwarden.data.autofill.password.model.PasswordRegisterCredentialResult

/**
* A no-op implementation of [PasswordCompletionManagerImpl] provided when the build version is below
* UPSIDE_DOWN_CAKE (34). These versions do not support [androidx.credentials.CredentialProvider].
*/
object PasswordCompletionManagerUnsupportedApiImpl : PasswordCompletionManager {
override fun completePasswordRegistration(result: PasswordRegisterCredentialResult) = Unit

override fun completePasswordGetCredentialRequest(result: PasswordGetCredentialsResult) = Unit
}
Loading

0 comments on commit c74bc86

Please sign in to comment.