libeufin

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

commit fa2705f9f449a0d94a9c92ed101c3e597f673bac
parent 14cabeb098524400fa64d404384a589b95509c96
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Wed, 23 Oct 2019 13:48:56 +0200

Generating INI response.

Diffstat:
Msandbox/src/main/kotlin/DB.kt | 17++++-------------
Asandbox/src/main/kotlin/EbicsResponse.kt | 44++++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/KeyManagementResponse.kt | 6+++---
Msandbox/src/main/kotlin/Main.kt | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Dsandbox/src/main/kotlin/Response.kt | 44--------------------------------------------
Msandbox/src/test/kotlin/ResponseTest.kt | 7++-----
Msandbox/src/test/resources/ebics_ini_request_sample.xml | 2+-
7 files changed, 194 insertions(+), 118 deletions(-)

diff --git a/sandbox/src/main/kotlin/DB.kt b/sandbox/src/main/kotlin/DB.kt @@ -11,7 +11,6 @@ const val EBICS_SYSTEM_ID_MAX_LENGTH = 10 const val PUBLIC_KEY_MAX_MODULUS_LENGTH = 256 // FIXME review this value! const val PUBLIC_KEY_MAX_EXPONENT_LENGTH = 256 // FIXME review this value! const val PRIV_KEY_MAX_LENGTH = 512 // FIXME review this value! -const val SQL_ENUM_SUBSCRIBER_STATES = "ENUM('NEW', 'PARTIALLI_INITIALIZED_INI', 'PARTIALLY_INITIALIZED_HIA', 'INITIALIZED', 'READY')" /** * All the states to give a subscriber. @@ -23,12 +22,12 @@ enum class SubscriberStates { NEW, /** - * Only INI electronic message was succesfully sent. + * Only INI electronic message was successfully sent. */ PARTIALLY_INITIALIZED_INI, /** - * Only HIA electronic message was succesfully sent. + * Only HIA electronic message was successfully sent. */ PARTIALLY_INITIALIZED_HIA, @@ -154,11 +153,7 @@ class EbicsSystem(id: EntityID<Int>) : IntEntity(id) { object EbicsPublicKeys: IntIdTable() { val modulus = binary("modulus", PUBLIC_KEY_MAX_MODULUS_LENGTH) val exponent = binary("exponent", PUBLIC_KEY_MAX_EXPONENT_LENGTH) - val state = customEnumeration( - "state", - "ENUM('MISSING', 'NEW', 'RELEASED')", - {KeyStates.values()[it as Int]}, - {it.name}) + val state = enumeration("state", KeyStates::class) } @@ -186,11 +181,7 @@ object EbicsSubscribers: IntIdTable() { val encryptionKey = reference("encryptionKey", EbicsPublicKeys).nullable() val authorizationKey = reference("authorizationKey", EbicsPublicKeys).nullable() - val state = customEnumeration( - "state", - SQL_ENUM_SUBSCRIBER_STATES, - {SubscriberStates.values()[it as Int]}, - {it.name}) + val state = enumeration("state", SubscriberStates::class) } class EbicsSubscriber(id: EntityID<Int>) : IntEntity(id) { diff --git a/sandbox/src/main/kotlin/EbicsResponse.kt b/sandbox/src/main/kotlin/EbicsResponse.kt @@ -0,0 +1,44 @@ +package tech.libeufin.sandbox + +import tech.libeufin.messages.ebics.response.EbicsResponse +import tech.libeufin.messages.ebics.response.ObjectFactory +import javax.xml.bind.JAXBElement +import javax.xml.namespace.QName + +/** + * Convenience wrapper around the main JAXB value. + * + * @param returnCode return code + * @param reportText EBICS-compliant error text token, e.g. "[EBICS_OK]" (mandatory brackets!) + * @param description short description about the response, e.g. "invalid signature". + */ +class EbicsResponse( + returnCode: String, + reportText: String +) { + + /** + * For now, the sandbox returns _only_ technical return codes, + * namely those that are _not_ related with business orders. Therefore, + * the relevant fields to fill are "ebicsResponse/header/mutable/report{Text,Code}". + * + * Once business return code will be returned, then the following fields will + * also have to be filled out: "ebicsResponse/body/report{Text,Code}". + */ + private val value = { + val of = ObjectFactory() + val tmp = of.createEbicsResponse() + tmp.header = of.createEbicsResponseHeader() + tmp.header.mutable = of.createResponseMutableHeaderType() + tmp.header.mutable.reportText = reportText + tmp.header.mutable.returnCode = returnCode + tmp + }() + + fun get(): JAXBElement<EbicsResponse> { + return JAXBElement( + QName("urn:org:ebics:H004", "ebicsResponse"), + EbicsResponse::class.java, + value) + } +} diff --git a/sandbox/src/main/kotlin/KeyManagementResponse.kt b/sandbox/src/main/kotlin/KeyManagementResponse.kt @@ -6,10 +6,10 @@ import javax.xml.bind.JAXBElement import javax.xml.namespace.QName class KeyManagementResponse( - version: String, - revision: Int, + version: String = "H004", + revision: Int = 1, returnCode: String, - orderId: String, + orderId: String = "<automatically generated by the bank>", reportText: String ) { diff --git a/sandbox/src/main/kotlin/Main.kt b/sandbox/src/main/kotlin/Main.kt @@ -43,31 +43,87 @@ import org.w3c.dom.Document import org.w3c.dom.Element import tech.libeufin.messages.ebics.keyrequest.EbicsUnsecuredRequest import tech.libeufin.messages.ebics.keyrequest.SignaturePubKeyOrderDataType -import tech.libeufin.messages.ebics.keyrequest.UnsecuredReqOrderDetailsType -import tech.libeufin.messages.ebics.keyresponse.EbicsKeyManagementResponse import java.math.BigInteger import java.nio.charset.StandardCharsets.US_ASCII import java.text.DateFormat -import java.util.* -import java.util.zip.GZIPInputStream -import javax.xml.bind.JAXBElement -import java.nio.charset.StandardCharsets.UTF_8 -import java.security.InvalidKeyException import java.security.KeyFactory import java.security.PublicKey -import java.security.interfaces.RSAPublicKey import java.security.spec.RSAPublicKeySpec -import java.util.zip.Inflater import java.util.zip.InflaterInputStream -import javax.xml.bind.JAXB - - val logger = LoggerFactory.getLogger("tech.libeufin.sandbox") val xmlProcess = XML() val getEbicsHostId = {"LIBEUFIN-SANDBOX"} +object UserUnknownHelper { + + fun getCode(): String { + return "091003" + } + + /** + * @param description: will be concatenated to the error token word. + * @return full error string: token + description + */ + fun getMessage(description: String = ""): String { + return "[EBICS_UNKNOWN_USER] $description" + } +} + + +object InvalidHostIdHelper { + + fun getCode(): String { + return "091011" + } + + /** + * @param description: will be concatenated to the error token word. + * @return full error string: token + description + */ + fun getMessage(description: String = ""): String { + return "[EBICS_INVALID_HOST_ID] $description" + } +} + +object InvalidXmlHelper { + + fun getCode(): String { + return "091010" + } + + /** + * @param description: will be concatenated to the error token word. + * @return full error string: token + description + */ + fun getMessage(description: String = ""): String { + return "[EBICS_INVALID_XML] $description" + } +} + +object InternalErrorHelper { + + fun getCode(): String { + return "061099" + } + + fun getMessage(description: String = ""): String { + return "[EBICS_INTERNAL_ERROR] $description" + } +} + +object OkHelper { + + fun getCode(): String { + return "000000" + } + + fun getMessage(description: String = ""): String { + return "[EBICS_OK] $description" + } +} + /** * Sometimes, JAXB is not able to figure out to which type * a certain XML node should be bound to. This happens when @@ -215,21 +271,17 @@ private suspend fun ApplicationCall.ebicsweb() { val body: String = receiveText() val bodyDocument: Document? = xmlProcess.parseStringIntoDom(body) - if (bodyDocument == null) { - respondText( - contentType = ContentType.Application.Xml, - status = HttpStatusCode.BadRequest - ) { "Bad request / Could not parse the body" } - return - } + if (bodyDocument == null || (!xmlProcess.validateFromDom(bodyDocument))) { + var response = EbicsResponse( + returnCode = InvalidXmlHelper.getCode(), + reportText = InvalidXmlHelper.getMessage() + ) - if (!xmlProcess.validateFromDom(bodyDocument)) { - logger.error("Invalid request received") respondText( contentType = ContentType.Application.Xml, status = HttpStatusCode.BadRequest - ) { "Bad request / invalid document" } + ) { xmlProcess.convertJaxbToString(response.get())!! } return } @@ -248,10 +300,16 @@ private suspend fun ApplicationCall.ebicsweb() { ) if (bodyJaxb.value.header.static.hostID != getEbicsHostId()) { - respond( - HttpStatusCode.NotFound, - SandboxError("Unknown HostID specified") - ) + + // return INI response! + val response = KeyManagementResponse( + returnCode = InvalidHostIdHelper.getCode(), + reportText = InvalidHostIdHelper.getMessage( )) + + respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.NotFound + ) { xmlProcess.convertJaxbToString(response.get())!! } return } val ebicsUserID = transaction { @@ -259,10 +317,16 @@ private suspend fun ApplicationCall.ebicsweb() { } if (ebicsUserID == null) { - respond( - HttpStatusCode.NotFound, - SandboxError("Ebics UserID not found") + val response = KeyManagementResponse( + returnCode = UserUnknownHelper.getCode(), + reportText = UserUnknownHelper.getMessage() ) + + respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.NotFound + ) { xmlProcess.convertJaxbToString(response.get())!! } + return } @@ -285,11 +349,16 @@ private suspend fun ApplicationCall.ebicsweb() { * shall the schema be patched to avoid having this if-block here? */ if (zkey.isEmpty()) { - logger.error("0-length key element given, invalid request") + 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 - ) { "Bad request / invalid document" } + ) { xmlProcess.convertJaxbToString(response.get())!! } return } @@ -320,10 +389,17 @@ private suspend fun ApplicationCall.ebicsweb() { } catch (e: Exception) { logger.info("User gave bad key, not storing it") e.printStackTrace() - respond( - HttpStatusCode.BadRequest, - SandboxError("Bad public key given") + val response = KeyManagementResponse( + returnCode = InvalidXmlHelper.getCode(), + reportText = InvalidXmlHelper.getMessage("Invalid key given") ) + + + respondText( + contentType = ContentType.Application.Xml, + status = HttpStatusCode.BadRequest + ) { xmlProcess.convertJaxbToString(response.get())!! } + return } @@ -340,10 +416,17 @@ private suspend fun ApplicationCall.ebicsweb() { * row is also (via a helper function) added into the EbicsSubscribers table. */ if (ebicsSubscriber == null) { - respond( - HttpStatusCode.InternalServerError, - SandboxError("Internal error, please contact customer service") + + val response = KeyManagementResponse( + returnCode = InternalErrorHelper.getCode(), + reportText = InternalErrorHelper.getMessage() ) + + respondText( + status = HttpStatusCode.InternalServerError, + contentType = ContentType.Application.Xml + ) { InternalErrorHelper.getMessage() } + return } @@ -354,33 +437,38 @@ private suspend fun ApplicationCall.ebicsweb() { exponent = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent state = KeyStates.NEW } + + ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_INI } - logger.debug("Signature key inserted in database.") + logger.info( + "Signature key inserted in database _and_ subscriber state changed accordingly" + ) // return INI response! val response = KeyManagementResponse( - "H004", - 1, - "000000", - "MOCK-ID", - "[EBICS_OK] OK") + returnCode = OkHelper.getCode(), + reportText = OkHelper.getMessage() + ) respondText( contentType = ContentType.Application.Xml, - status = HttpStatusCode.OK) { - xmlProcess.convertJaxbToString(response.get()).toString() - } + status = HttpStatusCode.OK + ) { xmlProcess.convertJaxbToString(response.get())!! } return } - } - respond( - HttpStatusCode.NotImplemented, - SandboxError("Not implemented") - ) - return + "HIA" -> { + + respond( + HttpStatusCode.NotImplemented, + SandboxError("Not implemented") + ) + return + + } + } } "ebicsHEVRequest" -> { diff --git a/sandbox/src/main/kotlin/Response.kt b/sandbox/src/main/kotlin/Response.kt @@ -1,44 +0,0 @@ -package tech.libeufin.sandbox - -import tech.libeufin.messages.ebics.response.EbicsResponse -import tech.libeufin.messages.ebics.response.ObjectFactory -import javax.xml.bind.JAXBElement -import javax.xml.namespace.QName - -/** - * Convenience wrapper around the main JAXB value. - * - * @param returnCode return code - * @param reportText EBICS-compliant error text token, e.g. "[EBICS_OK]" (mandatory brackets!) - * @param description short description about the response, e.g. "invalid signature". - */ -class Response( - returnCode: String, - reportText: String, - description: String) { - - /** - * For now, the sandbox returns _only_ technical return codes, - * namely those that are _not_ related with business orders. Therefore, - * the relevant fields to fill are "ebicsResponse/header/mutable/report{Text,Code}". - * - * Once business return code will be returned, then the following fields will - * also have to be filled out: "ebicsResponse/body/report{Text,Code}". - */ - private val value = { - val of = ObjectFactory() - val tmp = of.createEbicsResponse() - tmp.header = of.createEbicsResponseHeader() - tmp.header.mutable = of.createResponseMutableHeaderType() - tmp.header.mutable.reportText = "$reportText $description" - tmp.header.mutable.returnCode = returnCode - tmp - }() - - fun get(): JAXBElement<EbicsResponse> { - return JAXBElement( - QName("urn:org:ebics:H004", "ebicsResponse"), - EbicsResponse::class.java, - value) - } -} diff --git a/sandbox/src/test/kotlin/ResponseTest.kt b/sandbox/src/test/kotlin/ResponseTest.kt @@ -1,9 +1,6 @@ package tech.libeufin.sandbox -import org.junit.Assert import org.junit.Test -import org.junit.Assert.* -import org.junit.Before class ResponseTest { @@ -11,8 +8,8 @@ class ResponseTest { @Test fun loadResponse() { - val response = Response( - "0000", + val response = EbicsResponse( + "0000", "[EBICS_OK]", "All is OK." ) diff --git a/sandbox/src/test/resources/ebics_ini_request_sample.xml b/sandbox/src/test/resources/ebics_ini_request_sample.xml @@ -4,7 +4,7 @@ <static> <HostID>LIBEUFIN-SANDBOX</HostID> <PartnerID>flokid</PartnerID> - <UserID>flouid</UserID> + <UserID>u1</UserID> <!-- Such a not allowed renaming like this fixes the import of DOM into JAXB.