From 03d2e24cff63471bd6237b9d2f6a0d59b5b11c21 Mon Sep 17 00:00:00 2001
From: Himanshu Choudhary <22himanshu14@gmail.com>
Date: Tue, 9 Apr 2024 04:33:11 +0530
Subject: [PATCH] Export to Bitwarden json
- Added feature to export to bitwarden json
- [IN-PROGRESS] Import from bitwarden json
---
app/src/main/AndroidManifest.xml | 10 ++
.../keypass/exporter/AccountsExporter.kt | 14 +++
.../exporter/BitwardenAccountsExporter.kt | 98 +++++++++++++++++++
.../ui/exportPasswords/PasswordExporter.kt | 78 +++++++++++++++
.../ui/nav/DashboardComposeActivity.kt | 7 ++
.../ui/redux/states/ExportPasswordState.kt | 7 ++
.../keypass/ui/settings/MySettingsFragment.kt | 10 ++
app/src/main/res/values/strings.xml | 8 ++
app/src/main/res/xml/provider_paths.xml | 3 +
local.properties | 4 +-
10 files changed, 237 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/exporter/AccountsExporter.kt
create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/exporter/BitwardenAccountsExporter.kt
create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/exportPasswords/PasswordExporter.kt
create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ExportPasswordState.kt
create mode 100644 app/src/main/res/xml/provider_paths.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a19320dc3..36b3a7f95 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -54,6 +54,16 @@
android:enabled="false"
android:exported="false" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/exporter/AccountsExporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/exporter/AccountsExporter.kt
new file mode 100644
index 000000000..fd384bdd6
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/exporter/AccountsExporter.kt
@@ -0,0 +1,14 @@
+package com.yogeshpaliyal.keypass.exporter
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import com.yogeshpaliyal.common.data.AccountModel
+import java.io.File
+
+interface AccountsExporter {
+ fun getExporterTitle(): Int
+
+ fun getExporterDesc(): Int
+
+ fun export(context: Context, listOfAccounts: List) : File?
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/exporter/BitwardenAccountsExporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/exporter/BitwardenAccountsExporter.kt
new file mode 100644
index 000000000..05efee196
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/exporter/BitwardenAccountsExporter.kt
@@ -0,0 +1,98 @@
+package com.yogeshpaliyal.keypass.exporter
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.core.content.FileProvider
+import com.yogeshpaliyal.common.data.AccountModel
+import com.yogeshpaliyal.keypass.BuildConfig
+import com.yogeshpaliyal.keypass.R
+import org.json.JSONObject
+import java.io.File
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.UUID
+
+
+class BitwardenAccountsExporter : AccountsExporter {
+ override fun getExporterTitle(): Int {
+ return R.string.bitwarden_export
+ }
+
+ override fun getExporterDesc(): Int {
+ return R.string.bitwarden_export_desc
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun export(context: Context, listOfAccounts: List) : File {
+ val title = context.getString(getExporterTitle()).lowercase()
+ val path = context.filesDir
+ val letDirectory = File(path, "${title}_exports")
+ letDirectory.mkdirs()
+
+ val currentTime = LocalDateTime.now()
+ val dateTime = currentTime.format(DateTimeFormatter.ofPattern("ddMMyyyyHHmmSS"))
+
+ val exportFile: File = File(letDirectory, "${title}_export_${dateTime}.json")
+
+ val uuid:UUID = UUID.randomUUID()
+
+ var folderData : HashMap = HashMap()
+ folderData.put("id", uuid.toString())
+ folderData.put("name", "KeyPass")
+
+ var itemsData : MutableList> = mutableListOf()
+ listOfAccounts.forEach {
+ var accountData : HashMap = HashMap()
+ var loginData : HashMap = HashMap()
+ var uriData : HashMap = HashMap()
+ accountData.put("folderId", uuid.toString())
+ accountData.put("type", 1)
+ accountData.put("organizationId", it.uniqueId)
+ accountData.put("id", it.id)
+ accountData.put("name", it.title)
+ accountData.put("notes", it.notes)
+ accountData.put("favourite", false)
+ accountData.put("fields", intArrayOf())
+
+ loginData.put("username", it.username)
+ loginData.put("password", it.password)
+ loginData.put("totp", it.secret)
+
+ uriData.put("uri", it.site)
+ uriData.put("match", null)
+
+ loginData.put("uris", uriData)
+ accountData.put("login", loginData)
+ itemsData.add(accountData)
+ }
+
+ val exportData: HashMap = HashMap()
+ exportData.put("folders", listOf(folderData))
+ exportData.put("items", itemsData)
+
+ val jsonString: String = JSONObject(exportData).toString()
+ exportFile.writeText(jsonString)
+
+ return exportFile
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/exportPasswords/PasswordExporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/exportPasswords/PasswordExporter.kt
new file mode 100644
index 000000000..1bb8707a3
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/exportPasswords/PasswordExporter.kt
@@ -0,0 +1,78 @@
+package com.yogeshpaliyal.keypass.ui.exportPasswords
+
+import android.os.Build
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import com.yogeshpaliyal.keypass.R
+import com.yogeshpaliyal.keypass.exporter.BitwardenAccountsExporter
+import com.yogeshpaliyal.keypass.ui.commonComponents.DefaultBottomAppBar
+import com.yogeshpaliyal.keypass.ui.commonComponents.PreferenceItem
+import com.yogeshpaliyal.keypass.ui.home.DashboardViewModel
+import com.yogeshpaliyal.keypass.ui.redux.states.ExportPasswordState
+import java.io.BufferedWriter
+import java.io.OutputStream
+import java.io.OutputStreamWriter
+
+val listOfExporters = listOf(BitwardenAccountsExporter())
+
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+fun PasswordExporter(state: ExportPasswordState, mViewModel: DashboardViewModel = hiltViewModel()) {
+ var fileText : String = ""
+ val context = LocalContext.current
+ val listOfAccountsLiveData by mViewModel.mediator.observeAsState()
+
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.CreateDocument("text/plain"),
+ onResult = {
+ val outputStream : OutputStream? = context.contentResolver.openOutputStream(it!!)
+ val bufferedWriter : BufferedWriter = BufferedWriter(OutputStreamWriter(outputStream))
+ bufferedWriter.write(fileText)
+ bufferedWriter.flush()
+ bufferedWriter.close()
+ }
+ )
+
+ Scaffold(bottomBar = {
+ DefaultBottomAppBar()
+ }) {
+ LazyColumn(
+ modifier = Modifier
+ .padding(it)
+ .fillMaxWidth(1f)
+ .padding(16.dp)
+ ) {
+ item {
+ PreferenceItem(
+ title = R.string.export_passwords_title,
+ isCategory = true,
+ removeIconSpace = true
+ )
+ }
+ items(listOfExporters) {
+ PreferenceItem(
+ title = it.getExporterTitle(),
+ summary = it.getExporterDesc(),
+ removeIconSpace = true
+ ) {
+ val exportedFile = it.export(context, listOfAccountsLiveData!!.toList())
+ fileText = exportedFile!!.readText()
+ launcher.launch(exportedFile!!.name)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
index 43f898e82..2827ba145 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt
@@ -1,10 +1,12 @@
package com.yogeshpaliyal.keypass.ui.nav
+import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
@@ -30,6 +32,7 @@ import com.yogeshpaliyal.keypass.ui.backupsImport.BackupImporter
import com.yogeshpaliyal.keypass.ui.changeDefaultPasswordLength.ChangeDefaultPasswordLengthScreen
import com.yogeshpaliyal.keypass.ui.changePassword.ChangePassword
import com.yogeshpaliyal.keypass.ui.detail.AccountDetailPage
+import com.yogeshpaliyal.keypass.ui.exportPasswords.PasswordExporter
import com.yogeshpaliyal.keypass.ui.home.Homepage
import com.yogeshpaliyal.keypass.ui.nav.components.DashboardBottomSheet
import com.yogeshpaliyal.keypass.ui.nav.components.KeyPassBottomBar
@@ -42,6 +45,7 @@ import com.yogeshpaliyal.keypass.ui.redux.states.BackupImporterState
import com.yogeshpaliyal.keypass.ui.redux.states.BackupScreenState
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeAppPasswordState
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState
+import com.yogeshpaliyal.keypass.ui.redux.states.ExportPasswordState
import com.yogeshpaliyal.keypass.ui.redux.states.HomeState
import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState
import com.yogeshpaliyal.keypass.ui.redux.states.ScreenState
@@ -131,6 +135,7 @@ fun Dashboard() {
}
}
+@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CurrentPage() {
val currentScreen by selectState { this.currentScreen }
@@ -166,6 +171,8 @@ fun CurrentPage() {
}
is BackupImporterState -> BackupImporter(state = it)
+
+ is ExportPasswordState -> PasswordExporter(state = it)
}
}
}
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ExportPasswordState.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ExportPasswordState.kt
new file mode 100644
index 000000000..19a43c230
--- /dev/null
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ExportPasswordState.kt
@@ -0,0 +1,7 @@
+package com.yogeshpaliyal.keypass.ui.redux.states
+
+import com.yogeshpaliyal.keypass.exporter.AccountsExporter
+
+data class ExportPasswordState(
+ var selectedExporter : AccountsExporter? = null
+) : ScreenState()
diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
index 739ef3481..cb4ec006d 100644
--- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
+++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Feedback
import androidx.compose.material.icons.rounded.Fingerprint
+import androidx.compose.material.icons.rounded.ImportExport
import androidx.compose.material.icons.rounded.Password
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.Divider
@@ -41,6 +42,7 @@ import com.yogeshpaliyal.keypass.ui.redux.states.BackupImporterState
import com.yogeshpaliyal.keypass.ui.redux.states.BackupScreenState
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeAppPasswordState
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState
+import com.yogeshpaliyal.keypass.ui.redux.states.ExportPasswordState
import kotlinx.coroutines.launch
import org.reduxkotlin.compose.rememberTypedDispatcher
@@ -88,6 +90,14 @@ fun MySettingCompose() {
dispatchAction(NavigationAction(ChangeDefaultPasswordLengthState()))
}
+ PreferenceItem(
+ title = R.string.export_passwords,
+ summary = R.string.export_passwords_desc,
+ icon = Icons.Rounded.ImportExport
+ ) {
+ dispatchAction(NavigationAction(ExportPasswordState()))
+ }
+
BiometricsOption()
Divider(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b99be6db6..31c6442dc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,6 +18,8 @@
Backup Credentials to external storage
Imports Credentials
Restore your backups
+ Export Passwords
+ Export your passwords
Send feedback
Report technical issues or suggest new features
Share
@@ -89,6 +91,7 @@
Biometric features are currently unavailable.
Setup biometric on your device.
Unlock with biometric
+ Export Passwords
Please set password for your device first from phone settings
Authentication Failed
Authentication Error %s
@@ -99,4 +102,9 @@
Google Backup
Export
+ Bitwarden
+ Bitwarden export description
+ Test
+ Test export description
+
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 000000000..97a593408
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/local.properties b/local.properties
index d944e25d7..aeb544602 100644
--- a/local.properties
+++ b/local.properties
@@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
-#Tue May 09 21:09:59 IST 2023
-sdk.dir=/Users/yogesh.choudhary3/Library/Android/sdk
+#Fri Mar 15 15:24:04 IST 2024
+sdk.dir=/Users/himanshu/Library/Android/sdk