mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Add User Verification to database settings #2283
This commit is contained in:
@@ -325,15 +325,15 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mUserVerificationViewModel.uiState.collect { uIState ->
|
||||
when (uIState) {
|
||||
mUserVerificationViewModel.userVerificationState.collect { uVState ->
|
||||
when (uVState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
coordinatorLayout?.showError(uIState.error)
|
||||
coordinatorLayout?.showError(uVState.error)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
editEntry(uIState.dataToVerify.database, uIState.dataToVerify.entryId)
|
||||
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,15 +578,15 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mUserVerificationViewModel.uiState.collect { uIState ->
|
||||
when (uIState) {
|
||||
mUserVerificationViewModel.userVerificationState.collect { uVState ->
|
||||
when (uVState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
coordinatorLayout?.showError(uIState.error)
|
||||
coordinatorLayout?.showError(uVState.error)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
editEntry(uIState.dataToVerify.database, uIState.dataToVerify.entryId)
|
||||
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
|
||||
data class UserVerificationData(
|
||||
val database: ContextualDatabase? = null,
|
||||
val entryId: NodeId<*>? = null
|
||||
val entryId: NodeId<*>? = null,
|
||||
val preferenceKey: String? = null
|
||||
)
|
||||
@@ -9,6 +9,7 @@ import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.CheckDatabaseCredentialDialogFragment
|
||||
@@ -91,6 +92,17 @@ class UserVerificationHelper {
|
||||
return this.passkey != null
|
||||
}
|
||||
|
||||
fun Fragment.checkUserVerification(
|
||||
userVerificationViewModel: UserVerificationViewModel,
|
||||
dataToVerify: UserVerificationData
|
||||
) {
|
||||
if (context?.isAuthenticatorsAllowed() == true) {
|
||||
activity?.showUserVerificationDeviceCredential(userVerificationViewModel, dataToVerify)
|
||||
} else {
|
||||
activity?.showUserVerificationDatabaseCredential(userVerificationViewModel, dataToVerify)
|
||||
}
|
||||
}
|
||||
|
||||
fun FragmentActivity.checkUserVerification(
|
||||
userVerificationViewModel: UserVerificationViewModel,
|
||||
dataToVerify: UserVerificationData
|
||||
|
||||
@@ -175,7 +175,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
userVerificationViewModel.uiState.collect { uiState ->
|
||||
userVerificationViewModel.userVerificationState.collect { uiState ->
|
||||
when (uiState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
|
||||
@@ -42,6 +42,8 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
|
||||
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
|
||||
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
@@ -74,11 +76,16 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.getParcelableCompat
|
||||
import com.kunzisoft.keepass.utils.getSerializableCompat
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.SettingsViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval {
|
||||
|
||||
private val mSettingsViewModel: SettingsViewModel by activityViewModels()
|
||||
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
|
||||
private val mUserVerificationViewModel: UserVerificationViewModel by activityViewModels()
|
||||
|
||||
private val mDatabase: ContextualDatabase?
|
||||
get() = mDatabaseViewModel.database
|
||||
private var mDatabaseReadOnly: Boolean = false
|
||||
@@ -171,6 +178,45 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mUserVerificationViewModel.userVerificationState.collect { state ->
|
||||
when (state) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
mSettingsViewModel.showError(state.error)
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
state.dataToVerify.database?.let { database ->
|
||||
state.dataToVerify.preferenceKey?.let { preferenceKey ->
|
||||
// Main Preferences
|
||||
when (preferenceKey) {
|
||||
// Master Key
|
||||
getString(R.string.settings_database_change_credentials_key) -> {
|
||||
SetMainCredentialDialogFragment
|
||||
.getInstance(database.allowNoMasterKey)
|
||||
.show(parentFragmentManager, "passwordDialog")
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// TODO Settings in compose
|
||||
@Suppress("DEPRECATION")
|
||||
mSettingsViewModel.dialogFragment?.let { dialogFragment ->
|
||||
dialogFragment.setTargetFragment(
|
||||
this@NestedDatabaseSettingsFragment, 0
|
||||
)
|
||||
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
|
||||
}
|
||||
mSettingsViewModel.dialogFragment = null
|
||||
}
|
||||
}
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -325,7 +371,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
}
|
||||
// Change the recycle bin group
|
||||
recycleBinGroupPref?.setOnPreferenceClickListener {
|
||||
|
||||
true
|
||||
}
|
||||
// Recycle Bin group
|
||||
@@ -431,11 +476,17 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseMasterKeyPreference(database: ContextualDatabase) {
|
||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||
val changeCredentialKey = getString(R.string.settings_database_change_credentials_key)
|
||||
findPreference<Preference>(changeCredentialKey)?.apply {
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
SetMainCredentialDialogFragment.getInstance(database.allowNoMasterKey)
|
||||
.show(parentFragmentManager, "passwordDialog")
|
||||
checkUserVerification(
|
||||
mUserVerificationViewModel,
|
||||
UserVerificationData(
|
||||
database = database,
|
||||
preferenceKey = changeCredentialKey
|
||||
)
|
||||
)
|
||||
false
|
||||
}
|
||||
true
|
||||
@@ -730,9 +781,14 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
}
|
||||
|
||||
if (dialogFragment != null && !mDatabaseReadOnly) {
|
||||
@Suppress("DEPRECATION")
|
||||
dialogFragment.setTargetFragment(this, 0)
|
||||
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
|
||||
mSettingsViewModel.dialogFragment = dialogFragment
|
||||
checkUserVerification(
|
||||
mUserVerificationViewModel,
|
||||
UserVerificationData(
|
||||
database = mDatabase,
|
||||
preferenceKey = preference.key
|
||||
)
|
||||
)
|
||||
}
|
||||
// Could not be handled here. Try with the super method.
|
||||
else if (otherDialogFragment) {
|
||||
@@ -742,7 +798,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
context?.let { context ->
|
||||
mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
|
||||
}
|
||||
|
||||
@@ -28,9 +28,13 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
|
||||
@@ -41,6 +45,9 @@ import com.kunzisoft.keepass.database.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.showError
|
||||
import com.kunzisoft.keepass.viewmodels.SettingsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.joda.time.DateTime
|
||||
import java.util.Properties
|
||||
|
||||
@@ -49,6 +56,8 @@ open class SettingsActivity
|
||||
MainPreferenceFragment.Callback,
|
||||
SetMainCredentialDialogFragment.AssignMainCredentialDialogListener {
|
||||
|
||||
private val mSettingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
private var backupManager: BackupManager? = null
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
|
||||
@@ -118,7 +127,7 @@ open class SettingsActivity
|
||||
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
|
||||
toolbar?.setTitle(R.string.settings)
|
||||
else
|
||||
toolbar?.title = savedInstanceState?.getString(TITLE_KEY)
|
||||
toolbar?.title = savedInstanceState.getString(TITLE_KEY)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
@@ -145,6 +154,20 @@ open class SettingsActivity
|
||||
// Eat state
|
||||
intent.removeExtra(FRAGMENT_ARG)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mSettingsViewModel.settingsState.collect { settingsState ->
|
||||
when (settingsState) {
|
||||
is SettingsViewModel.SettingsState.Wait -> {}
|
||||
is SettingsViewModel.SettingsState.ShowError -> {
|
||||
coordinatorLayout?.showError(settingsState.error)
|
||||
mSettingsViewModel.errorShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class SettingsViewModel(application: Application): AndroidViewModel(application) {
|
||||
|
||||
private val mSettingsState = MutableStateFlow<SettingsState>(SettingsState.Wait)
|
||||
val settingsState: StateFlow<SettingsState> = mSettingsState
|
||||
|
||||
|
||||
var dialogFragment: DialogFragment? = null
|
||||
|
||||
fun showError(error: Throwable?) {
|
||||
mSettingsState.value = SettingsState.ShowError(error)
|
||||
}
|
||||
|
||||
fun errorShown() {
|
||||
mSettingsState.value = SettingsState.Wait
|
||||
}
|
||||
|
||||
sealed class SettingsState {
|
||||
object Wait: SettingsState()
|
||||
data class ShowError(
|
||||
val error: Throwable? = null
|
||||
): SettingsState()
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
class UserVerificationViewModel: ViewModel() {
|
||||
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
val userVerificationState: StateFlow<UIState> = mUiState
|
||||
|
||||
var dataToVerify: UserVerificationData = UserVerificationData()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user