Switch to URL-based Git config and refactor Git settings (#1008)
* Make Git config URL-based and refactor * Use Kotlin style null handling for string prefs * Also show an error if generated URL can't be parsed * Add some testcases for migration strategy Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
8f957ca994
commit
15aa929802
44 changed files with 395 additions and 585 deletions
|
@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Changed
|
||||
|
||||
- The Git repository URL can now be specified directly
|
||||
- Slightly reduce APK size
|
||||
- Always show the parent path in entries
|
||||
- Passwords will no longer be copied to the clipboard by default
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
package com.zeapo.pwdstore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
class MigrationsTest {
|
||||
|
||||
private fun checkOldKeysAreRemoved(context: Context) = with(context.sharedPrefs) {
|
||||
assertNull(getString(PreferenceKeys.GIT_REMOTE_PORT))
|
||||
assertNull(getString(PreferenceKeys.GIT_REMOTE_USERNAME))
|
||||
assertNull(getString(PreferenceKeys.GIT_REMOTE_SERVER))
|
||||
assertNull(getString(PreferenceKeys.GIT_REMOTE_LOCATION))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifySshWithCustomPortMigration() {
|
||||
val context = Application.instance.applicationContext
|
||||
context.sharedPrefs.edit {
|
||||
clear()
|
||||
putString(PreferenceKeys.GIT_REMOTE_PORT, "2200")
|
||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, "192.168.0.102")
|
||||
putString(PreferenceKeys.GIT_REMOTE_PROTOCOL, "ssh://")
|
||||
}
|
||||
runMigrations(context)
|
||||
checkOldKeysAreRemoved(context)
|
||||
assertEquals(
|
||||
context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||
"ssh://msfjarvis@192.168.0.102:2200/mnt/disk3/pass-repo"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifySshWithDefaultPortMigration() {
|
||||
val context = Application.instance.applicationContext
|
||||
context.sharedPrefs.edit {
|
||||
clear()
|
||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "/mnt/disk3/pass-repo")
|
||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, "192.168.0.102")
|
||||
putString(PreferenceKeys.GIT_REMOTE_PROTOCOL, "ssh://")
|
||||
}
|
||||
runMigrations(context)
|
||||
checkOldKeysAreRemoved(context)
|
||||
assertEquals(
|
||||
context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||
"msfjarvis@192.168.0.102:/mnt/disk3/pass-repo"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyHttpsWithGitHubMigration() {
|
||||
val context = Application.instance.applicationContext
|
||||
context.sharedPrefs.edit {
|
||||
clear()
|
||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, "msfjarvis")
|
||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, "Android-Password-Store/pass-test")
|
||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, "github.com")
|
||||
putString(PreferenceKeys.GIT_REMOTE_PROTOCOL, "https://")
|
||||
}
|
||||
runMigrations(context)
|
||||
checkOldKeysAreRemoved(context)
|
||||
assertEquals(
|
||||
context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_URL),
|
||||
"https://github.com/Android-Password-Store/pass-test"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package com.zeapo.pwdstore.git
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.git.BaseGitActivity.GitUpdateUrlResult
|
||||
import kotlin.test.assertEquals
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GitServerConfigActivityTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val activityRule = ActivityTestRule(GitServerConfigActivity::class.java, true)
|
||||
|
||||
private val activity get() = activityRule.activity
|
||||
|
||||
@Test
|
||||
fun invalidValuesFailPredictably() {
|
||||
setDefaultSshValues()
|
||||
onView(withId(R.id.server_port)).perform(replaceText("69420"))
|
||||
assertEquals(activity.updateUrl(), GitUpdateUrlResult.CustomPortRequiresAbsoluteUrlError)
|
||||
|
||||
setDefaultSshValues()
|
||||
onView(withId(R.id.server_url)).perform(replaceText(""))
|
||||
assertEquals(activity.updateUrl(), GitUpdateUrlResult.EmptyHostnameError)
|
||||
|
||||
setDefaultSshValues()
|
||||
onView(withId(R.id.server_port)).perform(replaceText("xyz_is_not_a_port"))
|
||||
assertEquals(activity.updateUrl(), GitUpdateUrlResult.NonNumericPortError)
|
||||
|
||||
setDefaultHttpsValues()
|
||||
onView(withId(R.id.server_port)).perform(replaceText("xyz_is_not_a_port"))
|
||||
assertEquals(activity.updateUrl(), GitUpdateUrlResult.NonNumericPortError)
|
||||
|
||||
setDefaultHttpsValues()
|
||||
onView(withId(R.id.server_url)).perform(replaceText(""))
|
||||
assertEquals(activity.updateUrl(), GitUpdateUrlResult.EmptyHostnameError)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun urlIsConstructedCorrectly() {
|
||||
setDefaultSshValues()
|
||||
activity.updateUrl()
|
||||
assertEquals("john_doe@github.com:john_doe/my_secret_repository", activity.url)
|
||||
|
||||
setDefaultSshValues()
|
||||
onView(withId(R.id.server_port)).perform(replaceText("69420"))
|
||||
onView(withId(R.id.server_url)).perform(replaceText("192.168.0.102"))
|
||||
onView(withId(R.id.server_path)).perform(replaceText("/home/john_doe/my_secret_repository"))
|
||||
activity.updateUrl()
|
||||
assertEquals("ssh://john_doe@192.168.0.102:69420/home/john_doe/my_secret_repository", activity.url)
|
||||
|
||||
setDefaultHttpsValues()
|
||||
activity.updateUrl()
|
||||
assertEquals("https://github.com/john_doe/my_secret_repository", activity.url)
|
||||
|
||||
setDefaultHttpsValues()
|
||||
onView(withId(R.id.server_port)).perform(replaceText("69420"))
|
||||
onView(withId(R.id.server_url)).perform(replaceText("192.168.0.102"))
|
||||
onView(withId(R.id.server_path)).perform(replaceText("/home/john_doe/my_secret_repository"))
|
||||
activity.updateUrl()
|
||||
assertEquals("https://192.168.0.102:69420/home/john_doe/my_secret_repository", activity.url)
|
||||
}
|
||||
|
||||
private fun <T> callMethod(message: String = "", viewMethod: (view: T) -> Unit): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getDescription(): String {
|
||||
return if (message.isBlank()) viewMethod.toString() else message
|
||||
}
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
viewMethod(view!! as T)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDefaultHttpsValues() {
|
||||
onView(withId(R.id.clone_protocol_group)).perform(callMethod<MaterialButtonToggleGroup> {
|
||||
it.check(R.id.clone_protocol_https)
|
||||
})
|
||||
onView(withId(R.id.connection_mode_group)).perform(callMethod<MaterialButtonToggleGroup> {
|
||||
it.check(R.id.connection_mode_password)
|
||||
})
|
||||
onView(withId(R.id.server_path)).perform(replaceText("john_doe/my_secret_repository"))
|
||||
onView(withId(R.id.server_port)).perform(replaceText(""))
|
||||
onView(withId(R.id.server_url)).perform(replaceText("github.com"))
|
||||
onView(withId(R.id.server_user)).perform(replaceText("john_doe"))
|
||||
}
|
||||
|
||||
private fun setDefaultSshValues() {
|
||||
onView(withId(R.id.clone_protocol_group)).perform(callMethod<MaterialButtonToggleGroup> {
|
||||
it.check(R.id.clone_protocol_ssh)
|
||||
})
|
||||
onView(withId(R.id.connection_mode_group)).perform(callMethod<MaterialButtonToggleGroup> {
|
||||
it.check(R.id.connection_mode_ssh_key)
|
||||
})
|
||||
onView(withId(R.id.server_path)).perform(replaceText("john_doe/my_secret_repository"))
|
||||
onView(withId(R.id.server_port)).perform(replaceText(""))
|
||||
onView(withId(R.id.server_url)).perform(replaceText("github.com"))
|
||||
onView(withId(R.id.server_user)).perform(replaceText("john_doe"))
|
||||
}
|
||||
}
|
|
@ -10,12 +10,12 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
|||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.ajalt.timberkt.Timber.DebugTree
|
||||
import com.github.ajalt.timberkt.Timber.plant
|
||||
import com.zeapo.pwdstore.git.config.setUpBouncyCastleForSshj
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
|
||||
@Suppress("Unused")
|
||||
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
@ -30,6 +30,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
|
|||
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
|
||||
setNightMode()
|
||||
setUpBouncyCastleForSshj()
|
||||
runMigrations(applicationContext)
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
|
@ -44,7 +45,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
|
|||
}
|
||||
|
||||
private fun setNightMode() {
|
||||
AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME, getString(R.string.app_theme_def))) {
|
||||
AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME) ?: getString(R.string.app_theme_def)) {
|
||||
"light" -> MODE_NIGHT_NO
|
||||
"dark" -> MODE_NIGHT_YES
|
||||
"follow_system" -> MODE_NIGHT_FOLLOW_SYSTEM
|
||||
|
|
|
@ -15,10 +15,10 @@ import android.os.Build
|
|||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.ajalt.timberkt.d
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.clipboard
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -46,7 +46,7 @@ class ClipboardService : Service() {
|
|||
|
||||
ACTION_START -> {
|
||||
val time = try {
|
||||
Integer.parseInt(settings.getString(PreferenceKeys.GENERAL_SHOW_TIME, "45") as String)
|
||||
Integer.parseInt(settings.getString(PreferenceKeys.GENERAL_SHOW_TIME) ?: "45")
|
||||
} catch (e: NumberFormatException) {
|
||||
45
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.os.Bundle
|
|||
import android.os.Handler
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.crypto.DecryptActivity
|
||||
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
|
|
83
app/src/main/java/com/zeapo/pwdstore/Migrations.kt
Normal file
83
app/src/main/java/com/zeapo/pwdstore/Migrations.kt
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.zeapo.pwdstore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.github.ajalt.timberkt.e
|
||||
import com.github.ajalt.timberkt.i
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.git.config.Protocol
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.net.URI
|
||||
|
||||
fun runMigrations(context: Context) {
|
||||
migrateToGitUrlBasedConfig(context)
|
||||
}
|
||||
|
||||
private fun migrateToGitUrlBasedConfig(context: Context) {
|
||||
val serverHostname = context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_SERVER)
|
||||
?: return
|
||||
i { "Migrating to URL-based Git config" }
|
||||
val serverPort = context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_PORT) ?: ""
|
||||
val serverUser = context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_USERNAME) ?: ""
|
||||
val serverPath = context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_LOCATION) ?: ""
|
||||
val protocol = Protocol.fromString(context.sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_PROTOCOL))
|
||||
|
||||
// Whether we need the leading ssh:// depends on the use of a custom port.
|
||||
val hostnamePart = serverHostname.removePrefix("ssh://")
|
||||
val url = when (protocol) {
|
||||
Protocol.Ssh -> {
|
||||
val userPart = if (serverUser.isEmpty()) "" else "${serverUser.trimEnd('@')}@"
|
||||
val portPart =
|
||||
if (serverPort == "22" || serverPort.isEmpty()) "" else ":$serverPort"
|
||||
if (portPart.isEmpty()) {
|
||||
"$userPart$hostnamePart:$serverPath"
|
||||
} else {
|
||||
// Only absolute paths are supported with custom ports.
|
||||
if (!serverPath.startsWith('/'))
|
||||
null
|
||||
else
|
||||
// We have to specify the ssh scheme as this is the only way to pass a custom
|
||||
// port.
|
||||
"ssh://$userPart$hostnamePart$portPart$serverPath"
|
||||
}
|
||||
}
|
||||
Protocol.Https -> {
|
||||
val portPart =
|
||||
if (serverPort == "443" || serverPort.isEmpty()) "" else ":$serverPort"
|
||||
val pathPart = serverPath.trimStart('/', ':')
|
||||
val urlWithFreeEntryScheme = "$hostnamePart$portPart/$pathPart"
|
||||
val url = when {
|
||||
urlWithFreeEntryScheme.startsWith("https://") -> urlWithFreeEntryScheme
|
||||
urlWithFreeEntryScheme.startsWith("http://") -> urlWithFreeEntryScheme.replaceFirst("http", "https")
|
||||
else -> "https://$urlWithFreeEntryScheme"
|
||||
}
|
||||
try {
|
||||
if (URI(url).rawAuthority != null)
|
||||
url
|
||||
else
|
||||
null
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.sharedPrefs.edit {
|
||||
remove(PreferenceKeys.GIT_REMOTE_LOCATION)
|
||||
remove(PreferenceKeys.GIT_REMOTE_PORT)
|
||||
remove(PreferenceKeys.GIT_REMOTE_SERVER)
|
||||
remove(PreferenceKeys.GIT_REMOTE_USERNAME)
|
||||
}
|
||||
if (url == null || !GitSettings.updateUrlIfValid(url)) {
|
||||
e { "Failed to migrate to URL-based Git config, generated URL is invalid" }
|
||||
}
|
||||
}
|
||||
|
|
@ -26,12 +26,12 @@ import com.zeapo.pwdstore.git.BaseGitActivity
|
|||
import com.zeapo.pwdstore.git.GitOperationActivity
|
||||
import com.zeapo.pwdstore.git.GitServerConfigActivity
|
||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.ui.OnOffItemAnimator
|
||||
import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter
|
||||
import com.zeapo.pwdstore.ui.dialogs.ItemCreationBottomSheet
|
||||
import com.zeapo.pwdstore.utils.PasswordItem
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import com.zeapo.pwdstore.utils.viewBinding
|
||||
import java.io.File
|
||||
|
@ -91,8 +91,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
|||
} else {
|
||||
// When authentication is set to ConnectionMode.None then the only git operation we
|
||||
// can run is a pull, so automatically fallback to that.
|
||||
val operationId = when (ConnectionMode.fromString(settings.getString
|
||||
(PreferenceKeys.GIT_REMOTE_AUTH, null))) {
|
||||
val operationId = when (GitSettings.connectionMode) {
|
||||
ConnectionMode.None -> BaseGitActivity.REQUEST_PULL
|
||||
else -> BaseGitActivity.REQUEST_SYNC
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package com.zeapo.pwdstore
|
|||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ShortcutInfo.Builder
|
||||
import android.content.pm.ShortcutManager
|
||||
|
@ -34,7 +33,6 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.ajalt.timberkt.d
|
||||
import com.github.ajalt.timberkt.e
|
||||
import com.github.ajalt.timberkt.i
|
||||
|
@ -52,6 +50,7 @@ import com.zeapo.pwdstore.git.BaseGitActivity
|
|||
import com.zeapo.pwdstore.git.GitOperationActivity
|
||||
import com.zeapo.pwdstore.git.GitServerConfigActivity
|
||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.ui.dialogs.FolderCreationDialogFragment
|
||||
import com.zeapo.pwdstore.utils.PasswordItem
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
|
@ -66,6 +65,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository.PasswordSortOrder.Companion.g
|
|||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.commitChange
|
||||
import com.zeapo.pwdstore.utils.contains
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.isInsideRepository
|
||||
import com.zeapo.pwdstore.utils.listFilesRecursively
|
||||
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
||||
|
@ -114,8 +114,8 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||
private val directoryChangeAction = registerForActivityResult(StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) &&
|
||||
settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO, null) != null) {
|
||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO, null)
|
||||
settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) != null) {
|
||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
||||
val dir = externalRepoPath?.let { File(it) }
|
||||
if (dir != null &&
|
||||
dir.exists() &&
|
||||
|
@ -252,8 +252,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
val menuRes = when {
|
||||
ConnectionMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH, null))
|
||||
== ConnectionMode.None -> R.menu.main_menu_no_auth
|
||||
GitSettings.connectionMode == ConnectionMode.None -> R.menu.main_menu_no_auth
|
||||
PasswordRepository.isGitRepo() -> R.menu.main_menu_git
|
||||
else -> R.menu.main_menu_non_git
|
||||
}
|
||||
|
@ -403,7 +402,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||
|
||||
private fun initializeRepositoryInfo() {
|
||||
val externalRepo = settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
|
||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO, null)
|
||||
val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
||||
if (externalRepo && !hasRequiredStoragePermissions()) {
|
||||
return
|
||||
}
|
||||
|
@ -849,7 +848,7 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
|
|||
}
|
||||
.setNegativeButton(resources.getString(R.string.location_sdcard)) { _, _ ->
|
||||
settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, true) }
|
||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO, null)
|
||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
||||
if (externalRepo == null) {
|
||||
val intent = Intent(activity, UserPreference::class.java)
|
||||
intent.putExtra("operation", "git_external")
|
||||
|
|
|
@ -15,7 +15,6 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import androidx.recyclerview.selection.ItemKeyProvider
|
||||
import androidx.recyclerview.selection.Selection
|
||||
|
|
|
@ -34,7 +34,6 @@ import androidx.preference.EditTextPreference
|
|||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.github.ajalt.timberkt.Timber.tag
|
||||
import com.github.ajalt.timberkt.d
|
||||
|
@ -54,6 +53,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository
|
|||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.autofillManager
|
||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -141,11 +141,11 @@ class UserPreference : AppCompatActivity() {
|
|||
// Misc preferences
|
||||
val appVersionPreference = findPreference<Preference>(PreferenceKeys.APP_VERSION)
|
||||
|
||||
selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO, getString(R.string.no_repo_selected))
|
||||
selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) ?: getString(R.string.no_repo_selected)
|
||||
viewSshKeyPreference?.isVisible = sharedPreferences.getBoolean(PreferenceKeys.USE_GENERATED_KEY, false)
|
||||
deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
|
||||
clearClipboard20xPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.GENERAL_SHOW_TIME, "45")?.toInt() != 0
|
||||
openkeystoreIdPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null)?.isNotEmpty()
|
||||
clearClipboard20xPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toInt() != 0
|
||||
openkeystoreIdPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty()
|
||||
?: false
|
||||
|
||||
updateAutofillSettings()
|
||||
|
@ -171,9 +171,9 @@ class UserPreference : AppCompatActivity() {
|
|||
|
||||
clearSavedPassPreference?.onPreferenceClickListener = ClickListener {
|
||||
encryptedPreferences.edit {
|
||||
if (encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD, null) != null)
|
||||
if (encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD) != null)
|
||||
remove(PreferenceKeys.HTTPS_PASSWORD)
|
||||
else if (encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE, null) != null)
|
||||
else if (encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) != null)
|
||||
remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
|
||||
}
|
||||
updateClearSavedPassphrasePrefs()
|
||||
|
@ -226,7 +226,7 @@ class UserPreference : AppCompatActivity() {
|
|||
}
|
||||
|
||||
selectExternalGitRepositoryPreference?.summary =
|
||||
sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO, context.getString(R.string.no_repo_selected))
|
||||
sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) ?: context.getString(R.string.no_repo_selected)
|
||||
selectExternalGitRepositoryPreference?.onPreferenceClickListener = ClickListener {
|
||||
prefsActivity.selectExternalGitRepository()
|
||||
true
|
||||
|
@ -321,7 +321,7 @@ class UserPreference : AppCompatActivity() {
|
|||
prefsActivity.storeCustomDictionaryPath()
|
||||
true
|
||||
}
|
||||
val dictUri = sharedPreferences.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, "")
|
||||
val dictUri = sharedPreferences.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT) ?: ""
|
||||
|
||||
if (!TextUtils.isEmpty(dictUri)) {
|
||||
setCustomDictSummary(prefCustomXkpwdDictionary, Uri.parse(dictUri))
|
||||
|
@ -377,8 +377,8 @@ class UserPreference : AppCompatActivity() {
|
|||
|
||||
private fun updateClearSavedPassphrasePrefs() {
|
||||
clearSavedPassPreference?.apply {
|
||||
val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE, null)
|
||||
val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD, null)
|
||||
val sshPass = encryptedPreferences.getString(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE)
|
||||
val httpsPass = encryptedPreferences.getString(PreferenceKeys.HTTPS_PASSWORD)
|
||||
if (sshPass == null && httpsPass == null) {
|
||||
isVisible = false
|
||||
return@apply
|
||||
|
|
|
@ -23,6 +23,8 @@ import com.zeapo.pwdstore.R
|
|||
import com.zeapo.pwdstore.model.PasswordEntry
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
@ -37,11 +39,7 @@ private fun ByteArray.base64(): String {
|
|||
return Base64.encodeToString(this, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
private fun Context.getDefaultUsername(): String? {
|
||||
return PreferenceManager
|
||||
.getDefaultSharedPreferences(this)
|
||||
.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME, null)
|
||||
}
|
||||
private fun Context.getDefaultUsername() = sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME)
|
||||
|
||||
private fun stableHash(array: Collection<ByteArray>): String {
|
||||
val hashes = array.map { it.sha256().base64() }
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
package com.zeapo.pwdstore.autofill.oreo
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
|
|
|
@ -6,8 +6,8 @@ package com.zeapo.pwdstore.autofill.oreo
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Patterns
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
|
@ -69,9 +69,10 @@ fun getSuffixPlusUpToOne(domain: String, suffix: String): String? {
|
|||
}
|
||||
|
||||
fun getCustomSuffixes(context: Context): Sequence<String> {
|
||||
return context.sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES, "")!!
|
||||
.splitToSequence('\n')
|
||||
.filter { it.isNotBlank() && it.first() != '.' && it.last() != '.' }
|
||||
return context.sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES)
|
||||
?.splitToSequence('\n')
|
||||
?.filter { it.isNotBlank() && it.first() != '.' && it.last() != '.' }
|
||||
?: emptySequence()
|
||||
}
|
||||
|
||||
suspend fun getCanonicalSuffix(context: Context, domain: String): String {
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.zeapo.pwdstore.R
|
|||
import com.zeapo.pwdstore.utils.OPENPGP_PROVIDER
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.clipboard
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import com.zeapo.pwdstore.utils.snackbar
|
||||
import java.io.File
|
||||
|
@ -254,8 +255,7 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
|
|||
|
||||
var clearAfter = 45
|
||||
try {
|
||||
clearAfter = (settings.getString(PreferenceKeys.GENERAL_SHOW_TIME, "45")
|
||||
?: "45").toInt()
|
||||
clearAfter = (settings.getString(PreferenceKeys.GENERAL_SHOW_TIME) ?: "45").toInt()
|
||||
} catch (_: NumberFormatException) {
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
|
|||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.commitChange
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.isInsideRepository
|
||||
import com.zeapo.pwdstore.utils.snackbar
|
||||
import com.zeapo.pwdstore.utils.viewBinding
|
||||
|
@ -220,7 +221,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||
}
|
||||
|
||||
private fun generatePassword() {
|
||||
when (settings.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE, KEY_PWGEN_TYPE_CLASSIC)) {
|
||||
when (settings.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) ?: KEY_PWGEN_TYPE_CLASSIC) {
|
||||
KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment()
|
||||
.show(supportFragmentManager, "generator")
|
||||
KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment()
|
||||
|
|
|
@ -5,20 +5,15 @@
|
|||
package com.zeapo.pwdstore.git
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.ajalt.timberkt.Timber.tag
|
||||
import com.github.ajalt.timberkt.e
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||
import com.zeapo.pwdstore.git.config.Protocol
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
||||
import com.zeapo.pwdstore.git.operation.BreakOutOfDetached
|
||||
import com.zeapo.pwdstore.git.operation.CloneOperation
|
||||
|
@ -28,11 +23,6 @@ import com.zeapo.pwdstore.git.operation.PushOperation
|
|||
import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
|
||||
import com.zeapo.pwdstore.git.operation.SyncOperation
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
|
@ -41,39 +31,8 @@ import kotlinx.coroutines.launch
|
|||
*/
|
||||
abstract class BaseGitActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var protocol: Protocol
|
||||
lateinit var connectionMode: ConnectionMode
|
||||
var url: String? = null
|
||||
lateinit var serverHostname: String
|
||||
lateinit var serverPort: String
|
||||
lateinit var serverUser: String
|
||||
lateinit var serverPath: String
|
||||
lateinit var username: String
|
||||
lateinit var email: String
|
||||
lateinit var branch: String
|
||||
private var identityBuilder: SshApiSessionFactory.IdentityBuilder? = null
|
||||
private var identity: SshApiSessionFactory.ApiIdentity? = null
|
||||
lateinit var settings: SharedPreferences
|
||||
private set
|
||||
private lateinit var encryptedSettings: SharedPreferences
|
||||
|
||||
@CallSuper
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
settings = sharedPrefs
|
||||
encryptedSettings = getEncryptedPrefs("git_operation")
|
||||
protocol = Protocol.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_PROTOCOL, null))
|
||||
connectionMode = ConnectionMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH, null))
|
||||
serverHostname = settings.getString(PreferenceKeys.GIT_REMOTE_SERVER, null) ?: ""
|
||||
serverPort = settings.getString(PreferenceKeys.GIT_REMOTE_PORT, null) ?: ""
|
||||
serverUser = settings.getString(PreferenceKeys.GIT_REMOTE_USERNAME, null) ?: ""
|
||||
serverPath = settings.getString(PreferenceKeys.GIT_REMOTE_LOCATION, null) ?: ""
|
||||
username = settings.getString(PreferenceKeys.GIT_CONFIG_USER_NAME, null) ?: ""
|
||||
email = settings.getString(PreferenceKeys.GIT_CONFIG_USER_EMAIL, null) ?: ""
|
||||
branch = settings.getString(PreferenceKeys.GIT_BRANCH_NAME, null) ?: "master"
|
||||
updateUrl()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
|
@ -95,77 +54,6 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
enum class GitUpdateUrlResult(val textRes: Int) {
|
||||
Ok(0),
|
||||
CustomPortRequiresAbsoluteUrlError(R.string.git_config_error_custom_port_absolute),
|
||||
EmptyHostnameError(R.string.git_config_error_hostname_empty),
|
||||
GenericError(R.string.git_config_error_generic),
|
||||
NonNumericPortError(R.string.git_config_error_nonnumeric_port)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the [url] field with the values that build it up. This function returns a
|
||||
* [GitUpdateUrlResult] indicating whether the values could be used to build a URL and only adds
|
||||
* the `origin` remote when they were. This check is not perfect, it is mostly meant to catch
|
||||
* syntax-related typos.
|
||||
*/
|
||||
fun updateUrl(): GitUpdateUrlResult {
|
||||
if (serverHostname.isEmpty())
|
||||
return GitUpdateUrlResult.EmptyHostnameError
|
||||
if (!serverPort.isDigitsOnly())
|
||||
return GitUpdateUrlResult.NonNumericPortError
|
||||
|
||||
val previousUrl = url ?: ""
|
||||
// Whether we need the leading ssh:// depends on the use of a custom port.
|
||||
val hostnamePart = serverHostname.removePrefix("ssh://")
|
||||
val newUrl = when (protocol) {
|
||||
Protocol.Ssh -> {
|
||||
val userPart = if (serverUser.isEmpty()) "" else "${serverUser.trimEnd('@')}@"
|
||||
val portPart =
|
||||
if (serverPort == "22" || serverPort.isEmpty()) "" else ":$serverPort"
|
||||
if (portPart.isEmpty()) {
|
||||
"$userPart$hostnamePart:$serverPath"
|
||||
} else {
|
||||
// Only absolute paths are supported with custom ports.
|
||||
if (!serverPath.startsWith('/'))
|
||||
return GitUpdateUrlResult.CustomPortRequiresAbsoluteUrlError
|
||||
val pathPart = serverPath
|
||||
// We have to specify the ssh scheme as this is the only way to pass a custom
|
||||
// port.
|
||||
"ssh://$userPart$hostnamePart$portPart$pathPart"
|
||||
}
|
||||
}
|
||||
Protocol.Https -> {
|
||||
val portPart =
|
||||
if (serverPort == "443" || serverPort.isEmpty()) "" else ":$serverPort"
|
||||
val pathPart = serverPath.trimStart('/', ':')
|
||||
val urlWithFreeEntryScheme = "$hostnamePart$portPart/$pathPart"
|
||||
val url = when {
|
||||
urlWithFreeEntryScheme.startsWith("https://") -> urlWithFreeEntryScheme
|
||||
urlWithFreeEntryScheme.startsWith("http://") -> urlWithFreeEntryScheme.replaceFirst("http", "https")
|
||||
else -> "https://$urlWithFreeEntryScheme"
|
||||
}
|
||||
try {
|
||||
if (URI(url).rawAuthority != null)
|
||||
url
|
||||
else
|
||||
return GitUpdateUrlResult.GenericError
|
||||
} catch (_: Exception) {
|
||||
return GitUpdateUrlResult.GenericError
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PasswordRepository.isInitialized)
|
||||
PasswordRepository.addRemote("origin", newUrl, true)
|
||||
// When the server changes, remote password and host key file should be deleted.
|
||||
if (previousUrl.isNotEmpty() && newUrl != previousUrl) {
|
||||
encryptedSettings.edit { remove(PreferenceKeys.HTTPS_PASSWORD) }
|
||||
File("$filesDir/.host_key").delete()
|
||||
}
|
||||
url = newUrl
|
||||
return GitUpdateUrlResult.Ok
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to launch the requested Git operation. Depending on the configured auth, it may not
|
||||
* be possible to launch the operation immediately. In that case, this function may launch an
|
||||
|
@ -176,7 +64,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||
* @param operation The type of git operation to launch
|
||||
*/
|
||||
suspend fun launchGitOperation(operation: Int) {
|
||||
if (url == null) {
|
||||
if (GitSettings.url == null) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return
|
||||
|
@ -185,7 +73,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||
// Before launching the operation with OpenKeychain auth, we need to issue several requests
|
||||
// to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
|
||||
// we just need to keep calling it until it returns a completed ApiIdentity.
|
||||
if (connectionMode == ConnectionMode.OpenKeychain && identity == null) {
|
||||
if (GitSettings.connectionMode == ConnectionMode.OpenKeychain && identity == null) {
|
||||
// Lazy initialization of the IdentityBuilder
|
||||
if (identityBuilder == null) {
|
||||
identityBuilder = SshApiSessionFactory.IdentityBuilder(this)
|
||||
|
@ -199,7 +87,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||
|
||||
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory())
|
||||
val op = when (operation) {
|
||||
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, url!!, this)
|
||||
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, GitSettings.url!!, this)
|
||||
REQUEST_PULL -> PullOperation(localDir, this)
|
||||
REQUEST_PUSH -> PushOperation(localDir, this)
|
||||
REQUEST_SYNC -> SyncOperation(localDir, this)
|
||||
|
@ -213,7 +101,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
|
|||
return
|
||||
}
|
||||
}
|
||||
op.executeAfterAuthentication(connectionMode, serverUser, identity)
|
||||
op.executeAfterAuthentication(GitSettings.connectionMode, identity)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||
|
|
|
@ -7,15 +7,14 @@ package com.zeapo.pwdstore.git
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Patterns
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.databinding.ActivityGitConfigBinding
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.viewBinding
|
||||
import kotlinx.coroutines.launch
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
|
@ -29,16 +28,16 @@ class GitConfigActivity : BaseGitActivity() {
|
|||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (username.isEmpty())
|
||||
if (GitSettings.authorName.isEmpty())
|
||||
binding.gitUserName.requestFocus()
|
||||
else
|
||||
binding.gitUserName.setText(username)
|
||||
binding.gitUserEmail.setText(email)
|
||||
binding.gitUserName.setText(GitSettings.authorName)
|
||||
binding.gitUserEmail.setText(GitSettings.authorEmail)
|
||||
val repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory())
|
||||
if (repo != null) {
|
||||
try {
|
||||
val objectId = repo.resolve(Constants.HEAD)
|
||||
val ref = repo.getRef("refs/heads/$branch")
|
||||
val ref = repo.getRef("refs/heads/${GitSettings.branch}")
|
||||
val head = if (ref.objectId.equals(objectId)) ref.name else "DETACHED"
|
||||
binding.gitCommitHash.text = String.format("%s (%s)", objectId.abbreviate(8).name(), head)
|
||||
|
||||
|
@ -60,12 +59,10 @@ class GitConfigActivity : BaseGitActivity() {
|
|||
.setPositiveButton(getString(R.string.dialog_ok), null)
|
||||
.show()
|
||||
} else {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_CONFIG_USER_EMAIL, email)
|
||||
putString(PreferenceKeys.GIT_CONFIG_USER_NAME, name)
|
||||
}
|
||||
PasswordRepository.setUserName(name)
|
||||
PasswordRepository.setUserEmail(email)
|
||||
GitSettings.authorEmail = email
|
||||
GitSettings.authorName = name
|
||||
PasswordRepository.setGitAuthorEmail(email)
|
||||
PasswordRepository.setGitAuthorName(name)
|
||||
Snackbar.make(binding.root, getString(R.string.git_server_config_save_success), Snackbar.LENGTH_SHORT).show()
|
||||
Handler().postDelayed(500) { finish() }
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.UserPreference
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -57,7 +58,7 @@ open class GitOperationActivity : BaseGitActivity() {
|
|||
* @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
|
||||
*/
|
||||
private suspend fun syncRepository(operation: Int) {
|
||||
if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isNullOrEmpty())
|
||||
if (GitSettings.url.isNullOrEmpty())
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(getString(R.string.set_information_dialog_text))
|
||||
.setPositiveButton(getString(R.string.dialog_positive)) { _, _ ->
|
||||
|
@ -72,7 +73,7 @@ open class GitOperationActivity : BaseGitActivity() {
|
|||
.show()
|
||||
else {
|
||||
// check that the remote origin is here, else add it
|
||||
PasswordRepository.addRemote("origin", url!!, true)
|
||||
PasswordRepository.addRemote("origin", GitSettings.url!!, true)
|
||||
launchGitOperation(operation)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,16 @@ package com.zeapo.pwdstore.git
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.View
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.databinding.ActivityGitCloneBinding
|
||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.git.config.Protocol
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.viewBinding
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -40,22 +38,22 @@ class GitServerConfigActivity : BaseGitActivity() {
|
|||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
binding.cloneProtocolGroup.check(when (protocol) {
|
||||
binding.cloneProtocolGroup.check(when (GitSettings.protocol) {
|
||||
Protocol.Ssh -> R.id.clone_protocol_ssh
|
||||
Protocol.Https -> R.id.clone_protocol_https
|
||||
})
|
||||
binding.cloneProtocolGroup.addOnButtonCheckedListener { _, checkedId, checked ->
|
||||
if (checked) {
|
||||
when (checkedId) {
|
||||
R.id.clone_protocol_https -> protocol = Protocol.Https
|
||||
R.id.clone_protocol_ssh -> protocol = Protocol.Ssh
|
||||
R.id.clone_protocol_https -> GitSettings.protocol = Protocol.Https
|
||||
R.id.clone_protocol_ssh -> GitSettings.protocol = Protocol.Ssh
|
||||
}
|
||||
updateConnectionModeToggleGroup()
|
||||
}
|
||||
}
|
||||
|
||||
binding.connectionModeGroup.apply {
|
||||
when (connectionMode) {
|
||||
when (GitSettings.connectionMode) {
|
||||
ConnectionMode.SshKey -> check(R.id.connection_mode_ssh_key)
|
||||
ConnectionMode.Password -> check(R.id.connection_mode_password)
|
||||
ConnectionMode.OpenKeychain -> check(R.id.connection_mode_open_keychain)
|
||||
|
@ -63,82 +61,37 @@ class GitServerConfigActivity : BaseGitActivity() {
|
|||
}
|
||||
addOnButtonCheckedListener { _, _, _ ->
|
||||
when (checkedButtonId) {
|
||||
R.id.connection_mode_ssh_key -> connectionMode = ConnectionMode.SshKey
|
||||
R.id.connection_mode_open_keychain -> connectionMode = ConnectionMode.OpenKeychain
|
||||
R.id.connection_mode_password -> connectionMode = ConnectionMode.Password
|
||||
View.NO_ID -> connectionMode = ConnectionMode.None
|
||||
R.id.connection_mode_ssh_key -> GitSettings.connectionMode = ConnectionMode.SshKey
|
||||
R.id.connection_mode_open_keychain -> GitSettings.connectionMode = ConnectionMode.OpenKeychain
|
||||
R.id.connection_mode_password -> GitSettings.connectionMode = ConnectionMode.Password
|
||||
View.NO_ID -> GitSettings.connectionMode = ConnectionMode.None
|
||||
}
|
||||
}
|
||||
}
|
||||
updateConnectionModeToggleGroup()
|
||||
|
||||
binding.serverUrl.apply {
|
||||
setText(serverHostname)
|
||||
doOnTextChanged { text, _, _, _ ->
|
||||
serverHostname = text.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
binding.serverPort.apply {
|
||||
setText(serverPort)
|
||||
doOnTextChanged { text, _, _, _ ->
|
||||
serverPort = text.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
binding.serverUser.apply {
|
||||
if (serverUser.isEmpty()) {
|
||||
requestFocus()
|
||||
} else {
|
||||
setText(serverUser)
|
||||
}
|
||||
doOnTextChanged { text, _, _, _ ->
|
||||
serverUser = text.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
binding.serverPath.apply {
|
||||
setText(serverPath)
|
||||
doOnTextChanged { text, _, _, _ ->
|
||||
serverPath = text.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
binding.serverBranch.apply {
|
||||
setText(branch)
|
||||
doOnTextChanged { text, _, _, _ ->
|
||||
branch = text.toString().trim()
|
||||
}
|
||||
}
|
||||
binding.serverUrl.setText(GitSettings.url)
|
||||
binding.serverBranch.setText(GitSettings.branch)
|
||||
|
||||
binding.saveButton.setOnClickListener {
|
||||
if (isClone && PasswordRepository.getRepository(null) == null)
|
||||
PasswordRepository.initialize()
|
||||
when (val result = updateUrl()) {
|
||||
GitUpdateUrlResult.Ok -> {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_REMOTE_PROTOCOL, protocol.pref)
|
||||
putString(PreferenceKeys.GIT_REMOTE_AUTH, connectionMode.pref)
|
||||
putString(PreferenceKeys.GIT_REMOTE_SERVER, serverHostname)
|
||||
putString(PreferenceKeys.GIT_REMOTE_PORT, serverPort)
|
||||
putString(PreferenceKeys.GIT_REMOTE_USERNAME, serverUser)
|
||||
putString(PreferenceKeys.GIT_REMOTE_LOCATION, serverPath)
|
||||
putString(PreferenceKeys.GIT_BRANCH_NAME, branch)
|
||||
}
|
||||
GitSettings.branch = binding.serverBranch.text.toString().trim()
|
||||
if (GitSettings.updateUrlIfValid(binding.serverUrl.text.toString().trim())) {
|
||||
if (!isClone) {
|
||||
Snackbar.make(binding.root, getString(R.string.git_server_config_save_success), Snackbar.LENGTH_SHORT).show()
|
||||
Handler().postDelayed(500) { finish() }
|
||||
} else {
|
||||
cloneRepository()
|
||||
}
|
||||
}
|
||||
else -> Snackbar.make(binding.root, getString(R.string.git_server_config_save_error_prefix, getString(result.textRes)), Snackbar.LENGTH_LONG).show()
|
||||
} else {
|
||||
Snackbar.make(binding.root, getString(R.string.git_server_config_save_error), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConnectionModeToggleGroup() {
|
||||
if (protocol == Protocol.Ssh) {
|
||||
if (GitSettings.protocol == Protocol.Ssh) {
|
||||
// Reset connection mode to SSH key if the current value (none) is not valid for SSH
|
||||
if (binding.connectionModeGroup.checkedButtonIds.isEmpty())
|
||||
binding.connectionModeGroup.check(R.id.connection_mode_ssh_key)
|
||||
|
@ -150,7 +103,7 @@ class GitServerConfigActivity : BaseGitActivity() {
|
|||
// Reset connection mode to password if the current value is not valid for HTTPS
|
||||
// Important note: This has to happen before disabling the other toggle buttons or they
|
||||
// won't uncheck.
|
||||
if (connectionMode !in listOf(ConnectionMode.None, ConnectionMode.Password))
|
||||
if (GitSettings.connectionMode !in listOf(ConnectionMode.None, ConnectionMode.Password))
|
||||
binding.connectionModeGroup.check(R.id.connection_mode_password)
|
||||
binding.connectionModeSshKey.isEnabled = false
|
||||
binding.connectionModeOpenKeychain.isEnabled = false
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.git.config
|
||||
|
||||
enum class ConnectionMode(val pref: String) {
|
||||
SshKey("ssh-key"),
|
||||
Password("username/password"),
|
||||
OpenKeychain("OpenKeychain"),
|
||||
None("None"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
private val map = values().associateBy(ConnectionMode::pref)
|
||||
fun fromString(type: String?): ConnectionMode {
|
||||
return map[type ?: return SshKey]
|
||||
?: throw IllegalArgumentException("$type is not a valid ConnectionMode")
|
||||
}
|
||||
}
|
||||
}
|
115
app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt
Normal file
115
app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.git.config
|
||||
|
||||
import androidx.core.content.edit
|
||||
import com.zeapo.pwdstore.Application
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
import org.eclipse.jgit.transport.URIish
|
||||
|
||||
enum class Protocol(val pref: String) {
|
||||
Ssh("ssh://"),
|
||||
Https("https://"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
private val map = values().associateBy(Protocol::pref)
|
||||
fun fromString(type: String?): Protocol {
|
||||
return map[type ?: return Ssh]
|
||||
?: throw IllegalArgumentException("$type is not a valid Protocol")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ConnectionMode(val pref: String) {
|
||||
SshKey("ssh-key"),
|
||||
Password("username/password"),
|
||||
OpenKeychain("OpenKeychain"),
|
||||
None("None"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
private val map = values().associateBy(ConnectionMode::pref)
|
||||
fun fromString(type: String?): ConnectionMode {
|
||||
return map[type ?: return SshKey]
|
||||
?: throw IllegalArgumentException("$type is not a valid ConnectionMode")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GitSettings {
|
||||
|
||||
private const val DEFAULT_BRANCH = "master"
|
||||
|
||||
private val settings by lazy { Application.instance.sharedPrefs }
|
||||
private val encryptedSettings by lazy { Application.instance.getEncryptedPrefs("git_operation") }
|
||||
|
||||
var protocol
|
||||
get() = Protocol.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_PROTOCOL))
|
||||
set(value) {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_REMOTE_PROTOCOL, value.pref)
|
||||
}
|
||||
}
|
||||
var connectionMode
|
||||
get() = ConnectionMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
|
||||
set(value) {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_REMOTE_AUTH, value.pref)
|
||||
}
|
||||
}
|
||||
var url
|
||||
get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL)
|
||||
private set(value) {
|
||||
require(value != null)
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_REMOTE_URL, value)
|
||||
}
|
||||
if (PasswordRepository.isInitialized)
|
||||
PasswordRepository.addRemote("origin", value, true)
|
||||
// When the server changes, remote password and host key file should be deleted.
|
||||
encryptedSettings.edit { remove(PreferenceKeys.HTTPS_PASSWORD) }
|
||||
File("${Application.instance.filesDir}/.host_key").delete()
|
||||
}
|
||||
var authorName
|
||||
get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME) ?: ""
|
||||
set(value) {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME, value)
|
||||
}
|
||||
}
|
||||
var authorEmail
|
||||
get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL) ?: ""
|
||||
set(value) {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL, value)
|
||||
}
|
||||
}
|
||||
var branch
|
||||
get() = settings.getString(PreferenceKeys.GIT_BRANCH_NAME) ?: DEFAULT_BRANCH
|
||||
set(value) {
|
||||
settings.edit {
|
||||
putString(PreferenceKeys.GIT_BRANCH_NAME, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateUrlIfValid(newUrl: String): Boolean {
|
||||
try {
|
||||
URIish(newUrl)
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
if (newUrl != url)
|
||||
url = newUrl
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
package com.zeapo.pwdstore.git.config
|
||||
|
||||
enum class Protocol(val pref: String) {
|
||||
Ssh("ssh://"),
|
||||
Https("https://"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
private val map = values().associateBy(Protocol::pref)
|
||||
fun fromString(type: String?): Protocol {
|
||||
return map[type ?: return Ssh]
|
||||
?: throw IllegalArgumentException("$type is not a valid Protocol")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,18 +18,12 @@ import com.jcraft.jsch.Identity;
|
|||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
import com.zeapo.pwdstore.R;
|
||||
import com.zeapo.pwdstore.git.BaseGitActivity;
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys;
|
||||
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.CredentialsProviderUserInfo;
|
||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||
import org.eclipse.jgit.transport.OpenSshConfig;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
import org.eclipse.jgit.util.Base64;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.openintents.ssh.authentication.ISshAuthenticationService;
|
||||
|
@ -52,11 +46,9 @@ public class SshApiSessionFactory extends JschConfigSessionFactory {
|
|||
*/
|
||||
public static final int POST_SIGNATURE = 301;
|
||||
|
||||
private final String username;
|
||||
private final Identity identity;
|
||||
|
||||
public SshApiSessionFactory(String username, Identity identity) {
|
||||
this.username = username;
|
||||
public SshApiSessionFactory(Identity identity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
|
@ -74,32 +66,6 @@ public class SshApiSessionFactory extends JschConfigSessionFactory {
|
|||
protected void configure(@NonNull OpenSshConfig.Host hc, Session session) {
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.setConfig("PreferredAuthentications", "publickey");
|
||||
|
||||
CredentialsProvider provider =
|
||||
new CredentialsProvider() {
|
||||
@Override
|
||||
public boolean isInteractive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(CredentialItem... items) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean get(URIish uri, CredentialItem... items)
|
||||
throws UnsupportedCredentialItem {
|
||||
for (CredentialItem item : items) {
|
||||
if (item instanceof CredentialItem.Username) {
|
||||
((CredentialItem.Username) item).setValue(username);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
UserInfo userInfo = new CredentialsProviderUserInfo(session, provider);
|
||||
session.setUserInfo(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -95,10 +95,10 @@ abstract class InteractivePasswordFinder : PasswordFinder {
|
|||
final override fun shouldRetry(resource: Resource<*>?) = true
|
||||
}
|
||||
|
||||
class SshjSessionFactory(private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
|
||||
class SshjSessionFactory(private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
|
||||
|
||||
override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession {
|
||||
return SshjSession(uri, username, authData, hostKeyFile).connect()
|
||||
return SshjSession(uri, uri.user, authData, hostKeyFile).connect()
|
||||
}
|
||||
|
||||
fun clearCredentials() {
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.zeapo.pwdstore.R
|
|||
import com.zeapo.pwdstore.UserPreference
|
||||
import com.zeapo.pwdstore.git.ErrorMessages
|
||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
||||
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
||||
import com.zeapo.pwdstore.git.config.SshAuthData
|
||||
|
@ -47,18 +48,15 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment
|
|||
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
|
||||
protected val repository = PasswordRepository.getRepository(gitDir)
|
||||
protected val git = Git(repository)
|
||||
protected val remoteBranch = PreferenceManager
|
||||
.getDefaultSharedPreferences(callingActivity.applicationContext)
|
||||
.getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
|
||||
protected val remoteBranch = GitSettings.branch
|
||||
|
||||
private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() {
|
||||
private class PasswordFinderCredentialsProvider(private val passwordFinder: PasswordFinder) : CredentialsProvider() {
|
||||
|
||||
override fun isInteractive() = true
|
||||
|
||||
override fun get(uri: URIish?, vararg items: CredentialItem): Boolean {
|
||||
for (item in items) {
|
||||
when (item) {
|
||||
is CredentialItem.Username -> item.value = username
|
||||
is CredentialItem.Password -> item.value = passwordFinder.reqPassword(null)
|
||||
else -> UnsupportedCredentialItem(uri, item.javaClass.name)
|
||||
}
|
||||
|
@ -67,26 +65,26 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment
|
|||
}
|
||||
|
||||
override fun supports(vararg items: CredentialItem) = items.all {
|
||||
it is CredentialItem.Username || it is CredentialItem.Password
|
||||
it is CredentialItem.Password
|
||||
}
|
||||
}
|
||||
|
||||
private fun withPasswordAuthentication(username: String, passwordFinder: InteractivePasswordFinder): GitOperation {
|
||||
val sessionFactory = SshjSessionFactory(username, SshAuthData.Password(passwordFinder), hostKeyFile)
|
||||
private fun withPasswordAuthentication(passwordFinder: InteractivePasswordFinder): GitOperation {
|
||||
val sessionFactory = SshjSessionFactory(SshAuthData.Password(passwordFinder), hostKeyFile)
|
||||
SshSessionFactory.setInstance(sessionFactory)
|
||||
this.provider = PasswordFinderCredentialsProvider(username, passwordFinder)
|
||||
this.provider = PasswordFinderCredentialsProvider(passwordFinder)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun withPublicKeyAuthentication(username: String, passphraseFinder: InteractivePasswordFinder): GitOperation {
|
||||
val sessionFactory = SshjSessionFactory(username, SshAuthData.PublicKeyFile(sshKeyFile, passphraseFinder), hostKeyFile)
|
||||
private fun withPublicKeyAuthentication(passphraseFinder: InteractivePasswordFinder): GitOperation {
|
||||
val sessionFactory = SshjSessionFactory(SshAuthData.PublicKeyFile(sshKeyFile, passphraseFinder), hostKeyFile)
|
||||
SshSessionFactory.setInstance(sessionFactory)
|
||||
this.provider = null
|
||||
return this
|
||||
}
|
||||
|
||||
private fun withOpenKeychainAuthentication(username: String, identity: SshApiSessionFactory.ApiIdentity?): GitOperation {
|
||||
SshSessionFactory.setInstance(SshApiSessionFactory(username, identity))
|
||||
private fun withOpenKeychainAuthentication(identity: SshApiSessionFactory.ApiIdentity?): GitOperation {
|
||||
SshSessionFactory.setInstance(SshApiSessionFactory(identity))
|
||||
this.provider = null
|
||||
return this
|
||||
}
|
||||
|
@ -117,7 +115,6 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment
|
|||
|
||||
suspend fun executeAfterAuthentication(
|
||||
connectionMode: ConnectionMode,
|
||||
username: String,
|
||||
identity: SshApiSessionFactory.ApiIdentity?
|
||||
) {
|
||||
when (connectionMode) {
|
||||
|
@ -136,12 +133,12 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment
|
|||
callingActivity.finish()
|
||||
}.show()
|
||||
} else {
|
||||
withPublicKeyAuthentication(username, CredentialFinder(callingActivity,
|
||||
connectionMode)).execute()
|
||||
withPublicKeyAuthentication(
|
||||
CredentialFinder(callingActivity, connectionMode)).execute()
|
||||
}
|
||||
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute()
|
||||
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(identity).execute()
|
||||
ConnectionMode.Password -> withPasswordAuthentication(
|
||||
username, CredentialFinder(callingActivity, connectionMode)).execute()
|
||||
CredentialFinder(callingActivity, connectionMode)).execute()
|
||||
ConnectionMode.None -> execute()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
package com.zeapo.pwdstore.pwgenxkpwd
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||
import com.zeapo.pwdstore.utils.getString
|
||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||
import java.io.File
|
||||
|
||||
|
@ -17,7 +17,7 @@ class XkpwdDictionary(context: Context) {
|
|||
|
||||
init {
|
||||
val prefs = context.sharedPrefs
|
||||
val uri = prefs.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, "")!!
|
||||
val uri = prefs.getString(PreferenceKeys.PREF_KEY_CUSTOM_DICT) ?: ""
|
||||
val customDictFile = File(context.filesDir, XKPWD_CUSTOM_DICT_FILE)
|
||||
|
||||
val lines = if (prefs.getBoolean(PreferenceKeys.PREF_KEY_IS_CUSTOM_DICT, false) &&
|
||||
|
|
|
@ -12,7 +12,6 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.content.edit
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jcraft.jsch.JSch
|
||||
import com.jcraft.jsch.KeyPair
|
||||
|
|
|
@ -10,7 +10,6 @@ import android.view.MotionEvent
|
|||
import android.view.View
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import androidx.recyclerview.selection.Selection
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
|
@ -101,6 +101,8 @@ fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
|
|||
val Context.sharedPrefs: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
|
||||
fun SharedPreferences.getString(key: String): String? = getString(key, null)
|
||||
|
||||
suspend fun FragmentActivity.commitChange(
|
||||
message: String,
|
||||
finishWithResultOnEnd: Intent? = null,
|
||||
|
|
|
@ -39,8 +39,7 @@ open class PasswordRepository protected constructor() {
|
|||
|
||||
@JvmStatic
|
||||
fun getSortOrder(settings: SharedPreferences): PasswordSortOrder {
|
||||
return valueOf(settings.getString(PreferenceKeys.SORT_ORDER, null)
|
||||
?: FOLDER_FIRST.name)
|
||||
return valueOf(settings.getString(PreferenceKeys.SORT_ORDER) ?: FOLDER_FIRST.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +154,7 @@ open class PasswordRepository protected constructor() {
|
|||
@JvmStatic
|
||||
fun getRepositoryDirectory(): File {
|
||||
return if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
|
||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO, null)
|
||||
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
|
||||
if (externalRepo != null)
|
||||
File(externalRepo)
|
||||
else
|
||||
|
@ -239,7 +238,7 @@ open class PasswordRepository protected constructor() {
|
|||
* @param username username
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setUserName(username: String) {
|
||||
fun setGitAuthorName(username: String) {
|
||||
setStringConfig("user", null, "name", username)
|
||||
}
|
||||
|
||||
|
@ -249,7 +248,7 @@ open class PasswordRepository protected constructor() {
|
|||
* @param email email
|
||||
*/
|
||||
@JvmStatic
|
||||
fun setUserEmail(email: String) {
|
||||
fun setGitAuthorEmail(email: String) {
|
||||
setStringConfig("user", null, "email", email)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,16 +23,21 @@ object PreferenceKeys {
|
|||
const val FILTER_RECURSIVELY = "filter_recursively"
|
||||
const val GENERAL_SHOW_TIME = "general_show_time"
|
||||
const val GIT_CONFIG = "git_config"
|
||||
const val GIT_CONFIG_USER_EMAIL = "git_config_user_email"
|
||||
const val GIT_CONFIG_USER_NAME = "git_config_user_name"
|
||||
const val GIT_CONFIG_AUTHOR_EMAIL = "git_config_user_email"
|
||||
const val GIT_CONFIG_AUTHOR_NAME = "git_config_user_name"
|
||||
const val GIT_EXTERNAL = "git_external"
|
||||
const val GIT_EXTERNAL_REPO = "git_external_repo"
|
||||
const val GIT_REMOTE_AUTH = "git_remote_auth"
|
||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||
const val GIT_REMOTE_LOCATION = "git_remote_location"
|
||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||
const val GIT_REMOTE_PORT = "git_remote_port"
|
||||
const val GIT_REMOTE_PROTOCOL = "git_remote_protocol"
|
||||
const val GIT_DELETE_REPO = "git_delete_repo"
|
||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||
const val GIT_REMOTE_SERVER = "git_remote_server"
|
||||
const val GIT_REMOTE_URL = "git_remote_url"
|
||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||
const val GIT_REMOTE_USERNAME = "git_remote_username"
|
||||
const val GIT_SERVER_INFO = "git_server_info"
|
||||
const val GIT_BRANCH_NAME = "git_branch"
|
||||
|
|
|
@ -65,81 +65,21 @@
|
|||
android:text="@string/clone_protocol_https" />
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/server_user_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/server_user"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/clone_protocol_group">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_user"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textWebEmailAddress"
|
||||
android:nextFocusForward="@id/server_url" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/label_server_url"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/server_url"
|
||||
app:layout_constraintEnd_toStartOf="@id/label_server_port"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/server_user_layout">
|
||||
app:layout_constraintTop_toBottomOf="@id/clone_protocol_group">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textWebEmailAddress"
|
||||
android:nextFocusForward="@id/server_port" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/label_server_port"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/server_port_hint"
|
||||
app:layout_constraintDimensionRatio="1:0.8"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/label_server_url"
|
||||
app:layout_constraintTop_toBottomOf="@id/server_user_layout">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:nextFocusForward="@id/server_path" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/label_server_path"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/server_path"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_server_url">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionNext"
|
||||
android:nextFocusForward="@id/server_branch"
|
||||
android:inputType="textWebEmailAddress" />
|
||||
|
||||
|
@ -150,10 +90,10 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Branch"
|
||||
android:hint="@string/server_branch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_server_path">
|
||||
app:layout_constraintTop_toBottomOf="@id/label_server_url">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_branch"
|
||||
|
|
|
@ -29,10 +29,6 @@
|
|||
<string name="initialize">إستخدام مجلد محلي</string>
|
||||
<string name="server_name">الخادوم</string>
|
||||
<string name="server_protocol">البروتوكول</string>
|
||||
<string name="server_url">عنوان الخادوم</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">مسار المستودع</string>
|
||||
<string name="server_user">إسم المستخدم</string>
|
||||
|
||||
<string name="connection_mode">نوع المصادقة</string>
|
||||
|
||||
|
|
|
@ -57,10 +57,6 @@
|
|||
<string name="external_repository_dialog_title">Vyberte kam ukládat hesla</string>
|
||||
<string name="server_name">Server</string>
|
||||
<string name="server_protocol">Protokol</string>
|
||||
<string name="server_url">URL serveru</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Cesta k repozitáři</string>
|
||||
<string name="server_user">Jméno</string>
|
||||
|
||||
<string name="connection_mode">Mód ověření</string>
|
||||
|
||||
|
|
|
@ -40,10 +40,6 @@
|
|||
<string name="initialize">Nutze lokalen Ordner</string>
|
||||
<string name="server_name">Server</string>
|
||||
<string name="server_protocol">Protokoll</string>
|
||||
<string name="server_url">Server URL</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Repo-Pfad</string>
|
||||
<string name="server_user">Nutzername</string>
|
||||
|
||||
<string name="connection_mode">Authentifizierungsmethode</string>
|
||||
|
||||
|
|
|
@ -54,10 +54,6 @@
|
|||
|
||||
<string name="server_name">Servidor</string>
|
||||
<string name="server_protocol">Protocolo</string>
|
||||
<string name="server_url">URL de servidor</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Ruta del repositorio</string>
|
||||
<string name="server_user">Nombre de usuario</string>
|
||||
|
||||
<string name="connection_mode">Modo de autenticación</string>
|
||||
|
||||
|
|
|
@ -60,10 +60,6 @@
|
|||
|
||||
<string name="server_name">Serveur</string>
|
||||
<string name="server_protocol">Protocole</string>
|
||||
<string name="server_url">URL du serveur</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Chemin du dépôt</string>
|
||||
<string name="server_user">Nom d\'utilisateur</string>
|
||||
|
||||
<string name="connection_mode">Méthode d\'authentification</string>
|
||||
|
||||
|
|
|
@ -41,10 +41,6 @@
|
|||
<string name="initialize">ローカルディレクトリーを使用する</string>
|
||||
<string name="server_name">サーバー</string>
|
||||
<string name="server_protocol">プロトコル</string>
|
||||
<string name="server_url">サーバー URL</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">リポジトリのパス</string>
|
||||
<string name="server_user">ユーザー名</string>
|
||||
|
||||
<string name="connection_mode">認証モード</string>
|
||||
|
||||
|
|
|
@ -68,10 +68,6 @@
|
|||
<string name="external_repository_dialog_text">Você deve selecionar um diretório onde armazenar suas senhas. Se você deseja armazenar suas senhas dentro do armazenamento oculto do aplicativo, cancele esta caixa de diálogo e desative a opção \"Repositório Externo\".</string>
|
||||
<string name="server_name">Servidor</string>
|
||||
<string name="server_protocol">Protocolo</string>
|
||||
<string name="server_url">URL do servidor</string>
|
||||
<string name="server_port_hint">Porta</string>
|
||||
<string name="server_path">Caminho do repositório</string>
|
||||
<string name="server_user">Usuário</string>
|
||||
<string name="connection_mode">Modo de autenticação</string>
|
||||
<!-- Git Config fragment -->
|
||||
<string name="git_user_name_hint">Usuário</string>
|
||||
|
@ -295,7 +291,6 @@
|
|||
<string name="connection_mode_ssh_key">Chave SSH</string>
|
||||
<string name="connection_mode_basic_authentication">Senha</string>
|
||||
<string name="git_server_config_save_success">Configuração salva com sucesso</string>
|
||||
<string name="git_server_config_save_error_prefix">Erro de configuração: %s</string>
|
||||
<string name="git_config_error_hostname_empty">hostname vazio</string>
|
||||
<string name="git_config_error_generic">por favor, verifique suas configurações e tente novamente</string>
|
||||
<string name="git_config_error_nonnumeric_port">porta deve ser numérica</string>
|
||||
|
|
|
@ -62,10 +62,6 @@
|
|||
|
||||
<string name="server_name">Сервер</string>
|
||||
<string name="server_protocol">Протокол</string>
|
||||
<string name="server_url">URL сервера</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Путь к репозиторию</string>
|
||||
<string name="server_user">Имя пользователя</string>
|
||||
|
||||
<string name="connection_mode">Тип авторизации</string>
|
||||
|
||||
|
|
|
@ -41,10 +41,6 @@
|
|||
<string name="initialize">使用本地目录</string>
|
||||
<string name="server_name">服务器</string>
|
||||
<string name="server_protocol">接口</string>
|
||||
<string name="server_url">服务器 URL</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Repo 路径</string>
|
||||
<string name="server_user">用户名</string>
|
||||
|
||||
<string name="connection_mode">认证模式</string>
|
||||
|
||||
|
|
|
@ -38,10 +38,6 @@
|
|||
<string name="initialize">使用本機目錄</string>
|
||||
<string name="server_name">伺服器</string>
|
||||
<string name="server_protocol">port</string>
|
||||
<string name="server_url">伺服器 URL</string>
|
||||
<string name="server_port_hint">22</string>
|
||||
<string name="server_path">Repo 路徑</string>
|
||||
<string name="server_user">使用者名稱</string>
|
||||
|
||||
<string name="connection_mode">認證模式</string>
|
||||
|
||||
|
|
|
@ -83,10 +83,8 @@
|
|||
|
||||
<string name="server_name">Server</string>
|
||||
<string name="server_protocol">Protocol</string>
|
||||
<string name="server_url">Server URL</string>
|
||||
<string name="server_port_hint">Port</string>
|
||||
<string name="server_path">Repo path</string>
|
||||
<string name="server_user">Username</string>
|
||||
<string name="server_url">Repository URL</string>
|
||||
<string name="server_branch">Branch</string>
|
||||
|
||||
<string name="connection_mode">Authentication Mode</string>
|
||||
|
||||
|
@ -326,7 +324,7 @@
|
|||
<string name="connection_mode_basic_authentication">Password</string>
|
||||
<string name="connection_mode_openkeychain" translatable="false">OpenKeychain</string>
|
||||
<string name="git_server_config_save_success">Successfully saved configuration</string>
|
||||
<string name="git_server_config_save_error_prefix">Configuration error: %s</string>
|
||||
<string name="git_server_config_save_error">The provided repository URL is not valid</string>
|
||||
<string name="git_config_error_hostname_empty">empty hostname</string>
|
||||
<string name="git_config_error_generic">please verify your settings and try again</string>
|
||||
<string name="git_config_error_nonnumeric_port">port must be numeric</string>
|
||||
|
|
Loading…
Reference in a new issue