From 7853cfe0034cc769358193f26afa3021c38a0554 Mon Sep 17 00:00:00 2001
From: Anthony Keenan <anthony.keenan@r3.com>
Date: Sat, 21 Jul 2018 11:54:02 +0100
Subject: [PATCH] [CORDA-1482] Make boolean config variables case insensitive
 (#3622)

* Make boolean config variables case insensitive

* Address review comments
---
 .../internal/config/ConfigUtilities.kt        | 15 ++++++++++-
 .../internal/network/NetworkBootstrapper.kt   |  3 ++-
 .../internal/config/ConfigParsingTest.kt      | 12 ++++++++-
 .../internal/cordapp/TypesafeCordappConfig.kt |  3 ++-
 .../config/NodeConfigurationImplTest.kt       | 25 ++++++++++---------
 .../net/corda/bootstrapper/volumes/Volume.kt  |  3 ++-
 6 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
index de1311ef78..cf3245837c 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt
@@ -121,7 +121,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
             Int::class -> getInt(path)
             Long::class -> getLong(path)
             Double::class -> getDouble(path)
-            Boolean::class -> getBoolean(path)
+            Boolean::class -> getBooleanCaseInsensitive(path)
             LocalDate::class -> LocalDate.parse(getString(path))
             Duration::class -> getDuration(path)
             Instant::class -> Instant.parse(getString(path))
@@ -276,6 +276,19 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
     }
 }
 
+// The typesafe .getBoolean function is case sensitive, this is a case insensitive version
+fun Config.getBooleanCaseInsensitive(path: String): Boolean {
+    try {
+        return getBoolean(path)
+    } catch(e:Exception) {
+        val stringVal = getString(path).toLowerCase()
+        if (stringVal == "true" || stringVal == "false") {
+            return stringVal.toBoolean()
+        }
+        throw e
+    }
+}
+
 private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config")
 
 enum class UnknownConfigKeysPolicy(private val handle: (Set<String>, logger: Logger) -> Unit) {
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
index 393e905c6e..2945421170 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt
@@ -21,6 +21,7 @@ import net.corda.core.utilities.days
 import net.corda.core.utilities.getOrThrow
 import net.corda.core.utilities.seconds
 import net.corda.nodeapi.internal.*
+import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
 import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
 import net.corda.serialization.internal.AMQP_P2P_CONTEXT
 import net.corda.serialization.internal.CordaSerializationMagic
@@ -289,7 +290,7 @@ class NetworkBootstrapper
             // The config contains the notary type
             val nodeConfig = configs[nodeInfoFile.parent]!!
             if (nodeConfig.hasPath("notary")) {
-                val validating = nodeConfig.getBoolean("notary.validating")
+                val validating = nodeConfig.getBooleanCaseInsensitive("notary.validating")
                 // And the node-info file contains the notary's identity
                 val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
                 NotaryInfo(nodeInfo.notaryIdentity(), validating)
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt
index 07de31c51d..cb92e9431b 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt
@@ -1,6 +1,7 @@
 package net.corda.nodeapi.internal.config
 
 import com.typesafe.config.Config
+import com.typesafe.config.ConfigException
 import com.typesafe.config.ConfigFactory.empty
 import com.typesafe.config.ConfigRenderOptions.defaults
 import com.typesafe.config.ConfigValueFactory
@@ -8,6 +9,7 @@ import net.corda.core.identity.CordaX500Name
 import net.corda.core.internal.div
 import net.corda.core.utilities.NetworkHostAndPort
 import org.assertj.core.api.Assertions.*
+import org.hibernate.exception.DataException
 import org.junit.Test
 import java.net.URL
 import java.nio.file.Path
@@ -41,8 +43,16 @@ class ConfigParsingTest {
     @Test
     fun Boolean() {
         testPropertyType<BooleanData, BooleanListData, Boolean>(true, false)
+        assertThat(config(Pair("value", "false")).parseAs<BooleanData>().value).isEqualTo(false)
+        assertThat(config(Pair("value", "False")).parseAs<BooleanData>().value).isEqualTo(false)
+        assertThat(config(Pair("value", "FALSE")).parseAs<BooleanData>().value).isEqualTo(false)
+        assertThat(config(Pair("value", "true")).parseAs<BooleanData>().value).isEqualTo(true)
+        assertThat(config(Pair("value", "True")).parseAs<BooleanData>().value).isEqualTo(true)
+        assertThat(config(Pair("value", "TRUE")).parseAs<BooleanData>().value).isEqualTo(true)
+        assertThatThrownBy { config(Pair("value", "stilton")).parseAs<BooleanData>().value }
+                .isInstanceOf(ConfigException.WrongType::class.java)
+                .hasMessageContaining("hardcoded value: value has type STRING rather than BOOLEAN")
     }
-
     @Test
     fun Enum() {
         testPropertyType<EnumData, EnumListData, TestEnum>(TestEnum.Value2, TestEnum.Value1, valuesToString = true)
diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt
index 73f5633350..31d9ed54b7 100644
--- a/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt
@@ -4,6 +4,7 @@ import com.typesafe.config.Config
 import com.typesafe.config.ConfigException
 import net.corda.core.cordapp.CordappConfig
 import net.corda.core.cordapp.CordappConfigException
+import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
 
 /**
  * Provides configuration from a typesafe config source
@@ -71,7 +72,7 @@ class TypesafeCordappConfig(private val cordappConfig: Config) : CordappConfig {
 
     override fun getBoolean(path: String): Boolean {
         try {
-            return cordappConfig.getBoolean(path)
+            return cordappConfig.getBooleanCaseInsensitive(path)
         } catch (e: ConfigException) {
             throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
         }
diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
index 22d68fab17..93c45b2c5b 100644
--- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt
@@ -4,6 +4,7 @@ import com.typesafe.config.*
 import net.corda.core.internal.toPath
 import net.corda.core.utilities.NetworkHostAndPort
 import net.corda.core.utilities.seconds
+import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
 import net.corda.testing.core.ALICE_NAME
 import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
 import net.corda.tools.shell.SSHDConfiguration
@@ -67,16 +68,16 @@ class NodeConfigurationImplTest {
         val os = System.getProperty("os.name")
 
         setSystemOs("Windows 98")
-        assertTrue(getConfig("test-config-empty.conf").getBoolean("devMode"))
+        assertTrue(getConfig("test-config-empty.conf").getBooleanCaseInsensitive("devMode"))
 
         setSystemOs("Mac Sierra")
-        assertTrue(getConfig("test-config-empty.conf").getBoolean("devMode"))
+        assertTrue(getConfig("test-config-empty.conf").getBooleanCaseInsensitive("devMode"))
 
         setSystemOs("Windows server 2008")
-        assertFalse(getConfig("test-config-empty.conf").getBoolean("devMode"))
+        assertFalse(getConfig("test-config-empty.conf").getBooleanCaseInsensitive("devMode"))
 
         setSystemOs("Linux")
-        assertFalse(getConfig("test-config-empty.conf").getBoolean("devMode"))
+        assertFalse(getConfig("test-config-empty.conf").getBooleanCaseInsensitive("devMode"))
 
         setSystemOs(os)
     }
@@ -87,22 +88,22 @@ class NodeConfigurationImplTest {
 
     @Test
     fun `Dev mode is read from the config over the autodetect logic`() {
-        assertTrue(getConfig("test-config-DevMode.conf").getBoolean("devMode"))
-        assertFalse(getConfig("test-config-noDevMode.conf").getBoolean("devMode"))
+        assertTrue(getConfig("test-config-DevMode.conf").getBooleanCaseInsensitive("devMode"))
+        assertFalse(getConfig("test-config-noDevMode.conf").getBooleanCaseInsensitive("devMode"))
     }
 
     @Test
     fun `Dev mode is true if overriden`() {
-        assertTrue(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBoolean("devMode"))
-        assertTrue(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBoolean("devMode"))
-        assertTrue(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBoolean("devMode"))
+        assertTrue(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
+        assertTrue(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
+        assertTrue(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
     }
 
     @Test
     fun `Dev mode is false if overriden`() {
-        assertFalse(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBoolean("devMode"))
-        assertFalse(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBoolean("devMode"))
-        assertFalse(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBoolean("devMode"))
+        assertFalse(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode"))
+        assertFalse(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode"))
+        assertFalse(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode"))
     }
 
     private fun getConfig(cfgName: String, overrides: Config = ConfigFactory.empty()): Config {
diff --git a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/volumes/Volume.kt b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/volumes/Volume.kt
index fa514dd168..4ec5d47f02 100644
--- a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/volumes/Volume.kt
+++ b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/volumes/Volume.kt
@@ -9,6 +9,7 @@ import net.corda.core.node.NotaryInfo
 import net.corda.core.serialization.deserialize
 import net.corda.nodeapi.internal.DEV_ROOT_CA
 import net.corda.nodeapi.internal.SignedNodeInfo
+import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
 import net.corda.nodeapi.internal.createDevNetworkMapCa
 import java.io.File
 import java.security.cert.X509Certificate
@@ -36,7 +37,7 @@ interface Volume {
 
     fun convertNodeIntoToNetworkParams(notaryFiles: List<Pair<File, File>>): NetworkParameters {
         val notaryInfos = notaryFiles.map { (configFile, nodeInfoFile) ->
-            val validating = ConfigFactory.parseFile(configFile).getConfig("notary").getBoolean("validating")
+            val validating = ConfigFactory.parseFile(configFile).getConfig("notary").getBooleanCaseInsensitive("validating")
             nodeInfoFile.readBytes().deserialize<SignedNodeInfo>().verified().let { NotaryInfo(it.legalIdentities.first(), validating) }
         }