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

Commit 32ce8f0

Browse files
committed
app: add viewmodel and fragment to import gpg keys
Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>
1 parent 9805887 commit 32ce8f0

File tree

5 files changed

+164
-1
lines changed

5 files changed

+164
-1
lines changed

.idea/deploymentTargetDropDown.xml

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/java/dev/msfjarvis/aps/ui/onboarding/activity/OnboardingActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ package dev.msfjarvis.aps.ui.onboarding.activity
77

88
import android.os.Bundle
99
import androidx.appcompat.app.AppCompatActivity
10+
import dagger.hilt.android.AndroidEntryPoint
1011
import dev.msfjarvis.aps.R
1112

13+
@AndroidEntryPoint
1214
class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) {
1315

1416
override fun onCreate(savedInstanceState: Bundle?) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
6+
package dev.msfjarvis.aps.ui.onboarding.fragments
7+
8+
import android.os.Bundle
9+
import android.view.View
10+
import android.widget.Toast
11+
import androidx.activity.result.contract.ActivityResultContracts
12+
import androidx.core.content.edit
13+
import androidx.fragment.app.Fragment
14+
import androidx.fragment.app.viewModels
15+
import com.github.michaelbull.result.onFailure
16+
import com.github.michaelbull.result.onSuccess
17+
import dagger.hilt.android.AndroidEntryPoint
18+
import dev.msfjarvis.aps.R
19+
import dev.msfjarvis.aps.databinding.FragmentKeySelectionBinding
20+
import dev.msfjarvis.aps.ui.onboarding.viewmodel.KeySelectionViewModel
21+
import dev.msfjarvis.aps.util.extensions.commitChange
22+
import dev.msfjarvis.aps.util.extensions.finish
23+
import dev.msfjarvis.aps.util.extensions.sharedPrefs
24+
import dev.msfjarvis.aps.util.extensions.unsafeLazy
25+
import dev.msfjarvis.aps.util.extensions.viewBinding
26+
import dev.msfjarvis.aps.util.settings.PreferenceKeys
27+
import kotlinx.coroutines.flow.onEach
28+
29+
@AndroidEntryPoint
30+
class GopenpgpKeySelectionFragment : Fragment(R.layout.fragment_key_selection) {
31+
32+
private val settings by unsafeLazy { requireActivity().applicationContext.sharedPrefs }
33+
private val binding by viewBinding(FragmentKeySelectionBinding::bind)
34+
private val viewModel: KeySelectionViewModel by viewModels()
35+
36+
private val gpgKeySelectAction = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
37+
if (uri == null) {
38+
// TODO: Use string resources here
39+
showError("No files chosen")
40+
return@registerForActivityResult
41+
}
42+
43+
val fis = requireContext().contentResolver.openInputStream(uri)
44+
if (fis == null) {
45+
showError("Error resolving content uri")
46+
return@registerForActivityResult
47+
}
48+
49+
viewModel.importKey(fis)
50+
}
51+
52+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
53+
super.onViewCreated(view, savedInstanceState)
54+
binding.selectKey.setOnClickListener {
55+
gpgKeySelectAction.launch("*/*")
56+
}
57+
58+
viewModel.importKeyStatus.onEach { result ->
59+
result.onSuccess {
60+
settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
61+
requireActivity().commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name)))
62+
finish()
63+
}.onFailure {
64+
showError(it.message!!)
65+
}
66+
}
67+
}
68+
69+
private fun showError(message: String) {
70+
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
71+
}
72+
73+
companion object {
74+
75+
fun newInstance() = GopenpgpKeySelectionFragment()
76+
}
77+
}

app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
157157
if (!PasswordRepository.isInitialized) {
158158
PasswordRepository.initialize()
159159
}
160-
parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
160+
parentFragmentManager.performTransactionWithBackStack(GopenpgpKeySelectionFragment.newInstance())
161161
}
162162
.onFailure { e ->
163163
e(e)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dev.msfjarvis.aps.ui.onboarding.viewmodel
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.github.michaelbull.result.Err
6+
import com.github.michaelbull.result.Ok
7+
import com.github.michaelbull.result.Result
8+
import com.github.michaelbull.result.map
9+
import com.github.michaelbull.result.onFailure
10+
import dagger.hilt.android.lifecycle.HiltViewModel
11+
import dev.msfjarvis.aps.data.crypto.KeyManager
12+
import dev.msfjarvis.aps.data.repo.PasswordRepository
13+
import java.io.File
14+
import java.io.InputStream
15+
import javax.inject.Inject
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.flow.MutableSharedFlow
18+
import kotlinx.coroutines.flow.asSharedFlow
19+
import kotlinx.coroutines.launch
20+
import kotlinx.coroutines.withContext
21+
22+
@HiltViewModel
23+
class KeySelectionViewModel @Inject constructor(private val keyManager: KeyManager) : ViewModel() {
24+
25+
private val _importKeyStatus = MutableSharedFlow<Result<Unit, Throwable>>()
26+
val importKeyStatus = _importKeyStatus.asSharedFlow()
27+
fun importKey(keyInputStream: InputStream) {
28+
viewModelScope.launch {
29+
val lines = keyInputStream.bufferedReader().readLines()
30+
31+
// Validate the incoming InputStream
32+
if (!validateKey(lines)) {
33+
_importKeyStatus.emit(Err<Throwable>(IllegalStateException("Selected file does not appear to be an GPG private key.")))
34+
return@launch
35+
}
36+
// Join InputStream and add key to KeyManager
37+
val fileContent = lines.joinToString("\n")
38+
keyManager.addKey(fileContent).onFailure { throwable ->
39+
_importKeyStatus.emit(Err(throwable))
40+
return@launch
41+
}
42+
// Create `.gpg-id` file
43+
createGpgIdFile().onFailure { throwable ->
44+
_importKeyStatus.emit(Err(throwable))
45+
return@launch
46+
}
47+
48+
_importKeyStatus.emit(Ok(Unit))
49+
}
50+
}
51+
52+
private fun validateKey(lines: List<String>): Boolean {
53+
// The file must have more than 2 lines, and the first and last line must have private key
54+
// markers.
55+
return (lines.size > 2 ||
56+
Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) &&
57+
Regex("END .* PRIVATE KEY").containsMatchIn(lines.last())
58+
)
59+
}
60+
61+
private suspend fun createGpgIdFile(): Result<Unit, Throwable> = withContext(Dispatchers.IO) {
62+
return@withContext keyManager.listKeyIds().map { keys ->
63+
val idFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id")
64+
idFile.writeText((keys + "").joinToString("\n"))
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)