aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2023-03-25 12:34:25 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2023-03-25 12:36:13 +0530
commit8af09d5bc8ce0286e88edb49428ada5b1fd89bd0 (patch)
tree4b213d823e159aeeebd5a78ba7a367477d6c6161
parent1e7401265676e79afbffb0396a0605726b93509e (diff)
feat: offer to import a PGP key when none are present
-rw-r--r--app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt7
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt22
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt46
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt6
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt6
-rw-r--r--app/src/main/res/values/strings.xml2
6 files changed, 68 insertions, 21 deletions
diff --git a/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt b/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
index fae13f21..f3dff15e 100644
--- a/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
+++ b/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
@@ -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,
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 e8e1f565..4e8c9891 100644
--- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
@@ -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(
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
index f2778e2d..14f6c843 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
@@ -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
@@ -81,13 +103,35 @@ 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
* clearing the clipboard.
*/
fun copyPasswordToClipboard(password: String?) {
copyTextToClipboard(password)
-
val clearAfter =
settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
?: Constants.DEFAULT_DECRYPTION_TIMEOUT
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 ce14c64d..3df2422f 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
@@ -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 {
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
index 9466daf2..1d32151f 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
@@ -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)
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9b28814f..c5569929 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -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>