Added password for ssh keys
This commit is contained in:
parent
b63614d68d
commit
da3f3c8935
@ -19,7 +19,11 @@ object CredentialsStateManager {
|
|||||||
|
|
||||||
sealed class CredentialsState {
|
sealed class CredentialsState {
|
||||||
object None : CredentialsState()
|
object None : CredentialsState()
|
||||||
object CredentialsRequested : CredentialsState()
|
sealed class CredentialsRequested : CredentialsState()
|
||||||
|
object SshCredentialsRequested : CredentialsRequested()
|
||||||
|
object HttpCredentialsRequested : CredentialsRequested()
|
||||||
object CredentialsDenied : CredentialsState()
|
object CredentialsDenied : CredentialsState()
|
||||||
data class CredentialsAccepted(val user: String, val password: String) : CredentialsState()
|
sealed class CredentialsAccepted : CredentialsState()
|
||||||
|
data class SshCredentialsAccepted(val password: String) : CredentialsAccepted()
|
||||||
|
data class HttpCredentialsAccepted(val user: String, val password: String) : CredentialsAccepted()
|
||||||
}
|
}
|
@ -1,32 +1,67 @@
|
|||||||
package app.credentials
|
package app.credentials
|
||||||
|
|
||||||
|
import org.apache.sshd.agent.SshAgent
|
||||||
|
import org.apache.sshd.agent.local.AgentImpl
|
||||||
|
import org.apache.sshd.agent.local.LocalAgentFactory
|
||||||
import org.apache.sshd.client.SshClient
|
import org.apache.sshd.client.SshClient
|
||||||
|
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractive
|
||||||
|
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory
|
||||||
|
import org.apache.sshd.client.auth.keyboard.UserInteraction
|
||||||
|
import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter
|
||||||
|
import org.apache.sshd.client.auth.password.UserAuthPassword
|
||||||
import org.apache.sshd.client.future.ConnectFuture
|
import org.apache.sshd.client.future.ConnectFuture
|
||||||
|
import org.apache.sshd.client.session.ClientSession
|
||||||
|
import org.apache.sshd.common.NamedResource
|
||||||
|
import org.apache.sshd.common.config.keys.FilePasswordProvider
|
||||||
|
import org.apache.sshd.common.keyprovider.FileKeyPairProvider
|
||||||
|
import org.apache.sshd.common.session.SessionContext
|
||||||
import org.eclipse.jgit.transport.RemoteSession
|
import org.eclipse.jgit.transport.RemoteSession
|
||||||
import org.eclipse.jgit.transport.URIish
|
import org.eclipse.jgit.transport.URIish
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.time.Duration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
|
||||||
private const val DEFAULT_SSH_PORT = 22
|
private const val DEFAULT_SSH_PORT = 22
|
||||||
|
|
||||||
class GRemoteSession @Inject constructor(
|
class GRemoteSession @Inject constructor(
|
||||||
private val processProvider: Provider<GProcess>,
|
private val processProvider: Provider<GProcess>,
|
||||||
) : RemoteSession {
|
) : RemoteSession {
|
||||||
|
private val credentialsStateManager = CredentialsStateManager
|
||||||
|
|
||||||
private val client = SshClient.setUpDefaultClient()
|
private val client = SshClient.setUpDefaultClient()
|
||||||
|
|
||||||
private var connectFuture: ConnectFuture? = null
|
private var connectFuture: ConnectFuture? = null
|
||||||
|
|
||||||
override fun exec(commandName: String, timeout: Int): Process {
|
override fun exec(commandName: String, timeout: Int): Process {
|
||||||
println(commandName)
|
println(commandName)
|
||||||
|
|
||||||
val connectFuture = checkNotNull(connectFuture)
|
val connectFuture = checkNotNull(connectFuture)
|
||||||
|
|
||||||
val session = connectFuture.clientSession
|
val session = connectFuture.clientSession
|
||||||
session.auth().verify()
|
|
||||||
|
val auth = session.auth()
|
||||||
|
auth.addListener { arg0 ->
|
||||||
|
println("Authentication completed with " + if (arg0.isSuccess) "success" else "failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
session.waitFor(
|
||||||
|
listOf(
|
||||||
|
ClientSession.ClientSessionEvent.WAIT_AUTH,
|
||||||
|
ClientSession.ClientSessionEvent.CLOSED,
|
||||||
|
ClientSession.ClientSessionEvent.AUTHED
|
||||||
|
), Duration.ofHours(2)
|
||||||
|
)
|
||||||
|
auth.verify()
|
||||||
|
|
||||||
val process = processProvider.get()
|
val process = processProvider.get()
|
||||||
process.setup(session, commandName)
|
process.setup(session, commandName)
|
||||||
return process
|
return process
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun disconnect() {
|
override fun disconnect() {
|
||||||
client.close()
|
client.close()
|
||||||
}
|
}
|
||||||
@ -39,6 +74,25 @@ class GRemoteSession @Inject constructor(
|
|||||||
} else
|
} else
|
||||||
uri.port
|
uri.port
|
||||||
|
|
||||||
|
val filePasswordProvider = object : FilePasswordProvider {
|
||||||
|
override fun getPassword(session: SessionContext?, resourceKey: NamedResource?, retryIndex: Int): String? {
|
||||||
|
credentialsStateManager.updateState(CredentialsState.SshCredentialsRequested)
|
||||||
|
|
||||||
|
var credentials = credentialsStateManager.currentCredentialsState
|
||||||
|
while (credentials is CredentialsState.CredentialsRequested) {
|
||||||
|
// TODO check if support for ED25519 with pwd can be added
|
||||||
|
credentials = credentialsStateManager.currentCredentialsState
|
||||||
|
}
|
||||||
|
|
||||||
|
return if(credentials !is CredentialsState.SshCredentialsAccepted)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
credentials.password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.filePasswordProvider = filePasswordProvider
|
||||||
|
|
||||||
val connectFuture = client.connect(uri.user, uri.host, port)
|
val connectFuture = client.connect(uri.user, uri.host, port)
|
||||||
connectFuture.await()
|
connectFuture.await()
|
||||||
|
|
||||||
|
@ -23,15 +23,14 @@ class HttpCredentialsProvider : CredentialsProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
|
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
|
||||||
credentialsStateManager.updateState(CredentialsState.CredentialsRequested)
|
credentialsStateManager.updateState(CredentialsState.HttpCredentialsRequested)
|
||||||
|
|
||||||
@Suppress("ControlFlowWithEmptyBody")
|
|
||||||
var credentials = credentialsStateManager.currentCredentialsState
|
var credentials = credentialsStateManager.currentCredentialsState
|
||||||
while (credentials is CredentialsState.CredentialsRequested) {
|
while (credentials is CredentialsState.CredentialsRequested) {
|
||||||
credentials = credentialsStateManager.currentCredentialsState
|
credentials = credentialsStateManager.currentCredentialsState
|
||||||
}
|
}
|
||||||
|
|
||||||
if(credentials is CredentialsState.CredentialsAccepted) {
|
if(credentials is CredentialsState.HttpCredentialsAccepted) {
|
||||||
val userItem = items.firstOrNull { it?.promptText == "Username" }
|
val userItem = items.firstOrNull { it?.promptText == "Username" }
|
||||||
val passwordItem = items.firstOrNull { it?.promptText == "Password" }
|
val passwordItem = items.firstOrNull { it?.promptText == "Password" }
|
||||||
|
|
||||||
|
@ -226,8 +226,12 @@ class GitManager @Inject constructor(
|
|||||||
credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
|
credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun credentialsAccepted(user: String, password: String) {
|
fun httpCredentialsAccepted(user: String, password: String) {
|
||||||
credentialsStateManager.updateState(CredentialsState.CredentialsAccepted(user, password))
|
credentialsStateManager.updateState(CredentialsState.HttpCredentialsAccepted(user, password))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sshCredentialsAccepted(password: String) {
|
||||||
|
credentialsStateManager.updateState(CredentialsState.SshCredentialsAccepted(password))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> {
|
suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> {
|
||||||
@ -284,6 +288,7 @@ class GitManager @Inject constructor(
|
|||||||
try {
|
try {
|
||||||
callback()
|
callback()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
|
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
|
||||||
} finally {
|
} finally {
|
||||||
_processing.value = false
|
_processing.value = false
|
||||||
|
@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.diff.DiffEntry
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import org.eclipse.jgit.lib.Constants
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StatusManager @Inject constructor() {
|
class StatusManager @Inject constructor() {
|
||||||
@ -68,6 +69,13 @@ class StatusManager @Inject constructor() {
|
|||||||
loadStatus(git)
|
loadStatus(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// suspend fun stageHunk(git: Git) {
|
||||||
|
//// val repository = git.repository
|
||||||
|
//// val objectInserter = repository.newObjectInserter()
|
||||||
|
//
|
||||||
|
//// objectInserter.insert(Constants.OBJ_BLOB,)
|
||||||
|
// }
|
||||||
|
|
||||||
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
|
||||||
git.reset()
|
git.reset()
|
||||||
.addPath(diffEntry.filePath)
|
.addPath(diffEntry.filePath)
|
||||||
|
@ -10,6 +10,7 @@ import app.credentials.CredentialsState
|
|||||||
import app.git.DiffEntryType
|
import app.git.DiffEntryType
|
||||||
import app.git.GitManager
|
import app.git.GitManager
|
||||||
import app.ui.dialogs.NewBranchDialog
|
import app.ui.dialogs.NewBranchDialog
|
||||||
|
import app.ui.dialogs.PasswordDialog
|
||||||
import app.ui.dialogs.UserPasswordDialog
|
import app.ui.dialogs.UserPasswordDialog
|
||||||
import openRepositoryDialog
|
import openRepositoryDialog
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
@ -32,7 +33,7 @@ fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) {
|
|||||||
|
|
||||||
val credentialsState by gitManager.credentialsState.collectAsState()
|
val credentialsState by gitManager.credentialsState.collectAsState()
|
||||||
|
|
||||||
if (credentialsState == CredentialsState.CredentialsRequested) {
|
if (credentialsState == CredentialsState.HttpCredentialsRequested) {
|
||||||
dialogManager.show {
|
dialogManager.show {
|
||||||
UserPasswordDialog(
|
UserPasswordDialog(
|
||||||
onReject = {
|
onReject = {
|
||||||
@ -40,7 +41,20 @@ fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) {
|
|||||||
dialogManager.dismiss()
|
dialogManager.dismiss()
|
||||||
},
|
},
|
||||||
onAccept = { user, password ->
|
onAccept = { user, password ->
|
||||||
gitManager.credentialsAccepted(user, password)
|
gitManager.httpCredentialsAccepted(user, password)
|
||||||
|
dialogManager.dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (credentialsState == CredentialsState.SshCredentialsRequested) {
|
||||||
|
dialogManager.show {
|
||||||
|
PasswordDialog(
|
||||||
|
onReject = {
|
||||||
|
gitManager.credentialsDenied()
|
||||||
|
dialogManager.dismiss()
|
||||||
|
},
|
||||||
|
onAccept = { password ->
|
||||||
|
gitManager.sshCredentialsAccepted(password)
|
||||||
dialogManager.dismiss()
|
dialogManager.dismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
83
src/main/kotlin/app/ui/dialogs/PasswordDialog.kt
Normal file
83
src/main/kotlin/app/ui/dialogs/PasswordDialog.kt
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package app.ui.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusOrder
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PasswordDialog(
|
||||||
|
onReject: () -> Unit,
|
||||||
|
onAccept: (password: String) -> Unit
|
||||||
|
) {
|
||||||
|
var passwordField by remember { mutableStateOf("") }
|
||||||
|
val passwordFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
val buttonFieldFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colors.background),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Introduce your default SSH key's password",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
.focusOrder(passwordFieldFocusRequester) {
|
||||||
|
this.next = buttonFieldFocusRequester
|
||||||
|
}
|
||||||
|
.width(300.dp),
|
||||||
|
value = passwordField,
|
||||||
|
singleLine = true,
|
||||||
|
label = { Text("Password", fontSize = 14.sp) },
|
||||||
|
textStyle = TextStyle(fontSize = 14.sp),
|
||||||
|
onValueChange = {
|
||||||
|
passwordField = it
|
||||||
|
},
|
||||||
|
visualTransformation = PasswordVisualTransformation()
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
onClick = {
|
||||||
|
onReject()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.focusOrder(buttonFieldFocusRequester) {
|
||||||
|
this.previous = passwordFieldFocusRequester
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onAccept(passwordField)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
passwordFieldFocusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user