libeufin

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

commit e387c1b0e30eca3b6faf9dd5a506ce6d69130bc6
parent a70730d13cb85abcd6d5066410785b5fba435ab1
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Mon, 11 Nov 2019 20:08:19 +0100

send valid HTD request

Diffstat:
Mnexus/src/main/kotlin/Main.kt | 90++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msandbox/src/main/python/libeufin-cli | 28++++++++++++++++++++++++++++
2 files changed, 111 insertions(+), 7 deletions(-)

diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -137,7 +137,8 @@ suspend inline fun <reified S>HttpClient.postToBank(url: String, body: String): try { return XMLUtil.convertStringToJaxb(response) } catch (e: Exception) { - throw UnparsableResponse(HttpStatusCode.BadRequest) + logger.warn("bank responded: ${response}") + throw UnparsableResponse(HttpStatusCode.BadRequest, response) } } @@ -166,19 +167,18 @@ fun getGregorianDate(): XMLGregorianCalendar { } data class NotAnIdError(val statusCode: HttpStatusCode) : Exception("String ID not convertible in number") +data class BankKeyMissing(val statusCode: HttpStatusCode) : Exception("Impossible operation: bank keys are missing") data class SubscriberNotFoundError(val statusCode: HttpStatusCode) : Exception("Subscriber not found in database") data class UnreachableBankError(val statusCode: HttpStatusCode) : Exception("Could not reach the bank") -data class UnparsableResponse(val statusCode: HttpStatusCode) : Exception("Bank responded with non-XML / non-EBICS " + - "content") -data class EbicsError(val codeError: String) : Exception("Bank did not accepted EBICS request, error is: " + codeError -) +data class UnparsableResponse(val statusCode: HttpStatusCode, val rawResponse: String) : Exception("bank responded: ${rawResponse}") +data class EbicsError(val codeError: String) : Exception("Bank did not accepted EBICS request, error is: ${codeError}") fun main() { dbCreateTables() testData() // gets always id == 1 val client = HttpClient(){ - expectSuccess = false // this way, does not throw exceptions on != 200 responses + expectSuccess = false // this way, it does not throw exceptions on != 200 responses. } val logger = LoggerFactory.getLogger("tech.libeufin.nexus") @@ -214,7 +214,7 @@ fun main() { exception<UnparsableResponse> { cause -> logger.error("Exception while handling '${call.request.uri}'", cause) - call.respondText("Could not parse bank response\n", ContentType.Text.Plain, HttpStatusCode + call.respondText("Could not parse bank response (${cause.message})\n", ContentType.Text.Plain, HttpStatusCode .InternalServerError) } @@ -233,6 +233,11 @@ fun main() { call.respondText("Bank gave EBICS-error response\n", ContentType.Text.Plain, HttpStatusCode.NotAcceptable) } + exception<BankKeyMissing> { cause -> + logger.error("Exception while handling '${call.request.uri}'", cause) + call.respondText("Impossible operation: get bank keys first\n", ContentType.Text.Plain, HttpStatusCode.NotAcceptable) + } + exception<javax.xml.bind.UnmarshalException> { cause -> logger.error("Exception while handling '${call.request.uri}'", cause) call.respondText( @@ -256,6 +261,77 @@ fun main() { return@get } + get("/ebics/subscribers/{id}/sendHtd") { + val id = expectId(call.parameters["id"]) + val (url, body, encPrivBlob) = transaction { + val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) + val request = EbicsRequest().apply { + version = "H004" + revision = 1 + header = EbicsRequest.Header().apply { + authenticate = true + static = EbicsRequest.StaticHeaderType().apply { + userID = subscriber.userID + partnerID = subscriber.partnerID + hostID = subscriber.hostID + nonce = getNonce(128) + timestamp = getGregorianDate() + partnerID = subscriber.partnerID + orderDetails = EbicsRequest.OrderDetails().apply { + orderType = "HTD" + orderAttribute = "DZHNN" + orderParams = EbicsRequest.StandardOrderParams() + } + bankPubKeyDigests = EbicsRequest.BankPubKeyDigests().apply { + authentication = EbicsTypes.PubKeyDigest().apply { + algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + version = "X002" + value = CryptoUtil.getEbicsPublicKeyHash( + CryptoUtil.loadRsaPublicKey( + (subscriber.bankAuthenticationPublicKey ?: throw BankKeyMissing(HttpStatusCode.NotAcceptable)).toByteArray() + ) + ) + } + encryption = EbicsTypes.PubKeyDigest().apply { + algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + version = "E002" + value = CryptoUtil.getEbicsPublicKeyHash( + CryptoUtil.loadRsaPublicKey( + (subscriber.bankEncryptionPublicKey ?: throw BankKeyMissing(HttpStatusCode.NotAcceptable)).toByteArray() + ) + ) + } + securityMedium = "0000" + } + mutable = EbicsRequest.MutableHeader().apply { + transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION + } + authSignature = SignatureType() + } + } + body = EbicsRequest.Body() + } + + val hpbText = XMLUtil.convertJaxbToString(request) + val hpbDoc = XMLUtil.parseStringIntoDom(hpbText) + + XMLUtil.signEbicsDocument( + hpbDoc, + CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()) + ) + + Triple(subscriber.ebicsURL, hpbDoc, subscriber.encryptionPrivateKey.toByteArray()) + } + + val response = client.postToBank<EbicsKeyManagementResponse>(url, body) + print("HTD response: " + XMLUtil.convertJaxbToString(response)) + + call.respond( + HttpStatusCode.NotImplemented, + SandboxError("Not implemented") + ) + } + get("/ebics/subscribers/{id}/keyletter") { val id = expectId(call.parameters["id"]) diff --git a/sandbox/src/main/python/libeufin-cli b/sandbox/src/main/python/libeufin-cli @@ -40,6 +40,34 @@ def ini(obj, customer_id): print(resp.content.decode("utf-8")) +@ebics.command(help="send HTD message") +@click.pass_context +@click.option( + "--customer-id", + help="numerical ID of the customer at the Nexus", + required=False, + default=1) +@click.option( + "--prepare", + help="Gets keying done before requesting HTD", + required=False, + default=True) +def htd(ctx, customer_id, prepare): + + if prepare: + ctx.invoke(ini) + ctx.invoke(hia) + ctx.invoke(sync) + + url = urljoin(ctx.obj["base_url"], "/ebics/subscribers/{}/sendHtd".format(customer_id)) + try: + resp = get(url) + except Exception: + print("Could not reach the bank") + return + + print(resp.content.decode("utf-8")) + @ebics.command(help="send HIA message") @click.pass_obj @click.option(