Added GPG signing support

Fixes #45
This commit is contained in:
Abdelilah El Aissaoui 2023-01-28 17:39:35 +01:00
parent 96cbdba8d9
commit 7de332be87
No known key found for this signature in database
GPG key ID: 7587FC860F594869
10 changed files with 156 additions and 10 deletions

View file

@ -24,12 +24,15 @@ repositories {
}
dependencies {
val jgit = "6.4.0.202211300538-r"
implementation(compose.desktop.currentOs)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.desktop.components.splitPane)
implementation(compose("org.jetbrains.compose.ui:ui-util"))
implementation(compose("org.jetbrains.compose.components:components-animatedimage"))
implementation("org.eclipse.jgit:org.eclipse.jgit:6.4.0.202211300538-r")
implementation("org.eclipse.jgit:org.eclipse.jgit:$jgit")
implementation("org.eclipse.jgit:org.eclipse.jgit.gpg.bc:$jgit")
implementation("org.apache.sshd:sshd-core:2.9.0")
implementation("com.google.dagger:dagger:2.44.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")

View file

@ -24,9 +24,11 @@ sealed class CredentialsState {
object None : CredentialsState()
sealed class CredentialsRequested : CredentialsState()
object SshCredentialsRequested : CredentialsRequested()
object GpgCredentialsRequested : CredentialsRequested()
object HttpCredentialsRequested : CredentialsRequested()
object CredentialsDenied : CredentialsState()
sealed class CredentialsAccepted : CredentialsState()
data class SshCredentialsAccepted(val password: String) : CredentialsAccepted()
data class GpgCredentialsAccepted(val password: String) : CredentialsAccepted()
data class HttpCredentialsAccepted(val user: String, val password: String) : CredentialsAccepted()
}

View file

@ -0,0 +1,43 @@
package com.jetpackduba.gitnuro.credentials
import org.eclipse.jgit.transport.CredentialItem
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.URIish
import javax.inject.Inject
private const val PASSWORD_FIELD_IDENTIFIER = "Passphrase"
class GpgCredentialsProvider @Inject constructor(
private val credentialsStateManager: CredentialsStateManager,
) : CredentialsProvider() {
override fun isInteractive(): Boolean = true
override fun supports(vararg items: CredentialItem?): Boolean {
println(items)
return true
}
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
val item = items.firstOrNull {
it?.promptText == PASSWORD_FIELD_IDENTIFIER
}
if (item != null && item is CredentialItem.Password) {
credentialsStateManager.updateState(CredentialsState.GpgCredentialsRequested)
var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsState.GpgCredentialsRequested) {
credentials = credentialsStateManager.currentCredentialsState
}
if (credentials is CredentialsState.GpgCredentialsAccepted) {
item.value = credentials.password.toCharArray()
return true
}
}
return false
}
}

View file

@ -1,17 +1,24 @@
package com.jetpackduba.gitnuro.git.workspace
import com.jetpackduba.gitnuro.credentials.GpgCredentialsProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.transport.CredentialItem
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.URIish
import javax.inject.Inject
class DoCommitUseCase @Inject constructor() {
class DoCommitUseCase @Inject constructor(
private val gpgCredentialsProvider: GpgCredentialsProvider,
) {
suspend operator fun invoke(git: Git, message: String, amend: Boolean): RevCommit = withContext(Dispatchers.IO) {
git.commit()
.setMessage(message)
.setAllowEmpty(amend) // Only allow empty commits when amending
.setAmend(amend)
.setCredentialsProvider(gpgCredentialsProvider)
.call()
}
}

View file

@ -22,9 +22,7 @@ import androidx.compose.ui.unit.sp
import com.jetpackduba.gitnuro.LoadingRepository
import com.jetpackduba.gitnuro.LocalTabScope
import com.jetpackduba.gitnuro.credentials.CredentialsState
import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog
import com.jetpackduba.gitnuro.ui.dialogs.PasswordDialog
import com.jetpackduba.gitnuro.ui.dialogs.UserPasswordDialog
import com.jetpackduba.gitnuro.ui.dialogs.*
import com.jetpackduba.gitnuro.ui.dialogs.settings.SettingsDialog
import com.jetpackduba.gitnuro.viewmodels.RepositorySelectionStatus
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
@ -191,7 +189,7 @@ fun CredentialsDialog(gitManager: TabViewModel) {
}
)
} else if (credentialsState == CredentialsState.SshCredentialsRequested) {
PasswordDialog(
SshPasswordDialog(
onReject = {
gitManager.credentialsDenied()
},
@ -199,5 +197,14 @@ fun CredentialsDialog(gitManager: TabViewModel) {
gitManager.sshCredentialsAccepted(password)
}
)
} else if (credentialsState == CredentialsState.GpgCredentialsRequested) {
GpgPasswordDialog(
onReject = {
gitManager.credentialsDenied()
},
onAccept = { password ->
gitManager.gpgCredentialsAccepted(password)
}
)
}
}

View file

@ -0,0 +1,38 @@
package com.jetpackduba.gitnuro.ui.dialogs
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
@Composable
fun GpgPasswordDialog(
onReject: () -> Unit,
onAccept: (password: String) -> Unit
) {
PasswordDialog(
"Introduce your GPG key's password",
"Your GPG key is protected with a password",
"key.svg",
onReject,
onAccept,
)
}

View file

@ -25,8 +25,11 @@ import com.jetpackduba.gitnuro.ui.components.PrimaryButton
@Composable
fun PasswordDialog(
title: String,
subtitle: String,
icon: String,
onReject: () -> Unit,
onAccept: (password: String) -> Unit
onAccept: (password: String) -> Unit,
) {
var passwordField by remember { mutableStateOf("") }
val passwordFieldFocusRequester = remember { FocusRequester() }
@ -39,7 +42,7 @@ fun PasswordDialog(
) {
Icon(
painterResource("lock.svg"),
painterResource(icon),
contentDescription = null,
modifier = Modifier
.size(64.dp)
@ -48,7 +51,7 @@ fun PasswordDialog(
)
Text(
text = "Introduce your SSH key's password",
text = title,
modifier = Modifier
.padding(bottom = 8.dp),
color = MaterialTheme.colors.onBackground,
@ -56,7 +59,7 @@ fun PasswordDialog(
)
Text(
text = "Your SSH key is protected with a password",
text = subtitle,
modifier = Modifier
.padding(bottom = 16.dp),
color = MaterialTheme.colors.onBackgroundSecondary,

View file

@ -0,0 +1,38 @@
package com.jetpackduba.gitnuro.ui.dialogs
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.outlinedTextFieldColors
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
import com.jetpackduba.gitnuro.ui.components.AdjustableOutlinedTextField
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
@Composable
fun SshPasswordDialog(
onReject: () -> Unit,
onAccept: (password: String) -> Unit
) {
PasswordDialog(
"Introduce your SSH key's password",
"Your SSH key is protected with a password",
"lock.svg",
onReject,
onAccept,
)
}

View file

@ -441,6 +441,10 @@ class TabViewModel @Inject constructor(
abortRebaseUseCase(git)
rebaseInteractiveViewModel = null // shouldn't be necessary but just to make sure
}
fun gpgCredentialsAccepted(password: String) {
credentialsStateManager.updateState(CredentialsState.GpgCredentialsAccepted(password))
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"/></g></svg>

After

Width:  |  Height:  |  Size: 404 B