From 080387ce758987d52a66141241bcd0363a5bbfbc Mon Sep 17 00:00:00 2001 From: agrahn Date: Mon, 22 Jul 2024 18:32:55 +0200 Subject: [PATCH] add checkbox in passphrase dialog to clear cache (#3127) * add checkbox in passphrase dialog to clear cache * instantiating PasswordDialog via newInstance, passing args as Bundle * refactor: put checkbox directly in the layout --------- Co-authored-by: Harsh Shandilya --- .../ui/autofill/AutofillDecryptActivity.kt | 16 ++++++++-- .../ui/crypto/DecryptActivity.kt | 16 ++++++++-- .../passwordstore/ui/crypto/PasswordDialog.kt | 29 ++++++++++++++++++- .../main/res/layout/dialog_password_entry.xml | 5 ++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt index b6e82b7a..c6441283 100644 --- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt @@ -11,6 +11,7 @@ import android.content.IntentSender import android.os.Build import android.os.Bundle import android.view.autofill.AutofillManager +import androidx.core.content.edit import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.lifecycleScope import app.passwordstore.Application.Companion.screenWasOff @@ -53,6 +54,7 @@ class AutofillDecryptActivity : BasePGPActivity() { @Inject lateinit var passphraseCache: PGPPassphraseCache private lateinit var directoryStructure: DirectoryStructure + private var clearCache = true override fun onStart() { super.onStart() @@ -114,7 +116,8 @@ class AutofillDecryptActivity : BasePGPActivity() { is Result.Success -> { /* clear passphrase cache on first use after application startup or if screen was off; also make sure to purge a stale cache after caching has been disabled via PGP settings */ - if (screenWasOff && settings.getBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, true)) { + clearCache = settings.getBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, true) + if (screenWasOff && clearCache) { passphraseCache.clearAllCachedPassphrases(this@AutofillDecryptActivity) screenWasOff = false } @@ -149,11 +152,16 @@ class AutofillDecryptActivity : BasePGPActivity() { decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "") return } - val dialog = PasswordDialog() + val dialog = + PasswordDialog.newInstance( + cacheEnabled = features.isEnabled(EnablePGPPassphraseCache), + clearCache = clearCache, + ) dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle -> if (key == PasswordDialog.PASSWORD_RESULT_KEY) { - val value = bundle.getString(PasswordDialog.PASSWORD_RESULT_KEY)!! + val value = bundle.getString(PasswordDialog.PASSWORD_PHRASE_KEY)!! + clearCache = bundle.getBoolean(PasswordDialog.PASSWORD_CLEAR_KEY) lifecycleScope.launch(dispatcherProvider.main()) { decryptWithPassphrase(File(filePath), identifiers, clientState, action, value) } @@ -185,6 +193,8 @@ class AutofillDecryptActivity : BasePGPActivity() { Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }, ) } + if (features.isEnabled(EnablePGPPassphraseCache)) + settings.edit { putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, clearCache) } } withContext(dispatcherProvider.main()) { finish() } } diff --git a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt index 405d6f4e..88d31dec 100644 --- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt @@ -9,6 +9,7 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.core.content.edit import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.lifecycleScope import app.passwordstore.Application.Companion.screenWasOff @@ -56,6 +57,7 @@ class DecryptActivity : BasePGPActivity() { private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) } private var passwordEntry: PasswordEntry? = null private var retries = 0 + private var clearCache = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -170,7 +172,8 @@ class DecryptActivity : BasePGPActivity() { is BiometricResult.Success -> { /* clear passphrase cache on first use after application startup or if screen was off; also make sure to purge a stale cache after caching has been disabled via PGP settings */ - if (screenWasOff && settings.getBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, true)) { + clearCache = settings.getBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, true) + if (screenWasOff && clearCache) { passphraseCache.clearAllCachedPassphrases(this@DecryptActivity) screenWasOff = false } @@ -200,14 +203,19 @@ class DecryptActivity : BasePGPActivity() { decryptWithPassphrase(passphrase = "", gpgIdentifiers, authResult) return } - val dialog = PasswordDialog() + val dialog = + PasswordDialog.newInstance( + cacheEnabled = features.isEnabled(EnablePGPPassphraseCache), + clearCache = clearCache, + ) if (isError) { dialog.setError() } dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle -> if (key == PasswordDialog.PASSWORD_RESULT_KEY) { - val passphrase = bundle.getString(PasswordDialog.PASSWORD_RESULT_KEY)!! + val passphrase = bundle.getString(PasswordDialog.PASSWORD_PHRASE_KEY)!! + clearCache = bundle.getBoolean(PasswordDialog.PASSWORD_CLEAR_KEY) lifecycleScope.launch(dispatcherProvider.main()) { decryptWithPassphrase(passphrase, gpgIdentifiers, authResult) { if (authResult is BiometricResult.Success) { @@ -231,6 +239,8 @@ class DecryptActivity : BasePGPActivity() { ) { val result = decryptPGPStream(passphrase, identifiers) if (result.isOk) { + if (features.isEnabled(EnablePGPPassphraseCache)) + settings.edit { putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, clearCache) } val entry = passwordEntryFactory.create(result.value.toByteArray()) passwordEntry = entry createPasswordUI(entry) diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordDialog.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordDialog.kt index c1dcd2d9..f65a7305 100644 --- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordDialog.kt +++ b/app/src/main/java/app/passwordstore/ui/crypto/PasswordDialog.kt @@ -25,11 +25,21 @@ class PasswordDialog : DialogFragment() { private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) } private var isError: Boolean = false + private var clearCacheChecked: Boolean = true override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = MaterialAlertDialogBuilder(requireContext()) builder.setView(binding.root) builder.setTitle(R.string.password) + + if (requireArguments().getBoolean(CACHE_ENABLED_EXTRA, false)) { + clearCacheChecked = requireArguments().getBoolean(AUTO_CLEAR_CACHE_EXTRA) + binding.autoClearCache.isChecked = clearCacheChecked + binding.autoClearCache.setOnCheckedChangeListener { _, isChecked -> + clearCacheChecked = isChecked + } + } + builder.setPositiveButton(android.R.string.ok) { _, _ -> setPasswordAndDismiss() } val dialog = builder.create() dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) @@ -64,11 +74,28 @@ class PasswordDialog : DialogFragment() { private fun setPasswordAndDismiss() { val password = binding.passwordEditText.text.toString() - setFragmentResult(PASSWORD_RESULT_KEY, bundleOf(PASSWORD_RESULT_KEY to password)) + setFragmentResult( + PASSWORD_RESULT_KEY, + bundleOf(PASSWORD_PHRASE_KEY to password, PASSWORD_CLEAR_KEY to clearCacheChecked), + ) dismissAllowingStateLoss() } companion object { + + private const val CACHE_ENABLED_EXTRA = "CACHE_ENABLED" + private const val AUTO_CLEAR_CACHE_EXTRA = "AUTO_CLEAR_CACHE" + const val PASSWORD_RESULT_KEY = "password_result" + const val PASSWORD_PHRASE_KEY = "password_phrase" + const val PASSWORD_CLEAR_KEY = "password_clear" + + fun newInstance(cacheEnabled: Boolean, clearCache: Boolean): PasswordDialog { + val extras = + bundleOf(CACHE_ENABLED_EXTRA to cacheEnabled, AUTO_CLEAR_CACHE_EXTRA to clearCache) + val fragment = PasswordDialog() + fragment.arguments = extras + return fragment + } } } diff --git a/app/src/main/res/layout/dialog_password_entry.xml b/app/src/main/res/layout/dialog_password_entry.xml index 0072d491..e2ad5a2f 100644 --- a/app/src/main/res/layout/dialog_password_entry.xml +++ b/app/src/main/res/layout/dialog_password_entry.xml @@ -27,4 +27,9 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07a9661b..404339ce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -381,4 +381,5 @@ Unlock passphrase cache AEAD encryption detected %1$s, see https://passwordstore.app/fix-aead for more information + Clear cached password on screen-off