CORDA-2128: Moved all the helper methods added to NetworkParameters.kt into internal (#4466)

This commit is contained in:
Shams Asari 2018-12-27 14:21:37 +00:00 committed by GitHub
parent 0388872175
commit c08f65a92c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 145 deletions

View File

@ -1,12 +1,14 @@
package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.DataVendingFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.serialization.CordaSerializable
@ -16,6 +18,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import org.slf4j.MDC
import java.security.PublicKey
// *Internal* Corda-specific utilities.
@ -71,9 +74,7 @@ private fun isPackageValid(packageName: String): Boolean {
}
}
/**
* Check if a string is a legal Java package name.
*/
/** Check if a string is a legal Java package name. */
fun requirePackageValid(name: String) {
require(isPackageValid(name)) { "Invalid Java package name: `$name`." }
}
@ -85,3 +86,33 @@ fun requirePackageValid(name: String) {
*/
@CordaSerializable
object RetrieveAnyTransactionPayload : ArrayList<Any>()
/**
* Returns true if the [fullClassName] is in a subpackage of [packageName].
* E.g.: "com.megacorp" owns "com.megacorp.tokens.MegaToken"
*
* Note: The ownership check is ignoring case to prevent people from just releasing a jar with: "com.megaCorp.megatoken" and pretend they are MegaCorp.
* By making the check case insensitive, the node will require that the jar is signed by MegaCorp, so the attack fails.
*/
private fun owns(packageName: String, fullClassName: String): Boolean = fullClassName.startsWith("$packageName.", ignoreCase = true)
/** Returns the public key of the package owner of the [contractClassName], or null if not owned. */
internal fun NetworkParameters.getPackageOwnerOf(contractClassName: ContractClassName): PublicKey? {
return packageOwnership.entries.singleOrNull { owns(it.key, contractClassName) }?.value
}
/**
* Returns the public key of the package owner if any of [contractClassNames] match, or null if not owned.
*/
internal fun NetworkParameters.getPackageOwnerOf(contractClassNames: Set<ContractClassName>): PublicKey? {
for (contractClassName in contractClassNames) {
val owner = getPackageOwnerOf(contractClassName)
if (owner != null) return owner
}
return null
}
// Make sure that packages don't overlap so that ownership is clear.
fun noPackageOverlap(packages: Collection<String>): Boolean {
return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
}

View File

@ -2,23 +2,17 @@ package net.corda.core.node
import net.corda.core.CordaRuntimeException
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.Party
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.noPackageOverlap
import net.corda.core.internal.requirePackageValid
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.utilities.days
import java.lang.reflect.Method
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaGetter
// DOCSTART 1
/**
@ -54,13 +48,13 @@ data class NetworkParameters(
) {
// DOCEND 1
@DeprecatedConstructorForDeserialization(1)
constructor (minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>
constructor(minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>
) : this(minimumPlatformVersion,
notaries,
maxMessageSize,
@ -73,14 +67,14 @@ data class NetworkParameters(
)
@DeprecatedConstructorForDeserialization(2)
constructor (minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
eventHorizon: Duration
constructor(minimumPlatformVersion: Int,
notaries: List<NotaryInfo>,
maxMessageSize: Int,
maxTransactionSize: Int,
modifiedTime: Instant,
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
eventHorizon: Duration
) : this(minimumPlatformVersion,
notaries,
maxMessageSize,
@ -92,32 +86,6 @@ data class NetworkParameters(
emptyMap()
)
companion object {
private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.asSequence()
.partition { it.isAutoAcceptable() }
private val autoAcceptableNamesAndGetters = memberPropertyPartition.first.associateBy({ it.name }, { it.javaGetter })
private val nonAutoAcceptableGetters = memberPropertyPartition.second.map { it.javaGetter }
val autoAcceptablePropertyNames = autoAcceptableNamesAndGetters.keys
/**
* Returns true if the [fullClassName] is in a subpackage of [packageName].
* E.g.: "com.megacorp" owns "com.megacorp.tokens.MegaToken"
*
* Note: The ownership check is ignoring case to prevent people from just releasing a jar with: "com.megaCorp.megatoken" and pretend they are MegaCorp.
* By making the check case insensitive, the node will require that the jar is signed by MegaCorp, so the attack fails.
*/
private fun owns(packageName: String, fullClassName: String) = fullClassName.startsWith("$packageName.", ignoreCase = true)
// Make sure that packages don't overlap so that ownership is clear.
fun noOverlap(packages: Collection<String>) = packages.all { currentPackage ->
packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.startsWith("$currentPackage.") }
}
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
return this.findAnnotation<AutoAcceptable>() != null
}
}
init {
require(minimumPlatformVersion > 0) { "Minimum platform level must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
@ -126,7 +94,7 @@ data class NetworkParameters(
require(maxTransactionSize > 0) { "Maximum transaction size must be at least 1" }
require(!eventHorizon.isNegative) { "Event Horizon must be a positive value" }
packageOwnership.keys.forEach(::requirePackageValid)
require(noOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
require(noPackageOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
}
fun copy(minimumPlatformVersion: Int,
@ -137,14 +105,16 @@ data class NetworkParameters(
epoch: Int,
whitelistedContractImplementations: Map<String, List<AttachmentId>>
): NetworkParameters {
return copy(minimumPlatformVersion = minimumPlatformVersion,
return copy(
minimumPlatformVersion = minimumPlatformVersion,
notaries = notaries,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
modifiedTime = modifiedTime,
epoch = epoch,
whitelistedContractImplementations = whitelistedContractImplementations,
eventHorizon = eventHorizon)
eventHorizon = eventHorizon
)
}
fun copy(minimumPlatformVersion: Int,
@ -156,14 +126,16 @@ data class NetworkParameters(
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
eventHorizon: Duration
): NetworkParameters {
return copy(minimumPlatformVersion = minimumPlatformVersion,
return copy(
minimumPlatformVersion = minimumPlatformVersion,
notaries = notaries,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
modifiedTime = modifiedTime,
epoch = epoch,
whitelistedContractImplementations = whitelistedContractImplementations,
eventHorizon = eventHorizon)
eventHorizon = eventHorizon
)
}
override fun toString(): String {
@ -176,43 +148,13 @@ data class NetworkParameters(
${whitelistedContractImplementations.entries.joinToString("\n ")}
}
eventHorizon=$eventHorizon
modifiedTime=$modifiedTime
epoch=$epoch,
packageOwnership= {
packageOwnership {
${packageOwnership.entries.joinToString("\n ") { "$it.key -> ${it.value.toStringShort()}" }}
}
modifiedTime=$modifiedTime
epoch=$epoch
}"""
}
/**
* Returns the public key of the package owner of the [contractClassName], or null if not owned.
*/
@VisibleForTesting
internal fun getPackageOwnerOf(contractClassName: ContractClassName): PublicKey? = this.packageOwnership.filterKeys { packageName -> owns(packageName, contractClassName) }.values.singleOrNull()
/**
* Returns the public key of the package owner if any of [contractClassName] match, or null if not owned.
*/
@VisibleForTesting
internal fun getPackageOwnerOf(contractClassNames: Set<ContractClassName>): PublicKey? {
val ownerKeys = contractClassNames.map { getPackageOwnerOf(it) }
return ownerKeys.find { it != null }
}
/**
* Returns true if the only properties changed in [newNetworkParameters] are [AutoAcceptable] and not
* included in the [excludedParameterNames]
*/
fun canAutoAccept(newNetworkParameters: NetworkParameters, excludedParameterNames: Set<String>): Boolean {
return nonAutoAcceptableGetters.none { valueChanged(newNetworkParameters, it) } &&
autoAcceptableNamesAndGetters.none { excludedParameterNames.contains(it.key) && valueChanged(newNetworkParameters, it.value) }
}
private fun valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
val propertyValue = getter?.invoke(this)
val newPropertyValue = getter?.invoke(newNetworkParameters)
return propertyValue != newPropertyValue
}
}
/**
@ -229,7 +171,3 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
* version.
*/
class ZoneVersionTooLowException(message: String) : CordaRuntimeException(message)
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
return this.findAnnotation<AutoAcceptable>() != null
}

View File

@ -1,7 +1,7 @@
package net.corda.core.node
import net.corda.core.crypto.generateKeyPair
import net.corda.core.node.services.AttachmentId
import net.corda.core.internal.getPackageOwnerOf
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.days
import net.corda.core.utilities.getOrThrow
@ -32,7 +32,8 @@ import kotlin.test.assertFails
class NetworkParametersTest {
private val mockNet = InternalMockNetwork(
defaultParameters = MockNetworkParameters(networkSendManuallyPumped = true),
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME))
)
@After
fun tearDown() {
@ -129,44 +130,8 @@ class NetworkParametersTest {
assertEquals(params.getPackageOwnerOf("com.exam.something.MyClass"), null)
}
@Test
fun `auto acceptance checks are correct`() {
val packageOwnership = mapOf(
"com.example1" to generateKeyPair().public,
"com.example2" to generateKeyPair().public)
val whitelistedContractImplementations = mapOf(
"example1" to listOf(AttachmentId.randomSHA256()),
"example2" to listOf(AttachmentId.randomSHA256()))
val netParams = testNetworkParameters()
val netParamsAutoAcceptable = netParams.copy(
packageOwnership = packageOwnership,
whitelistedContractImplementations = whitelistedContractImplementations)
val netParamsNotAutoAcceptable = netParamsAutoAcceptable.copy(
maxMessageSize = netParams.maxMessageSize + 1)
assert(netParams.canAutoAccept(netParams, emptySet())) {
"auto-acceptable if identical"
}
assert(netParams.canAutoAccept(netParams, NetworkParameters.autoAcceptablePropertyNames)) {
"auto acceptable if identical regardless of exclusions"
}
assert(netParams.canAutoAccept(netParamsAutoAcceptable, emptySet())) {
"auto-acceptable if only AutoAcceptable params have changed"
}
assert(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("modifiedTime"))) {
"auto-acceptable if only AutoAcceptable params have changed and excluded param has not changed"
}
assert(!netParams.canAutoAccept(netParamsNotAutoAcceptable, emptySet())) {
"not auto-acceptable if non-AutoAcceptable param has changed"
}
assert(!netParams.canAutoAccept(netParamsAutoAcceptable, setOf("whitelistedContractImplementations"))) {
"not auto-acceptable if only AutoAcceptable params have changed but one has been added to the exclusion set"
}
}
// Helpers
private fun dropParametersToDir(dir: Path, params: NetworkParameters) {
NetworkParametersCopier(params).install(dir)
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.SignedData
import net.corda.core.internal.*
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.AutoAcceptable
import net.corda.core.node.NetworkParameters
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.serialize
@ -21,6 +22,7 @@ import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.*
import rx.Subscription
import rx.subjects.PublishSubject
import java.lang.reflect.Method
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate
@ -28,6 +30,10 @@ import java.time.Duration
import java.util.*
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaGetter
import kotlin.system.exitProcess
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
@ -78,7 +84,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
this.autoAcceptNetworkParameters = networkParameterAcceptanceSettings.autoAcceptEnabled
this.excludedAutoAcceptNetworkParameters = networkParameterAcceptanceSettings.excludedAutoAcceptableParameters
val autoAcceptNetworkParametersNames = NetworkParameters.autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters
val autoAcceptNetworkParametersNames = autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters
if (autoAcceptNetworkParameters && autoAcceptNetworkParametersNames.isNotEmpty()) {
logger.info("Auto-accept enabled for network parameter changes which modify only: $autoAcceptNetworkParametersNames")
}
@ -250,3 +256,27 @@ The node will shutdown now.""")
}
}
}
private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.partition { it.isAutoAcceptable() }
private val autoAcceptableNamesAndGetters = memberPropertyPartition.first.associateBy({ it.name }, { it.javaGetter })
@VisibleForTesting
internal val autoAcceptablePropertyNames = autoAcceptableNamesAndGetters.keys
private val nonAutoAcceptableGetters = memberPropertyPartition.second.map { it.javaGetter }
/**
* Returns true if the only properties changed in [newNetworkParameters] are [AutoAcceptable] and not
* included in the [excludedParameterNames]
*/
@VisibleForTesting
internal fun NetworkParameters.canAutoAccept(newNetworkParameters: NetworkParameters, excludedParameterNames: Set<String>): Boolean {
return nonAutoAcceptableGetters.none { valueChanged(newNetworkParameters, it) } &&
autoAcceptableNamesAndGetters.none { it.key in excludedParameterNames && valueChanged(newNetworkParameters, it.value) }
}
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean = findAnnotation<AutoAcceptable>() != null
private fun NetworkParameters.valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
val propertyValue = getter?.invoke(this)
val newPropertyValue = getter?.invoke(newNetworkParameters)
return propertyValue != newPropertyValue
}

View File

@ -5,6 +5,7 @@ import com.google.common.jimfs.Jimfs
import com.nhaarman.mockito_kotlin.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -13,17 +14,20 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.serialize
import net.corda.core.utilities.millis
import net.corda.node.VersionInfo
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
import net.corda.core.internal.NODE_INFO_DIRECTORY
import net.corda.node.internal.NetworkParametersStorageInternal
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.*
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
import net.corda.testing.internal.DEV_ROOT_CA
@ -48,6 +52,8 @@ import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NetworkMapUpdaterTest {
@Rule
@ -68,7 +74,7 @@ class NetworkMapUpdaterTest {
private val networkParametersStorage: NetworkParametersStorageInternal = mock()
private lateinit var server: NetworkMapServer
private lateinit var networkMapClient: NetworkMapClient
private lateinit var updater: NetworkMapUpdater
private var updater: NetworkMapUpdater? = null
@Before
fun setUp() {
@ -82,7 +88,7 @@ class NetworkMapUpdaterTest {
@After
fun cleanUp() {
updater.close()
updater?.close()
fs.close()
server.close()
}
@ -95,7 +101,7 @@ class NetworkMapUpdaterTest {
networkParameters: NetworkParameters = server.networkParameters,
autoAcceptNetworkParameters: Boolean = true,
excludedAutoAcceptNetworkParameters: Set<String> = emptySet()) {
updater.start(DEV_ROOT_CA.certificate,
updater!!.start(DEV_ROOT_CA.certificate,
server.networkParameters.serialize().hash,
ourNodeInfo,
networkParameters,
@ -206,7 +212,7 @@ class NetworkMapUpdaterTest {
@Test
fun `emit new parameters update info on parameters update from network map`() {
setUpdater()
val paramsFeed = updater.trackParametersUpdate()
val paramsFeed = updater!!.trackParametersUpdate()
val snapshot = paramsFeed.snapshot
val updates = paramsFeed.updates.bufferUntilSubscribed()
assertEquals(null, snapshot)
@ -237,7 +243,7 @@ class NetworkMapUpdaterTest {
val newHash = newParameters.serialize().hash
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
assert(!updateFile.exists()) { "network parameters should not be auto accepted" }
updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
updater!!.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
verify(networkParametersStorage, times(1)).saveParameters(any())
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
@ -411,6 +417,32 @@ class NetworkMapUpdaterTest {
assert(networkMapCache.allNodeHashes.size == 1)
}
@Test
fun `auto acceptance checks are correct`() {
val packageOwnership = mapOf(
"com.example1" to generateKeyPair().public,
"com.example2" to generateKeyPair().public
)
val whitelistedContractImplementations = mapOf(
"example1" to listOf(AttachmentId.randomSHA256()),
"example2" to listOf(AttachmentId.randomSHA256())
)
val netParams = testNetworkParameters()
val netParamsAutoAcceptable = netParams.copy(
packageOwnership = packageOwnership,
whitelistedContractImplementations = whitelistedContractImplementations
)
val netParamsNotAutoAcceptable = netParamsAutoAcceptable.copy(maxMessageSize = netParams.maxMessageSize + 1)
assertTrue(netParams.canAutoAccept(netParams, emptySet()), "auto-acceptable if identical")
assertTrue(netParams.canAutoAccept(netParams, autoAcceptablePropertyNames), "auto acceptable if identical regardless of exclusions")
assertTrue(netParams.canAutoAccept(netParamsAutoAcceptable, emptySet()), "auto-acceptable if only AutoAcceptable params have changed")
assertTrue(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("modifiedTime")), "auto-acceptable if only AutoAcceptable params have changed and excluded param has not changed")
assertFalse(netParams.canAutoAccept(netParamsNotAutoAcceptable, emptySet()), "not auto-acceptable if non-AutoAcceptable param has changed")
assertFalse(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("whitelistedContractImplementations")), "not auto-acceptable if only AutoAcceptable params have changed but one has been added to the exclusion set")
}
private fun createMockNetworkMapCache(): NetworkMapCacheInternal {
return mock {
on { nodeReady }.thenReturn(nodeReadyFuture)

View File

@ -6,9 +6,8 @@ import net.corda.common.configuration.parsing.internal.get
import net.corda.common.configuration.parsing.internal.mapValid
import net.corda.common.configuration.parsing.internal.nested
import net.corda.common.validation.internal.Validated
import net.corda.core.internal.div
import net.corda.core.internal.noPackageOverlap
import net.corda.core.internal.requirePackageValid
import net.corda.core.node.NetworkParameters
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
import net.corda.nodeapi.internal.network.PackageOwner
@ -84,8 +83,12 @@ internal object NetworkParameterOverridesSpec : Configuration.Specification<Netw
override fun parseValid(configuration: Config): Valid<NetworkParametersOverrides> {
val packageOwnership = configuration[packageOwnership]
if (packageOwnership != null && !NetworkParameters.noOverlap(packageOwnership.map { it.javaPackageName })) {
return Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of("Package namespaces must not overlap", keyName = "packageOwnership", containingPath = listOf())).toSet())
if (packageOwnership != null && !noPackageOverlap(packageOwnership.map { it.javaPackageName })) {
return Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of(
"Package namespaces must not overlap",
keyName = "packageOwnership",
containingPath = listOf()
)).toSet())
}
return valid(NetworkParametersOverrides(
minimumPlatformVersion = configuration[minimumPlatformVersion],