feat: offer to import a PGP key when none are present

This commit is contained in:
Harsh Shandilya
2023-03-25 12:34:25 +05:30
parent 1e74012656
commit 8af09d5bc8
6 changed files with 68 additions and 21 deletions

View File

@@ -17,6 +17,7 @@ import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.getAll
import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.unwrap
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@@ -32,6 +33,12 @@ constructor(
@SettingsPreferences private val settings: SharedPreferences,
) {
suspend fun hasKeys(): Boolean {
return withContext(dispatcherProvider.io()) {
pgpKeyManager.getAllKeys().mapBoth(success = { it.isNotEmpty() }, failure = { false })
}
}
suspend fun decrypt(
password: String,
message: ByteArrayInputStream,

View File

@@ -12,10 +12,9 @@ import android.os.Build
import android.os.Bundle
import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import app.passwordstore.data.crypto.CryptoRepository
import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.ui.crypto.BasePgpActivity
import app.passwordstore.ui.crypto.PasswordDialog
import app.passwordstore.util.autofill.AutofillPreferences
import app.passwordstore.util.autofill.AutofillResponseBuilder
@@ -40,7 +39,7 @@ import logcat.logcat
@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint
class AutofillDecryptActivity : AppCompatActivity() {
class AutofillDecryptActivity : BasePgpActivity() {
companion object {
@@ -78,7 +77,6 @@ class AutofillDecryptActivity : AppCompatActivity() {
}
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
@Inject lateinit var repository: CryptoRepository
private lateinit var directoryStructure: DirectoryStructure
@@ -102,17 +100,19 @@ class AutofillDecryptActivity : AppCompatActivity() {
val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
directoryStructure = AutofillPreferences.directoryStructure(this)
logcat { action.toString() }
val dialog = PasswordDialog()
lifecycleScope.launch {
withContext(Dispatchers.Main) {
dialog.password.collectLatest { value ->
if (value != null) {
decrypt(File(filePath), clientState, action, value)
requireKeysExist {
val dialog = PasswordDialog()
lifecycleScope.launch {
withContext(Dispatchers.Main) {
dialog.password.collectLatest { value ->
if (value != null) {
decrypt(File(filePath), clientState, action, value)
}
}
}
}
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
}
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
}
private suspend fun decrypt(

View File

@@ -12,11 +12,16 @@ import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.CallSuper
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
import app.passwordstore.data.crypto.CryptoRepository
import app.passwordstore.injection.prefs.SettingsPreferences
import app.passwordstore.ui.pgp.PGPKeyImportActivity
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.clipboard
import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.snackbar
@@ -24,10 +29,13 @@ import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.services.ClipboardService
import app.passwordstore.util.settings.Constants
import app.passwordstore.util.settings.PreferenceKeys
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import javax.inject.Inject
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Suppress("Registered")
@AndroidEntryPoint
@@ -46,8 +54,22 @@ open class BasePgpActivity : AppCompatActivity() {
*/
val name: String by unsafeLazy { File(fullPath).nameWithoutExtension }
/** Action to invoke if [keyImportAction] succeeds. */
var onKeyImport: (() -> Unit)? = null
private val keyImportAction =
registerForActivityResult(StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
onKeyImport?.invoke()
onKeyImport = null
} else {
finish()
}
}
/** [SharedPreferences] instance used by subclasses to persist settings */
@SettingsPreferences @Inject lateinit var settings: SharedPreferences
@Inject lateinit var repository: CryptoRepository
@Inject lateinit var dispatcherProvider: DispatcherProvider
/**
* [onCreate] sets the window up with the right flags to prevent auth leaks through screenshots or
@@ -80,6 +102,29 @@ open class BasePgpActivity : AppCompatActivity() {
}
}
/**
* Function to execute [onKeysExist] only if there are PGP keys imported in the app's key manager.
*/
fun requireKeysExist(onKeysExist: () -> Unit) {
lifecycleScope.launch {
val hasKeys = repository.hasKeys()
if (!hasKeys) {
withContext(dispatcherProvider.main()) {
MaterialAlertDialogBuilder(this@BasePgpActivity)
.setTitle(resources.getString(R.string.no_keys_imported_dialog_title))
.setMessage(resources.getString(R.string.no_keys_imported_dialog_message))
.setPositiveButton(resources.getString(R.string.button_label_import)) { _, _ ->
onKeyImport = onKeysExist
keyImportAction.launch(Intent(this@BasePgpActivity, PGPKeyImportActivity::class.java))
}
.show()
}
} else {
onKeysExist()
}
}
}
/**
* Copies a provided [password] string to the clipboard. This wraps [copyTextToClipboard] to hide
* the default [Snackbar] and starts off an instance of [ClipboardService] to provide a way of
@@ -87,7 +132,6 @@ open class BasePgpActivity : AppCompatActivity() {
*/
fun copyPasswordToClipboard(password: String?) {
copyTextToClipboard(password)
val clearAfter =
settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
?: Constants.DEFAULT_DECRYPTION_TIMEOUT

View File

@@ -11,12 +11,10 @@ import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
import app.passwordstore.data.crypto.CryptoRepository
import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.data.password.FieldItem
import app.passwordstore.databinding.DecryptLayoutBinding
import app.passwordstore.ui.adapters.FieldItemAdapter
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.extensions.viewBinding
@@ -48,8 +46,6 @@ class DecryptActivity : BasePgpActivity() {
private val binding by viewBinding(DecryptLayoutBinding::inflate)
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
@Inject lateinit var repository: CryptoRepository
@Inject lateinit var dispatcherProvider: DispatcherProvider
private var passwordEntry: PasswordEntry? = null
private var retries = 0
@@ -67,7 +63,7 @@ class DecryptActivity : BasePgpActivity() {
true
}
}
askPassphrase(isError = false)
requireKeysExist { askPassphrase(isError = false) }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@@ -25,7 +25,6 @@ import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
import app.passwordstore.crypto.GpgIdentifier
import app.passwordstore.data.crypto.CryptoRepository
import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.databinding.PasswordCreationActivityBinding
@@ -71,7 +70,6 @@ class PasswordCreationActivity : BasePgpActivity() {
private val binding by viewBinding(PasswordCreationActivityBinding::inflate)
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
@Inject lateinit var repository: CryptoRepository
private val suggestedName by unsafeLazy { intent.getStringExtra(EXTRA_FILE_NAME) }
private val suggestedPass by unsafeLazy { intent.getStringExtra(EXTRA_PASSWORD) }
@@ -268,11 +266,11 @@ class PasswordCreationActivity : BasePgpActivity() {
}
R.id.save_password -> {
copy = false
encrypt()
requireKeysExist { encrypt() }
}
R.id.save_and_copy_password -> {
copy = true
encrypt()
requireKeysExist { encrypt() }
}
else -> return super.onOptionsItemSelected(item)
}

View File

@@ -369,4 +369,6 @@
<string name="pgp_key_manager_delete_confirmation_dialog_title">Delete key?</string>
<string name="git_utils_reset_remote_branch_title">Remote branch name</string>
<string name="pgp_key_manager_no_keys_guidance">Import a key using the add button below</string>
<string name="no_keys_imported_dialog_title">No keys imported</string>
<string name="no_keys_imported_dialog_message">There are no PGP keys imported in the app yet, press the button below to pick a key file</string>
</resources>