diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 69a9b8e3b..483a0f162 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -559,15 +559,17 @@ class GroupActivity : DatabaseLockActivity(), } lifecycleScope.launch { - // Initialize the parameters - mMainCredentialViewModel.uiState.collect { uiState -> - when (uiState) { - is MainCredentialViewModel.UIState.Loading -> {} - is MainCredentialViewModel.UIState.OnMainCredentialEntered -> { - mergeDatabaseFrom(uiState.databaseUri, uiState.mainCredential) - } - is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> { - // Noting here + 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 -> { + mMainCredentialViewModel.onActionReceived() + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt index 8d00561a3..a7062fe29 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt @@ -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,69 +97,77 @@ 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() { - override fun onAuthenticationError( - errorCode: Int, - errString: CharSequence - ) { - super.onAuthenticationError(errorCode, errString) - when (errorCode) { - BiometricPrompt.ERROR_CANCELED, - BiometricPrompt.ERROR_NEGATIVE_BUTTON, - BiometricPrompt.ERROR_USER_CANCELED -> { - // No operation - Log.i("UserVerification", "$errString") - } - - else -> { - toastError(SecurityException("Authentication error: $errString")) - } + BiometricPrompt( + this, ContextCompat.getMainExecutor(this), + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError( + errorCode: Int, + errString: CharSequence + ) { + super.onAuthenticationError(errorCode, errString) + when (errorCode) { + BiometricPrompt.ERROR_CANCELED, + BiometricPrompt.ERROR_NEGATIVE_BUTTON, + BiometricPrompt.ERROR_USER_CANCELED -> { + // No operation + Log.i("UserVerification", "$errString") } - userVerificationViewModel.onUserVerificationFailed(dataToVerify) - } - override fun onAuthenticationSucceeded( - result: BiometricPrompt.AuthenticationResult - ) { - super.onAuthenticationSucceeded(result) - userVerificationViewModel.onUserVerificationSucceeded(dataToVerify) + else -> { + toastError(SecurityException("Authentication error: $errString")) + } } + userVerificationViewModel.onUserVerificationFailed(dataToVerify) + } - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - toastError(SecurityException(getString(R.string.device_unlock_not_recognized))) - userVerificationViewModel.onUserVerificationFailed(dataToVerify) - } - }).authenticate( - BiometricPrompt.PromptInfo.Builder() - .setTitle(getString(R.string.user_verification_required)) - .setAllowedAuthenticators(ALLOWED_AUTHENTICATORS) - .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 - ) - } - } + override fun onAuthenticationSucceeded( + result: BiometricPrompt.AuthenticationResult + ) { + super.onAuthenticationSucceeded(result) + userVerificationViewModel.onUserVerificationSucceeded(dataToVerify) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + toastError(SecurityException(getString(R.string.device_unlock_not_recognized))) + userVerificationViewModel.onUserVerificationFailed(dataToVerify) + } + }).authenticate( + BiometricPrompt.PromptInfo.Builder() + .setTitle(getString(R.string.user_verification_required)) + .setAllowedAuthenticators(ALLOWED_AUTHENTICATORS) + .setConfirmationRequired(false) + .build() + ) } } 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() + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt index 2fa8a0dd7..a701809b2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt @@ -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? = @@ -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 - askUserVerification( - userVerificationViewModel = userVerificationViewModel, - userVerificationCondition = intent.getUserVerificationCondition(), - dataToVerify = UserVerificationData(database) - ) + if (isAuthenticatorsAllowed()) { + askUserVerification( + userVerificationViewModel = userVerificationViewModel, + userVerificationCondition = intent.getUserVerificationCondition(), + dataToVerify = UserVerificationData(database) + ) + } else { + showUserVerificationMessage { + userVerificationViewModel.onUserVerificationFailed() + } + } } override fun onDatabaseActionFinished( diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt index 7512a27d8..fca226a01 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -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() diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/MainCredentialViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/MainCredentialViewModel.kt index 97f51afdb..595d7347e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/MainCredentialViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/MainCredentialViewModel.kt @@ -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( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 09239d12b..1bab61e52 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -774,4 +774,7 @@ Unable to return the passkey No passkey found with relying party %1$s and credentialIds %2$s User verification required + Set up user verification + Set up user verification to use passkeys + To use passkeys, make sure you have a device screen lock set up \ No newline at end of file