fix: Copy protected field behind UV #2283

This commit is contained in:
J-Jamet
2025-12-03 15:04:35 +01:00
parent eb41233e57
commit 3567fa797b
11 changed files with 221 additions and 89 deletions

View File

@@ -56,10 +56,11 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TagsAdapter import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestUnprotectField import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
@@ -330,11 +331,26 @@ class EntryActivity : DatabaseLockActivity() {
when (entryState) { when (entryState) {
is EntryViewModel.EntryState.Loading -> {} is EntryViewModel.EntryState.Loading -> {}
is EntryViewModel.EntryState.RequestUnprotectField -> { is EntryViewModel.EntryState.RequestUnprotectField -> {
requestUnprotectField( mDatabase?.let { database ->
userVerificationViewModel = mUserVerificationViewModel, requestShowUnprotectField(
database = mDatabase, userVerificationViewModel = mUserVerificationViewModel,
protectedFieldView = entryState.protectedFieldView database = database,
) protectedFieldView = entryState.protectedFieldView
)
}
mEntryViewModel.actionPerformed()
}
is EntryViewModel.EntryState.RequestCopyProtectedField -> {
mDatabase?.let { database ->
checkUserVerification(
userVerificationViewModel = mUserVerificationViewModel,
dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.COPY_PROTECTED_FIELD,
database = database,
field = entryState.field,
)
)
}
mEntryViewModel.actionPerformed() mEntryViewModel.actionPerformed()
} }
} }
@@ -351,10 +367,24 @@ class EntryActivity : DatabaseLockActivity() {
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
// Edit Entry if corresponding data val data = uVState.dataToVerify
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId) when (data.actionType) {
// Unprotect field if corresponding data UserVerificationActionType.SHOW_PROTECTED_FIELD -> {
uVState.dataToVerify.protectedFieldView?.unprotect() // Unprotect field by its view
data.protectedFieldView?.unprotect()
}
UserVerificationActionType.COPY_PROTECTED_FIELD -> {
// Copy field value
data.field?.let {
mEntryViewModel.copyToClipboard(it)
}
}
UserVerificationActionType.EDIT_ENTRY -> {
// Edit Entry
editEntry(data.database, data.entryId)
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
} }
@@ -531,10 +561,16 @@ class EntryActivity : DatabaseLockActivity() {
when (item.itemId) { when (item.itemId) {
R.id.menu_edit -> { R.id.menu_edit -> {
if (mEntryViewModel.entryInfo?.isUserVerificationNeeded() == true) { if (mEntryViewModel.entryInfo?.isUserVerificationNeeded() == true) {
checkUserVerification( mDatabase?.let { database ->
userVerificationViewModel = mUserVerificationViewModel, checkUserVerification(
dataToVerify = UserVerificationData(mDatabase, mEntryViewModel.mainEntryId) userVerificationViewModel = mUserVerificationViewModel,
) dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.EDIT_ENTRY,
database = database,
entryId = mEntryViewModel.mainEntryId
)
)
}
} else { } else {
editEntry(mDatabase, mEntryViewModel.mainEntryId) editEntry(mDatabase, mEntryViewModel.mainEntryId)
} }

View File

@@ -63,7 +63,8 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecia
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestUnprotectField import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
@@ -403,11 +404,13 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.actionPerformed() mEntryEditViewModel.actionPerformed()
} }
is EntryEditViewModel.EntryEditState.RequestUnprotectField -> { is EntryEditViewModel.EntryEditState.RequestUnprotectField -> {
requestUnprotectField( mDatabase?.let { database ->
userVerificationViewModel = mUserVerificationViewModel, requestShowUnprotectField(
database = mDatabase, userVerificationViewModel = mUserVerificationViewModel,
protectedFieldView = uiState.protectedFieldView database = database,
) protectedFieldView = uiState.protectedFieldView
)
}
mEntryEditViewModel.actionPerformed() mEntryEditViewModel.actionPerformed()
} }
} }
@@ -424,7 +427,12 @@ class EntryEditActivity : DatabaseLockActivity(),
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
uVState.dataToVerify.protectedFieldView?.unprotect() when (uVState.dataToVerify.actionType) {
UserVerificationActionType.SHOW_PROTECTED_FIELD -> {
uVState.dataToVerify.protectedFieldView?.unprotect()
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
} }

View File

@@ -75,6 +75,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded
@@ -586,7 +587,15 @@ class GroupActivity : DatabaseLockActivity(),
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId) val data = uVState.dataToVerify
when (data.actionType) {
UserVerificationActionType.EDIT_ENTRY -> {
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId)
}
UserVerificationActionType.MERGE_FROM_DATABASE -> {}
UserVerificationActionType.SAVE_TO_DATABASE -> {}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
} }
@@ -1101,7 +1110,11 @@ class GroupActivity : DatabaseLockActivity(),
if ((node as Entry).getEntryInfo(database).isUserVerificationNeeded()) { if ((node as Entry).getEntryInfo(database).isUserVerificationNeeded()) {
checkUserVerification( checkUserVerification(
userVerificationViewModel = mUserVerificationViewModel, userVerificationViewModel = mUserVerificationViewModel,
dataToVerify = UserVerificationData(database, node.nodeId) dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.EDIT_ENTRY,
database = database,
entryId = node.nodeId
)
) )
} else { } else {
editEntry(database, node.nodeId) editEntry(database, node.nodeId)

View File

@@ -16,13 +16,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
import com.kunzisoft.keepass.view.TemplateView import com.kunzisoft.keepass.view.TemplateView
@@ -50,8 +47,6 @@ class EntryFragment: DatabaseFragment() {
private lateinit var uuidContainerView: View private lateinit var uuidContainerView: View
private lateinit var uuidReferenceView: TextView private lateinit var uuidReferenceView: TextView
private var mClipboardHelper: ClipboardHelper? = null
private val mEntryViewModel: EntryViewModel by activityViewModels() private val mEntryViewModel: EntryViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater,
@@ -66,10 +61,6 @@ class EntryFragment: DatabaseFragment() {
savedInstanceState: Bundle?) { savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
context?.let { context ->
mClipboardHelper = ClipboardHelper(context)
}
rootView = view rootView = view
// Hide only the first time // Hide only the first time
if (savedInstanceState == null) { if (savedInstanceState == null) {
@@ -152,18 +143,14 @@ class EntryFragment: DatabaseFragment() {
private fun assignEntryInfo(entryInfo: EntryInfo?) { private fun assignEntryInfo(entryInfo: EntryInfo?) {
// Set copy buttons // Set copy buttons
templateView.apply { templateView.apply {
setOnUnprotectClickListener { _, textEditFieldView -> setOnUnprotectClickListener { protectedFieldView ->
mEntryViewModel.requestUnprotectField(textEditFieldView) mEntryViewModel.requestUnprotectField(protectedFieldView)
} }
setOnAskCopySafeClickListener { setOnAskCopySafeClickListener {
showClipboardDialog() showClipboardDialog()
} }
setOnCopyActionClickListener { field -> setOnCopyActionClickListener { field, protectedFieldView ->
mClipboardHelper?.timeoutCopyToClipboard( mEntryViewModel.requestCopyField(field, protectedFieldView)
TemplateField.getLocalizedName(context, field.name),
field.protectedValue.stringValue,
field.protectedValue.isProtected
)
} }
} }
@@ -251,7 +238,7 @@ class EntryFragment: DatabaseFragment() {
fun launchEntryCopyEducationAction() { fun launchEntryCopyEducationAction() {
val appNameString = getString(R.string.app_name) val appNameString = getString(R.string.app_name)
mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString) mEntryViewModel.copyToClipboard(appNameString)
} }
companion object { companion object {

View File

@@ -1,12 +1,25 @@
package com.kunzisoft.keepass.credentialprovider package com.kunzisoft.keepass.credentialprovider
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.view.ProtectedFieldView import com.kunzisoft.keepass.view.ProtectedFieldView
data class UserVerificationData( data class UserVerificationData(
val actionType: UserVerificationActionType,
val database: ContextualDatabase? = null, val database: ContextualDatabase? = null,
val entryId: NodeId<*>? = null, val entryId: NodeId<*>? = null,
val field: Field? = null,
val protectedFieldView: ProtectedFieldView? = null, val protectedFieldView: ProtectedFieldView? = null,
val preferenceKey: String? = null val preferenceKey: String? = null
) )
enum class UserVerificationActionType {
LAUNCH_PASSKEY_CEREMONY,
SHOW_PROTECTED_FIELD,
COPY_PROTECTED_FIELD,
EDIT_ENTRY,
EDIT_DATABASE_SETTING,
MERGE_FROM_DATABASE,
SAVE_TO_DATABASE
}

View File

@@ -104,15 +104,16 @@ class UserVerificationHelper {
activity?.checkUserVerification(userVerificationViewModel, dataToVerify) activity?.checkUserVerification(userVerificationViewModel, dataToVerify)
} }
fun FragmentActivity.requestUnprotectField( fun FragmentActivity.requestShowUnprotectField(
userVerificationViewModel: UserVerificationViewModel, userVerificationViewModel: UserVerificationViewModel,
database: ContextualDatabase?, database: ContextualDatabase,
protectedFieldView: ProtectedFieldView protectedFieldView: ProtectedFieldView
) { ) {
if (protectedFieldView.isCurrentlyProtected()) { if (protectedFieldView.isCurrentlyProtected()) {
checkUserVerification( checkUserVerification(
userVerificationViewModel = userVerificationViewModel, userVerificationViewModel = userVerificationViewModel,
dataToVerify = UserVerificationData( dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.SHOW_PROTECTED_FIELD,
database = database, database = database,
protectedFieldView = protectedFieldView protectedFieldView = protectedFieldView
) )

View File

@@ -45,6 +45,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.addUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.addUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
@@ -179,12 +180,18 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
when (uiState) { when (uiState) {
is UserVerificationViewModel.UIState.Loading -> {} is UserVerificationViewModel.UIState.Loading -> {}
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
passkeyLauncherViewModel.launchActionIfNeeded( val data = uiState.dataToVerify
userVerified = true, when (data.actionType) {
intent = intent, UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY -> {
specialMode = mSpecialMode, passkeyLauncherViewModel.launchActionIfNeeded(
database = uiState.dataToVerify.database userVerified = true,
) intent = intent,
specialMode = mSpecialMode,
database = uiState.dataToVerify.database
)
}
else -> {}
}
userVerificationViewModel.onUserVerificationReceived() userVerificationViewModel.onUserVerificationReceived()
} }
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> { is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
@@ -207,7 +214,10 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
if (userVerificationNeeded) { if (userVerificationNeeded) {
checkUserVerification( checkUserVerification(
userVerificationViewModel = userVerificationViewModel, userVerificationViewModel = userVerificationViewModel,
dataToVerify = UserVerificationData(database) dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY,
database = database
)
) )
} else { } else {
passkeyLauncherViewModel.launchActionIfNeeded( passkeyLauncherViewModel.launchActionIfNeeded(

View File

@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
@@ -188,28 +189,34 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
state.dataToVerify.database?.let { database -> val data = state.dataToVerify
state.dataToVerify.preferenceKey?.let { preferenceKey -> when (data.actionType) {
UserVerificationActionType.EDIT_DATABASE_SETTING -> {
val database = data.database
val preferenceKey = data.preferenceKey
if (database != null && preferenceKey != null) {
// Main Preferences // Main Preferences
when (preferenceKey) { when (preferenceKey) {
// Master Key // Master Key
getString(R.string.settings_database_change_credentials_key) -> { getString(R.string.settings_database_change_credentials_key) -> {
SetMainCredentialDialogFragment SetMainCredentialDialogFragment
.getInstance(database.allowNoMasterKey) .getInstance(database.allowNoMasterKey)
.show(parentFragmentManager, "passwordDialog") .show(parentFragmentManager, "passwordDialog")
}
else -> {}
} }
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
} }
// TODO Settings in compose
@Suppress("DEPRECATION")
mSettingsViewModel.dialogFragment?.let { dialogFragment ->
dialogFragment.setTargetFragment(
this@NestedDatabaseSettingsFragment, 0
)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
mSettingsViewModel.dialogFragment = null
} }
else -> {}
} }
mUserVerificationViewModel.onUserVerificationReceived() mUserVerificationViewModel.onUserVerificationReceived()
} }
@@ -483,6 +490,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
checkUserVerification( checkUserVerification(
mUserVerificationViewModel, mUserVerificationViewModel,
UserVerificationData( UserVerificationData(
actionType = UserVerificationActionType.EDIT_DATABASE_SETTING,
database = database, database = database,
preferenceKey = changeCredentialKey preferenceKey = changeCredentialKey
) )
@@ -513,7 +521,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
// To reassign color listener after orientation change // To reassign color listener after orientation change
val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
chromaDialog?.onColorSelectedListener = colorSelectedListener chromaDialog?.onColorSelectedListener = colorSelectedListener
} catch (e: Exception) {} } catch (_: Exception) {}
return view return view
} }
@@ -785,6 +793,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
checkUserVerification( checkUserVerification(
mUserVerificationViewModel, mUserVerificationViewModel,
UserVerificationData( UserVerificationData(
actionType = UserVerificationActionType.EDIT_DATABASE_SETTING,
database = mDatabase, database = mDatabase,
preferenceKey = preference.key preferenceKey = preference.key
) )

View File

@@ -25,8 +25,8 @@ class TemplateView @JvmOverloads constructor(context: Context,
: TemplateAbstractView<TextFieldView, TextFieldView, DateTimeFieldView> : TemplateAbstractView<TextFieldView, TextFieldView, DateTimeFieldView>
(context, attrs, defStyle) { (context, attrs, defStyle) {
private var mOnUnprotectClickListener: ((Field, ProtectedFieldView) -> Unit)? = null private var mOnUnprotectClickListener: ((ProtectedFieldView) -> Unit)? = null
fun setOnUnprotectClickListener(listener: ((Field, ProtectedFieldView) -> Unit)?) { fun setOnUnprotectClickListener(listener: ((ProtectedFieldView) -> Unit)?) {
this.mOnUnprotectClickListener = listener this.mOnUnprotectClickListener = listener
} }
@@ -34,8 +34,8 @@ class TemplateView @JvmOverloads constructor(context: Context,
fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) { fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) {
this.mOnAskCopySafeClickListener = listener this.mOnAskCopySafeClickListener = listener
} }
private var mOnCopyActionClickListener: ((Field) -> Unit)? = null private var mOnCopyActionClickListener: ((Field, ProtectedFieldView) -> Unit)? = null
fun setOnCopyActionClickListener(listener: ((Field) -> Unit)? = null) { fun setOnCopyActionClickListener(listener: ((Field, ProtectedFieldView) -> Unit)? = null) {
this.mOnCopyActionClickListener = listener this.mOnCopyActionClickListener = listener
} }
@@ -64,7 +64,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
else TextFieldView(it)).apply { else TextFieldView(it)).apply {
applyFontVisibility(mFontInVisibility) applyFontVisibility(mFontInVisibility)
setProtection(field.protectedValue.isProtected) { setProtection(field.protectedValue.isProtected) {
mOnUnprotectClickListener?.invoke(field, this) mOnUnprotectClickListener?.invoke(this)
} }
label = templateAttribute.alias label = templateAttribute.alias
?: TemplateField.getLocalizedName(context, field.name) ?: TemplateField.getLocalizedName(context, field.name)
@@ -85,7 +85,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { label, value -> setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(true, value))) ?.invoke(
Field(
name = label,
value = ProtectedString(
enableProtection = true,
string = value
)
), this
)
} }
} else { } else {
setCopyButtonState(TextFieldView.ButtonState.GONE) setCopyButtonState(TextFieldView.ButtonState.GONE)
@@ -96,7 +104,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { label, value -> setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(false, value))) ?.invoke(
Field(
name = label,
value = ProtectedString(
enableProtection = false,
string = value
)
), this
)
} }
} }
} }
@@ -184,9 +200,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
value = otpElement.tokenString value = otpElement.tokenString
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { _, _ -> setCopyButtonClickListener { _, _ ->
mOnCopyActionClickListener?.invoke(Field( mOnCopyActionClickListener?.invoke(
otpElement.type.name, Field(
ProtectedString(false, otpElement.token))) name = otpElement.type.name,
value = ProtectedString(
enableProtection = false,
string = otpElement.token
)
), this
)
} }
textDirection = TEXT_DIRECTION_LTR textDirection = TEXT_DIRECTION_LTR
mLastOtpTokenView = this mLastOtpTokenView = this

View File

@@ -19,17 +19,22 @@
*/ */
package com.kunzisoft.keepass.viewmodels package com.kunzisoft.keepass.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.IOActionTask import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.view.ProtectedFieldView import com.kunzisoft.keepass.view.ProtectedFieldView
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,7 +42,7 @@ import kotlinx.coroutines.flow.StateFlow
import java.util.UUID import java.util.UUID
class EntryViewModel: ViewModel() { class EntryViewModel(application: Application): AndroidViewModel(application) {
var mainEntryId: NodeId<UUID>? = null var mainEntryId: NodeId<UUID>? = null
private set private set
@@ -50,6 +55,8 @@ class EntryViewModel: ViewModel() {
var entryLoaded = false var entryLoaded = false
private set private set
private var mClipboardHelper: ClipboardHelper = ClipboardHelper(application)
val entryInfoHistory : LiveData<EntryInfoHistory?> get() = _entryInfoHistory val entryInfoHistory : LiveData<EntryInfoHistory?> get() = _entryInfoHistory
private val _entryInfoHistory = MutableLiveData<EntryInfoHistory?>() private val _entryInfoHistory = MutableLiveData<EntryInfoHistory?>()
@@ -136,6 +143,14 @@ class EntryViewModel: ViewModel() {
mEntryState.value = EntryState.RequestUnprotectField(fieldView) mEntryState.value = EntryState.RequestUnprotectField(fieldView)
} }
fun requestCopyField(field: Field, fieldView: ProtectedFieldView) {
// Only request the User Verification if the field is protected and not shown
if (field.protectedValue.isProtected && fieldView.isCurrentlyProtected())
mEntryState.value = EntryState.RequestCopyProtectedField(field)
else
copyToClipboard(field)
}
fun onOtpElementUpdated(optElement: OtpElement?) { fun onOtpElementUpdated(optElement: OtpElement?) {
_onOtpElementUpdated.value = optElement _onOtpElementUpdated.value = optElement
} }
@@ -156,6 +171,18 @@ class EntryViewModel: ViewModel() {
_sectionSelected.value = section _sectionSelected.value = section
} }
fun copyToClipboard(field: Field) {
mClipboardHelper.timeoutCopyToClipboard(
TemplateField.getLocalizedName(getApplication(), field.name),
field.protectedValue.stringValue,
field.protectedValue.isProtected
)
}
fun copyToClipboard(text: String) {
mClipboardHelper.timeoutCopyToClipboard(text, text)
}
fun actionPerformed() { fun actionPerformed() {
mEntryState.value = EntryState.Loading mEntryState.value = EntryState.Loading
} }
@@ -186,6 +213,9 @@ class EntryViewModel: ViewModel() {
data class RequestUnprotectField( data class RequestUnprotectField(
val protectedFieldView: ProtectedFieldView val protectedFieldView: ProtectedFieldView
): EntryState() ): EntryState()
data class RequestCopyProtectedField(
val field: Field
): EntryState()
} }
companion object { companion object {

View File

@@ -15,16 +15,17 @@ class UserVerificationViewModel: ViewModel() {
private val mUiState = MutableStateFlow<UIState>(UIState.Loading) private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
val userVerificationState: StateFlow<UIState> = mUiState val userVerificationState: StateFlow<UIState> = mUiState
var dataToVerify: UserVerificationData = UserVerificationData() var dataToVerify: UserVerificationData? = null
fun checkMainCredential(checkString: String) { fun checkMainCredential(checkString: String) {
// Check the password part // Check the password part
if (dataToVerify.database?.checkKey(getCheckKey(checkString)) == true) val data = dataToVerify
onUserVerificationSucceeded(dataToVerify) if (data?.database?.checkKey(getCheckKey(checkString)) == true)
onUserVerificationSucceeded(data)
else { else {
onUserVerificationFailed(dataToVerify, InvalidCredentialsDatabaseException()) onUserVerificationFailed(dataToVerify, InvalidCredentialsDatabaseException())
} }
dataToVerify = UserVerificationData() dataToVerify = null
} }
fun onUserVerificationSucceeded(dataToVerify: UserVerificationData) { fun onUserVerificationSucceeded(dataToVerify: UserVerificationData) {
@@ -32,7 +33,7 @@ class UserVerificationViewModel: ViewModel() {
} }
fun onUserVerificationFailed( fun onUserVerificationFailed(
dataToVerify: UserVerificationData = UserVerificationData(), dataToVerify: UserVerificationData? = null,
error: Throwable? = null error: Throwable? = null
) { ) {
this.dataToVerify = dataToVerify this.dataToVerify = dataToVerify
@@ -45,9 +46,11 @@ class UserVerificationViewModel: ViewModel() {
sealed class UIState { sealed class UIState {
object Loading: UIState() object Loading: UIState()
data class OnUserVerificationSucceeded(val dataToVerify: UserVerificationData): UIState() data class OnUserVerificationSucceeded(
val dataToVerify: UserVerificationData
): UIState()
data class OnUserVerificationCanceled( data class OnUserVerificationCanceled(
val dataToVerify: UserVerificationData, val dataToVerify: UserVerificationData?,
val error: Throwable? val error: Throwable?
): UIState() ): UIState()
} }