refactor: use a ViewModel to pass around decryption passphrase

This commit is contained in:
Harsh Shandilya
2023-01-19 17:01:47 +05:30
parent bffa31314f
commit 2fbad7ef6b
4 changed files with 60 additions and 29 deletions

View File

@@ -11,6 +11,7 @@ import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -21,6 +22,7 @@ import app.passwordstore.util.autofill.AutofillPreferences
import app.passwordstore.util.autofill.AutofillResponseBuilder import app.passwordstore.util.autofill.AutofillResponseBuilder
import app.passwordstore.util.autofill.DirectoryStructure import app.passwordstore.util.autofill.DirectoryStructure
import app.passwordstore.util.extensions.asLog import app.passwordstore.util.extensions.asLog
import app.passwordstore.util.viewmodel.PasswordViewModel
import com.github.androidpasswordstore.autofillparser.AutofillAction import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
@@ -77,6 +79,7 @@ class AutofillDecryptActivity : AppCompatActivity() {
} }
} }
private val viewModel: PasswordViewModel by viewModels()
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
@Inject lateinit var repository: CryptoRepository @Inject lateinit var repository: CryptoRepository
@@ -105,10 +108,8 @@ class AutofillDecryptActivity : AppCompatActivity() {
val dialog = PasswordDialog() val dialog = PasswordDialog()
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
dialog.password.collectLatest { value -> viewModel.password.collectLatest { value ->
if (value != null) { decrypt(File(filePath), clientState, action, value)
decrypt(File(filePath), clientState, action, value)
}
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.data.crypto.CryptoRepository import app.passwordstore.data.crypto.CryptoRepository
@@ -22,6 +23,7 @@ import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.extensions.viewBinding import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.settings.Constants import app.passwordstore.util.settings.Constants
import app.passwordstore.util.settings.PreferenceKeys import app.passwordstore.util.settings.PreferenceKeys
import app.passwordstore.util.viewmodel.PasswordViewModel
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
@@ -45,6 +47,7 @@ import logcat.logcat
@AndroidEntryPoint @AndroidEntryPoint
class DecryptActivity : BasePgpActivity() { class DecryptActivity : BasePgpActivity() {
private val viewModel: PasswordViewModel by viewModels()
private val binding by viewBinding(DecryptLayoutBinding::inflate) private val binding by viewBinding(DecryptLayoutBinding::inflate)
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) } private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
@@ -67,7 +70,7 @@ class DecryptActivity : BasePgpActivity() {
true true
} }
} }
askPassphrase(isError = false) askPassphrase()
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -138,34 +141,36 @@ class DecryptActivity : BasePgpActivity() {
) )
} }
private fun askPassphrase(isError: Boolean) { private fun askPassphrase() {
if (retries < MAX_RETRIES) { if (retries < MAX_RETRIES) {
retries += 1 retries += 1
} else { } else {
finish() finish()
} }
val dialog = PasswordDialog() showPasswordDialog(isError = false)
if (isError) {
dialog.setError()
}
lifecycleScope.launch(dispatcherProvider.main()) { lifecycleScope.launch(dispatcherProvider.main()) {
dialog.password.collectLatest { value -> viewModel.password.collectLatest { value ->
if (value != null) { when (val result = decryptWithPassphrase(value)) {
when (val result = decryptWithPassphrase(value)) { is Ok -> {
is Ok -> { val entry = passwordEntryFactory.create(result.value.toByteArray())
val entry = passwordEntryFactory.create(result.value.toByteArray()) passwordEntry = entry
passwordEntry = entry createPasswordUI(entry)
createPasswordUI(entry) startAutoDismissTimer()
startAutoDismissTimer() }
} is Err -> {
is Err -> { logcat(ERROR) { result.error.stackTraceToString() }
logcat(ERROR) { result.error.stackTraceToString() } showPasswordDialog(isError = true)
askPassphrase(isError = true)
}
} }
} }
} }
} }
}
private fun showPasswordDialog(isError: Boolean) {
val dialog = PasswordDialog()
if (isError) {
dialog.setError()
}
dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
} }

View File

@@ -12,22 +12,20 @@ import android.view.KeyEvent
import android.view.WindowManager import android.view.WindowManager
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.databinding.DialogPasswordEntryBinding import app.passwordstore.databinding.DialogPasswordEntryBinding
import app.passwordstore.util.extensions.finish import app.passwordstore.util.extensions.finish
import app.passwordstore.util.extensions.unsafeLazy import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.viewmodel.PasswordViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
/** [DialogFragment] to request a password from the user and forward it along. */ /** [DialogFragment] to request a password from the user and forward it along. */
class PasswordDialog : DialogFragment() { class PasswordDialog : DialogFragment() {
private val viewModel: PasswordViewModel by activityViewModels()
private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) } private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) }
private var isError: Boolean = false private var isError: Boolean = false
private val _password = MutableStateFlow<String?>(null)
val password = _password.asStateFlow()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
@@ -66,7 +64,7 @@ class PasswordDialog : DialogFragment() {
} }
private fun setPasswordAndDismiss() { private fun setPasswordAndDismiss() {
_password.update { binding.passwordEditText.text.toString() } viewModel.setPassword(binding.passwordEditText.text.toString())
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }
} }

View File

@@ -0,0 +1,27 @@
/*
* Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package app.passwordstore.util.viewmodel
import androidx.lifecycle.ViewModel
import app.passwordstore.ui.crypto.PasswordDialog
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.update
/**
* ViewModel for classes that use [PasswordDialog] for getting user input. [PasswordDialog] will
* ensure that it retrieves an instance of this [ViewModel] from the Activity scope and will post
* values into [PasswordViewModel.password].
*/
class PasswordViewModel : ViewModel() {
private val _password = MutableStateFlow("")
val password = _password.asStateFlow().dropWhile(String::isBlank)
fun setPassword(pass: String) {
_password.update { pass }
}
}