libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit a64a7a2a1202a22df50eb076c392842275d8bf36
parent fa2705f9f449a0d94a9c92ed101c3e597f673bac
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Wed, 23 Oct 2019 14:52:22 +0200

Introducing HIA logic.

And sharing common parts between INI and HIA handling.

Diffstat:
Msandbox/src/main/kotlin/DB.kt | 4++--
Msandbox/src/main/kotlin/Main.kt | 220+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msandbox/src/main/kotlin/XML.kt | 5+++--
Msandbox/src/test/kotlin/DbTest.kt | 2+-
Asandbox/src/test/kotlin/HiaLoadTest.kt | 34++++++++++++++++++++++++++++++++++
Msandbox/src/test/kotlin/ResponseTest.kt | 3+--
Asandbox/src/test/resources/HIA.xml | 21+++++++++++++++++++++
Asandbox/src/test/resources/HPB.xml | 43+++++++++++++++++++++++++++++++++++++++++++
8 files changed, 235 insertions(+), 97 deletions(-)

diff --git a/sandbox/src/main/kotlin/DB.kt b/sandbox/src/main/kotlin/DB.kt @@ -179,7 +179,7 @@ object EbicsSubscribers: IntIdTable() { val signatureKey = reference("signatureKey", EbicsPublicKeys).nullable() val encryptionKey = reference("encryptionKey", EbicsPublicKeys).nullable() - val authorizationKey = reference("authorizationKey", EbicsPublicKeys).nullable() + val authenticationKey = reference("authorizationKey", EbicsPublicKeys).nullable() val state = enumeration("state", SubscriberStates::class) } @@ -193,7 +193,7 @@ class EbicsSubscriber(id: EntityID<Int>) : IntEntity(id) { var signatureKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.signatureKey var encryptionKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.encryptionKey - var authorizationKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.authorizationKey + var authenticationKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.authenticationKey var state by EbicsSubscribers.state } diff --git a/sandbox/src/main/kotlin/Main.kt b/sandbox/src/main/kotlin/Main.kt @@ -41,7 +41,9 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.LoggerFactory import org.w3c.dom.Document import org.w3c.dom.Element +import tech.libeufin.messages.ebics.keyrequest.AuthenticationPubKeyInfoType import tech.libeufin.messages.ebics.keyrequest.EbicsUnsecuredRequest +import tech.libeufin.messages.ebics.keyrequest.HIARequestOrderDataType import tech.libeufin.messages.ebics.keyrequest.SignaturePubKeyOrderDataType import java.math.BigInteger import java.nio.charset.StandardCharsets.US_ASCII @@ -55,6 +57,8 @@ import java.util.zip.InflaterInputStream val logger = LoggerFactory.getLogger("tech.libeufin.sandbox") val xmlProcess = XML() val getEbicsHostId = {"LIBEUFIN-SANDBOX"} +val getEbicsVersion = {"H004"} +val getEbicsRevision = {1} object UserUnknownHelper { @@ -142,15 +146,17 @@ object OkHelper { * @return the modified document */ fun downcastXml(document: Document, node: String, type: String) : Document { - - val x: Element = document.getElementsByTagName("OrderDetails")?.item(0) as Element + logger.debug("Downcasting: ${xmlProcess.convertDomToString(document)}") + val x: Element = document.getElementsByTagNameNS( + "urn:org:ebics:H004", + "OrderDetails" + )?.item(0) as Element x.setAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "type", type ) - return document } @@ -330,55 +336,82 @@ private suspend fun ApplicationCall.ebicsweb() { return } + val ebicsSubscriber = transaction { + EbicsSubscriber.find { + EbicsSubscribers.userId eq EntityID(ebicsUserID.id.value, EbicsUsers) + }.firstOrNull() + } + + /** + * Should _never_ happen, as upon a EBICS' user creation, a EBICS' subscriber + * row is also (via a helper function) added into the EbicsSubscribers table. + */ + if (ebicsSubscriber == null) { + + val response = KeyManagementResponse( + returnCode = InternalErrorHelper.getCode(), + reportText = InternalErrorHelper.getMessage() + ) + + respondText( + status = HttpStatusCode.InternalServerError, + contentType = ContentType.Application.Xml + ) { InternalErrorHelper.getMessage() } + + return + } + logger.info("Serving a ${bodyJaxb.value.header.static.orderDetails.orderType} request") - when (bodyJaxb.value.header.static.orderDetails.orderType) { - "INI" -> { + /** + * NOTE: the JAXB interface has some automagic mechanism that decodes + * the Base64 string into its byte[] form _at the same time_ it instantiates + * the object; in other words, there is no need to perform here the decoding. + */ + val zkey = bodyJaxb.value.body.dataTransfer.orderData.value + + /** + * The validation enforces zkey to be a base64 value, but does not check + * whether it is given _empty_ or not; will check explicitly here. FIXME: + * shall the schema be patched to avoid having this if-block here? + */ + if (zkey.isEmpty()) { + logger.info("0-length key element given, invalid request") + var response = KeyManagementResponse( + returnCode = InvalidXmlHelper.getCode(), + reportText = InvalidXmlHelper.getMessage("Key field was empty") + ) - /** - * NOTE: the JAXB interface has some automagic mechanism that decodes - * the Base64 string into its byte[] form _at the same time_ it instantiates - * the object; in other words, there is no need to perform here the decoding. - */ - val zkey = bodyJaxb.value.body.dataTransfer.orderData.value - - /** - * The validation enforces zkey to be a base64 value, but does not check - * whether it is given _empty_ or not; will check explicitly here. FIXME: - * shall the schema be patched to avoid having this if-block here? - */ - if (zkey.isEmpty()) { - logger.info("0-length key element given, invalid request") - var response = KeyManagementResponse( - returnCode = InvalidXmlHelper.getCode(), - reportText = InvalidXmlHelper.getMessage("Key field was empty") - ) + respondText( + contentType = ContentType.Text.Plain, + status = HttpStatusCode.BadRequest + ) { xmlProcess.convertJaxbToString(response.get())!! } - respondText( - contentType = ContentType.Text.Plain, - status = HttpStatusCode.BadRequest - ) { xmlProcess.convertJaxbToString(response.get())!! } + return + } - return - } + /** + * This value holds the bytes[] of a XML "SignaturePubKeyOrderData" document + * and at this point is valid and _never_ empty. + */ + val inflater = InflaterInputStream(zkey.inputStream()) + var payload = ByteArray(1) {inflater.read().toByte()} - /** - * This value holds the bytes[] of a XML "SignaturePubKeyOrderData" document - * and at this point is valid and _never_ empty. - */ - val inflater = InflaterInputStream(zkey.inputStream()) - var result = ByteArray(1) {inflater.read().toByte()} + while (inflater.available() == 1) { + payload += inflater.read().toByte() + } + + inflater.close() - while (inflater.available() == 1) { - result += inflater.read().toByte() - } - inflater.close() + when (bodyJaxb.value.header.static.orderDetails.orderType) { + + "INI" -> { val keyObject = xmlProcess.convertStringToJaxb( SignaturePubKeyOrderDataType::class.java, - result.toString(US_ASCII) + payload.toString(US_ASCII) ) try { @@ -394,7 +427,6 @@ private suspend fun ApplicationCall.ebicsweb() { reportText = InvalidXmlHelper.getMessage("Invalid key given") ) - respondText( contentType = ContentType.Application.Xml, status = HttpStatusCode.BadRequest @@ -403,72 +435,78 @@ private suspend fun ApplicationCall.ebicsweb() { return } - // At this point: (1) key is valid and (2) Ebics user exists (check- - // -ed above) => key can be inserted in database. - val ebicsSubscriber = transaction { - EbicsSubscriber.find { - EbicsSubscribers.userId eq EntityID(ebicsUserID.id.value, EbicsUsers) - }.firstOrNull() + // put try-catch block here? (FIXME) + transaction { + ebicsSubscriber.signatureKey = EbicsPublicKey.new { + modulus = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus + exponent = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent + state = KeyStates.NEW + } + + ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_INI } - /** - * Should _never_ happen, as upon a EBICS' user creation, a EBICS' subscriber - * row is also (via a helper function) added into the EbicsSubscribers table. - */ - if (ebicsSubscriber == null) { + logger.info("Signature key inserted in database _and_ subscriber state changed accordingly") + } + + "HIA" -> { + + val keyObject = xmlProcess.convertStringToJaxb( + HIARequestOrderDataType::class.java, + payload.toString(US_ASCII) + ) + try { + loadRsaPublicKey( + keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, + keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent + ) + loadRsaPublicKey( + keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, + keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.exponent + ) + } catch (e: Exception) { + logger.info("User gave bad at lease one invalid HIA key") + e.printStackTrace() val response = KeyManagementResponse( - returnCode = InternalErrorHelper.getCode(), - reportText = InternalErrorHelper.getMessage() + returnCode = InvalidXmlHelper.getCode(), + reportText = InvalidXmlHelper.getMessage("Bad keys given") ) respondText( - status = HttpStatusCode.InternalServerError, - contentType = ContentType.Application.Xml - ) { InternalErrorHelper.getMessage() } + contentType = ContentType.Application.Xml, + status = HttpStatusCode.BadRequest + ) { xmlProcess.convertJaxbToString(response.get())!! } return } + // user exists and keys are good. + // put try-catch block here? (FIXME) transaction { - ebicsSubscriber.signatureKey = EbicsPublicKey.new { - modulus = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus - exponent = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent + ebicsSubscriber.authenticationKey = EbicsPublicKey.new { + modulus = keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus + exponent = keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent state = KeyStates.NEW } - ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_INI + ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_HIA } - - logger.info( - "Signature key inserted in database _and_ subscriber state changed accordingly" - ) - - // return INI response! - val response = KeyManagementResponse( - returnCode = OkHelper.getCode(), - reportText = OkHelper.getMessage() - ) - - respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.OK - ) { xmlProcess.convertJaxbToString(response.get())!! } - - return } + } - "HIA" -> { + val response = KeyManagementResponse( + returnCode = OkHelper.getCode(), + reportText = OkHelper.getMessage() + ) - respond( - HttpStatusCode.NotImplemented, - SandboxError("Not implemented") - ) - return + respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.OK + ) { xmlProcess.convertJaxbToString(response.get())!! } - } - } + return } "ebicsHEVRequest" -> { @@ -492,10 +530,12 @@ private suspend fun ApplicationCall.ebicsweb() { else -> { /* Log to console and return "unknown type" */ logger.info("Unknown message, just logging it!") - respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.NotFound - ) { "Not found" } + logger.debug(body) + + respond( + HttpStatusCode.NotImplemented, + SandboxError("Not Implemented") + ) return } } diff --git a/sandbox/src/main/kotlin/XML.kt b/sandbox/src/main/kotlin/XML.kt @@ -177,7 +177,8 @@ class XML { */ fun <T>convertDomToJaxb(finalType: Class<T>, document: Document) : JAXBElement<T> { - val jc = JAXBContext.newInstance(finalType) + // val jc = JAXBContext.newInstance(finalType) + val jc = JAXBContext.newInstance("tech.libeufin.messages.ebics.keyresponse") /* Marshalling the object into the document. */ val m = jc.createUnmarshaller() @@ -242,7 +243,7 @@ class XML { * @param document the DOM to extract the string from. * @return the final String, or null if errors occur. */ - fun convertDocumentToString(document: Document): String? { + fun convertDomToString(document: Document): String? { try { /* Make Transformer. */ diff --git a/sandbox/src/test/kotlin/DbTest.kt b/sandbox/src/test/kotlin/DbTest.kt @@ -35,7 +35,7 @@ class DbTest { exponent = "BINARYVALUE".toByteArray() state = KeyStates.NEW } - subscriber.authorizationKey = key + subscriber.authenticationKey = key } } diff --git a/sandbox/src/test/kotlin/HiaLoadTest.kt b/sandbox/src/test/kotlin/HiaLoadTest.kt @@ -0,0 +1,33 @@ +package tech.libeufin.sandbox + +import org.junit.Test +import org.w3c.dom.Element +import tech.libeufin.messages.ebics.keyrequest.EbicsUnsecuredRequest + +class HiaLoadTest { + + @Test + fun hiaLoad() { + + val processor = XML() + val classLoader = ClassLoader.getSystemClassLoader() + val hia = classLoader.getResource("HIA.xml") + val hiaDom = processor.parseStringIntoDom(hia.readText()) + val x: Element = hiaDom?.getElementsByTagNameNS( + "urn:org:ebics:H004", + "OrderDetails")?.item(0) as Element + + x.setAttributeNS( + "http://www.w3.org/2001/XMLSchema-instance", + "type", + "UnsecuredReqOrderDetailsType" + ) + + processor.convertDomToJaxb<EbicsUnsecuredRequest>( + EbicsUnsecuredRequest::class.java, + hiaDom!!) + } + + + +} +\ No newline at end of file diff --git a/sandbox/src/test/kotlin/ResponseTest.kt b/sandbox/src/test/kotlin/ResponseTest.kt @@ -10,8 +10,7 @@ class ResponseTest { fun loadResponse() { val response = EbicsResponse( "0000", - "[EBICS_OK]", - "All is OK." + "[EBICS_OK] All is okay" ) print(xmlprocess.convertJaxbToString(response.get())) diff --git a/sandbox/src/test/resources/HIA.xml b/sandbox/src/test/resources/HIA.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ebics:ebicsUnsecuredRequest xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebics="urn:org:ebics:H004" xmlns="http://www.w3.org/2001/XMLSchema" Version="H004" Revision="1"> + <ebics:header authenticate="true"> + <ebics:static> + <ebics:HostID>EBIXQUAL</ebics:HostID> + <ebics:PartnerID>EXCHANGE</ebics:PartnerID> + <ebics:UserID>TALER</ebics:UserID> + <ebics:OrderDetails> + <ebics:OrderType>HIA</ebics:OrderType> + <ebics:OrderAttribute>DZNNN</ebics:OrderAttribute> + </ebics:OrderDetails> + <ebics:SecurityMedium>0000</ebics:SecurityMedium> + </ebics:static> + <ebics:mutable/> + </ebics:header> + <ebics:body> + <ebics:DataTransfer> + <ebics:OrderData>eJy9lsmSo0gWRff1FWnRSywTZxZhkVHGLASIeZB2zCAxiVHw9a2OjMiurIxadi/fu9evO+bvuPHy572uvsxpP5Rt8/0J+QaevqRN3CZlk39/msbs6+7pz9c/XtKojIfnvcxY6W1Kh1Hvk7TnwzH88ljfDM/J8P2pGMfuGYaXZfm2YN/aPodRAAAMaPjhSYYy/9fTu/st7RHfN88P2/N7OAD4T8PD/Uvgm+Ut0wYAebf9w5YIHGiqHRdpHT69vp+cmcYibcYyDsfHhxpTpKSr3GTth/6j44XVlL6+JMOzZTO/1FqbTNU0vDImw77Af2n8RxTuXds80l+ZQyHGV0GJiP1FzaeRvXG12BaUfsx1fuqYwTfVhGg76QzjdDWD0owyOb2LSqwsvRZSFgCHqItQ40zl1wrG1O68Uzj76ux21QKzmXGPUO0cMhcF6tnVM/jlWikrJ5ThnF+iA7Q6dt1z0X4D5ynRyru7gEsQCv5g5S0K48eW6YNluLOctlfF2m96ye3Q2UmiUNwM1TK5BoEfu2FZiACinrXgvmc0/+iZd2rA6ezAcUuDZhYizJHOIsOJjoNjSt2oBd13plF2jLqdlF0bdjgJbRKk0v6hXZEpWaULbHViTd4Ej5eveWrkiQtC1oFtlMSVQMFjjAHEjgPm5UrYVyxweq9PeyvmZ2ktBsrkZXazMyJ4nA1gdAJwK+tlNUZTDqOUprF2/hbVHGIMR7divENoajVO1WNpWFxIrZmfE2ok2QzhNNXFvaPcFTBXhrULeDLcO8f1Yr0WwJh4LNfNgzMQPQmleN7qDZxTAiaHto0B2HfxZrs5u6GmL6O520Q/CJAJKZ2zyOihMBaWo3eBz1pVWRlcP0W1Yd8Vvkk3qONkvmavUonYzKFRRA3cw/3NMug+QyHu0uH7XquQuI/ULuFH3GskGtldN9usrRNZk6YdsSgckp4+ntt0Qc8lVaxcyWBWIMrVET4jvnbhqCM0WBZ/T6yo2a8p3t0W66KHGb0LtYw9i811JtlT6EALea/epvjn5L5Vvwz9Dy6csk7tMay7VxQgu68I8hUgDoI8I+AZI84v8N9dH51fePoMQe/Hk/MaAIB+LPrc8bn6O8JCE/dr9z/FWwWJsslUKCywthM01MsRLJRgfaN0uUED91KvcmEXlJ1RDW5DcBDtAz5DztWQ7xbCUSDFZIReNHwgAbPAY9ZKZ+XsDZ3fHGUCWkb3uFHZPm143VLYyZgwTxXWileOe9n36Uw6LvgEnHwiw7kX9z2OHmuyEHDNDxzVyYWT4o1rxBxbHMpPUTGPLSGM0HYbdBOW4hn1hq0KgMxz8Tyo5fnmhkJyDmfUDbuRW6tQkTVHvmCH6uKPgscOAG91kha6uTRKSanwq2Bu2IkS/Gt1346EcS8JXYRVJDjch4VTFiF1ZN8lBzlERdal8LgPd8hsHgt6wvyyNSSXTCi69cpuIInmXEKYsrT9wsMJRi1sTCsAt7PHJXv0yYL4jeM2xMh4186hyzyj4UoW7MJWKTqknmI96NZL+QGAYDpMPqJI4Qd8TUxW3kR17Rezelh9EzpxE9izmsQuLVPBfZSnLT3xaj6ubKEADD8oTeQ2wSQjI0MEhygqHcpTb3VOxrxyjrlSLBlP2oYqPGgYS1rCoTiKeUI8LmyVHHHO949ROLmo6y/D/pLUh9AyLoxIxo60pCLiLDwdXB+4t81uF88aB6mVdswZhM9GEkJmdZbvRt5Tk4cQoCKybY4MA1r1bMj9VqEf76ppXw7X0k40iIhAKjML5/aztJCLfW1Knb42bEND0y3P1BkzArnvGxVgxv8d7//i9wGu8Be0f1d/Vz7BNuzHJu1l/lUIuD1zlISfB/ipvFvd4a1yGFWwPkzvvY/yk7+X1z/+DS9KDOM=</ebics:OrderData> + </ebics:DataTransfer> + </ebics:body> +</ebics:ebicsUnsecuredRequest> diff --git a/sandbox/src/test/resources/HPB.xml b/sandbox/src/test/resources/HPB.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ebics:ebicsNoPubKeyDigestsRequest xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebics="urn:org:ebics:H004" xmlns="http://www.w3.org/2001/XMLSchema" Version="H004" Revision="1"> + <ebics:header authenticate="true"> + <ebics:static> + <ebics:HostID>EBIXQUAL</ebics:HostID> + <ebics:Nonce>0749134D19E160DA4ACA366180113D44</ebics:Nonce> + <ebics:Timestamp>2018-11-01T11:10:35Z</ebics:Timestamp> + <ebics:PartnerID>EXCHANGE</ebics:PartnerID> + <ebics:UserID>TALER</ebics:UserID> + <ebics:OrderDetails> + <ebics:OrderType>HPB</ebics:OrderType> + <ebics:OrderAttribute>DZHNN</ebics:OrderAttribute> + </ebics:OrderDetails> + <ebics:SecurityMedium>0000</ebics:SecurityMedium> + </ebics:static> + <ebics:mutable/> + </ebics:header> + <ebics:AuthSignature> + <ds:SignedInfo> + <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> + <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> + <ds:Reference URI="#xpointer(//*[@authenticate='true'])"> + <ds:Transforms> + <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> + </ds:Transforms> + <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> + <ds:DigestValue>yuwpcvmVrNrc1t58aulF6TiqKO+CNe7Dhwaa6V/cKws=</ds:DigestValue> + </ds:Reference> + </ds:SignedInfo> + <ds:SignatureValue>chhF4/yxSz2WltTR0/DPf01WyncNx3P1XJDr0SylVMVWWSY9S1dYyJKgGOW+g7C/ +HYzrGcFwKrejf79DH3F2Ek8NJLsAFzf/0oxff2eYEe0SlxjXmgsubeMOy5PKB9Ag +ZiQYMiNy9gaatqcW79E3n/r1nD/lwLsped/4jzWdY+Gfj3z6d18vymmGymbHqIaR +hawk/Iu/tpMQ3dbvIFbzn9LLMmzfQzG2ZPy3BiQNVWr3aSLl9qG4U9zeK6OyH2/Z +g1EEnjfJa/+pTCeJmyoDBwgaJWcuCRQmWjvxvbM4ckYnrFkhvLf24on2ydmUeipp +sMl8q1khyWUC0P0h6otZhD1SUdf1rt14r16bdy1r0ieTVm6m3qXhcX5MXagvFci0 +0OE2mOgf/GXE2WiJLAbRh06s9OvAzHUq4QGQwbkprMBMFw4uxONyNzYl+F9aA5Ic +Awebf7/dfDJIHZc8XkwY1jNJrmCTzRyTP5eDN4bDPoHqTotDo1CMFaHtnkygg/Lg +qoirV8mHfqnO4XQBOCUiHZ6mzz81l+O7dYg65cYx9Z76q2cv1PxsMb7Eo4nvux5S +QPuuid0G5lomHXM/uM3mu4vXcDluCoffgTDimxs0I9X+PB7a2vgSMezwYkj8dA69 +vszH1hwek7DRbRfKUo6HUxl49Gsk0XYG/K30M5fS5JE=</ds:SignatureValue> + </ebics:AuthSignature> + <ebics:body/> +</ebics:ebicsNoPubKeyDigestsRequest>