feat: Dialog to ask device credential #2283

This commit is contained in:
J-Jamet
2025-11-29 15:36:53 +01:00
parent 108a61905e
commit f6774b6d51
6 changed files with 95 additions and 87 deletions

View File

@@ -559,15 +559,17 @@ class GroupActivity : DatabaseLockActivity(),
}
lifecycleScope.launch {
// Initialize the parameters
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mMainCredentialViewModel.uiState.collect { uiState ->
when (uiState) {
is MainCredentialViewModel.UIState.Loading -> {}
is MainCredentialViewModel.UIState.OnMainCredentialEntered -> {
mergeDatabaseFrom(uiState.databaseUri, uiState.mainCredential)
mMainCredentialViewModel.onActionReceived()
}
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
// Noting here
mMainCredentialViewModel.onActionReceived()
}
}
}
}

View File

@@ -2,7 +2,9 @@ package com.kunzisoft.keepass.credentialprovider
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
@@ -11,8 +13,6 @@ import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment.Companion.TAG_ASK_MAIN_CREDENTIAL
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.utils.getEnumExtra
@@ -97,10 +97,9 @@ class UserVerificationHelper {
userVerificationCondition: Boolean,
dataToVerify: UserVerificationData
) {
if (userVerificationCondition) {
if (isAuthenticatorsAllowed() && userVerificationCondition) {
// Important to check the nullable database here
dataToVerify.database?.let {
if (isAuthenticatorsAllowed()) {
BiometricPrompt(
this, ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
@@ -143,23 +142,32 @@ class UserVerificationHelper {
.setConfirmationRequired(false)
.build()
)
} else {
// TODO Check fragment
var mainCredentialDialogFragment = supportFragmentManager
.findFragmentByTag(TAG_ASK_MAIN_CREDENTIAL) as? MainCredentialDialogFragment?
if (mainCredentialDialogFragment == null) {
mainCredentialDialogFragment = MainCredentialDialogFragment
.getInstance(dataToVerify.database.fileUri)
mainCredentialDialogFragment.show(
supportFragmentManager,
TAG_ASK_MAIN_CREDENTIAL
)
}
}
}
} else {
userVerificationViewModel.onUserVerificationSucceeded(dataToVerify)
}
}
fun FragmentActivity.showUserVerificationMessage(
onActionPerformed: () -> Unit
) {
AlertDialog.Builder(this)
.setTitle(getString(R.string.set_up_user_verification_passkeys_title))
.setMessage(getString(R.string.set_up_user_verification_passkeys_description))
.setPositiveButton(resources.getString(R.string.set_up_user_verification)
) { _, _ ->
startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
onActionPerformed()
}
.setNegativeButton(resources.getString(android.R.string.cancel)
) { _, _ ->
onActionPerformed()
}
.setOnDismissListener {
onActionPerformed()
}
.create()
.show()
}
}
}

View File

@@ -50,6 +50,8 @@ import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.askUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.getUserVerificationCondition
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.getUserVerifiedWithAuth
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isAuthenticatorsAllowed
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.showUserVerificationMessage
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
@@ -64,7 +66,6 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
import com.kunzisoft.keepass.view.toastError
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch
import java.util.UUID
@@ -73,7 +74,6 @@ import java.util.UUID
class PasskeyLauncherActivity : DatabaseLockActivity() {
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
private val mainCredentialViewModel: MainCredentialViewModel by viewModels()
private val userVerificationViewModel: UserVerificationViewModel by viewModels()
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
@@ -175,19 +175,6 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
}
}
}
lifecycleScope.launch {
mainCredentialViewModel.uiState.collect { uiState ->
when (uiState) {
is MainCredentialViewModel.UIState.Loading -> {}
is MainCredentialViewModel.UIState.OnMainCredentialEntered -> {
checkMainCredential(uiState.mainCredential)
}
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
userVerificationViewModel.onUserVerificationFailed()
}
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
userVerificationViewModel.uiState.collect { uiState ->
@@ -215,12 +202,17 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
super.onUnknownDatabaseRetrieved(database)
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
// When a database is opened
if (isAuthenticatorsAllowed()) {
askUserVerification(
userVerificationViewModel = userVerificationViewModel,
userVerificationCondition = intent.getUserVerificationCondition(),
dataToVerify = UserVerificationData(database)
)
} else {
showUserVerificationMessage {
userVerificationViewModel.onUserVerificationFailed()
}
}
}
override fun onDatabaseActionFinished(

View File

@@ -412,7 +412,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
warningAlertDialog = AlertDialog.Builder(activity)
.setMessage(message)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(resources.getString(android.R.string.ok)
) { _, _ ->
validate?.invoke()

View File

@@ -28,6 +28,10 @@ class MainCredentialViewModel: ViewModel() {
mUiState.value = UIState.OnMainCredentialCanceled(databaseUri, MainCredential())
}
fun onActionReceived() {
mUiState.value = UIState.Loading
}
sealed class UIState {
object Loading: UIState()
data class OnMainCredentialEntered(

View File

@@ -774,4 +774,7 @@
<string name="error_passkey_result">Unable to return the passkey</string>
<string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string>
<string name="user_verification_required">User verification required</string>
<string name="set_up_user_verification">Set up user verification</string>
<string name="set_up_user_verification_passkeys_title">Set up user verification to use passkeys</string>
<string name="set_up_user_verification_passkeys_description">To use passkeys, make sure you have a device screen lock set up</string>
</resources>