mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Unprotect with User Verification #2283
This commit is contained in:
@@ -323,6 +323,27 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mEntryViewModel.entryState.collect { entryState ->
|
||||
when (entryState) {
|
||||
is EntryViewModel.EntryState.Loading -> {}
|
||||
is EntryViewModel.EntryState.RequestUnprotectField -> {
|
||||
val fieldView = entryState.protectedFieldView
|
||||
if (fieldView.isCurrentlyProtected()) {
|
||||
checkUserVerification(
|
||||
userVerificationViewModel = mUserVerificationViewModel,
|
||||
dataToVerify = UserVerificationData(protectedFieldView = fieldView)
|
||||
)
|
||||
} else {
|
||||
fieldView.protect()
|
||||
}
|
||||
mEntryViewModel.actionPerformed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mUserVerificationViewModel.userVerificationState.collect { uVState ->
|
||||
@@ -333,7 +354,10 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
// Edit Entry if corresponding data
|
||||
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId)
|
||||
// Unprotect field if corresponding data
|
||||
uVState.dataToVerify.protectedFieldView?.unprotect()
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
searchAction = {
|
||||
// Nothing when search retrieved
|
||||
},
|
||||
selectionAction = { intentSender, typeMode, searchInfo ->
|
||||
selectionAction = { _, typeMode, _ ->
|
||||
when(typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD ->
|
||||
|
||||
@@ -152,10 +152,12 @@ class EntryFragment: DatabaseFragment() {
|
||||
private fun assignEntryInfo(entryInfo: EntryInfo?) {
|
||||
// Set copy buttons
|
||||
templateView.apply {
|
||||
setOnUnprotectClickListener { _, textEditFieldView ->
|
||||
mEntryViewModel.requestUnprotectField(textEditFieldView)
|
||||
}
|
||||
setOnAskCopySafeClickListener {
|
||||
showClipboardDialog()
|
||||
}
|
||||
|
||||
setOnCopyActionClickListener { field ->
|
||||
mClipboardHelper?.timeoutCopyToClipboard(
|
||||
TemplateField.getLocalizedName(context, field.name),
|
||||
@@ -242,7 +244,7 @@ class EntryFragment: DatabaseFragment() {
|
||||
fun firstEntryFieldCopyView(): View? {
|
||||
return try {
|
||||
templateView.getActionImageView()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,9 @@ class UserVerificationHelper {
|
||||
* Check if the User needs to be verified for this entry
|
||||
*/
|
||||
fun EntryInfo.isUserVerificationNeeded(): Boolean {
|
||||
return this.passkey != null
|
||||
// Apply to any entry with protected content
|
||||
// Not only this.passkey != null
|
||||
return true
|
||||
}
|
||||
|
||||
fun Fragment.checkUserVerification(
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.kunzisoft.keepass.view
|
||||
import android.view.View.OnClickListener
|
||||
|
||||
interface ProtectedFieldView {
|
||||
fun setOnUnprotectClickListener(onUnprotectClickListener: OnClickListener?)
|
||||
fun setProtection(protection: Boolean, onUnprotectClickListener: OnClickListener?)
|
||||
fun protect()
|
||||
fun unprotect()
|
||||
fun isCurrentlyProtected(): Boolean
|
||||
|
||||
@@ -127,11 +127,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
PasswordTextEditFieldView(it)
|
||||
else TextEditFieldView(it)).apply {
|
||||
// hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout
|
||||
if (field.protectedValue.isProtected) {
|
||||
setOnUnprotectClickListener {
|
||||
setProtection(field.protectedValue.isProtected) {
|
||||
mOnUnprotectClickListener?.invoke(field, this)
|
||||
}
|
||||
}
|
||||
default = templateAttribute.default
|
||||
setMaxChars(templateAttribute.options.getNumberChars())
|
||||
setMaxLines(templateAttribute.options.getNumberLines())
|
||||
@@ -198,7 +196,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
val value = field.protectedValue.toString().trim()
|
||||
type = dateInstantType
|
||||
activation = value.isNotEmpty()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
type = dateInstantType
|
||||
activation = false
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
: TemplateAbstractView<TextFieldView, TextFieldView, DateTimeFieldView>
|
||||
(context, attrs, defStyle) {
|
||||
|
||||
private var mOnUnprotectClickListener: ((Field, ProtectedFieldView) -> Unit)? = null
|
||||
fun setOnUnprotectClickListener(listener: ((Field, ProtectedFieldView) -> Unit)?) {
|
||||
this.mOnUnprotectClickListener = listener
|
||||
}
|
||||
|
||||
private var mOnAskCopySafeClickListener: (() -> Unit)? = null
|
||||
fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) {
|
||||
this.mOnAskCopySafeClickListener = listener
|
||||
@@ -58,7 +63,9 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
PasskeyTextFieldView(it)
|
||||
else TextFieldView(it)).apply {
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
setProtection(field.protectedValue.isProtected)
|
||||
setProtection(field.protectedValue.isProtected) {
|
||||
mOnUnprotectClickListener?.invoke(field, this)
|
||||
}
|
||||
label = templateAttribute.alias
|
||||
?: TemplateField.getLocalizedName(context, field.name)
|
||||
setMaxChars(templateAttribute.options.getNumberChars())
|
||||
@@ -114,7 +121,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
try {
|
||||
val value = field.protectedValue.toString().trim()
|
||||
activation = value.isNotEmpty()
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
activation = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,8 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
override fun setOnUnprotectClickListener(onUnprotectClickListener: OnClickListener?) {
|
||||
override fun setProtection(protection: Boolean, onUnprotectClickListener: OnClickListener?) {
|
||||
if (protection) {
|
||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
/*
|
||||
@@ -180,6 +181,7 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
onUnprotectClickListener?.onClick(this@TextEditFieldView)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
override fun isCurrentlyProtected(): Boolean {
|
||||
return valueView.transformationMethod == PasswordTransformationMethod.getInstance()
|
||||
|
||||
@@ -42,7 +42,8 @@ import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
|
||||
open class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: RelativeLayout(context, attrs, defStyle), GenericTextFieldView {
|
||||
: RelativeLayout(context, attrs, defStyle),
|
||||
GenericTextFieldView, ProtectedFieldView {
|
||||
|
||||
protected var labelViewId = ViewCompat.generateViewId()
|
||||
private var valueViewId = ViewCompat.generateViewId()
|
||||
@@ -204,17 +205,30 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean) {
|
||||
override fun setProtection(protection: Boolean, onUnprotectClickListener: OnClickListener?) {
|
||||
showButton.isVisible = protection
|
||||
showButton.isSelected = true
|
||||
showButton.setOnClickListener {
|
||||
showButton.isSelected = !showButton.isSelected
|
||||
changeProtectedValueParameters()
|
||||
onUnprotectClickListener?.onClick(this@TextFieldView)
|
||||
}
|
||||
changeProtectedValueParameters()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun isCurrentlyProtected(): Boolean {
|
||||
return showButton.isSelected
|
||||
}
|
||||
|
||||
override fun protect() {
|
||||
showButton.isSelected = !showButton.isSelected
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
override fun unprotect() {
|
||||
showButton.isSelected = !showButton.isSelected
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
protected fun changeProtectedValueParameters() {
|
||||
valueView.apply {
|
||||
if (showButton.isVisible) {
|
||||
|
||||
@@ -31,6 +31,9 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.utils.IOActionTask
|
||||
import com.kunzisoft.keepass.view.ProtectedFieldView
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@@ -67,6 +70,9 @@ class EntryViewModel: ViewModel() {
|
||||
val historySelected : LiveData<EntryHistory> get() = _historySelected
|
||||
private val _historySelected = SingleLiveEvent<EntryHistory>()
|
||||
|
||||
private val mEntryState = MutableStateFlow<EntryState>(EntryState.Loading)
|
||||
val entryState: StateFlow<EntryState> = mEntryState
|
||||
|
||||
fun loadDatabase(database: ContextualDatabase?) {
|
||||
loadEntry(database, mainEntryId, historyPosition)
|
||||
}
|
||||
@@ -126,6 +132,10 @@ class EntryViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun requestUnprotectField(fieldView: ProtectedFieldView) {
|
||||
mEntryState.value = EntryState.RequestUnprotectField(fieldView)
|
||||
}
|
||||
|
||||
fun onOtpElementUpdated(optElement: OtpElement?) {
|
||||
_onOtpElementUpdated.value = optElement
|
||||
}
|
||||
@@ -146,6 +156,10 @@ class EntryViewModel: ViewModel() {
|
||||
_sectionSelected.value = section
|
||||
}
|
||||
|
||||
fun actionPerformed() {
|
||||
mEntryState.value = EntryState.Loading
|
||||
}
|
||||
|
||||
data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
|
||||
var historyPosition: Int,
|
||||
val template: Template,
|
||||
@@ -167,6 +181,13 @@ class EntryViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
sealed class EntryState {
|
||||
object Loading: EntryState()
|
||||
data class RequestUnprotectField(
|
||||
val protectedFieldView: ProtectedFieldView
|
||||
): EntryState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = EntryViewModel::class.java.name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user