Refactor key import flow and implement support for replacing

This commit is contained in:
Harsh Shandilya 2022-07-17 22:21:55 +05:30
parent b9f4da71ea
commit 4ed98c9fda
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
2 changed files with 67 additions and 26 deletions

View file

@ -13,7 +13,10 @@ import app.passwordstore.R
import app.passwordstore.crypto.KeyUtils.tryGetId import app.passwordstore.crypto.KeyUtils.tryGetId
import app.passwordstore.crypto.PGPKey import app.passwordstore.crypto.PGPKey
import app.passwordstore.crypto.PGPKeyManager import app.passwordstore.crypto.PGPKeyManager
import com.github.michaelbull.result.mapBoth import app.passwordstore.crypto.errors.KeyAlreadyExistsException
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -23,6 +26,11 @@ import kotlinx.coroutines.runBlocking
@AndroidEntryPoint @AndroidEntryPoint
class PGPKeyImportActivity : AppCompatActivity() { class PGPKeyImportActivity : AppCompatActivity() {
/**
* A [ByteArray] containing the contents of the previously selected file. This is necessary for
* the replacement case where we do not want users to have to pick the file again.
*/
private var lastBytes: ByteArray? = null
@Inject lateinit var keyManager: PGPKeyManager @Inject lateinit var keyManager: PGPKeyManager
private val pgpKeyImportAction = private val pgpKeyImportAction =
@ -35,36 +43,68 @@ class PGPKeyImportActivity : AppCompatActivity() {
contentResolver.openInputStream(uri) contentResolver.openInputStream(uri)
?: throw IllegalStateException("Failed to open selected file") ?: throw IllegalStateException("Failed to open selected file")
val bytes = keyInputStream.use { `is` -> `is`.readBytes() } val bytes = keyInputStream.use { `is` -> `is`.readBytes() }
val (key, error) = runBlocking { keyManager.addKey(PGPKey(bytes)) } importKey(bytes, false)
if (error != null) throw error
key
} }
.mapBoth( .run(::handleImportResult)
{ key ->
if (key != null) {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.pgp_key_import_succeeded))
.setMessage(getString(R.string.pgp_key_import_succeeded_message, tryGetId(key)))
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
.setOnCancelListener { finish() }
.show()
} else {
finish()
}
},
{ throwable ->
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.pgp_key_import_failed))
.setMessage(throwable.message)
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
.setOnCancelListener { finish() }
.show()
}
)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
pgpKeyImportAction.launch(arrayOf("*/*")) pgpKeyImportAction.launch(arrayOf("*/*"))
} }
override fun onDestroy() {
lastBytes = null
super.onDestroy()
}
private fun importKey(bytes: ByteArray, replace: Boolean): PGPKey? {
lastBytes = bytes
val (key, error) = runBlocking { keyManager.addKey(PGPKey(bytes), replace = replace) }
if (replace) {
lastBytes = null
}
if (error != null) throw error
return key
}
private fun handleImportResult(result: Result<PGPKey?, Throwable>) {
when (result) {
is Ok<PGPKey?> -> {
val key = result.value
if (key == null) {
finish()
// This return convinces Kotlin that the control flow for `key == null` definitely
// terminates here and allows for a smart cast below.
return
}
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.pgp_key_import_succeeded))
.setMessage(getString(R.string.pgp_key_import_succeeded_message, tryGetId(key)))
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
.setOnCancelListener { finish() }
.show()
}
is Err<Throwable> -> {
if (result.error is KeyAlreadyExistsException && lastBytes != null) {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.pgp_key_import_failed))
.setMessage(getString(R.string.pgp_key_import_failed_replace_message))
.setPositiveButton(R.string.dialog_yes) { _, _ ->
handleImportResult(runCatching { importKey(lastBytes!!, replace = true) })
}
.setNegativeButton(R.string.dialog_no) { _, _ -> finish() }
.setOnCancelListener { finish() }
.show()
} else {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.pgp_key_import_failed))
.setMessage(result.error.message)
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
.setOnCancelListener { finish() }
.show()
}
}
}
}
} }

View file

@ -372,6 +372,7 @@
<string name="place_shortcut_on_home_screen">Place shortcut on home screen</string> <string name="place_shortcut_on_home_screen">Place shortcut on home screen</string>
<string name="password_list_fab_content_description">Create new password or folder</string> <string name="password_list_fab_content_description">Create new password or folder</string>
<string name="pgp_key_import_failed">Failed to import PGP key</string> <string name="pgp_key_import_failed">Failed to import PGP key</string>
<string name="pgp_key_import_failed_replace_message">An existing key with this ID was found, do you want to replace it?</string>
<string name="pgp_key_import_succeeded">Successfully imported PGP key</string> <string name="pgp_key_import_succeeded">Successfully imported PGP key</string>
<string name="pgp_key_import_succeeded_message">The key ID of the imported key is given below, please review it for correctness:\n%1$s</string> <string name="pgp_key_import_succeeded_message">The key ID of the imported key is given below, please review it for correctness:\n%1$s</string>
<string name="pref_category_pgp_title">PGP settings</string> <string name="pref_category_pgp_title">PGP settings</string>