CORDA-786 - Add whitelist testing for external custom serializers

Update Docs
This commit is contained in:
Katelyn Baker 2017-11-24 12:04:17 +00:00
parent f931b74aec
commit 6881350493
2 changed files with 99 additions and 28 deletions

View File

@ -1,30 +1,29 @@
Pluggable Serializers for CorDapps
==================================
To be serializable by Corda Java classes must be compiled with the -parameter switch to enable mathcing of ass property
to constructor parameter. However, when this isn't possible CorDapps can provide custom proxy serialiszers that Corda
can use to move from types it cannot serialiser to an interim represtnation that it can with the transformation to and
from this proxy object being handled by the supplied serialiser.
To be serializable by Corda Java classes must be compiled with the -parameter switch to enable matching of it's properties
to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only constuct
objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that
they cannot be easily modified for simple serailization, CorDapps can provide custom proxy serializers that Corda
can use to move from types it cannot serializer to an interim representation that it can with the transformation to and
from this proxy object being handled by the supplied serializer.
Serializer Location
-------------------
Custom serializers should be placed in the plugins directory fo a CorDapp or a sub directory (placing it in a sub
directory however does require that directory be added to the list of locations scanned within the jar)
Custom serializers should be placed in the plugins directory of a CorDapp or a sub directory thereof. These
classes will be scanned and loaded by the CorDapp loading process.
Writing a Custom Serializer
--------------------------
---------------------------
Serializers must
* Inherit from net.corda.core.serialization.SerializationCustomSerializer
* Be annotated with the @CordaCustomSerializer annotation
* Provide a proxy class to transform the objectto and from
* Provide a proxy class to transform the object to and from
* Have that proxy class annotated with the @CordaCustomSerializerProxy annotation
Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types
Example
-------
Consider this example class
.. sourcecode:: java
@ -43,7 +42,10 @@ Consider this example class
public int getB() { return b; }
}
This would require a serialiser as follows
Without a custom serializer we cannot serialise this class as there is no public constructor that facilitates the
initialisation of al of its's properties.
To be serializable by Corda this would require a custom serializer as follows
.. sourcecode:: kotlin
@CordaCustomSerializer
@ -64,6 +66,9 @@ This would require a serialiser as follows
override val ptype: Type get() = Proxy::class.java
}
Whitelisting
------------
By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such
classes don't need explicitly adding to the CorDapp's whitelist

View File

@ -3,7 +3,10 @@ package net.corda.nodeapi.internal.serialization.amqp
import org.junit.Test
import net.corda.core.serialization.CordaCustomSerializer
import net.corda.core.serialization.CordaCustomSerializerProxy
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationCustomSerializer
import org.assertj.core.api.Assertions
import java.io.NotSerializableException
import java.lang.reflect.Type
import kotlin.test.assertEquals
@ -18,31 +21,25 @@ class CorDappSerializerTests {
override val type: Type get() = NeedsProxy::class.java
override val ptype: Type get() = Proxy::class.java
override fun fromProxy(proxy: Any) : Any {
println ("NeedsProxyProxySerialiser - fromProxy")
return NeedsProxy((proxy as Proxy).proxy_a_)
}
override fun toProxy(obj: Any) : Any {
println ("NeedsProxyProxySerialiser - to Proxy")
return Proxy((obj as NeedsProxy).a)
}
override fun fromProxy(proxy: Any) : Any = NeedsProxy((proxy as Proxy).proxy_a_)
override fun toProxy(obj: Any) : Any = Proxy((obj as NeedsProxy).a)
}
// Standard proxy serialiser used internally, here for comparison purposes
class InternalProxySerialiser(factory: SerializerFactory) :
CustomSerializer.Proxy<NeedsProxy, InternalProxySerialiser.Proxy> (
// Standard proxy serializer used internally, here for comparison purposes
class InternalProxySerializer(factory: SerializerFactory) :
CustomSerializer.Proxy<NeedsProxy, InternalProxySerializer.Proxy> (
NeedsProxy::class.java,
InternalProxySerialiser.Proxy::class.java,
InternalProxySerializer.Proxy::class.java,
factory) {
data class Proxy(val proxy_a_: String)
override fun toProxy(obj: NeedsProxy): Proxy {
println ("InternalProxySerialiser - toProxy")
println ("InternalProxySerializer - toProxy")
return Proxy(obj.a)
}
override fun fromProxy(proxy: Proxy): NeedsProxy {
println ("InternalProxySerialiser - fromProxy")
println ("InternalProxySerializer - fromProxy")
return NeedsProxy(proxy.proxy_a_)
}
}
@ -56,7 +53,7 @@ class CorDappSerializerTests {
val msg = "help"
proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory))
internalProxyFactory.register (InternalProxySerialiser(internalProxyFactory))
internalProxyFactory.register (InternalProxySerializer(internalProxyFactory))
val needsProxy = NeedsProxy(msg)
@ -89,4 +86,73 @@ class CorDappSerializerTests {
assertEquals(tv1, objFromDefault.obj.a)
assertEquals(tv2, objFromDefault.obj.b.a)
}
@Test
fun testWithWhitelistNotAllowed() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
private val allowedClasses = emptySet<String>()
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
Assertions.assertThatThrownBy {
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2)))
}.isInstanceOf(NotSerializableException::class.java)
}
@Test
fun testWithWhitelistAllowed() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
private val allowedClasses = hashSetOf(
A::class.java.name,
NeedsProxy::class.java.name)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
val obj = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
assertEquals(tv1, obj.a)
assertEquals(tv2, obj.b.a)
}
// The custom type not being whitelisted won't matter here because the act of adding a
// custom serializer bypasses the whitelist
@Test
fun testWithWhitelistAllowedOuterOnly() {
data class A (val a: Int, val b: NeedsProxy)
class WL : ClassWhitelist {
// explicitly don't add NeedsProxy
private val allowedClasses = hashSetOf(A::class.java.name)
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
}
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
val tv1 = 100
val tv2 = "pants schmants"
val obj = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
assertEquals(tv1, obj.a)
assertEquals(tv2, obj.b.a)
}
}