mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
Added Certificate setting to the login screen (#172)
* Added Certificate setting to the login screen, user can now specify certificates path and keystore/truststore password in the certificate setting screen.
This commit is contained in:
parent
e9345e2999
commit
9f5e6d3921
@ -5,6 +5,9 @@ import javafx.beans.Observable
|
||||
import javafx.beans.property.ObjectProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.createDirectories
|
||||
import net.corda.core.div
|
||||
import net.corda.core.exists
|
||||
import tornadofx.Component
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
@ -16,10 +19,8 @@ import kotlin.reflect.jvm.javaType
|
||||
|
||||
class SettingsModel(path: Path = Paths.get("conf")) : Component(), Observable {
|
||||
// Using CordaExplorer as config file name instead of TornadoFX default.
|
||||
private val path = {
|
||||
if (!Files.exists(path)) Files.createDirectories(path)
|
||||
path.resolve("CordaExplorer.properties")
|
||||
}()
|
||||
private val path = path.apply { if (!exists()) createDirectories() } / "CordaExplorer.properties"
|
||||
|
||||
private val listeners = mutableListOf<InvalidationListener>()
|
||||
|
||||
// Delegate to config.
|
||||
@ -29,6 +30,9 @@ class SettingsModel(path: Path = Paths.get("conf")) : Component(), Observable {
|
||||
private var username: String by config
|
||||
private var reportingCurrency: Currency by config
|
||||
private var fullscreen: Boolean by config
|
||||
private var certificatesDir: Path by config
|
||||
private var keyStorePassword: String by config
|
||||
private var trustStorePassword: String by config
|
||||
|
||||
// Create observable Properties.
|
||||
val reportingCurrencyProperty = writableConfigProperty(SettingsModel::reportingCurrency)
|
||||
@ -37,6 +41,10 @@ class SettingsModel(path: Path = Paths.get("conf")) : Component(), Observable {
|
||||
val portProperty = writableConfigProperty(SettingsModel::port)
|
||||
val usernameProperty = writableConfigProperty(SettingsModel::username)
|
||||
val fullscreenProperty = writableConfigProperty(SettingsModel::fullscreen)
|
||||
val certificatesDirProperty = writableConfigProperty(SettingsModel::certificatesDir)
|
||||
// TODO : We should encrypt all passwords in config file.
|
||||
val keyStorePasswordProperty = writableConfigProperty(SettingsModel::keyStorePassword)
|
||||
val trustStorePasswordProperty = writableConfigProperty(SettingsModel::trustStorePassword)
|
||||
|
||||
init {
|
||||
load()
|
||||
@ -59,6 +67,7 @@ class SettingsModel(path: Path = Paths.get("conf")) : Component(), Observable {
|
||||
Int::class.java -> string(metadata.name, "0").toInt() as T
|
||||
Boolean::class.java -> boolean(metadata.name) as T
|
||||
Currency::class.java -> currency(string(metadata.name, "USD")) as T
|
||||
Path::class.java -> Paths.get(string(metadata.name, "")).toAbsolutePath() as T
|
||||
else -> throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
package net.corda.explorer.views
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.scene.control.*
|
||||
import net.corda.client.fxutils.map
|
||||
import net.corda.client.model.NodeMonitorModel
|
||||
import net.corda.client.model.objectProperty
|
||||
import net.corda.core.exists
|
||||
import net.corda.explorer.model.SettingsModel
|
||||
import net.corda.node.services.config.NodeSSLConfiguration
|
||||
import net.corda.node.services.config.configureTestSSL
|
||||
import org.controlsfx.dialog.ExceptionDialog
|
||||
import tornadofx.View
|
||||
import tornadofx.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class LoginView : View() {
|
||||
@ -20,6 +26,7 @@ class LoginView : View() {
|
||||
private val passwordTextField by fxid<PasswordField>()
|
||||
private val rememberMeCheckBox by fxid<CheckBox>()
|
||||
private val fullscreenCheckBox by fxid<CheckBox>()
|
||||
private val certificateButton by fxid<Button>()
|
||||
private val portProperty = SimpleIntegerProperty()
|
||||
|
||||
private val rememberMe by objectProperty(SettingsModel::rememberMeProperty)
|
||||
@ -27,6 +34,9 @@ class LoginView : View() {
|
||||
private val host by objectProperty(SettingsModel::hostProperty)
|
||||
private val port by objectProperty(SettingsModel::portProperty)
|
||||
private val fullscreen by objectProperty(SettingsModel::fullscreenProperty)
|
||||
private val certificatesDir by objectProperty(SettingsModel::certificatesDirProperty)
|
||||
private val keyStorePasswordProperty by objectProperty(SettingsModel::keyStorePasswordProperty)
|
||||
private val trustStorePasswordProperty by objectProperty(SettingsModel::trustStorePasswordProperty)
|
||||
|
||||
fun login() {
|
||||
val status = Dialog<LoginStatus>().apply {
|
||||
@ -36,8 +46,7 @@ class LoginView : View() {
|
||||
ButtonBar.ButtonData.OK_DONE -> try {
|
||||
root.isDisable = true
|
||||
// TODO : Run this async to avoid UI lockup.
|
||||
// TODO : Use proper SSL certificate.
|
||||
getModel<NodeMonitorModel>().register(HostAndPort.fromParts(hostTextField.text, portProperty.value), configureTestSSL(), usernameTextField.text, passwordTextField.text)
|
||||
getModel<NodeMonitorModel>().register(HostAndPort.fromParts(hostTextField.text, portProperty.value), configureSSL(), usernameTextField.text, passwordTextField.text)
|
||||
if (!rememberMe.value) {
|
||||
username.value = ""
|
||||
host.value = ""
|
||||
@ -70,6 +79,18 @@ class LoginView : View() {
|
||||
if (status != LoginStatus.loggedIn) login()
|
||||
}
|
||||
|
||||
private fun configureSSL(): NodeSSLConfiguration {
|
||||
val sslConfig = object : NodeSSLConfiguration {
|
||||
override val certificatesDirectory: Path get() = certificatesDir.get()
|
||||
override val keyStorePassword: String get() = keyStorePasswordProperty.get()
|
||||
override val trustStorePassword: String get() = trustStorePasswordProperty.get()
|
||||
}
|
||||
// TODO : Don't use dev certificates.
|
||||
return if (sslConfig.keyStorePath.exists()) sslConfig else configureTestSSL().apply {
|
||||
alert(Alert.AlertType.WARNING, "", "KeyStore not found in certificates directory.\nDEV certificates will be used by default.")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// Restrict text field to Integer only.
|
||||
portTextField.textFormatter = intFormatter().apply { portProperty.bind(this.valueProperty()) }
|
||||
@ -78,6 +99,44 @@ class LoginView : View() {
|
||||
usernameTextField.textProperty().bindBidirectional(username)
|
||||
hostTextField.textProperty().bindBidirectional(host)
|
||||
portTextField.textProperty().bindBidirectional(port)
|
||||
certificateButton.setOnAction {
|
||||
Dialog<ButtonType>().apply {
|
||||
title = "Certificates Settings"
|
||||
initOwner(root.scene.window)
|
||||
dialogPane.content = gridpane {
|
||||
vgap = 10.0
|
||||
hgap = 5.0
|
||||
row("Certificates Directory :") {
|
||||
textfield {
|
||||
prefWidth = 400.0
|
||||
textProperty().bind(certificatesDir.map(Path::toString))
|
||||
isEditable = false
|
||||
}
|
||||
button {
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.FOLDER_OPEN_ALT)
|
||||
maxHeight = Double.MAX_VALUE
|
||||
setOnAction {
|
||||
chooseDirectory(owner = dialogPane.scene.window) {
|
||||
initialDirectoryProperty().bind(certificatesDir.map(Path::toFile))
|
||||
}?.let {
|
||||
certificatesDir.set(it.toPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
row("KeyStore Password :") { passwordfield(keyStorePasswordProperty) }
|
||||
row("TrustStore Password :") { passwordfield(trustStorePasswordProperty) }
|
||||
}
|
||||
dialogPane.buttonTypes.addAll(ButtonType.APPLY, ButtonType.CANCEL)
|
||||
}.showAndWait().get().let {
|
||||
when (it) {
|
||||
ButtonType.APPLY -> getModel<SettingsModel>().commit()
|
||||
// Discard changes.
|
||||
else -> getModel<SettingsModel>().load()
|
||||
}
|
||||
}
|
||||
}
|
||||
certificateButton.tooltip("Certificate Configuration")
|
||||
}
|
||||
|
||||
private enum class LoginStatus {
|
||||
|
@ -158,6 +158,18 @@
|
||||
-fx-background-color: -color-4;
|
||||
}
|
||||
|
||||
.certificateButton.button, .certificateButton.button:hover{
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.certificateIcon{
|
||||
-fx-fill: -color-2;
|
||||
}
|
||||
|
||||
.certificateIcon:hover{
|
||||
-fx-fill: -color-3;
|
||||
}
|
||||
|
||||
.login {
|
||||
-fx-background-color: -color-0;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView?>
|
||||
<DialogPane styleClass="login" stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112-ea"
|
||||
xmlns:fx="http://javafx.com/fxml/1">
|
||||
<padding>
|
||||
@ -21,20 +22,27 @@
|
||||
</VBox>
|
||||
</top>
|
||||
<center>
|
||||
<GridPane hgap="10" prefWidth="400" vgap="10">
|
||||
<GridPane hgap="10" vgap="10">
|
||||
<Label text="Corda Node :" GridPane.halignment="RIGHT"/>
|
||||
<TextField fx:id="hostTextField" promptText="Host" GridPane.columnIndex="1"/>
|
||||
<TextField fx:id="portTextField" prefWidth="100" promptText="Port" GridPane.columnIndex="2"/>
|
||||
|
||||
<Button id="certificateButton" fx:id="certificateButton" GridPane.columnIndex="3" styleClass="certificateButton">
|
||||
<padding>
|
||||
<Insets right="6"/>
|
||||
</padding>
|
||||
<graphic>
|
||||
<FontAwesomeIconView styleClass="certificateIcon" glyphName="LOCK" glyphSize="20"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Label text="Username :" GridPane.rowIndex="1" GridPane.halignment="RIGHT"/>
|
||||
<TextField fx:id="usernameTextField" promptText="Username" GridPane.columnIndex="1"
|
||||
GridPane.columnSpan="2" GridPane.rowIndex="1"/>
|
||||
GridPane.columnSpan="3" GridPane.rowIndex="1"/>
|
||||
|
||||
<Label text="Password :" GridPane.rowIndex="2" GridPane.halignment="RIGHT"/>
|
||||
<PasswordField fx:id="passwordTextField" promptText="Password" GridPane.columnIndex="1"
|
||||
GridPane.columnSpan="2" GridPane.rowIndex="2"/>
|
||||
GridPane.columnSpan="3" GridPane.rowIndex="2"/>
|
||||
|
||||
<HBox spacing="20" GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="2">
|
||||
<HBox spacing="20" GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="3">
|
||||
<CheckBox fx:id="rememberMeCheckBox" text="Remember me"/>
|
||||
<CheckBox fx:id="fullscreenCheckBox" text="Fullscreen mode"/>
|
||||
</HBox>
|
||||
|
Loading…
x
Reference in New Issue
Block a user