Converted FullNodeConfiguration into a data class and added ability to parse Configs into data classes

This commit is contained in:
Shams Asari 2017-04-07 11:23:25 +01:00
parent 868a490bba
commit c17fe29a62
32 changed files with 544 additions and 301 deletions

View File

@ -85,7 +85,7 @@ path to the node's base directory.
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
:webAddress: The host and port on which the bundled webserver will listen if it is started.
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative
@ -123,7 +123,7 @@ path to the node's base directory.
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
following fields:
:user: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
:username: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
:password: The password
:permissions: A list of permission strings which RPC methods can use to control access

View File

@ -95,7 +95,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
webPort 10007
cordapps = []
rpcUsers = [
['user' : "user",
['username' : "user",
'password' : "password",
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
]

View File

@ -18,7 +18,7 @@ networkMapService : {
}
useHTTPS : false
rpcUsers : [
{ user=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
]
devMode : true
// Certificate signing service will be hosted by R3 in the near future.

View File

@ -2,6 +2,7 @@ package net.corda.docs
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.nodeapi.config.parseAs
import net.corda.verifier.Verifier
import org.junit.Test
import java.nio.file.Path
@ -30,13 +31,10 @@ class ExampleConfigTest {
"example-network-map-node.conf"
) {
val baseDirectory = Paths.get("some-example-base-dir")
FullNodeConfiguration(
baseDirectory,
ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
configFile = it
)
)
).parseAs<FullNodeConfiguration>()
}
}

View File

@ -123,7 +123,7 @@ When starting a standalone node using a configuration file we must supply the RP
.. code-block:: text
rpcUsers : [
{ user=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
{ username=user, password=password, permissions=[ StartFlow.net.corda.flows.CashFlow ] }
]
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar manner:
@ -131,7 +131,7 @@ When using the gradle Cordformation plugin to configure and deploy a node you mu
.. code-block:: text
rpcUsers = [
['user' : "user",
['username' : "user",
'password' : "password",
'permissions' : ["StartFlow.net.corda.flows.CashFlow"]]
]

View File

@ -36,7 +36,7 @@ class Node {
/**
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
* The recommended current structure is:
* [[['user': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
* The above is a list to a map of keys to values using Groovy map and list shorthands.
*
* @note Incorrect configurations will not cause a DSL error.

View File

@ -38,4 +38,9 @@ dependencies {
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
// Unit testing helpers.
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile project(':test-utils')
}

View File

@ -12,6 +12,7 @@ import net.corda.core.flows.FlowException
import net.corda.core.serialization.*
import net.corda.core.toFuture
import net.corda.core.toObservable
import net.corda.nodeapi.config.OldConfig
import org.apache.commons.fileupload.MultipartStream
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -24,7 +25,11 @@ val rpcLog: Logger by lazy { LoggerFactory.getLogger("net.corda.rpc") }
/** Used in the RPC wire protocol to wrap an observation with the handle of the observable it's intended for. */
data class MarshalledObservation(val forHandle: Int, val what: Notification<*>)
data class User(val username: String, val password: String, val permissions: Set<String>) {
data class User(
@OldConfig("user")
val username: String,
val password: String,
val permissions: Set<String>) {
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
}

View File

@ -2,73 +2,125 @@ package net.corda.nodeapi.config
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import com.typesafe.config.ConfigUtil
import net.corda.core.noneOrSingle
import org.slf4j.LoggerFactory
import java.net.Proxy
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant
import java.time.LocalDate
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaType
import kotlin.reflect.KType
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, enumValueString: String): T {
return java.lang.Enum.valueOf(clazz, enumValueString)
}
private class DummyEnum : Enum<DummyEnum>("", 0)
@Target(AnnotationTarget.PROPERTY)
annotation class OldConfig(val value: String)
@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// TODO Move other config parsing to use parseAs and remove this
operator fun <T> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
if (metadata.returnType.isMarkedNullable && !hasPath(metadata.name)) {
return null as T
}
val returnType = metadata.returnType.javaType
return when (metadata.returnType.javaType) {
String::class.java -> getString(metadata.name) as T
Int::class.java -> getInt(metadata.name) as T
Integer::class.java -> getInt(metadata.name) as T
Long::class.java -> getLong(metadata.name) as T
Double::class.java -> getDouble(metadata.name) as T
Boolean::class.java -> getBoolean(metadata.name) as T
LocalDate::class.java -> LocalDate.parse(getString(metadata.name)) as T
Instant::class.java -> Instant.parse(getString(metadata.name)) as T
HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T
Path::class.java -> Paths.get(getString(metadata.name)) as T
URL::class.java -> URL(getString(metadata.name)) as T
Properties::class.java -> getProperties(metadata.name) as T
else -> {
if (returnType is Class<*> && Enum::class.java.isAssignableFrom(returnType)) {
return enumBridge(returnType as Class<DummyEnum>, getString(metadata.name)) as T
}
throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
}
}
return getValueInternal(metadata.name, metadata.returnType)
}
/**
* Helper class for optional configurations
*/
class OptionalConfig<out T>(val conf: Config, val lambda: () -> T) {
operator fun getValue(receiver: Any, metadata: KProperty<*>): T {
return if (conf.hasPath(metadata.name)) conf.getValue(receiver, metadata) else lambda()
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
require(clazz.isData) { "Only Kotlin data classes can be parsed" }
val constructor = clazz.primaryConstructor!!
val args = constructor.parameters
.filterNot { it.isOptional && !hasPath(it.name!!) }
.associateBy({ it }) { param ->
// Get the matching property for this parameter
val property = clazz.memberProperties.first { it.name == param.name }
val path = defaultToOldPath(property)
getValueInternal<Any>(path, param.type)
}
return constructor.callBy(args)
}
fun <T> Config.getOrElse(lambda: () -> T): OptionalConfig<T> = OptionalConfig(this, lambda)
inline fun <reified T : Any> Config.parseAs(): T = parseAs(T::class)
fun Config.getProperties(path: String): Properties {
val obj = this.getObject(path)
val props = Properties()
for ((property, objectValue) in obj.entries) {
props.setProperty(property, objectValue.unwrapped().toString())
}
return props
fun Config.toProperties(): Properties {
return entrySet().associateByTo(
Properties(),
{ ConfigUtil.splitPath(it.key).joinToString(".") },
{ it.value.unwrapped().toString() })
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> Config.getListOrElse(path: String, default: Config.() -> List<T>): List<T> {
return if (hasPath(path)) {
(if (T::class == String::class) getStringList(path) else getConfigList(path)) as List<T>
private fun <T> Config.getValueInternal(path: String, type: KType): T {
return (if (type.arguments.isEmpty()) getSingleValue(path, type) else getCollectionValue(path, type)) as T
}
private fun Config.getSingleValue(path: String, type: KType): Any? {
if (type.isMarkedNullable && !hasPath(path)) return null
val typeClass = type.jvmErasure
return when (typeClass) {
String::class -> getString(path)
Int::class -> getInt(path)
Long::class -> getLong(path)
Double::class -> getDouble(path)
Boolean::class -> getBoolean(path)
LocalDate::class -> LocalDate.parse(getString(path))
Instant::class -> Instant.parse(getString(path))
HostAndPort::class -> HostAndPort.fromString(getString(path))
Path::class -> Paths.get(getString(path))
URL::class -> URL(getString(path))
Properties::class -> getConfig(path).toProperties()
else -> if (typeClass.java.isEnum) {
parseEnum(typeClass.java, getString(path))
} else {
this.default()
getConfig(path).parseAs(typeClass)
}
}
}
private fun Config.getCollectionValue(path: String, type: KType): Collection<Any> {
val typeClass = type.jvmErasure
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
if (!hasPath(path)) {
return if (typeClass == List::class) emptyList() else emptySet()
}
val values: List<Any> = when (elementClass) {
String::class -> getStringList(path)
Int::class -> getIntList(path)
Long::class -> getLongList(path)
Double::class -> getDoubleList(path)
Boolean::class -> getBooleanList(path)
LocalDate::class -> getStringList(path).map(LocalDate::parse)
Instant::class -> getStringList(path).map(Instant::parse)
HostAndPort::class -> getStringList(path).map(HostAndPort::fromString)
Path::class -> getStringList(path).map { Paths.get(it) }
URL::class -> getStringList(path).map(::URL)
Properties::class -> getConfigList(path).map(Config::toProperties)
else -> if (elementClass.java.isEnum) {
getStringList(path).map { parseEnum(elementClass.java, it) }
} else {
getConfigList(path).map { it.parseAs(elementClass) }
}
}
return if (typeClass == Set::class) values.toSet() else values
}
private fun Config.defaultToOldPath(property: KProperty<*>): String {
if (!hasPath(property.name)) {
val oldConfig = property.annotations.filterIsInstance<OldConfig>().noneOrSingle()
if (oldConfig != null && hasPath(oldConfig.value)) {
logger.warn("Config key ${oldConfig.value} has been deprecated and will be removed in a future release. " +
"Use ${property.name} instead")
return oldConfig.value
}
}
return property.name
}
@Suppress("UNCHECKED_CAST")
private fun parseEnum(enumType: Class<*>, name: String): Enum<*> = enumBridge(enumType as Class<Proxy.Type>, name) // Any enum will do
private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T = java.lang.Enum.valueOf(clazz, name)
private val logger = LoggerFactory.getLogger("net.corda.nodeapi.config")

View File

@ -0,0 +1,238 @@
package net.corda.nodeapi.config
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory.empty
import com.typesafe.config.ConfigRenderOptions.defaults
import com.typesafe.config.ConfigValueFactory
import net.corda.core.div
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant
import java.time.LocalDate
import java.util.*
import kotlin.reflect.full.primaryConstructor
class ConfigParsingTest {
@Test
fun `String`() {
testPropertyType<StringData, StringListData, String>("hello world!", "bye")
}
@Test
fun `Int`() {
testPropertyType<IntData, IntListData, Int>(1, 2)
}
@Test
fun `Long`() {
testPropertyType<LongData, LongListData, Long>(Long.MAX_VALUE, Long.MIN_VALUE)
}
@Test
fun `Double`() {
testPropertyType<DoubleData, DoubleListData, Double>(1.2, 3.4)
}
@Test
fun `Boolean`() {
testPropertyType<BooleanData, BooleanListData, Boolean>(true, false)
}
@Test
fun `Enum`() {
testPropertyType<EnumData, EnumListData, TestEnum>(TestEnum.Value2, TestEnum.Value1, valuesToString = true)
}
@Test
fun `LocalDate`() {
testPropertyType<LocalDateData, LocalDateListData, LocalDate>(LocalDate.now(), LocalDate.now().plusDays(1), valuesToString = true)
}
@Test
fun `Instant`() {
testPropertyType<InstantData, InstantListData, Instant>(Instant.now(), Instant.now().plusMillis(100), valuesToString = true)
}
@Test
fun `HostAndPort`() {
testPropertyType<HostAndPortData, HostAndPortListData, HostAndPort>(
HostAndPort.fromParts("localhost", 2223),
HostAndPort.fromParts("localhost", 2225),
valuesToString = true)
}
@Test
fun `Path`() {
val path = Paths.get("tmp") / "test"
testPropertyType<PathData, PathListData, Path>(path, path / "file", valuesToString = true)
}
@Test
fun `URL`() {
testPropertyType<URLData, URLListData, URL>(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true)
}
@Test
fun `flat Properties`() {
val config = config("value" to mapOf("key" to "prop"))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key"] = "prop" })
}
@Test
fun `Properties key with dot`() {
val config = config("value" to mapOf("key.key2" to "prop"))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["key.key2"] = "prop" })
}
@Test
fun `nested Properties`() {
val config = config("value" to mapOf("first" to mapOf("second" to "prop")))
assertThat(config.parseAs<PropertiesData>().value).isEqualTo(Properties().apply { this["first.second"] = "prop" })
}
@Test
fun `List of Properties`() {
val config = config("values" to listOf(emptyMap(), mapOf("key" to "prop")))
assertThat(config.parseAs<PropertiesListData>().values).containsExactly(
Properties(),
Properties().apply { this["key"] = "prop" })
}
@Test
fun `Set`() {
val config = config("values" to listOf("a", "a", "b"))
assertThat(config.parseAs<StringSetData>().values).containsOnly("a", "b")
assertThat(empty().parseAs<StringSetData>().values).isEmpty()
}
@Test
fun `multi property data class`() {
val data = config(
"b" to true,
"i" to 123,
"l" to listOf("a", "b"))
.parseAs<MultiPropertyData>()
assertThat(data.i).isEqualTo(123)
assertThat(data.b).isTrue()
assertThat(data.l).containsExactly("a", "b")
}
@Test
fun `nested data classes`() {
val config = config(
"first" to mapOf(
"value" to "nested"))
val data = NestedData(StringData("nested"))
assertThat(config.parseAs<NestedData>()).isEqualTo(data)
}
@Test
fun `List of data classes`() {
val config = config(
"list" to listOf(
mapOf("value" to "1"),
mapOf("value" to "2")))
val data = DataListData(listOf(StringData("1"), StringData("2")))
assertThat(config.parseAs<DataListData>()).isEqualTo(data)
}
@Test
fun `default value property`() {
assertThat(config("a" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 2))
assertThat(config("a" to 3, "defaultOfTwo" to 3).parseAs<DefaultData>()).isEqualTo(DefaultData(3, 3))
}
@Test
fun `nullable property`() {
assertThat(empty().parseAs<NullableData>().nullable).isNull()
assertThat(config("nullable" to null).parseAs<NullableData>().nullable).isNull()
assertThat(config("nullable" to "not null").parseAs<NullableData>().nullable).isEqualTo("not null")
}
@Test
fun `old config property`() {
assertThat(config("oldValue" to "old").parseAs<OldData>().newValue).isEqualTo("old")
assertThat(config("newValue" to "new").parseAs<OldData>().newValue).isEqualTo("new")
}
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
value1: V,
value2: V,
valuesToString: Boolean = false) {
testSingleProperty<S, V>(value1, valuesToString)
testListProperty<L, V>(value1, value2, valuesToString)
}
private inline fun <reified T : SingleData<V>, V : Any> testSingleProperty(value: V, valueToString: Boolean) {
val constructor = T::class.primaryConstructor!!
val config = config("value" to if (valueToString) value.toString() else value)
val data = constructor.call(value)
assertThat(config.parseAs<T>().value).isEqualTo(data.value)
}
private inline fun <reified T : ListData<V>, V : Any> testListProperty(value1: V, value2: V, valuesToString: Boolean) {
val rawValues = listOf(value1, value2)
val configValues = if (valuesToString) rawValues.map(Any::toString) else rawValues
val constructor = T::class.primaryConstructor!!
for (n in 0..2) {
val config = config("values" to configValues.take(n))
val data = constructor.call(rawValues.take(n))
assertThat(config.parseAs<T>().values).isEqualTo(data.values)
}
assertThat(empty().parseAs<T>().values).isEmpty()
}
private fun config(vararg values: Pair<String, *>): Config {
val config = ConfigValueFactory.fromMap(mapOf(*values))
println(config.render(defaults().setOriginComments(false)))
return config.toConfig()
}
private interface SingleData<out T> {
val value: T
}
private interface ListData<out T> {
val values: List<T>
}
data class StringData(override val value: String) : SingleData<String>
data class StringListData(override val values: List<String>) : ListData<String>
data class StringSetData(val values: Set<String>)
data class IntData(override val value: Int) : SingleData<Int>
data class IntListData(override val values: List<Int>) : ListData<Int>
data class LongData(override val value: Long) : SingleData<Long>
data class LongListData(override val values: List<Long>) : ListData<Long>
data class DoubleData(override val value: Double) : SingleData<Double>
data class DoubleListData(override val values: List<Double>) : ListData<Double>
data class BooleanData(override val value: Boolean) : SingleData<Boolean>
data class BooleanListData(override val values: List<Boolean>) : ListData<Boolean>
data class EnumData(override val value: TestEnum) : SingleData<TestEnum>
data class EnumListData(override val values: List<TestEnum>) : ListData<TestEnum>
data class LocalDateData(override val value: LocalDate) : SingleData<LocalDate>
data class LocalDateListData(override val values: List<LocalDate>) : ListData<LocalDate>
data class InstantData(override val value: Instant) : SingleData<Instant>
data class InstantListData(override val values: List<Instant>) : ListData<Instant>
data class HostAndPortData(override val value: HostAndPort) : SingleData<HostAndPort>
data class HostAndPortListData(override val values: List<HostAndPort>) : ListData<HostAndPort>
data class PathData(override val value: Path) : SingleData<Path>
data class PathListData(override val values: List<Path>) : ListData<Path>
data class URLData(override val value: URL) : SingleData<URL>
data class URLListData(override val values: List<URL>) : ListData<URL>
data class PropertiesData(override val value: Properties) : SingleData<Properties>
data class PropertiesListData(override val values: List<Properties>) : ListData<Properties>
data class MultiPropertyData(val i: Int, val b: Boolean, val l: List<String>)
data class NestedData(val first: StringData)
data class DataListData(val list: List<StringData>)
data class DefaultData(val a: Int, val defaultOfTwo: Int = 2)
data class NullableData(val nullable: String?)
data class OldData(
@OldConfig("oldValue")
val newValue: String)
enum class TestEnum { Value1, Value2 }
}

View File

@ -1,10 +1,11 @@
package net.corda.node
import com.typesafe.config.Config
import joptsimple.OptionParser
import joptsimple.util.EnumConverter
import net.corda.core.div
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.nodeapi.config.parseAs
import org.slf4j.event.Level
import java.io.PrintStream
import java.nio.file.Path
@ -64,7 +65,9 @@ data class CmdLineOptions(val baseDirectory: Path,
val isVersion: Boolean,
val noLocalShell: Boolean,
val sshdServer: Boolean) {
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): FullNodeConfiguration {
return ConfigHelper
.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
.parseAs<FullNodeConfiguration>()
}
}

View File

@ -2,7 +2,6 @@
package net.corda.node
import com.jcabi.manifests.Manifests
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import joptsimple.OptionException
import net.corda.core.*
@ -86,9 +85,7 @@ fun main(args: Array<String>) {
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
val conf = try {
val conf = cmdlineOptions.loadConfig()
checkConfigVersion(conf)
FullNodeConfiguration(cmdlineOptions.baseDirectory, conf)
cmdlineOptions.loadConfig()
} catch (e: ConfigException) {
println("Unable to load the configuration file: ${e.rootCause.message}")
exitProcess(2)
@ -157,16 +154,6 @@ fun main(args: Array<String>) {
exitProcess(0)
}
private fun checkConfigVersion(conf: Config) {
// TODO: Remove this check in future milestone.
if (conf.hasPath("artemisAddress")) {
// artemisAddress has been renamed to p2pAddress in M10.
println("artemisAddress has been renamed to p2pAddress in M10, please upgrade your configuration file and start Corda node again.")
println("Corda will now exit...")
exitProcess(1)
}
}
private fun checkJavaVersion() {
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
try {

View File

@ -27,6 +27,7 @@ import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.parseAs
import okhttp3.OkHttpClient
import okhttp3.Request
import org.slf4j.Logger
@ -115,6 +116,7 @@ data class NodeHandle(
val nodeInfo: NodeInfo,
val rpc: CordaRPCOps,
val configuration: FullNodeConfiguration,
val webAddress: HostAndPort,
val process: Process
) {
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!)
@ -421,7 +423,7 @@ class DriverDSL(
"useTestClock" to useTestClock,
"rpcUsers" to rpcUsers.map {
mapOf(
"user" to it.username,
"username" to it.username,
"password" to it.password,
"permissions" to it.permissions
)
@ -429,22 +431,19 @@ class DriverDSL(
"verifierType" to verifierType.name
) + customOverrides
val configuration = FullNodeConfiguration(
baseDirectory,
ConfigHelper.loadConfig(
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
allowMissingConfig = true,
configOverrides = configOverrides
)
)
configOverrides = configOverrides)
val configuration = config.parseAs<FullNodeConfiguration>()
val processFuture = startNode(executorService, configuration, quasarJarPath, debugPort, systemProperties)
val processFuture = startNode(executorService, configuration, config, quasarJarPath, debugPort, systemProperties)
registerProcess(processFuture)
return processFuture.flatMap { process ->
// We continue to use SSL enabled port for RPC when its for node user.
establishRpc(p2pAddress, configuration).flatMap { rpc ->
rpc.waitUntilRegisteredWithNetworkMap().map {
NodeHandle(rpc.nodeIdentity(), rpc, configuration, process)
NodeHandle(rpc.nodeIdentity(), rpc, configuration, webAddress, process)
}
}
}
@ -482,34 +481,29 @@ class DriverDSL(
}
}
private fun queryWebserver(configuration: FullNodeConfiguration, process: Process): HostAndPort? {
val protocol = if (configuration.useHTTPS) {
"https://"
} else {
"http://"
}
val url = URL(protocol + configuration.webAddress.toString() + "/api/status")
val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
private fun queryWebserver(handle: NodeHandle, process: Process): HostAndPort {
val protocol = if (handle.configuration.useHTTPS) "https://" else "http://"
val url = URL("$protocol${handle.webAddress}/api/status")
val client = OkHttpClient.Builder().connectTimeout(5, SECONDS).readTimeout(60, SECONDS).build()
while (process.isAlive) try {
val response = client.newCall(Request.Builder().url(url).build()).execute()
if (response.isSuccessful && (response.body().string() == "started")) {
return configuration.webAddress
return handle.webAddress
}
} catch(e: ConnectException) {
log.debug("Retrying webserver info at ${configuration.webAddress}")
log.debug("Retrying webserver info at ${handle.webAddress}")
}
log.error("Webserver at ${configuration.webAddress} has died")
return null
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
}
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = DriverDSL.startWebserver(executorService, handle.configuration, debugPort)
val process = DriverDSL.startWebserver(executorService, handle, debugPort)
registerProcess(process)
return process.map {
queryWebserver(handle.configuration, it)!!
queryWebserver(handle, it)
}
}
@ -537,7 +531,7 @@ class DriverDSL(
)
log.info("Starting network-map-service")
val startNode = startNode(executorService, FullNodeConfiguration(baseDirectory, config), quasarJarPath, debugPort, systemProperties)
val startNode = startNode(executorService, config.parseAs<FullNodeConfiguration>(), config, quasarJarPath, debugPort, systemProperties)
registerProcess(startNode)
}
@ -553,13 +547,14 @@ class DriverDSL(
private fun startNode(
executorService: ListeningScheduledExecutorService,
nodeConf: FullNodeConfiguration,
config: Config,
quasarJarPath: String,
debugPort: Int?,
overriddenSystemProperties: Map<String, String>
): ListenableFuture<Process> {
return executorService.submit<Process> {
// Write node.conf
writeConfig(nodeConf.baseDirectory, "node.conf", nodeConf.config)
writeConfig(nodeConf.baseDirectory, "node.conf", config)
val systemProperties = mapOf(
"name" to nodeConf.myLegalName,
@ -586,19 +581,19 @@ class DriverDSL(
private fun startWebserver(
executorService: ListeningScheduledExecutorService,
nodeConf: FullNodeConfiguration,
handle: NodeHandle,
debugPort: Int?
): ListenableFuture<Process> {
return executorService.submit<Process> {
val className = "net.corda.webserver.WebServer"
ProcessUtilities.startJavaProcess(
className = className, // cannot directly get class for this, so just use string
arguments = listOf("--base-directory", nodeConf.baseDirectory.toString()),
arguments = listOf("--base-directory", handle.configuration.baseDirectory.toString()),
jdwpPort = debugPort,
extraJvmArguments = listOf("-Dname=node-${nodeConf.p2pAddress}-webserver"),
extraJvmArguments = listOf("-Dname=node-${handle.configuration.p2pAddress}-webserver"),
errorLogPath = Paths.get("error.$className.log")
)
}.flatMap { process -> addressMustBeBound(executorService, nodeConf.webAddress).map { process } }
}.flatMap { process -> addressMustBeBound(executorService, handle.webAddress).map { process } }
}
}
}

View File

@ -26,6 +26,7 @@ import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.RaftUniquenessProvider
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.services.transactions.RaftNonValidatingNotaryService
import net.corda.node.services.transactions.*
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
@ -128,7 +129,7 @@ class Node(override val configuration: FullNodeConfiguration,
}
override fun makeMessagingService(): MessagingServiceInternal {
userService = RPCUserServiceImpl(configuration)
userService = RPCUserServiceImpl(configuration.rpcUsers)
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) obtainLegalIdentity().owningKey else null
return NodeMessagingClient(

View File

@ -1,7 +1,6 @@
package net.corda.node.services
import net.corda.core.flows.FlowLogic
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.User
/**
@ -16,13 +15,8 @@ interface RPCUserService {
// TODO Store passwords as salted hashes
// TODO Or ditch this and consider something like Apache Shiro
class RPCUserServiceImpl(config: NodeConfiguration) : RPCUserService {
private val _users = config.rpcUsers.associateBy(User::username)
override fun getUser(username: String): User? = _users[username]
override val users: List<User> get() = _users.values.toList()
class RPCUserServiceImpl(override val users: List<User>) : RPCUserService {
override fun getUser(username: String): User? = users.find { it.username == username }
}
fun startFlowPermission(className: String) = "StartFlow.$className"

View File

@ -1,7 +1,6 @@
package net.corda.node.services.config
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import net.corda.core.div
import net.corda.core.node.NodeVersionInfo
import net.corda.core.node.services.ServiceInfo
@ -12,19 +11,13 @@ import net.corda.node.services.messaging.CertificateChainCheckPolicy
import net.corda.node.services.network.NetworkMapService
import net.corda.node.utilities.TestClock
import net.corda.nodeapi.User
import net.corda.nodeapi.config.getListOrElse
import net.corda.nodeapi.config.getOrElse
import net.corda.nodeapi.config.getValue
import net.corda.nodeapi.config.OldConfig
import net.corda.nodeapi.config.SSLConfiguration
import java.net.URL
import java.nio.file.Path
import java.util.*
enum class VerifierType {
InMemory,
OutOfProcess
}
interface NodeConfiguration : net.corda.nodeapi.config.SSLConfiguration {
interface NodeConfiguration : SSLConfiguration {
val baseDirectory: Path
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
val myLegalName: String
@ -32,83 +25,89 @@ interface NodeConfiguration : net.corda.nodeapi.config.SSLConfiguration {
val nearestCity: String
val emailAddress: String
val exportJMXto: String
val dataSourceProperties: Properties get() = Properties()
val rpcUsers: List<User> get() = emptyList()
val dataSourceProperties: Properties
val rpcUsers: List<User>
val devMode: Boolean
val certificateSigningService: URL
val certificateChainCheckPolicies: Map<String, CertificateChainCheckPolicy>
val certificateChainCheckPolicies: List<CertChainPolicyConfig>
val verifierType: VerifierType
}
/**
* [baseDirectory] is not retrieved from the config file but rather from a command line argument.
*/
class FullNodeConfiguration(override val baseDirectory: Path, val config: Config) : NodeConfiguration {
override val myLegalName: String by config
override val nearestCity: String by config
override val emailAddress: String by config
override val exportJMXto: String get() = "http"
override val keyStorePassword: String by config
override val trustStorePassword: String by config
override val dataSourceProperties: Properties by config
override val devMode: Boolean by config.getOrElse { false }
override val certificateSigningService: URL by config
override val networkMapService: NetworkMapInfo? = config.getOptionalConfig("networkMapService")?.run {
NetworkMapInfo(
HostAndPort.fromString(getString("address")),
getString("legalName"))
}
override val rpcUsers: List<User> = config
.getListOrElse<Config>("rpcUsers") { emptyList() }
.map {
val username = it.getString("user")
require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" }
val password = it.getString("password")
val permissions = it.getListOrElse<String>("permissions") { emptyList() }.toSet()
User(username, password, permissions)
}
override val certificateChainCheckPolicies = config.getOptionalConfig("certificateChainCheckPolicies")?.run {
entrySet().associateByTo(HashMap(), { it.key }, { parseCertificateChainCheckPolicy(getConfig(it.key)) })
} ?: emptyMap<String, CertificateChainCheckPolicy>()
override val verifierType: VerifierType by config
val useHTTPS: Boolean by config
val p2pAddress: HostAndPort by config
val rpcAddress: HostAndPort? by config
val webAddress: HostAndPort by config
data class FullNodeConfiguration(
// TODO Remove this subsitution value and use baseDirectory as the subsitution instead
@Deprecated(
"This is a subsitution value which points to the baseDirectory and is manually added into the config before parsing",
ReplaceWith("baseDirectory"))
val basedir: Path,
override val myLegalName: String,
override val nearestCity: String,
override val emailAddress: String,
override val keyStorePassword: String,
override val trustStorePassword: String,
override val dataSourceProperties: Properties,
override val certificateSigningService: URL,
override val networkMapService: NetworkMapInfo?,
override val rpcUsers: List<User>,
override val verifierType: VerifierType,
val useHTTPS: Boolean,
@OldConfig("artemisAddress")
val p2pAddress: HostAndPort,
val rpcAddress: HostAndPort?,
// TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker.
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
val messagingServerAddress: HostAndPort? by config
val extraAdvertisedServiceIds: List<String> = config.getListOrElse<String>("extraAdvertisedServiceIds") { emptyList() }
val useTestClock: Boolean by config.getOrElse { false }
val notaryNodeId: Int? by config
val notaryNodeAddress: HostAndPort? by config
val notaryClusterAddresses: List<HostAndPort> = config
.getListOrElse<String>("notaryClusterAddresses") { emptyList() }
.map { HostAndPort.fromString(it) }
val messagingServerAddress: HostAndPort?,
val extraAdvertisedServiceIds: List<String>,
val notaryNodeId: Int?,
val notaryNodeAddress: HostAndPort?,
val notaryClusterAddresses: List<HostAndPort>,
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>,
override val devMode: Boolean = false,
val useTestClock: Boolean = false
) : NodeConfiguration {
/** This is not retrieved from the config file but rather from a command line argument. */
@Suppress("DEPRECATION")
override val baseDirectory: Path get() = basedir
override val exportJMXto: String get() = "http"
fun createNode(nodeVersionInfo: NodeVersionInfo): Node {
init {
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
// TODO Move this to ArtemisMessagingServer
rpcUsers.forEach {
require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" }
}
}
fun createNode(nodeVersionInfo: NodeVersionInfo): Node {
val advertisedServices = extraAdvertisedServiceIds
.filter(String::isNotBlank)
.map { ServiceInfo.parse(it) }
.toMutableSet()
if (networkMapService == null) advertisedServices.add(ServiceInfo(NetworkMapService.type))
if (networkMapService == null) advertisedServices += ServiceInfo(NetworkMapService.type)
return Node(this, advertisedServices, nodeVersionInfo, if (useTestClock) TestClock() else NodeClock())
}
}
private fun parseCertificateChainCheckPolicy(config: Config): CertificateChainCheckPolicy {
val policy = config.getString("policy")
return when (policy) {
"Any" -> CertificateChainCheckPolicy.Any
"RootMustMatch" -> CertificateChainCheckPolicy.RootMustMatch
"LeafMustMatch" -> CertificateChainCheckPolicy.LeafMustMatch
"MustContainOneOf" -> CertificateChainCheckPolicy.MustContainOneOf(config.getStringList("trustedAliases").toSet())
else -> throw IllegalArgumentException("Invalid certificate chain check policy $policy")
}
enum class VerifierType {
InMemory,
OutOfProcess
}
private fun Config.getOptionalConfig(path: String): Config? = if (hasPath(path)) getConfig(path) else null
enum class CertChainPolicyType {
Any,
RootMustMatch,
LeafMustMatch,
MustContainOneOf
}
data class CertChainPolicyConfig(val role: String, val policy: CertChainPolicyType, val trustedAliases: Set<String>) {
val certificateChainCheckPolicy: CertificateChainCheckPolicy get() {
return when (policy) {
CertChainPolicyType.Any -> CertificateChainCheckPolicy.Any
CertChainPolicyType.RootMustMatch -> CertificateChainCheckPolicy.RootMustMatch
CertChainPolicyType.LeafMustMatch -> CertificateChainCheckPolicy.LeafMustMatch
CertChainPolicyType.MustContainOneOf -> CertificateChainCheckPolicy.MustContainOneOf(trustedAliases)
}
}
}

View File

@ -4,16 +4,13 @@ import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import io.netty.handler.ssl.SslHandler
import net.corda.core.ThreadBox
import net.corda.core.*
import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.div
import net.corda.core.minutes
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.seconds
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.node.printBasicNodeInfo
@ -251,8 +248,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
)
val keyStore = X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
val trustStore = X509Utilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val certChecks = defaultCertPolicies.mapValues {
(config.certificateChainCheckPolicies[it.key] ?: it.value).createCheck(keyStore, trustStore)
val certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) ->
val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy
(configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore)
}
val securityConfig = object : SecurityConfiguration() {
// Override to make it work with our login module

View File

@ -15,9 +15,9 @@ import net.corda.core.messaging.RPCReturnsObservables
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.debug
import net.corda.node.services.RPCUserService
import net.corda.node.utilities.AffinityExecutor
import net.corda.core.utilities.debug
import net.corda.nodeapi.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
import org.apache.activemq.artemis.api.core.Message

View File

@ -1,76 +0,0 @@
package net.corda.node.services
import com.typesafe.config.ConfigFactory
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.nodeapi.User
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.nio.file.Paths
class RPCUserServiceImplTest {
@Test
fun `missing config`() {
val service = loadWithContents("{}")
assertThat(service.getUser("user")).isNull()
assertThat(service.users).isEmpty()
}
@Test
fun `no users`() {
val service = loadWithContents("rpcUsers : []")
assertThat(service.getUser("user")).isNull()
assertThat(service.users).isEmpty()
}
@Test
fun `no permissions`() {
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
val expectedUser = User("user1", "letmein", permissions = emptySet())
assertThat(service.getUser("user1")).isEqualTo(expectedUser)
assertThat(service.users).containsOnly(expectedUser)
}
@Test
fun `single permission, which is in lower case`() {
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[cash] }]")
assertThat(service.getUser("user1")?.permissions).containsOnly("cash")
}
@Test
fun `two permissions, which are upper case`() {
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[CASH, ADMIN] }]")
assertThat(service.getUser("user1")?.permissions).containsOnly("CASH", "ADMIN")
}
@Test
fun `two users`() {
val service = loadWithContents("""rpcUsers : [
{ user=user, password=password, permissions=[ADMIN] }
{ user=user2, password=password2, permissions=[] }
]""")
val user1 = User("user", "password", permissions = setOf("ADMIN"))
val user2 = User("user2", "password2", permissions = emptySet())
assertThat(service.getUser("user")).isEqualTo(user1)
assertThat(service.getUser("user2")).isEqualTo(user2)
assertThat(service.users).containsOnly(user1, user2)
}
@Test
fun `unknown user`() {
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
assertThat(service.getUser("test")).isNull()
}
@Test
fun `Artemis special characters not permitted in usernames`() {
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user.1, password=letmein }]") }.hasMessageContaining(".")
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user*1, password=letmein }]") }.hasMessageContaining("*")
assertThatThrownBy { loadWithContents("""rpcUsers : [{ user="user#1", password=letmein }]""") }.hasMessageContaining("#")
}
private fun loadWithContents(configString: String): RPCUserServiceImpl {
return RPCUserServiceImpl(FullNodeConfiguration(Paths.get("."), ConfigFactory.parseString(configString)))
}
}

View File

@ -0,0 +1,21 @@
package net.corda.node.services.config
import net.corda.nodeapi.User
import net.corda.testing.testConfiguration
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.nio.file.Paths
class FullNodeConfigurationTest {
@Test
fun `Artemis special characters not permitted in RPC usernames`() {
fun configWithRPCUsername(username: String): FullNodeConfiguration {
return testConfiguration(Paths.get("."), "NodeA", 0).copy(
rpcUsers = listOf(User(username, "pass", emptySet())))
}
assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".")
assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*")
assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#")
}
}

View File

@ -5,7 +5,6 @@ import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import com.typesafe.config.ConfigFactory.empty
import net.corda.core.crypto.composite
import net.corda.core.crypto.generateKeyPair
import net.corda.core.messaging.Message
@ -72,7 +71,7 @@ class ArtemisMessagingTests {
@Before
fun setUp() {
val baseDirectory = temporaryFolder.root.toPath()
userService = RPCUserServiceImpl(FullNodeConfiguration(baseDirectory, empty()))
userService = RPCUserServiceImpl(emptyList())
config = TestNodeConfiguration(
baseDirectory = baseDirectory,
myLegalName = "me",

View File

@ -52,7 +52,7 @@ dependencies {
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
ext.rpcUsers = [ ['user' : "demo", 'password' : "demo", 'permissions' : ["StartFlow.net.corda.flows.FinalityFlow"]] ]
ext.rpcUsers = [ ['username' : "demo", 'password' : "demo", 'permissions' : ["StartFlow.net.corda.flows.FinalityFlow"]] ]
directory "./build/nodes"
networkMap "Controller"

View File

@ -70,7 +70,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
webPort 10007
cordapps = []
rpcUsers = [
['user' : "bankUser",
['username' : "bankUser",
'password' : "test",
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow",
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"]]
@ -85,7 +85,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
webPort 10010
cordapps = []
rpcUsers = [
['user' : "bigCorpUser",
['username' : "bigCorpUser",
'password' : "test",
'permissions' : ["StartFlow.net.corda.flows.CashPaymentFlow"]]
]

View File

@ -83,7 +83,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', 'generateN
p2pPort 10002
rpcPort 10003
cordapps = []
rpcUsers = [['user': "demo", 'password': "demo", 'permissions': [
rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
'StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove',
'StartFlow.net.corda.flows.NotaryFlow$Client'
]]]

View File

@ -59,7 +59,7 @@ dependencies {
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
ext.rpcUsers = [['user': "demo", 'password': "demo", 'permissions': [
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester',
"StartFlow.net.corda.traderdemo.flow.SellerFlow"
]]]

View File

@ -5,8 +5,6 @@ package net.corda.testing
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.ListenableFuture
import com.typesafe.config.Config
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
@ -20,12 +18,11 @@ import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.NetworkMapInfo
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.node.services.config.VerifierType
import net.corda.node.services.messaging.CertificateChainCheckPolicy
import net.corda.node.services.config.*
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.utilities.AddOrRemove.ADD
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.testing.node.MockIdentityService
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties
@ -162,22 +159,47 @@ inline fun <reified P : FlowLogic<*>> AbstractNode.initiateSingleShotFlow(
return future
}
// TODO Replace this with testConfiguration
data class TestNodeConfiguration(
override val baseDirectory: Path,
override val myLegalName: String,
override val networkMapService: NetworkMapInfo?,
override val keyStorePassword: String = "cordacadevpass",
override val trustStorePassword: String = "trustpass",
override val rpcUsers: List<User> = emptyList(),
override val dataSourceProperties: Properties = makeTestDataSourceProperties(myLegalName),
override val nearestCity: String = "Null Island",
override val emailAddress: String = "",
override val exportJMXto: String = "",
override val devMode: Boolean = true,
override val certificateSigningService: URL = URL("http://localhost"),
override val certificateChainCheckPolicies: Map<String, CertificateChainCheckPolicy> = emptyMap(),
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList(),
override val verifierType: VerifierType = VerifierType.InMemory) : NodeConfiguration
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))
fun testConfiguration(baseDirectory: Path, legalName: String, basePort: Int): FullNodeConfiguration {
return FullNodeConfiguration(
basedir = baseDirectory,
myLegalName = legalName,
networkMapService = null,
nearestCity = "Null Island",
emailAddress = "",
keyStorePassword = "cordacadevpass",
trustStorePassword = "trustpass",
dataSourceProperties = makeTestDataSourceProperties(legalName),
certificateSigningService = URL("http://localhost"),
rpcUsers = emptyList(),
verifierType = VerifierType.InMemory,
useHTTPS = false,
p2pAddress = HostAndPort.fromParts("localhost", basePort),
rpcAddress = HostAndPort.fromParts("localhost", basePort + 1),
messagingServerAddress = null,
extraAdvertisedServiceIds = emptyList(),
notaryNodeId = null,
notaryNodeAddress = null,
notaryClusterAddresses = emptyList(),
certificateChainCheckPolicies = emptyList(),
devMode = true)
}
@JvmOverloads
fun configureTestSSL(legalName: String = "Mega Corp."): SSLConfiguration = object : SSLConfiguration {

View File

@ -14,6 +14,7 @@ import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.User
import net.corda.nodeapi.config.parseAs
import net.corda.testing.MOCK_NODE_VERSION_INFO
import net.corda.testing.getFreeLocalPorts
import org.junit.After
@ -126,7 +127,7 @@ abstract class NodeBasedTest {
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
"rpcUsers" to rpcUsers.map {
mapOf(
"user" to it.username,
"username" to it.username,
"password" to it.password,
"permissions" to it.permissions
)
@ -134,7 +135,7 @@ abstract class NodeBasedTest {
) + configOverrides
)
val node = FullNodeConfiguration(baseDirectory, config).createNode(MOCK_NODE_VERSION_INFO)
val node = config.parseAs<FullNodeConfiguration>().createNode(MOCK_NODE_VERSION_INFO)
node.start()
nodes += node
thread(name = legalName) {

View File

@ -30,7 +30,7 @@ class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeL
private val databaseWithCloseable: Pair<Closeable, Database> = configureDatabase(config.dataSourceProperties)
val database: Database get() = databaseWithCloseable.second
val userService = RPCUserServiceImpl(config)
val userService = RPCUserServiceImpl(config.rpcUsers)
val monitoringService = MonitoringService(MetricRegistry())
val identity: KeyPair = generateKeyPair()
val executor = ServiceAffinityExecutor(config.myLegalName, 1)

View File

@ -5,14 +5,14 @@ import net.corda.nodeapi.User
import java.util.*
fun User.toMap(): Map<String, Any> = mapOf(
"user" to username,
"username" to username,
"password" to password,
"permissions" to permissions
)
@Suppress("UNCHECKED_CAST")
fun toUser(map: Map<String, Any>) = User(
map.getOrElse("user", { "none" }) as String,
map.getOrElse("username", { "none" }) as String,
map.getOrElse("password", { "none" }) as String,
LinkedHashSet<String>(map.getOrElse("permissions", { emptyList<String>() }) as Collection<String>)
)

View File

@ -7,11 +7,15 @@ import net.corda.core.div
import net.corda.node.internal.NetworkMapInfo
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.nodeapi.User
import net.corda.nodeapi.config.parseAs
import net.corda.webserver.WebServerConfig
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.test.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class NodeConfigTest {
@ -129,7 +133,7 @@ class NodeConfigTest {
+ "\"p2pAddress\":\"localhost:10001\","
+ "\"rpcAddress\":\"localhost:40002\","
+ "\"rpcUsers\":["
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"user\":\"jenny\"}"
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"username\":\"jenny\"}"
+ "],"
+ "\"useTestClock\":true,"
+ "\"webAddress\":\"localhost:20001\""
@ -159,7 +163,7 @@ class NodeConfigTest {
+ "\"p2pAddress\":\"localhost:10001\","
+ "\"rpcAddress\":\"localhost:40002\","
+ "\"rpcUsers\":["
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"user\":\"jenny\"}"
+ "{\"password\":\"letmein\",\"permissions\":[\"ALL\"],\"username\":\"jenny\"}"
+ "],"
+ "\"useTestClock\":true,"
+ "\"webAddress\":\"localhost:20001\""
@ -184,11 +188,10 @@ class NodeConfigTest {
.withValue("basedir", ConfigValueFactory.fromAnyRef(baseDir.toString()))
.withFallback(ConfigFactory.parseResources("reference.conf"))
.resolve()
val fullConfig = FullNodeConfiguration(baseDir, nodeConfig)
val fullConfig = nodeConfig.parseAs<FullNodeConfiguration>()
assertEquals("My Name", fullConfig.myLegalName)
assertEquals("Stockholm", fullConfig.nearestCity)
assertEquals(localPort(20001), fullConfig.webAddress)
assertEquals(localPort(40002), fullConfig.rpcAddress)
assertEquals(localPort(10001), fullConfig.p2pAddress)
assertEquals(listOf("my.service"), fullConfig.extraAdvertisedServiceIds)

View File

@ -2,7 +2,7 @@ package net.corda.demobench.model
import net.corda.nodeapi.User
import org.junit.Test
import kotlin.test.*
import kotlin.test.assertEquals
class UserTest {
@ -17,7 +17,7 @@ class UserTest {
@Test
fun createFromMap() {
val map = mapOf(
"user" to "MyName",
"username" to "MyName",
"password" to "MyPassword",
"permissions" to listOf("Flow.MyFlow")
)
@ -31,7 +31,7 @@ class UserTest {
fun userToMap() {
val user = User("MyName", "MyPassword", setOf("Flow.MyFlow"))
val map = user.toMap()
assertEquals("MyName", map["user"])
assertEquals("MyName", map["username"])
assertEquals("MyPassword", map["password"])
assertEquals(setOf("Flow.MyFlow"), map["permissions"])
}

View File

@ -3,9 +3,7 @@ package net.corda.webserver
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import net.corda.core.div
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.getListOrElse
import net.corda.nodeapi.config.getValue
import java.nio.file.Path