Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 8af09d5

Browse files
committed
feat: offer to import a PGP key when none are present
1 parent 1e74012 commit 8af09d5

File tree

6 files changed

+68
-21
lines changed

6 files changed

+68
-21
lines changed

app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import app.passwordstore.util.coroutines.DispatcherProvider
1717
import app.passwordstore.util.settings.PreferenceKeys
1818
import com.github.michaelbull.result.Result
1919
import com.github.michaelbull.result.getAll
20+
import com.github.michaelbull.result.mapBoth
2021
import com.github.michaelbull.result.unwrap
2122
import java.io.ByteArrayInputStream
2223
import java.io.ByteArrayOutputStream
@@ -32,6 +33,12 @@ constructor(
3233
@SettingsPreferences private val settings: SharedPreferences,
3334
) {
3435

36+
suspend fun hasKeys(): Boolean {
37+
return withContext(dispatcherProvider.io()) {
38+
pgpKeyManager.getAllKeys().mapBoth(success = { it.isNotEmpty() }, failure = { false })
39+
}
40+
}
41+
3542
suspend fun decrypt(
3643
password: String,
3744
message: ByteArrayInputStream,

app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt

+11-11
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import android.os.Build
1212
import android.os.Bundle
1313
import android.view.autofill.AutofillManager
1414
import androidx.annotation.RequiresApi
15-
import androidx.appcompat.app.AppCompatActivity
1615
import androidx.lifecycle.lifecycleScope
17-
import app.passwordstore.data.crypto.CryptoRepository
1816
import app.passwordstore.data.passfile.PasswordEntry
17+
import app.passwordstore.ui.crypto.BasePgpActivity
1918
import app.passwordstore.ui.crypto.PasswordDialog
2019
import app.passwordstore.util.autofill.AutofillPreferences
2120
import app.passwordstore.util.autofill.AutofillResponseBuilder
@@ -40,7 +39,7 @@ import logcat.logcat
4039

4140
@RequiresApi(Build.VERSION_CODES.O)
4241
@AndroidEntryPoint
43-
class AutofillDecryptActivity : AppCompatActivity() {
42+
class AutofillDecryptActivity : BasePgpActivity() {
4443

4544
companion object {
4645

@@ -78,7 +77,6 @@ class AutofillDecryptActivity : AppCompatActivity() {
7877
}
7978

8079
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
81-
@Inject lateinit var repository: CryptoRepository
8280

8381
private lateinit var directoryStructure: DirectoryStructure
8482

@@ -102,17 +100,19 @@ class AutofillDecryptActivity : AppCompatActivity() {
102100
val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
103101
directoryStructure = AutofillPreferences.directoryStructure(this)
104102
logcat { action.toString() }
105-
val dialog = PasswordDialog()
106-
lifecycleScope.launch {
107-
withContext(Dispatchers.Main) {
108-
dialog.password.collectLatest { value ->
109-
if (value != null) {
110-
decrypt(File(filePath), clientState, action, value)
103+
requireKeysExist {
104+
val dialog = PasswordDialog()
105+
lifecycleScope.launch {
106+
withContext(Dispatchers.Main) {
107+
dialog.password.collectLatest { value ->
108+
if (value != null) {
109+
decrypt(File(filePath), clientState, action, value)
110+
}
111111
}
112112
}
113113
}
114+
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
114115
}
115-
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
116116
}
117117

118118
private suspend fun decrypt(

app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt

+45-1
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,30 @@ import android.os.Build
1212
import android.os.Bundle
1313
import android.os.PersistableBundle
1414
import android.view.WindowManager
15+
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
1516
import androidx.annotation.CallSuper
1617
import androidx.annotation.StringRes
1718
import androidx.appcompat.app.AppCompatActivity
19+
import androidx.lifecycle.lifecycleScope
1820
import app.passwordstore.R
21+
import app.passwordstore.data.crypto.CryptoRepository
1922
import app.passwordstore.injection.prefs.SettingsPreferences
23+
import app.passwordstore.ui.pgp.PGPKeyImportActivity
24+
import app.passwordstore.util.coroutines.DispatcherProvider
2025
import app.passwordstore.util.extensions.clipboard
2126
import app.passwordstore.util.extensions.getString
2227
import app.passwordstore.util.extensions.snackbar
2328
import app.passwordstore.util.extensions.unsafeLazy
2429
import app.passwordstore.util.services.ClipboardService
2530
import app.passwordstore.util.settings.Constants
2631
import app.passwordstore.util.settings.PreferenceKeys
32+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2733
import com.google.android.material.snackbar.Snackbar
2834
import dagger.hilt.android.AndroidEntryPoint
2935
import java.io.File
3036
import javax.inject.Inject
37+
import kotlinx.coroutines.launch
38+
import kotlinx.coroutines.withContext
3139

3240
@Suppress("Registered")
3341
@AndroidEntryPoint
@@ -46,8 +54,22 @@ open class BasePgpActivity : AppCompatActivity() {
4654
*/
4755
val name: String by unsafeLazy { File(fullPath).nameWithoutExtension }
4856

57+
/** Action to invoke if [keyImportAction] succeeds. */
58+
var onKeyImport: (() -> Unit)? = null
59+
private val keyImportAction =
60+
registerForActivityResult(StartActivityForResult()) {
61+
if (it.resultCode == RESULT_OK) {
62+
onKeyImport?.invoke()
63+
onKeyImport = null
64+
} else {
65+
finish()
66+
}
67+
}
68+
4969
/** [SharedPreferences] instance used by subclasses to persist settings */
5070
@SettingsPreferences @Inject lateinit var settings: SharedPreferences
71+
@Inject lateinit var repository: CryptoRepository
72+
@Inject lateinit var dispatcherProvider: DispatcherProvider
5173

5274
/**
5375
* [onCreate] sets the window up with the right flags to prevent auth leaks through screenshots or
@@ -80,14 +102,36 @@ open class BasePgpActivity : AppCompatActivity() {
80102
}
81103
}
82104

105+
/**
106+
* Function to execute [onKeysExist] only if there are PGP keys imported in the app's key manager.
107+
*/
108+
fun requireKeysExist(onKeysExist: () -> Unit) {
109+
lifecycleScope.launch {
110+
val hasKeys = repository.hasKeys()
111+
if (!hasKeys) {
112+
withContext(dispatcherProvider.main()) {
113+
MaterialAlertDialogBuilder(this@BasePgpActivity)
114+
.setTitle(resources.getString(R.string.no_keys_imported_dialog_title))
115+
.setMessage(resources.getString(R.string.no_keys_imported_dialog_message))
116+
.setPositiveButton(resources.getString(R.string.button_label_import)) { _, _ ->
117+
onKeyImport = onKeysExist
118+
keyImportAction.launch(Intent(this@BasePgpActivity, PGPKeyImportActivity::class.java))
119+
}
120+
.show()
121+
}
122+
} else {
123+
onKeysExist()
124+
}
125+
}
126+
}
127+
83128
/**
84129
* Copies a provided [password] string to the clipboard. This wraps [copyTextToClipboard] to hide
85130
* the default [Snackbar] and starts off an instance of [ClipboardService] to provide a way of
86131
* clearing the clipboard.
87132
*/
88133
fun copyPasswordToClipboard(password: String?) {
89134
copyTextToClipboard(password)
90-
91135
val clearAfter =
92136
settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
93137
?: Constants.DEFAULT_DECRYPTION_TIMEOUT

app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ import android.view.Menu
1111
import android.view.MenuItem
1212
import androidx.lifecycle.lifecycleScope
1313
import app.passwordstore.R
14-
import app.passwordstore.data.crypto.CryptoRepository
1514
import app.passwordstore.data.passfile.PasswordEntry
1615
import app.passwordstore.data.password.FieldItem
1716
import app.passwordstore.databinding.DecryptLayoutBinding
1817
import app.passwordstore.ui.adapters.FieldItemAdapter
19-
import app.passwordstore.util.coroutines.DispatcherProvider
2018
import app.passwordstore.util.extensions.getString
2119
import app.passwordstore.util.extensions.unsafeLazy
2220
import app.passwordstore.util.extensions.viewBinding
@@ -48,8 +46,6 @@ class DecryptActivity : BasePgpActivity() {
4846
private val binding by viewBinding(DecryptLayoutBinding::inflate)
4947
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
5048
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
51-
@Inject lateinit var repository: CryptoRepository
52-
@Inject lateinit var dispatcherProvider: DispatcherProvider
5349

5450
private var passwordEntry: PasswordEntry? = null
5551
private var retries = 0
@@ -67,7 +63,7 @@ class DecryptActivity : BasePgpActivity() {
6763
true
6864
}
6965
}
70-
askPassphrase(isError = false)
66+
requireKeysExist { askPassphrase(isError = false) }
7167
}
7268

7369
override fun onCreateOptionsMenu(menu: Menu): Boolean {

app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import androidx.core.widget.doAfterTextChanged
2525
import androidx.lifecycle.lifecycleScope
2626
import app.passwordstore.R
2727
import app.passwordstore.crypto.GpgIdentifier
28-
import app.passwordstore.data.crypto.CryptoRepository
2928
import app.passwordstore.data.passfile.PasswordEntry
3029
import app.passwordstore.data.repo.PasswordRepository
3130
import app.passwordstore.databinding.PasswordCreationActivityBinding
@@ -71,7 +70,6 @@ class PasswordCreationActivity : BasePgpActivity() {
7170

7271
private val binding by viewBinding(PasswordCreationActivityBinding::inflate)
7372
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
74-
@Inject lateinit var repository: CryptoRepository
7573

7674
private val suggestedName by unsafeLazy { intent.getStringExtra(EXTRA_FILE_NAME) }
7775
private val suggestedPass by unsafeLazy { intent.getStringExtra(EXTRA_PASSWORD) }
@@ -268,11 +266,11 @@ class PasswordCreationActivity : BasePgpActivity() {
268266
}
269267
R.id.save_password -> {
270268
copy = false
271-
encrypt()
269+
requireKeysExist { encrypt() }
272270
}
273271
R.id.save_and_copy_password -> {
274272
copy = true
275-
encrypt()
273+
requireKeysExist { encrypt() }
276274
}
277275
else -> return super.onOptionsItemSelected(item)
278276
}

app/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,6 @@
369369
<string name="pgp_key_manager_delete_confirmation_dialog_title">Delete key?</string>
370370
<string name="git_utils_reset_remote_branch_title">Remote branch name</string>
371371
<string name="pgp_key_manager_no_keys_guidance">Import a key using the add button below</string>
372+
<string name="no_keys_imported_dialog_title">No keys imported</string>
373+
<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>
372374
</resources>

0 commit comments

Comments
 (0)