Added network credentials management

This commit is contained in:
Abdelilah El Aissaoui 2021-10-02 19:12:27 +02:00
parent 54f013d291
commit 8db1f183ff
6 changed files with 156 additions and 8 deletions

View File

@ -1,3 +1,5 @@
import credentials.CredentialsState
import credentials.CredentialsStateManager
import git.* import git.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -18,6 +20,7 @@ class GitManager {
private val branchesManager = BranchesManager() private val branchesManager = BranchesManager()
private val stashManager = StashManager() private val stashManager = StashManager()
private val diffManager = DiffManager() private val diffManager = DiffManager()
private val credentialsStateManager = CredentialsStateManager
private val managerScope = CoroutineScope(SupervisorJob()) private val managerScope = CoroutineScope(SupervisorJob())
@ -51,6 +54,9 @@ class GitManager {
val latestDirectoryOpened: File? val latestDirectoryOpened: File?
get() = File(preferences.latestOpenedRepositoryPath).parentFile get() = File(preferences.latestOpenedRepositoryPath).parentFile
val credentialsState: StateFlow<CredentialsState>
get() = credentialsStateManager.credentialsState
private var git: Git? = null private var git: Git? = null
val safeGit: Git val safeGit: Git
@ -172,6 +178,14 @@ class GitManager {
fun statusShouldBeUpdated() { fun statusShouldBeUpdated() {
_lastTimeChecked.value = System.currentTimeMillis() _lastTimeChecked.value = System.currentTimeMillis()
} }
fun credentialsDenied() {
credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
}
fun credentialsAccepted(user: String, password: String) {
credentialsStateManager.updateState(CredentialsState.CredentialsAccepted(user, password))
}
} }

View File

@ -1,8 +1,15 @@
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import credentials.CredentialsState
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.Repository import org.eclipse.jgit.lib.Repository
@ -29,8 +36,45 @@ fun RepositorySelected(gitManager: GitManager, repository: Repository) {
val selectedIndexCommitLog = remember { mutableStateOf(-1) } val selectedIndexCommitLog = remember { mutableStateOf(-1) }
val credentialsState by gitManager.credentialsState.collectAsState()
if (credentialsState == CredentialsState.CredentialsRequested) {
var userField by remember { mutableStateOf("") }
var passwordField by remember { mutableStateOf("") }
Dialog(
onCloseRequest = {
gitManager.credentialsDenied()
},
title = "Introduce your remote server credentials",
) {
Column {
OutlinedTextField(
value = userField,
label = { Text("User", fontSize = 14.sp) },
textStyle = TextStyle(fontSize = 14.sp),
onValueChange = {
userField = it
},
)
OutlinedTextField(
value = passwordField,
label = { Text("Password", fontSize = 14.sp) },
textStyle = TextStyle(fontSize = 14.sp),
onValueChange = {
passwordField = it
},
)
Button(onClick = {gitManager.credentialsAccepted(userField, passwordField)}) {
Text("Ok")
}
}
}
}
Row { Row {
Column ( Column(
modifier = Modifier modifier = Modifier
.widthIn(min = 300.dp) .widthIn(min = 300.dp)
.weight(0.15f) .weight(0.15f)
@ -58,7 +102,7 @@ fun RepositorySelected(gitManager: GitManager, repository: Repository) {
} else } else
commit.parents.first() commit.parents.first()
val oldTreeParser = if(parent != null) val oldTreeParser = if (parent != null)
prepareTreeParser(repository, parent) prepareTreeParser(repository, parent)
else { else {
CanonicalTreeParser() CanonicalTreeParser()

View File

@ -1,11 +1,25 @@
package credentials package credentials
class CredentialsStateManager { import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
//TODO Mark as a singleton when dagger is implemented
object CredentialsStateManager {
private val _credentialsState = MutableStateFlow<CredentialsState>(CredentialsState.None)
val credentialsState: StateFlow<CredentialsState>
get() = _credentialsState
val currentCredentialsState: CredentialsState
get() = credentialsState.value
fun updateState(newCredentialsState: CredentialsState) {
_credentialsState.value = newCredentialsState
}
} }
sealed class CredentialsState { sealed class CredentialsState {
object CredentialsRequested: CredentialsState() object None : CredentialsState()
object CredentialsDenied: CredentialsState() object CredentialsRequested : CredentialsState()
data class CredentialsAccepted(val user: String, val password: String): CredentialsState() object CredentialsDenied : CredentialsState()
data class CredentialsAccepted(val user: String, val password: String) : CredentialsState()
} }

View File

@ -0,0 +1,51 @@
package credentials
import org.eclipse.jgit.transport.CredentialItem
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.URIish
class HttpCredentialsProvider : CredentialsProvider() {
private val credentialsStateManager = CredentialsStateManager
override fun isInteractive(): Boolean {
return true
}
override fun supports(vararg items: CredentialItem?): Boolean {
println(items)
val fields = items.map { credentialItem -> credentialItem?.promptText }
return if (fields.isEmpty()) {
true
} else
fields.size == 2 &&
fields.contains("Username") &&
fields.contains("Password")
}
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
credentialsStateManager.updateState(CredentialsState.CredentialsRequested)
@Suppress("ControlFlowWithEmptyBody")
var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsState.CredentialsRequested) {
credentials = credentialsStateManager.currentCredentialsState
}
if(credentials is CredentialsState.CredentialsAccepted) {
val userItem = items.firstOrNull { it?.promptText == "Username" }
val passwordItem = items.firstOrNull { it?.promptText == "Password" }
if(userItem is CredentialItem.Username &&
passwordItem is CredentialItem.Password) {
userItem.value = credentials.user
passwordItem.value = credentials.password.toCharArray()
return true
}
}
return false
}
}

View File

@ -1,22 +1,42 @@
package git package git
import credentials.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.transport.CredentialsProvider import org.eclipse.jgit.transport.*
class RemoteOperationsManager { class RemoteOperationsManager {
private val sessionManager = GSessionManager()
suspend fun pull(git: Git) = withContext(Dispatchers.IO) { suspend fun pull(git: Git) = withContext(Dispatchers.IO) {
git git
.pull() .pull()
.setTransportConfigCallback {
if (it is SshTransport) {
it.sshSessionFactory = sessionManager.generateSshSessionFactory()
} else if (it is HttpTransport) {
it.credentialsProvider = HttpCredentialsProvider()
}
}
.setCredentialsProvider(CredentialsProvider.getDefault()) .setCredentialsProvider(CredentialsProvider.getDefault())
.call() .call()
} }
suspend fun push(git: Git) = withContext(Dispatchers.IO) { suspend fun push(git: Git) = withContext(Dispatchers.IO) {
val currentBranchRefSpec = git.repository.fullBranch
git git
.push() .push()
.setRefSpecs(RefSpec(currentBranchRefSpec))
.setPushTags() .setPushTags()
.setTransportConfigCallback {
if (it is SshTransport) {
it.sshSessionFactory = sessionManager.generateSshSessionFactory()
} else if (it is HttpTransport) {
it.credentialsProvider = HttpCredentialsProvider()
}
}
.call() .call()
} }
} }

View File

@ -112,6 +112,7 @@ fun GMenu(
onPopStash: () -> Unit, onPopStash: () -> Unit,
) { ) {
val openHovering = remember { mutableStateOf(false) } val openHovering = remember { mutableStateOf(false) }
val pullHovering = remember { mutableStateOf(false) }
Row( Row(
modifier = Modifier modifier = Modifier
@ -130,8 +131,12 @@ fun GMenu(
) )
MenuButton( MenuButton(
title = "Pull", title = "Pull",
hovering = pullHovering,
icon = painterResource("download.svg"), icon = painterResource("download.svg"),
onClick = onPull, onClick = {
pullHovering.value = false
onPull()
},
enabled = isRepositoryOpen, enabled = isRepositoryOpen,
) )
MenuButton( MenuButton(