Implemented proxy settings logic

Fixes #113
This commit is contained in:
Abdelilah El Aissaoui 2023-09-10 18:26:11 +02:00
parent be616315f8
commit 0a8c8ac1ed
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
4 changed files with 284 additions and 21 deletions

View File

@ -26,6 +26,7 @@ import com.jetpackduba.gitnuro.logging.printError
import com.jetpackduba.gitnuro.managers.AppStateManager import com.jetpackduba.gitnuro.managers.AppStateManager
import com.jetpackduba.gitnuro.managers.TempFilesManager import com.jetpackduba.gitnuro.managers.TempFilesManager
import com.jetpackduba.gitnuro.preferences.AppSettings import com.jetpackduba.gitnuro.preferences.AppSettings
import com.jetpackduba.gitnuro.preferences.ProxySettings
import com.jetpackduba.gitnuro.system.systemSeparator import com.jetpackduba.gitnuro.system.systemSeparator
import com.jetpackduba.gitnuro.theme.AppTheme import com.jetpackduba.gitnuro.theme.AppTheme
import com.jetpackduba.gitnuro.theme.Theme import com.jetpackduba.gitnuro.theme.Theme
@ -36,11 +37,17 @@ import com.jetpackduba.gitnuro.ui.components.RepositoriesTabPanel
import com.jetpackduba.gitnuro.ui.components.TabInformation import com.jetpackduba.gitnuro.ui.components.TabInformation
import com.jetpackduba.gitnuro.ui.components.emptyTabInformation import com.jetpackduba.gitnuro.ui.components.emptyTabInformation
import com.jetpackduba.gitnuro.ui.context_menu.AppPopupMenu import com.jetpackduba.gitnuro.ui.context_menu.AppPopupMenu
import com.jetpackduba.gitnuro.ui.dialogs.settings.ProxyType
import kotlinx.coroutines.launch
import org.eclipse.jgit.lib.GpgSigner import org.eclipse.jgit.lib.GpgSigner
import java.io.File import java.io.File
import java.net.Authenticator
import java.net.PasswordAuthentication
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "App" private const val TAG = "App"
val LocalTabScope = compositionLocalOf { emptyTabInformation() } val LocalTabScope = compositionLocalOf { emptyTabInformation() }
@ -73,6 +80,9 @@ class App {
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
fun start(args: Array<String>) { fun start(args: Array<String>) {
tabsManager.appComponent = this.appComponent tabsManager.appComponent = this.appComponent
initProxySettings()
val windowPlacement = appSettings.windowPlacement.toWindowPlacement val windowPlacement = appSettings.windowPlacement.toWindowPlacement
val dirToOpen = getDirToOpen(args) val dirToOpen = getDirToOpen(args)
@ -118,7 +128,8 @@ class App {
state = windowState, state = windowState,
icon = painterResource(AppIcons.LOGO), icon = painterResource(AppIcons.LOGO),
) { ) {
val compositionValues: MutableList<ProvidedValue<*>> = mutableListOf(LocalTextContextMenu provides AppPopupMenu()) val compositionValues: MutableList<ProvidedValue<*>> =
mutableListOf(LocalTextContextMenu provides AppPopupMenu())
if (scale != -1f) { if (scale != -1f) {
compositionValues.add(LocalDensity provides Density(scale, 1f)) compositionValues.add(LocalDensity provides Density(scale, 1f))
@ -146,6 +157,71 @@ class App {
} }
} }
private fun initProxySettings() {
appStateManager.appScope.launch {
appSettings.proxyFlow.collect { proxySettings ->
if (proxySettings.useProxy) {
when (proxySettings.proxyType) {
ProxyType.HTTP -> setHttpProxy(proxySettings)
ProxyType.SOCKS -> setSocksProxy(proxySettings)
}
} else {
clearProxySettings()
}
}
}
}
private fun clearProxySettings() {
System.setProperty("http.proxyHost", "")
System.setProperty("http.proxyPort", "")
System.setProperty("https.proxyHost", "")
System.setProperty("https.proxyPort", "")
System.setProperty("socksProxyHost", "")
System.setProperty("socksProxyPort", "")
}
private fun setHttpProxy(proxySettings: ProxySettings) {
System.setProperty("http.proxyHost", proxySettings.hostName)
System.setProperty("http.proxyPort", proxySettings.hostPort.toString())
System.setProperty("https.proxyHost", proxySettings.hostName)
System.setProperty("https.proxyPort", proxySettings.hostPort.toString())
if (proxySettings.useAuth) {
Authenticator.setDefault(
object : Authenticator() {
public override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(proxySettings.hostUser, proxySettings.hostPassword.toCharArray())
}
}
)
System.setProperty("http.proxyUser", proxySettings.hostUser)
System.setProperty("http.proxyPassword", proxySettings.hostPassword)
System.setProperty("https.proxyUser", proxySettings.hostUser)
System.setProperty("https.proxyPassword", proxySettings.hostPassword)
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "")
}
}
private fun setSocksProxy(proxySettings: ProxySettings) {
System.setProperty("socksProxyHost", proxySettings.hostName)
System.setProperty("socksProxyPort", proxySettings.hostPort.toString())
if (proxySettings.useAuth) {
Authenticator.setDefault(
object : Authenticator() {
public override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(proxySettings.hostUser, proxySettings.hostPassword.toCharArray())
}
}
)
System.setProperty("java.net.socks.username", proxySettings.hostUser)
System.setProperty("java.net.socks.password", proxySettings.hostPassword)
}
}
private fun addDirTab(dirToOpen: File) { private fun addDirTab(dirToOpen: File) {
val absolutePath = dirToOpen.normalize().absolutePath val absolutePath = dirToOpen.normalize().absolutePath
.removeSuffix(systemSeparator) .removeSuffix(systemSeparator)

View File

@ -5,6 +5,7 @@ import com.jetpackduba.gitnuro.system.OS
import com.jetpackduba.gitnuro.system.currentOs import com.jetpackduba.gitnuro.system.currentOs
import com.jetpackduba.gitnuro.theme.ColorsScheme import com.jetpackduba.gitnuro.theme.ColorsScheme
import com.jetpackduba.gitnuro.theme.Theme import com.jetpackduba.gitnuro.theme.Theme
import com.jetpackduba.gitnuro.ui.dialogs.settings.ProxyType
import com.jetpackduba.gitnuro.viewmodels.TextDiffType import com.jetpackduba.gitnuro.viewmodels.TextDiffType
import com.jetpackduba.gitnuro.viewmodels.textDiffTypeFromValue import com.jetpackduba.gitnuro.viewmodels.textDiffTypeFromValue
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -30,6 +31,13 @@ private const val PREF_DIFF_TYPE = "diffType"
private const val PREF_DIFF_FULL_FILE = "diffFullFile" private const val PREF_DIFF_FULL_FILE = "diffFullFile"
private const val PREF_SWAP_UNCOMMITTED_CHANGES = "inverseUncommittedChanges" private const val PREF_SWAP_UNCOMMITTED_CHANGES = "inverseUncommittedChanges"
private const val PREF_TERMINAL_PATH = "terminalPath" private const val PREF_TERMINAL_PATH = "terminalPath"
private const val PREF_USE_PROXY = "useProxy"
private const val PREF_PROXY_TYPE = "proxyType"
private const val PREF_PROXY_HOST_NAME = "proxyHostName"
private const val PREF_PROXY_PORT = "proxyPort"
private const val PREF_PROXY_USE_AUTH = "proxyUseAuth"
private const val PREF_PROXY_USER = "proxyHostUser"
private const val PREF_PROXY_PASSWORD = "proxyHostPassword"
private const val PREF_CACHE_CREDENTIALS_IN_MEMORY = "credentialsInMemory" private const val PREF_CACHE_CREDENTIALS_IN_MEMORY = "credentialsInMemory"
@ -86,6 +94,20 @@ class AppSettings @Inject constructor() {
private val _terminalPathFlow = MutableStateFlow(terminalPath) private val _terminalPathFlow = MutableStateFlow(terminalPath)
val terminalPathFlow = _terminalPathFlow.asStateFlow() val terminalPathFlow = _terminalPathFlow.asStateFlow()
private val _proxyFlow = MutableStateFlow(
ProxySettings(
useProxy,
proxyType,
proxyHostName,
proxyPortNumber,
proxyUseAuth,
proxyHostUser,
proxyHostPassword,
)
)
val proxyFlow = _proxyFlow.asStateFlow()
var latestTabsOpened: String var latestTabsOpened: String
get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "") get() = preferences.get(PREF_LATEST_REPOSITORIES_TABS_OPENED, "")
set(value) { set(value) {
@ -238,6 +260,62 @@ class AppSettings @Inject constructor() {
_terminalPathFlow.value = value _terminalPathFlow.value = value
} }
var useProxy: Boolean
get() {
return preferences.getBoolean(PREF_USE_PROXY, false)
}
set(value) {
preferences.putBoolean(PREF_USE_PROXY, value)
_proxyFlow.value = _proxyFlow.value.copy(useProxy = value)
}
var proxyUseAuth: Boolean
get() {
return preferences.getBoolean(PREF_PROXY_USE_AUTH, false)
}
set(value) {
preferences.putBoolean(PREF_PROXY_USE_AUTH, value)
_proxyFlow.value = _proxyFlow.value.copy(useAuth = value)
}
var proxyType: ProxyType
get() {
val value = preferences.getInt(PREF_PROXY_TYPE, ProxyType.HTTP.value)
return ProxyType.fromInt(value)
}
set(value) {
preferences.putInt(PREF_PROXY_TYPE, value.value)
_proxyFlow.value = _proxyFlow.value.copy(proxyType = value)
}
var proxyHostName: String
get() = preferences.get(PREF_PROXY_HOST_NAME, "")
set(value) {
preferences.put(PREF_PROXY_HOST_NAME, value)
_proxyFlow.value = _proxyFlow.value.copy(hostName = value)
}
var proxyPortNumber: Int
get() = preferences.getInt(PREF_PROXY_PORT, 80)
set(value) {
preferences.putInt(PREF_PROXY_PORT, value)
_proxyFlow.value = _proxyFlow.value.copy(hostPort = value)
}
var proxyHostUser: String
get() = preferences.get(PREF_PROXY_USER, "")
set(value) {
preferences.put(PREF_PROXY_USER, value)
_proxyFlow.value = _proxyFlow.value.copy(hostUser = value)
}
var proxyHostPassword: String
get() = preferences.get(PREF_PROXY_PASSWORD, "")
set(value) {
preferences.put(PREF_PROXY_PASSWORD, value)
_proxyFlow.value = _proxyFlow.value.copy(hostPassword = value)
}
fun saveCustomTheme(filePath: String) { fun saveCustomTheme(filePath: String) {
val file = File(filePath) val file = File(filePath)
val content = file.readText() val content = file.readText()
@ -254,7 +332,30 @@ class AppSettings @Inject constructor() {
_customThemeFlow.value = Json.decodeFromString<ColorsScheme>(themeJson) _customThemeFlow.value = Json.decodeFromString<ColorsScheme>(themeJson)
} }
} }
private fun loadProxySettings() {
_proxyFlow.value = ProxySettings(
useProxy,
proxyType,
proxyHostName,
proxyPortNumber,
proxyUseAuth,
proxyHostUser,
proxyHostPassword,
)
} }
}
data class ProxySettings(
val useProxy: Boolean,
val proxyType: ProxyType,
val hostName: String,
val hostPort: Int,
val useAuth: Boolean,
val hostUser: String,
val hostPassword: String,
)
// TODO migrate old prefs path to new one? // TODO migrate old prefs path to new one?
fun initPreferencesPath() { fun initPreferencesPath() {

View File

@ -47,7 +47,7 @@ val settings = listOf(
SettingsEntry.Entry(AppIcons.CLOUD, "Remote actions") { RemoteActions(it) }, SettingsEntry.Entry(AppIcons.CLOUD, "Remote actions") { RemoteActions(it) },
SettingsEntry.Section("Network"), SettingsEntry.Section("Network"),
SettingsEntry.Entry(AppIcons.NETWORK, "Proxy") { Proxy() }, SettingsEntry.Entry(AppIcons.NETWORK, "Proxy") { Proxy(it) },
SettingsEntry.Entry(AppIcons.PASSWORD, "Authentication") { Authentication(it) }, SettingsEntry.Entry(AppIcons.PASSWORD, "Authentication") { Authentication(it) },
SettingsEntry.Section("Tools"), SettingsEntry.Section("Tools"),
@ -55,24 +55,32 @@ val settings = listOf(
) )
@Composable @Composable
fun Proxy() { fun Proxy(settingsViewModel: SettingsViewModel) {
var useProxy by remember { mutableStateOf(false) } var useProxy by remember { mutableStateOf(settingsViewModel.useProxy) }
var hostName by remember { mutableStateOf("") } var hostName by remember { mutableStateOf(settingsViewModel.proxyHostName) }
var portNumber by remember { mutableStateOf(80) } var portNumber by remember { mutableStateOf(settingsViewModel.proxyPortNumber) }
var login by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var useAuth by remember { mutableStateOf(settingsViewModel.proxyUseAuth) }
var user by remember { mutableStateOf(settingsViewModel.proxyHostUser) }
var password by remember { mutableStateOf(settingsViewModel.proxyHostPassword) }
val proxyTypes = listOf(ProxyType.HTTP, ProxyType.SOCKS) val proxyTypes = listOf(ProxyType.HTTP, ProxyType.SOCKS)
val proxyTypesDropDownOptions = proxyTypes.map { DropDownOption(it, it.name) } val proxyTypesDropDownOptions = proxyTypes.map { DropDownOption(it, it.name) }
var currentProxyType by remember { mutableStateOf(proxyTypesDropDownOptions.first()) }
var currentProxyType by remember {
mutableStateOf(proxyTypesDropDownOptions.first { it.value == settingsViewModel.proxyType })
}
Column { Column {
SettingToggle( SettingToggle(
title = "Use proxy", title = "Use proxy",
subtitle = "Set up your proxy configuration if needed", subtitle = "Set up your proxy configuration if needed",
value = useProxy, value = useProxy,
onValueChanged = { useProxy = it }, onValueChanged = {
useProxy = it
settingsViewModel.useProxy = it
},
) )
SettingDropDown( SettingDropDown(
@ -80,14 +88,20 @@ fun Proxy() {
subtitle = "Pick between HTTP or SOCKS", subtitle = "Pick between HTTP or SOCKS",
dropDownOptions = proxyTypesDropDownOptions, dropDownOptions = proxyTypesDropDownOptions,
currentOption = currentProxyType, currentOption = currentProxyType,
onOptionSelected = { currentProxyType = it } onOptionSelected = {
currentProxyType = it
settingsViewModel.proxyType = it.value
}
) )
SettingTextInput( SettingTextInput(
title = "Host name", title = "Host name",
subtitle = "", subtitle = "",
value = hostName, value = hostName,
onValueChanged = { hostName = it }, onValueChanged = {
hostName = it
settingsViewModel.proxyHostName = it
},
enabled = useProxy, enabled = useProxy,
) )
@ -95,16 +109,32 @@ fun Proxy() {
title = "Port number", title = "Port number",
subtitle = "", subtitle = "",
value = portNumber, value = portNumber,
onValueChanged = { portNumber = it }, onValueChanged = {
portNumber = it
settingsViewModel.proxyPortNumber = it
},
enabled = useProxy, enabled = useProxy,
) )
SettingToggle(
title = "Proxy authentication",
subtitle = "Use your credentials to provide your identity the proxy server",
value = useAuth,
onValueChanged = {
useAuth = it
settingsViewModel.proxyUseAuth = it
}
)
SettingTextInput( SettingTextInput(
title = "Login", title = "Login",
subtitle = "", subtitle = "",
value = login, value = user,
onValueChanged = { login = it }, onValueChanged = {
enabled = useProxy, user = it
settingsViewModel.proxyHostUser = it
},
enabled = useProxy && useAuth,
) )
@ -112,9 +142,12 @@ fun Proxy() {
title = "Password", title = "Password",
subtitle = "", subtitle = "",
value = password, value = password,
onValueChanged = { password = it }, onValueChanged = {
password = it
settingsViewModel.proxyHostPassword = it
},
isPassword = true, isPassword = true,
enabled = useProxy, enabled = useProxy && useAuth,
) )
} }
@ -691,7 +724,17 @@ private fun isValidFloat(value: String): Boolean {
} }
} }
enum class ProxyType { enum class ProxyType(val value: Int) {
HTTP, HTTP(1),
SOCKS, SOCKS(2);
companion object {
fun fromInt(value: Int): ProxyType {
return when (value) {
HTTP.value -> HTTP
SOCKS.value -> SOCKS
else -> throw NotImplementedError("Proxy type unknown")
}
}
}
} }

View File

@ -7,6 +7,7 @@ import com.jetpackduba.gitnuro.preferences.AppSettings
import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase
import com.jetpackduba.gitnuro.system.PickerType import com.jetpackduba.gitnuro.system.PickerType
import com.jetpackduba.gitnuro.theme.Theme import com.jetpackduba.gitnuro.theme.Theme
import com.jetpackduba.gitnuro.ui.dialogs.settings.ProxyType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -84,6 +85,48 @@ class SettingsViewModel @Inject constructor(
appSettings.terminalPath = value appSettings.terminalPath = value
} }
var useProxy: Boolean
get() = appSettings.useProxy
set(value) {
appSettings.useProxy = value
}
var proxyType: ProxyType
get() = appSettings.proxyType
set(value) {
appSettings.proxyType = value
}
var proxyHostName: String
get() = appSettings.proxyHostName
set(value) {
appSettings.proxyHostName = value
}
var proxyPortNumber: Int
get() = appSettings.proxyPortNumber
set(value) {
appSettings.proxyPortNumber = value
}
var proxyUseAuth: Boolean
get() = appSettings.proxyUseAuth
set(value) {
appSettings.proxyUseAuth = value
}
var proxyHostUser: String
get() = appSettings.proxyHostUser
set(value) {
appSettings.proxyHostUser = value
}
var proxyHostPassword: String
get() = appSettings.proxyHostPassword
set(value) {
appSettings.proxyHostPassword = value
}
fun saveCustomTheme(filePath: String): Error? { fun saveCustomTheme(filePath: String): Error? {
return try { return try {
appSettings.saveCustomTheme(filePath) appSettings.saveCustomTheme(filePath)