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 <me@msfjarvis.dev>
This commit is contained in:
agrahn 2024-07-22 18:32:55 +02:00 committed by GitHub
parent 457ecade9c
commit 080387ce75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 7 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.core.content.edit
import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.Application.Companion.screenWasOff import app.passwordstore.Application.Companion.screenWasOff
@ -53,6 +54,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
@Inject lateinit var passphraseCache: PGPPassphraseCache @Inject lateinit var passphraseCache: PGPPassphraseCache
private lateinit var directoryStructure: DirectoryStructure private lateinit var directoryStructure: DirectoryStructure
private var clearCache = true
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -114,7 +116,8 @@ class AutofillDecryptActivity : BasePGPActivity() {
is Result.Success -> { is Result.Success -> {
/* clear passphrase cache on first use after application startup or if screen was off; /* 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 */ 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) passphraseCache.clearAllCachedPassphrases(this@AutofillDecryptActivity)
screenWasOff = false screenWasOff = false
} }
@ -149,11 +152,16 @@ class AutofillDecryptActivity : BasePGPActivity() {
decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "") decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "")
return return
} }
val dialog = PasswordDialog() val dialog =
PasswordDialog.newInstance(
cacheEnabled = features.isEnabled(EnablePGPPassphraseCache),
clearCache = clearCache,
)
dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle -> dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle ->
if (key == PasswordDialog.PASSWORD_RESULT_KEY) { 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()) { lifecycleScope.launch(dispatcherProvider.main()) {
decryptWithPassphrase(File(filePath), identifiers, clientState, action, value) decryptWithPassphrase(File(filePath), identifiers, clientState, action, value)
} }
@ -185,6 +193,8 @@ class AutofillDecryptActivity : BasePGPActivity() {
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }, Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) },
) )
} }
if (features.isEnabled(EnablePGPPassphraseCache))
settings.edit { putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, clearCache) }
} }
withContext(dispatcherProvider.main()) { finish() } withContext(dispatcherProvider.main()) { finish() }
} }

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.core.content.edit
import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.Application.Companion.screenWasOff import app.passwordstore.Application.Companion.screenWasOff
@ -56,6 +57,7 @@ class DecryptActivity : BasePGPActivity() {
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) } private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
private var passwordEntry: PasswordEntry? = null private var passwordEntry: PasswordEntry? = null
private var retries = 0 private var retries = 0
private var clearCache = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -170,7 +172,8 @@ class DecryptActivity : BasePGPActivity() {
is BiometricResult.Success -> { is BiometricResult.Success -> {
/* clear passphrase cache on first use after application startup or if screen was off; /* 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 */ 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) passphraseCache.clearAllCachedPassphrases(this@DecryptActivity)
screenWasOff = false screenWasOff = false
} }
@ -200,14 +203,19 @@ class DecryptActivity : BasePGPActivity() {
decryptWithPassphrase(passphrase = "", gpgIdentifiers, authResult) decryptWithPassphrase(passphrase = "", gpgIdentifiers, authResult)
return return
} }
val dialog = PasswordDialog() val dialog =
PasswordDialog.newInstance(
cacheEnabled = features.isEnabled(EnablePGPPassphraseCache),
clearCache = clearCache,
)
if (isError) { if (isError) {
dialog.setError() dialog.setError()
} }
dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle -> dialog.setFragmentResultListener(PasswordDialog.PASSWORD_RESULT_KEY) { key, bundle ->
if (key == PasswordDialog.PASSWORD_RESULT_KEY) { 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()) { lifecycleScope.launch(dispatcherProvider.main()) {
decryptWithPassphrase(passphrase, gpgIdentifiers, authResult) { decryptWithPassphrase(passphrase, gpgIdentifiers, authResult) {
if (authResult is BiometricResult.Success) { if (authResult is BiometricResult.Success) {
@ -231,6 +239,8 @@ class DecryptActivity : BasePGPActivity() {
) { ) {
val result = decryptPGPStream(passphrase, identifiers) val result = decryptPGPStream(passphrase, identifiers)
if (result.isOk) { if (result.isOk) {
if (features.isEnabled(EnablePGPPassphraseCache))
settings.edit { putBoolean(PreferenceKeys.CLEAR_PASSPHRASE_CACHE, clearCache) }
val entry = passwordEntryFactory.create(result.value.toByteArray()) val entry = passwordEntryFactory.create(result.value.toByteArray())
passwordEntry = entry passwordEntry = entry
createPasswordUI(entry) createPasswordUI(entry)

View file

@ -25,11 +25,21 @@ class PasswordDialog : DialogFragment() {
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 var clearCacheChecked: Boolean = true
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
builder.setView(binding.root) builder.setView(binding.root)
builder.setTitle(R.string.password) 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() } builder.setPositiveButton(android.R.string.ok) { _, _ -> setPasswordAndDismiss() }
val dialog = builder.create() val dialog = builder.create()
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
@ -64,11 +74,28 @@ class PasswordDialog : DialogFragment() {
private fun setPasswordAndDismiss() { private fun setPasswordAndDismiss() {
val password = binding.passwordEditText.text.toString() 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() dismissAllowingStateLoss()
} }
companion object { 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_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
}
} }
} }

View file

@ -27,4 +27,9 @@
<requestFocus /> <requestFocus />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/auto_clear_cache"
android:layout_width="match_parent"
android:text="@string/clear_cached_password_on_screen_off"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

View file

@ -381,4 +381,5 @@
<string name="biometric_prompt_title_gpg_passphrase_cache">Unlock passphrase cache</string> <string name="biometric_prompt_title_gpg_passphrase_cache">Unlock passphrase cache</string>
<string name="aead_detect_title">AEAD encryption detected</string> <string name="aead_detect_title">AEAD encryption detected</string>
<string name="aead_detect_message">%1$s, see https://passwordstore.app/fix-aead for more information</string> <string name="aead_detect_message">%1$s, see https://passwordstore.app/fix-aead for more information</string>
<string name="clear_cached_password_on_screen_off">Clear cached password on screen-off</string>
</resources> </resources>