Skip to content

Commit fb4529f

Browse files
Happy0adridadou
andauthored
Add support for external signature content (#254)
* Add support for external signature content. * External signature type should always have the placeholder text until replaced by the external service. * Add new input and output fields to external signature input call. * Add new TextElement for external signature placeholder text. * Always apply white-text class to external signature. * try with style instead of class. * Fix formatting. * Add accessToken parameter to external signature call. * Add token expiry and refresh token fields. * Add refresh token and expiry date. * Fix syntax * print error. * Try custom encoding. * Remove custom decoder and replace with a new strategy. * Remove print statements. * Verify external signature properly. * Fix formatting. Co-authored-by: David Roon <[email protected]>
1 parent d661f4e commit fb4529f

File tree

9 files changed

+116
-15
lines changed

9 files changed

+116
-15
lines changed

shared/src/main/scala/org/adridadou/openlaw/oracles/OpenlawSignatureOracle.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ final case class OpenlawSignatureOracle(
2424
data: EthereumData,
2525
signatureEvent: SignatureEvent
2626
): Result[Boolean] = signatureEvent match {
27+
case event: ExternalSignatureEvent =>
28+
event.getServiceName match {
29+
case None => Failure(s"No registered with name ${event.getServiceName}")
30+
case Some(serviceName) => {
31+
val externalAccount =
32+
externalSignatureAccounts.get(serviceName) match {
33+
case Some(account) => Success(account)
34+
case None =>
35+
Failure(s"unknown service ${serviceName.serviceName}")
36+
}
37+
38+
val signedData = EthereumData(crypto.sha256(event.email.email))
39+
.merge(EthereumData(crypto.sha256(data.data)))
40+
41+
externalAccount.flatMap { signatureServiceAccount =>
42+
EthereumAddress(
43+
crypto
44+
.validateECSignature(signedData.data, event.signature.signature)
45+
).map(derivedAddress => {
46+
signatureServiceAccount.withLeading0x === derivedAddress.withLeading0x
47+
})
48+
}
49+
50+
}
51+
}
52+
2753
case event: SignatureEvent =>
2854
val signedData = EthereumData(crypto.sha256(event.email.email))
2955
.merge(EthereumData(crypto.sha256(data.data)))

shared/src/main/scala/org/adridadou/openlaw/parser/template/OpenlawTemplateLanguageParserService.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ class OpenlawTemplateLanguageParserService {
259259
)
260260
case txt: FreeText =>
261261
Success(agreementPrinter.text(txt.elem))
262+
263+
case placeholder: SignaturePlaceholder =>
264+
Success(agreementPrinter.signaturePlaceholder(placeholder.text))
265+
262266
case image: ImageElement =>
263267
Success(agreementPrinter.image(image))
264268
case link: Link =>

shared/src/main/scala/org/adridadou/openlaw/parser/template/StructuredDocument.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,9 @@ final case class ImageElement(url: String) extends AgreementElement {
15021502
final case class FreeText(elem: TextElement) extends AgreementElement {
15031503
override def serialize: Json = this.asJson
15041504
}
1505+
final case class SignaturePlaceholder(text: String) extends AgreementElement {
1506+
override def serialize: Json = this.asJson
1507+
}
15051508
final case class Link(label: String, url: String) extends AgreementElement {
15061509
override def serialize: Json = this.asJson
15071510
}

shared/src/main/scala/org/adridadou/openlaw/parser/template/formatters/SignatureFormatter.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,39 @@ package org.adridadou.openlaw.parser.template.formatters
33
import org.adridadou.openlaw.OpenlawValue
44
import org.adridadou.openlaw.parser.template._
55
import org.adridadou.openlaw.parser.template.expressions.Expression
6-
import org.adridadou.openlaw.parser.template.variableTypes.Identity
6+
import org.adridadou.openlaw.parser.template.variableTypes.{
7+
ExternalSignature,
8+
ExternalSignatureType,
9+
Identity
10+
}
711
import org.adridadou.openlaw.result.{Failure, Result, Success}
812

913
/**
1014
* Created by davidroon on 12.06.17.
1115
*/
1216
class SignatureFormatter extends Formatter {
17+
18+
private def formatExternalSignature(
19+
expression: Expression
20+
): Result[List[AgreementElement]] = {
21+
Success(List(SignaturePlaceholder(missingSignatureText(expression))))
22+
}
23+
24+
private def formatExternalSignatureString(
25+
expression: Expression
26+
): Result[String] = {
27+
Success(missingSignatureText(expression))
28+
}
29+
1330
override def format(
1431
expression: Expression,
1532
value: OpenlawValue,
1633
executionResult: TemplateExecutionResult
1734
): Result[List[AgreementElement]] = value match {
35+
case externalSignature: ExternalSignature =>
36+
// External signatures are just placeholder text until the external service replaces them with the signature
37+
// applied by the user
38+
formatExternalSignature(expression)
1839
case identity: Identity =>
1940
executionResult
2041
.getSignatureProof(identity)
@@ -28,6 +49,7 @@ class SignatureFormatter extends Formatter {
2849
)
2950
})
3051
.getOrElse(Success(missingValueFormat(expression)))
52+
3153
case other =>
3254
Failure(
3355
"invalid type " + other.getClass.getSimpleName + ". expecting Identity"
@@ -45,6 +67,8 @@ class SignatureFormatter extends Formatter {
4567
.getSignatureProof(identity)
4668
.map(proof => Success(s"/s/ ${proof.fullName}"))
4769
.getOrElse(Success(missingSignatureText(expression)))
70+
case externalSignature: ExternalSignature =>
71+
formatExternalSignatureString(expression)
4872
case other =>
4973
Failure(
5074
"invalid type " + other.getClass.getSimpleName + ". expecting Identity"

shared/src/main/scala/org/adridadou/openlaw/parser/template/printers/AgreementPrinter.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ trait AgreementPrinter[T] {
1919
def title(title: TemplateTitle): AgreementPrinter[T]
2020
def link(link: Link): AgreementPrinter[T]
2121
def text(txt: TextElement): AgreementPrinter[T]
22+
def signaturePlaceholder(text: String): AgreementPrinter[T]
2223
def conditionalStart(): AgreementPrinter[T] =
2324
newState(state.copy(conditionalDepth = state.conditionalDepth + 1))
2425
def conditionalStartWithElse(): AgreementPrinter[T] =

shared/src/main/scala/org/adridadou/openlaw/parser/template/printers/XHtmlAgreementPrinter.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import java.util.concurrent.atomic.AtomicInteger
44

55
import cats.implicits._
66
import org.adridadou.openlaw.parser.template._
7-
import org.adridadou.openlaw.parser.template.variableTypes.IdentityType
7+
import org.adridadou.openlaw.parser.template.variableTypes.{
8+
ExternalSignature,
9+
IdentityType
10+
}
811
import scalatags.Text.all._
912
import slogging._
1013

@@ -335,6 +338,18 @@ final case class XHtmlAgreementPrinter(
335338
continue(a(href := url)(label) +: elems)
336339
})
337340

341+
case SignaturePlaceholder(str) =>
342+
// Generate text output
343+
val innerFrag = text(str)
344+
345+
val spanFrag: List[Frag] = List(
346+
span(`style` := "color: white;")(innerFrag)
347+
)
348+
349+
tailRecurse(xs, conditionalBlockDepth, inSection, { elems =>
350+
continue(spanFrag ++ elems)
351+
})
352+
338353
case PlainText(str) =>
339354
tailRecurse(xs, conditionalBlockDepth, inSection, { elems =>
340355
continue(text(str) ++ elems)

shared/src/main/scala/org/adridadou/openlaw/parser/template/variableTypes/AbstractStructureType.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.adridadou.openlaw.parser.template.variableTypes
33
import cats.implicits._
44
import io.circe.{Decoder, Encoder, HCursor, Json}
55
import cats.kernel.Eq
6+
import io.circe.Json.JString
67
import org.adridadou.openlaw.parser.template._
78
import io.circe.parser._
89
import io.circe.syntax._
@@ -215,20 +216,33 @@ final case class DefinedStructureType(structure: Structure, typeName: String)
215216
executionResult: TemplateExecutionResult
216217
): Result[OpenlawMap[VariableName, OpenlawValue]] =
217218
for {
218-
values <- decode[Map[String, String]](value).leftMap(FailureException(_))
219+
values <- decode[Map[String, Json]](value).leftMap(FailureException(_))
219220
list <- structure.typeDefinition
220221
.flatMap {
221222
case (fieldName, fieldType) =>
222223
values
223224
.get(fieldName.name)
224225
.map(value =>
225-
fieldType.cast(value, executionResult).map(fieldName -> _)
226+
fieldType
227+
.cast(jsonToString(value), executionResult)
228+
.map(fieldName -> _)
226229
)
227230
}
228231
.toList
229232
.sequence
230233
} yield OpenlawMap(list.toMap)
231234

235+
private def jsonToString(json: Json): String = {
236+
json.fold[String](
237+
"null",
238+
bool => bool.toString,
239+
number => number.toString,
240+
string => string,
241+
array => array.asJson.noSpaces,
242+
jsonObject => jsonObject.asJson.noSpaces
243+
)
244+
}
245+
232246
override def internalFormat(value: OpenlawValue): Result[String] =
233247
VariableType
234248
.convert[OpenlawMap[VariableName, OpenlawValue]](value)

shared/src/main/scala/org/adridadou/openlaw/parser/template/variableTypes/ExternalCall.scala

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ object IntegratedServiceDefinition {
2828
val parser = new OpenlawTemplateLanguageParserService()
2929
val engine = new OpenlawExecutionEngine()
3030
private val signatureDefinitionStr =
31-
"[[Input:Structure(signerEmail: Text; contractContentBase64: Text; contractTitle: Text)]] " +
32-
"[[Output:Structure(signerEmail: Text; signature: Text; recordLink: Text)]]"
31+
"[[Input:Structure(signerEmail: Text; contractContentBase64: Text; contractTitle: Text; signaturePlaceholderText: Text; accessToken: Text; tokenExpiry: Number; refreshToken: Text)]] " +
32+
"[[Output:Structure(signerEmail: Text; signature: Text; recordLink: Text; pdfContentsBase64: Text)]]"
3333

3434
private val storageDefinitionStr =
3535
"[[Input:Structure(accessToken: Text; operation: Text; filePath: Text; fileBase64Content: Text)]] " +
@@ -41,10 +41,12 @@ object IntegratedServiceDefinition {
4141
IntegratedServiceDefinition(storageDefinitionStr).getOrThrow()
4242

4343
def apply(definition: String): result.Result[IntegratedServiceDefinition] = {
44-
for {
44+
val result = for {
4545
i <- getStructure(definition, "Input")
4646
o <- getStructure(definition, "Output")
4747
} yield new IntegratedServiceDefinition(i, o)
48+
49+
result
4850
}
4951

5052
private def getStructure(
@@ -159,26 +161,36 @@ object StorageOutput {
159161
final case class SignatureInput(
160162
signerEmail: Email,
161163
contractContentBase64: String,
162-
contractTitle: String
164+
contractTitle: String,
165+
// The text that the signature service should match to determine where on the document the user should sign.
166+
signaturePlaceholderText: String,
167+
accessToken: String,
168+
// When the token will expire as a unix timestamp
169+
tokenExpiry: Long,
170+
// The token to refresh the access token
171+
refreshToken: String
163172
)
173+
164174
object SignatureInput {
165-
implicit val signatureInputEnc: Encoder[SignatureInput] = deriveEncoder
166175
implicit val signatureInputDec: Decoder[SignatureInput] = deriveDecoder
167-
implicit val signatureInputEq: Eq[SignatureInput] = Eq.fromUniversalEquals
176+
177+
implicit val signatureInputEnc: Encoder[SignatureInput] = deriveEncoder
168178
}
169179

170180
final case class SignatureOutput(
171181
signerEmail: Email,
172182
signature: EthereumSignature,
173-
recordLink: String
183+
recordLink: String,
184+
pdfContentsBase64: String
174185
)
175186
object SignatureOutput {
176187
implicit val signatureOutputEnc: Encoder[SignatureOutput] =
177188
Encoder.instance[SignatureOutput] { output =>
178189
Json.obj(
179190
"signerEmail" -> Json.fromString(output.signerEmail.email),
180191
"signature" -> Json.fromString(output.signature.toString),
181-
"recordLink" -> Json.fromString(output.recordLink)
192+
"recordLink" -> Json.fromString(output.recordLink),
193+
"pdfContentsBase64" -> Json.fromString(output.pdfContentsBase64)
182194
)
183195
}
184196
implicit val signatureOutputDec: Decoder[SignatureOutput] =
@@ -187,10 +199,12 @@ object SignatureOutput {
187199
signerEmail <- c.downField("signerEmail").as[Email]
188200
signature <- c.downField("signature").as[String]
189201
recordLink <- c.downField("recordLink").as[String]
202+
pdfContentsBase64 <- c.downField("pdfContentsBase64").as[String]
190203
} yield SignatureOutput(
191204
signerEmail,
192205
EthereumSignature(signature).getOrThrow(),
193-
recordLink
206+
recordLink,
207+
pdfContentsBase64
194208
)
195209
}
196210
implicit val signatureOutputEq: Eq[SignatureOutput] = Eq.fromUniversalEquals

shared/src/main/scala/org/adridadou/openlaw/parser/template/variableTypes/ExternalSignatureType.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io.circe.generic.semiauto._
77
import org.adridadou.openlaw.parser.template._
88
import org.adridadou.openlaw.parser.template.formatters.{
99
Formatter,
10-
NoopFormatter
10+
SignatureFormatter
1111
}
1212
import org.adridadou.openlaw.result.{Failure, FailureException, Result, Success}
1313
import org.adridadou.openlaw.result.Implicits._
@@ -37,7 +37,7 @@ case object ExternalSignatureType extends VariableType("ExternalSignature") {
3737
)
3838
}
3939

40-
override def defaultFormatter: Formatter = new NoopFormatter
40+
override def defaultFormatter: Formatter = new SignatureFormatter
4141

4242
override def construct(
4343
constructorParams: Parameter,

0 commit comments

Comments
 (0)