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:
Patrick Kuo 2017-01-20 17:16:55 +00:00 committed by GitHub
parent e9345e2999
commit 9f5e6d3921
4 changed files with 102 additions and 14 deletions

View File

@ -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}")
}
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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>