@@ -10,10 +10,15 @@ import android.os.Bundle
10
10
import android.view.Menu
11
11
import android.view.MenuItem
12
12
import androidx.lifecycle.lifecycleScope
13
- import com.github.michaelbull.result.unwrap
13
+ import com.github.michaelbull.result.Err
14
+ import com.github.michaelbull.result.onFailure
15
+ import com.github.michaelbull.result.onSuccess
14
16
import com.google.android.material.dialog.MaterialAlertDialogBuilder
15
17
import dagger.hilt.android.AndroidEntryPoint
16
18
import dev.msfjarvis.aps.R
19
+ import dev.msfjarvis.aps.data.crypto.CryptoHandler
20
+ import dev.msfjarvis.aps.data.crypto.KeyManager
21
+ import dev.msfjarvis.aps.data.crypto.KeyPair
17
22
import dev.msfjarvis.aps.data.passfile.PasswordEntry
18
23
import dev.msfjarvis.aps.data.password.FieldItem
19
24
import dev.msfjarvis.aps.data.repo.PasswordRepository
@@ -23,7 +28,10 @@ import dev.msfjarvis.aps.injection.crypto.CryptoSet
23
28
import dev.msfjarvis.aps.injection.crypto.KeyManagerSet
24
29
import dev.msfjarvis.aps.injection.password.PasswordEntryFactory
25
30
import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter
31
+ import dev.msfjarvis.aps.ui.onboarding.activity.OnboardingActivity
32
+ import dev.msfjarvis.aps.util.FeatureFlags
26
33
import dev.msfjarvis.aps.util.extensions.findTillRoot
34
+ import dev.msfjarvis.aps.util.extensions.snackbar
27
35
import dev.msfjarvis.aps.util.extensions.unsafeLazy
28
36
import dev.msfjarvis.aps.util.extensions.viewBinding
29
37
import java.io.File
@@ -59,7 +67,15 @@ class GopenpgpDecryptActivity : BasePgpActivity() {
59
67
}
60
68
}
61
69
62
- showPassphraseDialog()
70
+ lifecycleScope.launch {
71
+ val crypto = cryptos.first { it.canHandle(fullPath) }
72
+ val keyManager = keyManagers.first { it.canHandle(fullPath) }
73
+ val keyIds = getKeyIds(fullPath)
74
+
75
+ if (checkKeys(keyManager, keyIds)) {
76
+ showPassphraseDialog(crypto, keyManager, keyIds)
77
+ }
78
+ }
63
79
}
64
80
65
81
override fun onCreateOptionsMenu (menu : Menu ? ): Boolean {
@@ -87,7 +103,11 @@ class GopenpgpDecryptActivity : BasePgpActivity() {
87
103
return true
88
104
}
89
105
90
- private fun showPassphraseDialog () {
106
+ private fun showPassphraseDialog (
107
+ crypto : CryptoHandler ,
108
+ keyManager : KeyManager <KeyPair >,
109
+ keyIds : List <String >
110
+ ) {
91
111
val view = layoutInflater.inflate(R .layout.dialog_passphrase_input, binding.root, false )
92
112
val dialogBinding = DialogPassphraseInputBinding .bind(view)
93
113
@@ -96,7 +116,7 @@ class GopenpgpDecryptActivity : BasePgpActivity() {
96
116
.setPositiveButton(" Unlock" ) { dialog, _ ->
97
117
dialog.dismiss()
98
118
val passphrase = dialogBinding.input.text.toString().toByteArray()
99
- decrypt(passphrase)
119
+ decrypt(crypto, keyManager, keyIds, passphrase)
100
120
}
101
121
.setNegativeButton(" Cancel" ) { dialog, _ ->
102
122
dialog.dismiss()
@@ -123,7 +143,12 @@ class GopenpgpDecryptActivity : BasePgpActivity() {
123
143
* result triggers they can be repopulated with new data.
124
144
*/
125
145
private fun editPassword () {
126
- val intent = Intent (this , PasswordCreationActivity ::class .java)
146
+ val intent =
147
+ Intent (
148
+ this ,
149
+ if (FeatureFlags .ENABLE_GOPENPGP ) GopenpgpPasswordCreationActivity ::class .java
150
+ else PasswordCreationActivity ::class .java
151
+ )
127
152
intent.putExtra(" FILE_PATH" , relativeParentPath)
128
153
intent.putExtra(" REPO_PATH" , repoPath)
129
154
intent.putExtra(PasswordCreationActivity .EXTRA_FILE_NAME , name)
@@ -147,56 +172,86 @@ class GopenpgpDecryptActivity : BasePgpActivity() {
147
172
)
148
173
}
149
174
150
- private fun decrypt (passphrase : ByteArray ) {
175
+ private suspend fun checkKeys (keyManager : KeyManager <KeyPair >, keyIds : List <String >): Boolean {
176
+ if (keyIds.isEmpty()) {
177
+ // This probably means the store is not set correctly, it shouldn't happen but we will still
178
+ // show a snackbar
179
+ withContext(Dispatchers .Main ) { snackbar(message = getString(R .string.gpg_id_not_found)) }
180
+ return false
181
+ }
182
+
183
+ if (keyManager.getKeyById(keyIds[0 ]) is Err ) {
184
+ snackbar(
185
+ message = getString(R .string.snackbar_key_not_found),
186
+ actionText = getString(R .string.import_action_text)
187
+ ) {
188
+ startActivity(OnboardingActivity .createKeyImportIntent(this @GopenpgpDecryptActivity))
189
+ finish()
190
+ }
191
+ return false
192
+ }
193
+
194
+ return true
195
+ }
196
+
197
+ private fun decrypt (
198
+ crypto : CryptoHandler ,
199
+ keyManager : KeyManager <KeyPair >,
200
+ keyIds : List <String >,
201
+ passphrase : ByteArray
202
+ ) {
203
+ // TODO: Binary GPG files do not work for now, need to fix that
151
204
lifecycleScope.launch {
152
205
// TODO(msfjarvis): native methods are fallible, add error handling once out of testing
153
206
val message = withContext(Dispatchers .IO ) { File (fullPath).readBytes() }
154
- val crypto = cryptos.first { it.canHandle(fullPath) }
155
- val keyManager = keyManagers.first { it.canHandle(fullPath) }
156
- val keyIds = getKeyIds(fullPath)
157
- if (keyIds.isEmpty()) {
158
- // TODO: Show option to import gpg key here
159
- return @launch
160
- }
161
- val result =
162
- withContext(Dispatchers .IO ) {
163
- val privateKey =
164
- keyManager.getKeyById(keyIds[0 ]).unwrap().getPrivateKey().decodeToString()
165
-
166
- // TODO: this throws an error if passphrase is incorrect
167
- crypto.decrypt(
168
- privateKey,
169
- passphrase,
170
- message,
171
- )
172
- }
173
- startAutoDismissTimer()
174
- val entry = passwordEntryFactory.create(lifecycleScope, result)
175
- passwordEntry = entry
176
- invalidateOptionsMenu()
177
- val items = arrayListOf<FieldItem >()
178
- val adapter = FieldItemAdapter (emptyList(), true ) { text -> copyTextToClipboard(text) }
179
- if (! entry.password.isNullOrBlank()) {
180
- items.add(FieldItem .createPasswordField(entry.password!! ))
181
- }
182
207
183
- if (entry.hasTotp()) {
184
- lifecycleScope.launch {
185
- items.add(FieldItem .createOtpField(entry.totp.value))
186
- entry.totp.collect { code ->
187
- withContext(Dispatchers .Main ) { adapter.updateOTPCode(code) }
208
+ withContext(Dispatchers .IO ) {
209
+ keyManager
210
+ .getKeyById(keyIds[0 ])
211
+ .onSuccess { keyPair ->
212
+ val privateKey = keyPair.getPrivateKey().decodeToString()
213
+ val result = crypto.decrypt(privateKey, passphrase, message)
214
+ showPassword(result)
215
+ }
216
+ .onFailure {
217
+ snackbar(
218
+ message = getString(R .string.snackbar_key_not_found),
219
+ actionText = getString(R .string.import_action_text)
220
+ ) {
221
+ startActivity(OnboardingActivity .createKeyImportIntent(this @GopenpgpDecryptActivity))
222
+ }
188
223
}
189
- }
190
224
}
225
+ }
226
+ }
191
227
192
- if (! entry.username.isNullOrBlank()) {
193
- items.add(FieldItem .createUsernameField(entry.username!! ))
194
- }
228
+ private suspend fun showPassword (password : ByteArray ) {
229
+ startAutoDismissTimer()
230
+ val entry = passwordEntryFactory.create(lifecycleScope, password)
231
+ passwordEntry = entry
232
+ invalidateOptionsMenu()
233
+ val items = arrayListOf<FieldItem >()
234
+ val adapter = FieldItemAdapter (emptyList(), true ) { text -> copyTextToClipboard(text) }
235
+ if (! entry.password.isNullOrBlank()) {
236
+ items.add(FieldItem .createPasswordField(entry.password!! ))
237
+ }
195
238
196
- entry.extraContent.forEach { (key, value) ->
197
- items.add(FieldItem (key, value, FieldItem .ActionType .COPY ))
239
+ if (entry.hasTotp()) {
240
+ lifecycleScope.launch {
241
+ items.add(FieldItem .createOtpField(entry.totp.value))
242
+ entry.totp.collect { code -> withContext(Dispatchers .Main ) { adapter.updateOTPCode(code) } }
198
243
}
244
+ }
245
+
246
+ if (! entry.username.isNullOrBlank()) {
247
+ items.add(FieldItem .createUsernameField(entry.username!! ))
248
+ }
249
+
250
+ entry.extraContent.forEach { (key, value) ->
251
+ items.add(FieldItem (key, value, FieldItem .ActionType .COPY ))
252
+ }
199
253
254
+ withContext(Dispatchers .Main ) {
200
255
binding.recyclerView.adapter = adapter
201
256
adapter.updateItems(items)
202
257
}
0 commit comments