mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-31 14:25:28 +00:00
Switch password authentication over to SSHJ (#811)
* Switch password authentication over to SSHJ * Address review comments and refactor further
This commit is contained in:
@@ -8,6 +8,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox
|
import com.google.android.material.checkbox.MaterialCheckBox
|
||||||
@@ -16,7 +17,6 @@ import com.google.android.material.textfield.TextInputEditText
|
|||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.UserPreference
|
import com.zeapo.pwdstore.UserPreference
|
||||||
import com.zeapo.pwdstore.git.config.ConnectionMode
|
import com.zeapo.pwdstore.git.config.ConnectionMode
|
||||||
import com.zeapo.pwdstore.git.config.GitConfigSessionFactory
|
|
||||||
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
|
||||||
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
|
||||||
import com.zeapo.pwdstore.git.config.SshAuthData
|
import com.zeapo.pwdstore.git.config.SshAuthData
|
||||||
@@ -24,37 +24,129 @@ import com.zeapo.pwdstore.git.config.SshjSessionFactory
|
|||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
import com.zeapo.pwdstore.utils.getEncryptedPrefs
|
||||||
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
import com.zeapo.pwdstore.utils.requestInputFocusOnView
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordFinder
|
||||||
import org.eclipse.jgit.api.GitCommand
|
import org.eclipse.jgit.api.GitCommand
|
||||||
|
import org.eclipse.jgit.errors.UnsupportedCredentialItem
|
||||||
import org.eclipse.jgit.lib.Repository
|
import org.eclipse.jgit.lib.Repository
|
||||||
|
import org.eclipse.jgit.transport.CredentialItem
|
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider
|
||||||
import org.eclipse.jgit.transport.SshSessionFactory
|
import org.eclipse.jgit.transport.SshSessionFactory
|
||||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
|
import org.eclipse.jgit.transport.URIish
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
|
||||||
|
private class GitOperationCredentialFinder(val callingActivity: Activity, val connectionMode: ConnectionMode) : InteractivePasswordFinder() {
|
||||||
|
|
||||||
|
override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
|
||||||
|
require(connectionMode == ConnectionMode.Password)
|
||||||
|
val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
|
||||||
|
val credentialPref: String
|
||||||
|
@StringRes val messageRes: Int
|
||||||
|
@StringRes val hintRes: Int
|
||||||
|
@StringRes val rememberRes: Int
|
||||||
|
@StringRes val errorRes: Int
|
||||||
|
when (connectionMode) {
|
||||||
|
ConnectionMode.SshKey -> {
|
||||||
|
credentialPref = "ssh_key_local_passphrase"
|
||||||
|
messageRes = R.string.passphrase_dialog_text
|
||||||
|
hintRes = R.string.ssh_keygen_passphrase
|
||||||
|
rememberRes = R.string.git_operation_remember_passphrase
|
||||||
|
errorRes = R.string.git_operation_wrong_passphrase
|
||||||
|
}
|
||||||
|
ConnectionMode.Password -> {
|
||||||
|
// Could be either an SSH or an HTTPS password
|
||||||
|
credentialPref = "https_password"
|
||||||
|
messageRes = R.string.password_dialog_text
|
||||||
|
hintRes = R.string.git_operation_hint_password
|
||||||
|
rememberRes = R.string.git_operation_remember_password
|
||||||
|
errorRes = R.string.git_operation_wrong_password
|
||||||
|
}
|
||||||
|
else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords")
|
||||||
|
}
|
||||||
|
val storedCredential = gitOperationPrefs.getString(credentialPref, null)
|
||||||
|
if (isRetry)
|
||||||
|
gitOperationPrefs.edit { remove(credentialPref) }
|
||||||
|
if (storedCredential.isNullOrEmpty()) {
|
||||||
|
val layoutInflater = LayoutInflater.from(callingActivity)
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null)
|
||||||
|
val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential)
|
||||||
|
editCredential.setHint(hintRes)
|
||||||
|
val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential)
|
||||||
|
rememberCredential.setText(rememberRes)
|
||||||
|
if (isRetry)
|
||||||
|
editCredential.error = callingActivity.resources.getString(errorRes)
|
||||||
|
MaterialAlertDialogBuilder(callingActivity).run {
|
||||||
|
setTitle(R.string.passphrase_dialog_title)
|
||||||
|
setMessage(messageRes)
|
||||||
|
setView(dialogView)
|
||||||
|
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
|
val credential = editCredential.text.toString()
|
||||||
|
if (rememberCredential.isChecked) {
|
||||||
|
gitOperationPrefs.edit {
|
||||||
|
putString(credentialPref, credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cont.resume(credential)
|
||||||
|
}
|
||||||
|
setNegativeButton(R.string.dialog_cancel) { _, _ ->
|
||||||
|
cont.resume(null)
|
||||||
|
}
|
||||||
|
setOnCancelListener {
|
||||||
|
cont.resume(null)
|
||||||
|
}
|
||||||
|
create()
|
||||||
|
}.run {
|
||||||
|
requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cont.resume(storedCredential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param gitDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) {
|
abstract class GitOperation(gitDir: File, internal val callingActivity: Activity) {
|
||||||
|
|
||||||
protected val repository: Repository? = PasswordRepository.getRepository(fileDir)
|
protected val repository: Repository? = PasswordRepository.getRepository(gitDir)
|
||||||
internal var provider: UsernamePasswordCredentialsProvider? = null
|
internal var provider: CredentialsProvider? = null
|
||||||
internal var command: GitCommand<*>? = null
|
internal var command: GitCommand<*>? = null
|
||||||
private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key")
|
private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key")
|
||||||
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
|
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
|
||||||
|
|
||||||
/**
|
private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() {
|
||||||
* Sets the authentication using user/pwd scheme
|
|
||||||
*
|
override fun isInteractive() = true
|
||||||
* @param username the username
|
|
||||||
* @param password the password
|
override fun get(uri: URIish?, vararg items: CredentialItem): Boolean {
|
||||||
* @return the current object
|
for (item in items) {
|
||||||
*/
|
when (item) {
|
||||||
internal open fun setAuthentication(username: String, password: String): GitOperation {
|
is CredentialItem.Username -> item.value = username
|
||||||
SshSessionFactory.setInstance(GitConfigSessionFactory())
|
is CredentialItem.Password -> item.value = passwordFinder.reqPassword(null)
|
||||||
this.provider = UsernamePasswordCredentialsProvider(username, password)
|
else -> UnsupportedCredentialItem(uri, item.javaClass.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supports(vararg items: CredentialItem) = items.all {
|
||||||
|
it is CredentialItem.Username || it is CredentialItem.Password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withPasswordAuthentication(username: String, passwordFinder: InteractivePasswordFinder): GitOperation {
|
||||||
|
val sessionFactory = SshjSessionFactory(username, SshAuthData.Password(passwordFinder), hostKeyFile)
|
||||||
|
SshSessionFactory.setInstance(sessionFactory)
|
||||||
|
this.provider = PasswordFinderCredentialsProvider(username, passwordFinder)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,146 +157,58 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun withOpenKeychainAuthentication(username: String, identity: SshApiSessionFactory.ApiIdentity?): GitOperation {
|
||||||
* Sets the authentication using OpenKeystore scheme
|
|
||||||
*
|
|
||||||
* @param identity The identiy to use
|
|
||||||
* @return the current object
|
|
||||||
*/
|
|
||||||
private fun setAuthentication(username: String, identity: SshApiSessionFactory.ApiIdentity?): GitOperation {
|
|
||||||
SshSessionFactory.setInstance(SshApiSessionFactory(username, identity))
|
SshSessionFactory.setInstance(SshApiSessionFactory(username, identity))
|
||||||
this.provider = null
|
this.provider = null
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSshKey(make: Boolean) {
|
||||||
|
try {
|
||||||
|
// Ask the UserPreference to provide us with the ssh-key
|
||||||
|
// onResult has to be handled by the callingActivity
|
||||||
|
val intent = Intent(callingActivity.applicationContext, UserPreference::class.java)
|
||||||
|
intent.putExtra("operation", if (make) "make_ssh_key" else "get_ssh_key")
|
||||||
|
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Exception caught :(")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the GitCommand in an async task
|
* Executes the GitCommand in an async task
|
||||||
*/
|
*/
|
||||||
abstract fun execute()
|
abstract fun execute()
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the GitCommand in an async task after creating the authentication
|
|
||||||
*
|
|
||||||
* @param connectionMode the server-connection mode
|
|
||||||
* @param username the username
|
|
||||||
* @param identity the api identity to use for auth in OpenKeychain connection mode
|
|
||||||
*/
|
|
||||||
fun executeAfterAuthentication(
|
fun executeAfterAuthentication(
|
||||||
connectionMode: ConnectionMode,
|
connectionMode: ConnectionMode,
|
||||||
username: String,
|
username: String,
|
||||||
identity: SshApiSessionFactory.ApiIdentity?
|
identity: SshApiSessionFactory.ApiIdentity?
|
||||||
) {
|
) {
|
||||||
val encryptedSettings = callingActivity.applicationContext.getEncryptedPrefs("git_operation")
|
|
||||||
when (connectionMode) {
|
when (connectionMode) {
|
||||||
ConnectionMode.SshKey -> {
|
ConnectionMode.SshKey -> if (!sshKeyFile.exists()) {
|
||||||
if (!sshKeyFile.exists()) {
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
.setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text))
|
||||||
.setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text))
|
.setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title))
|
||||||
.setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title))
|
.setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ ->
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ ->
|
getSshKey(false)
|
||||||
try {
|
}
|
||||||
// Ask the UserPreference to provide us with the ssh-key
|
.setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ ->
|
||||||
// onResult has to be handled by the callingActivity
|
getSshKey(true)
|
||||||
val intent = Intent(callingActivity.applicationContext, UserPreference::class.java)
|
}
|
||||||
intent.putExtra("operation", "get_ssh_key")
|
.setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ ->
|
||||||
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE)
|
// Finish the blank GitActivity so user doesn't have to press back
|
||||||
} catch (e: Exception) {
|
callingActivity.finish()
|
||||||
println("Exception caught :(")
|
}.show()
|
||||||
e.printStackTrace()
|
} else {
|
||||||
}
|
withPublicKeyAuthentication(username, GitOperationCredentialFinder(callingActivity,
|
||||||
}
|
connectionMode)).execute()
|
||||||
.setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ ->
|
|
||||||
try {
|
|
||||||
// Duplicated code
|
|
||||||
val intent = Intent(callingActivity.applicationContext, UserPreference::class.java)
|
|
||||||
intent.putExtra("operation", "make_ssh_key")
|
|
||||||
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("Exception caught :(")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ ->
|
|
||||||
// Finish the blank GitActivity so user doesn't have to press back
|
|
||||||
callingActivity.finish()
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
withPublicKeyAuthentication(username, InteractivePasswordFinder { cont, isRetry ->
|
|
||||||
val storedPassphrase = encryptedSettings.getString("ssh_key_local_passphrase", null)
|
|
||||||
if (isRetry)
|
|
||||||
encryptedSettings.edit { putString("ssh_key_local_passphrase", null) }
|
|
||||||
if (storedPassphrase.isNullOrEmpty()) {
|
|
||||||
val layoutInflater = LayoutInflater.from(callingActivity)
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
val dialogView = layoutInflater.inflate(R.layout.git_passphrase_layout, null)
|
|
||||||
val editPassphrase = dialogView.findViewById<TextInputEditText>(R.id.git_auth_passphrase)
|
|
||||||
val rememberPassphrase = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase)
|
|
||||||
if (isRetry)
|
|
||||||
editPassphrase.error = callingActivity.resources.getString(R.string.git_operation_wrong_passphrase)
|
|
||||||
MaterialAlertDialogBuilder(callingActivity).run {
|
|
||||||
setTitle(R.string.passphrase_dialog_title)
|
|
||||||
setMessage(R.string.passphrase_dialog_text)
|
|
||||||
setView(dialogView)
|
|
||||||
setPositiveButton(R.string.dialog_ok) { _, _ ->
|
|
||||||
val passphrase = editPassphrase.text.toString()
|
|
||||||
if (rememberPassphrase.isChecked) {
|
|
||||||
encryptedSettings.edit {
|
|
||||||
putString("ssh_key_local_passphrase", passphrase)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cont.resume(passphrase)
|
|
||||||
}
|
|
||||||
setNegativeButton(R.string.dialog_cancel) { _, _ ->
|
|
||||||
cont.resume(null)
|
|
||||||
}
|
|
||||||
setOnCancelListener {
|
|
||||||
cont.resume(null)
|
|
||||||
}
|
|
||||||
create()
|
|
||||||
}.run {
|
|
||||||
requestInputFocusOnView<TextInputEditText>(R.id.git_auth_passphrase)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cont.resume(storedPassphrase)
|
|
||||||
}
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConnectionMode.OpenKeychain -> {
|
|
||||||
setAuthentication(username, identity).execute()
|
|
||||||
}
|
|
||||||
ConnectionMode.Password -> {
|
|
||||||
@SuppressLint("InflateParams") val dialogView = callingActivity.layoutInflater.inflate(R.layout.git_passphrase_layout, null)
|
|
||||||
val passwordView = dialogView.findViewById<TextInputEditText>(R.id.git_auth_passphrase)
|
|
||||||
val password = encryptedSettings.getString("https_password", null)
|
|
||||||
if (password != null && password.isNotEmpty()) {
|
|
||||||
setAuthentication(username, password).execute()
|
|
||||||
} else {
|
|
||||||
val dialog = MaterialAlertDialogBuilder(callingActivity)
|
|
||||||
.setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title))
|
|
||||||
.setMessage(callingActivity.resources.getString(R.string.password_dialog_text))
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
|
||||||
if (dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked) {
|
|
||||||
encryptedSettings.edit { putString("https_password", passwordView.text.toString()) }
|
|
||||||
}
|
|
||||||
// authenticate using the user/pwd and then execute the command
|
|
||||||
setAuthentication(username, passwordView.text.toString()).execute()
|
|
||||||
}
|
|
||||||
.setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ ->
|
|
||||||
callingActivity.finish()
|
|
||||||
}
|
|
||||||
.setOnCancelListener { callingActivity.finish() }
|
|
||||||
.create()
|
|
||||||
dialog.requestInputFocusOnView<TextInputEditText>(R.id.git_auth_passphrase)
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConnectionMode.None -> {
|
|
||||||
execute()
|
|
||||||
}
|
}
|
||||||
|
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute()
|
||||||
|
ConnectionMode.Password -> withPasswordAuthentication(
|
||||||
|
username, GitOperationCredentialFinder(callingActivity, connectionMode)).execute()
|
||||||
|
ConnectionMode.None -> execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,17 +220,15 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
|||||||
when (SshSessionFactory.getInstance()) {
|
when (SshSessionFactory.getInstance()) {
|
||||||
is SshApiSessionFactory -> {
|
is SshApiSessionFactory -> {
|
||||||
PreferenceManager.getDefaultSharedPreferences(callingActivity.applicationContext)
|
PreferenceManager.getDefaultSharedPreferences(callingActivity.applicationContext)
|
||||||
.edit { putString("ssh_openkeystore_keyid", null) }
|
.edit { remove("ssh_openkeystore_keyid") }
|
||||||
}
|
}
|
||||||
is SshjSessionFactory -> {
|
is SshjSessionFactory -> {
|
||||||
callingActivity.applicationContext
|
callingActivity.applicationContext
|
||||||
.getEncryptedPrefs("git_operation")
|
.getEncryptedPrefs("git_operation")
|
||||||
.edit { remove("ssh_key_local_passphrase") }
|
.edit {
|
||||||
}
|
remove("ssh_key_local_passphrase")
|
||||||
is GitConfigSessionFactory -> {
|
remove("https_password")
|
||||||
callingActivity.applicationContext
|
}
|
||||||
.getEncryptedPrefs("git_operation")
|
|
||||||
.edit { remove("https_password") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +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
|
|
||||||
|
|
||||||
import com.jcraft.jsch.JSch
|
|
||||||
import com.jcraft.jsch.JSchException
|
|
||||||
import com.jcraft.jsch.Session
|
|
||||||
import org.eclipse.jgit.transport.JschConfigSessionFactory
|
|
||||||
import org.eclipse.jgit.transport.OpenSshConfig
|
|
||||||
import org.eclipse.jgit.util.FS
|
|
||||||
|
|
||||||
open class GitConfigSessionFactory : JschConfigSessionFactory() {
|
|
||||||
|
|
||||||
override fun configure(hc: OpenSshConfig.Host, session: Session) {
|
|
||||||
session.setConfig("StrictHostKeyChecking", "no")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(JSchException::class)
|
|
||||||
override fun getJSch(hc: OpenSshConfig.Host, fs: FS): JSch {
|
|
||||||
val jsch = super.getJSch(hc, fs)
|
|
||||||
jsch.removeAllIdentity()
|
|
||||||
return jsch
|
|
||||||
}
|
|
||||||
}
|
|
@@ -26,6 +26,7 @@ import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
|||||||
import org.eclipse.jgit.transport.CredentialItem;
|
import org.eclipse.jgit.transport.CredentialItem;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
import org.eclipse.jgit.transport.CredentialsProviderUserInfo;
|
import org.eclipse.jgit.transport.CredentialsProviderUserInfo;
|
||||||
|
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||||
import org.eclipse.jgit.transport.OpenSshConfig;
|
import org.eclipse.jgit.transport.OpenSshConfig;
|
||||||
import org.eclipse.jgit.transport.URIish;
|
import org.eclipse.jgit.transport.URIish;
|
||||||
import org.eclipse.jgit.util.Base64;
|
import org.eclipse.jgit.util.Base64;
|
||||||
@@ -43,7 +44,7 @@ import org.openintents.ssh.authentication.util.SshAuthenticationApiUtils;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
public class SshApiSessionFactory extends GitConfigSessionFactory {
|
public class SshApiSessionFactory extends JschConfigSessionFactory {
|
||||||
/**
|
/**
|
||||||
* Intent request code indicating a completed signature that should be posted to an outstanding
|
* Intent request code indicating a completed signature that should be posted to an outstanding
|
||||||
* ApiIdentity
|
* ApiIdentity
|
||||||
|
@@ -37,12 +37,14 @@ sealed class SshAuthData {
|
|||||||
class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
|
class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractivePasswordFinder(val askForPassword: (cont: Continuation<String?>, isRetry: Boolean) -> Unit) : PasswordFinder {
|
abstract class InteractivePasswordFinder : PasswordFinder {
|
||||||
|
|
||||||
private var isRetry = false
|
private var isRetry = false
|
||||||
private var shouldRetry = true
|
private var shouldRetry = true
|
||||||
|
|
||||||
override fun reqPassword(resource: Resource<*>?): CharArray {
|
abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean)
|
||||||
|
|
||||||
|
final override fun reqPassword(resource: Resource<*>?): CharArray {
|
||||||
val password = runBlocking(Dispatchers.Main) {
|
val password = runBlocking(Dispatchers.Main) {
|
||||||
suspendCoroutine<String?> { cont ->
|
suspendCoroutine<String?> { cont ->
|
||||||
askForPassword(cont, isRetry)
|
askForPassword(cont, isRetry)
|
||||||
@@ -57,7 +59,7 @@ class InteractivePasswordFinder(val askForPassword: (cont: Continuation<String?>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldRetry(resource: Resource<*>?) = shouldRetry
|
final override fun shouldRetry(resource: Resource<*>?) = shouldRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
class SshjSessionFactory(private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
|
class SshjSessionFactory(private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/git_auth_passphrase"
|
android:id="@+id/git_auth_credential"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/ssh_keygen_passphrase"
|
android:hint="@string/ssh_keygen_passphrase"
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.checkbox.MaterialCheckBox
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
android:id="@+id/git_auth_remember_passphrase"
|
android:id="@+id/git_auth_remember_credential"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/remember_the_passphrase"
|
android:text="@string/git_operation_remember_passphrase"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/git_auth_passphrase_layout" />
|
app:layout_constraintTop_toBottomOf="@+id/git_auth_passphrase_layout" />
|
@@ -208,7 +208,8 @@
|
|||||||
<string name="git_push_nff_error">La subida fue rechazada por el servidor, Ejecuta \'Descargar desde servidor\' antes de subir o pulsa \'Sincronizar con servidor\' para realizar ambas acciones.</string>
|
<string name="git_push_nff_error">La subida fue rechazada por el servidor, Ejecuta \'Descargar desde servidor\' antes de subir o pulsa \'Sincronizar con servidor\' para realizar ambas acciones.</string>
|
||||||
<string name="git_push_generic_error">El envío fue rechazado por el servidor, la razón:</string>
|
<string name="git_push_generic_error">El envío fue rechazado por el servidor, la razón:</string>
|
||||||
<string name="jgit_error_push_dialog_text">Ocurrió un error durante el envío:</string>
|
<string name="jgit_error_push_dialog_text">Ocurrió un error durante el envío:</string>
|
||||||
<string name="remember_the_passphrase">Recordar contraseñagit (inseguro)</string>
|
<string name="hotp_remember_clear_choice">Limpiar preferencia para incremento HOTP</string>
|
||||||
|
<string name="git_operation_remember_passphrase">Recordar contraseñagit (inseguro)</string>
|
||||||
<string name="hackish_tools">Hackish tools</string>
|
<string name="hackish_tools">Hackish tools</string>
|
||||||
<string name="abort_rebase">Abortar rebase</string>
|
<string name="abort_rebase">Abortar rebase</string>
|
||||||
<string name="commit_hash">Hash del commit</string>
|
<string name="commit_hash">Hash del commit</string>
|
||||||
|
@@ -209,7 +209,8 @@
|
|||||||
<string name="git_push_generic_error">Poussée rejetée par le dépôt distant, raison:</string>
|
<string name="git_push_generic_error">Poussée rejetée par le dépôt distant, raison:</string>
|
||||||
<string name="git_push_other_error">Pousser au dépôt distant sans avance rapide rejetée. Vérifiez la variable receive.denyNonFastForwards dans le fichier de configuration du répertoire de destination.</string>
|
<string name="git_push_other_error">Pousser au dépôt distant sans avance rapide rejetée. Vérifiez la variable receive.denyNonFastForwards dans le fichier de configuration du répertoire de destination.</string>
|
||||||
<string name="jgit_error_push_dialog_text">Une erreur s\'est produite lors de l\'opération de poussée:</string>
|
<string name="jgit_error_push_dialog_text">Une erreur s\'est produite lors de l\'opération de poussée:</string>
|
||||||
<string name="remember_the_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string>
|
<string name="hotp_remember_clear_choice">Effacer les préférences enregistrées pour l’incrémentation HOTP</string>
|
||||||
|
<string name="git_operation_remember_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string>
|
||||||
<string name="hackish_tools">Outils de hack</string>
|
<string name="hackish_tools">Outils de hack</string>
|
||||||
<string name="commit_hash">Commettre la clé</string>
|
<string name="commit_hash">Commettre la clé</string>
|
||||||
<string name="crypto_extra_edit_hint">nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire</string>
|
<string name="crypto_extra_edit_hint">nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire</string>
|
||||||
|
@@ -270,7 +270,8 @@
|
|||||||
<string name="git_push_generic_error">Запись изменений была отклонена удаленным репозиторием, причина:</string>
|
<string name="git_push_generic_error">Запись изменений была отклонена удаленным репозиторием, причина:</string>
|
||||||
<string name="git_push_other_error">Удаленный репозиторий отклонил запись изменений без быстрой перемотки вперед. Проверьте переменную receive.denyNonFastForwards в файле конфигурации репозитория назначения.</string>
|
<string name="git_push_other_error">Удаленный репозиторий отклонил запись изменений без быстрой перемотки вперед. Проверьте переменную receive.denyNonFastForwards в файле конфигурации репозитория назначения.</string>
|
||||||
<string name="jgit_error_push_dialog_text">В хоте операции записи изменений возникла ошибка:</string>
|
<string name="jgit_error_push_dialog_text">В хоте операции записи изменений возникла ошибка:</string>
|
||||||
<string name="remember_the_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string>
|
<string name="hotp_remember_clear_choice">Очистить сохраненные настройки для увеличения HOTP</string>
|
||||||
|
<string name="git_operation_remember_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string>
|
||||||
<string name="hackish_tools">Костыльные инструменты</string>
|
<string name="hackish_tools">Костыльные инструменты</string>
|
||||||
<string name="abort_rebase">Прервать перебазирование и записать изменения в новую ветку</string>
|
<string name="abort_rebase">Прервать перебазирование и записать изменения в новую ветку</string>
|
||||||
<string name="reset_to_remote">Полный сброс до состояния удаленной ветки</string>
|
<string name="reset_to_remote">Полный сброс до состояния удаленной ветки</string>
|
||||||
|
@@ -307,7 +307,8 @@
|
|||||||
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
|
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
|
||||||
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
|
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
|
||||||
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
|
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
|
||||||
<string name="remember_the_passphrase">Remember key passphrase</string>
|
<string name="hotp_remember_clear_choice">Clear saved preference for HOTP incrementing</string>
|
||||||
|
<string name="git_operation_remember_passphrase">Remember key passphrase</string>
|
||||||
<string name="hackish_tools">Hackish tools</string>
|
<string name="hackish_tools">Hackish tools</string>
|
||||||
<string name="abort_rebase">Abort rebase and push new branch</string>
|
<string name="abort_rebase">Abort rebase and push new branch</string>
|
||||||
<string name="reset_to_remote">Hard reset to remote branch</string>
|
<string name="reset_to_remote">Hard reset to remote branch</string>
|
||||||
@@ -360,6 +361,7 @@
|
|||||||
<string name="git_operation_unable_to_open_ssh_key_title">Unable to open the ssh-key</string>
|
<string name="git_operation_unable_to_open_ssh_key_title">Unable to open the ssh-key</string>
|
||||||
<string name="git_operation_unable_to_open_ssh_key_message">Please check that it was imported.</string>
|
<string name="git_operation_unable_to_open_ssh_key_message">Please check that it was imported.</string>
|
||||||
<string name="git_operation_wrong_passphrase">Wrong passphrase</string>
|
<string name="git_operation_wrong_passphrase">Wrong passphrase</string>
|
||||||
|
<string name="git_operation_wrong_password">Wrong password</string>
|
||||||
<string name="bottom_sheet_create_new_folder">Create new folder</string>
|
<string name="bottom_sheet_create_new_folder">Create new folder</string>
|
||||||
<string name="bottom_sheet_create_new_password">Create new password</string>
|
<string name="bottom_sheet_create_new_password">Create new password</string>
|
||||||
<string name="autofill_onboarding_dialog_title">New, revamped Autofill!</string>
|
<string name="autofill_onboarding_dialog_title">New, revamped Autofill!</string>
|
||||||
@@ -369,4 +371,6 @@
|
|||||||
<string name="pref_debug_logging_title">Debug logging</string>
|
<string name="pref_debug_logging_title">Debug logging</string>
|
||||||
<string name="preference_default_username_summary">If Autofill is unable to determine a username from your password file or directory structure, it will use the value specified here</string>
|
<string name="preference_default_username_summary">If Autofill is unable to determine a username from your password file or directory structure, it will use the value specified here</string>
|
||||||
<string name="preference_default_username_title">Default username</string>
|
<string name="preference_default_username_title">Default username</string>
|
||||||
|
<string name="git_operation_remember_password">Remember password</string>
|
||||||
|
<string name="git_operation_hint_password">Password</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Reference in New Issue
Block a user