libeufin

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

commit b048de329d6201e31cccc543f60c7f0bf1fa0162
parent 04ca190f445629ae1c6fa4c6a7df3824196e6edd
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Thu, 19 Mar 2020 19:10:04 +0100

Move zip/unzip to util.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 15++-------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 40+++++++++++++++++++++++++++++++++++++++-
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 23++++++-----------------
Mutil/build.gradle | 2++
Autil/src/main/kotlin/zip.kt | 39+++++++++++++++++++++++++++++++++++++++
5 files changed, 88 insertions(+), 31 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt @@ -28,16 +28,4 @@ fun chunkString(input: String): String { fun expectId(param: String?): String { return param ?: throw NexusError(HttpStatusCode.BadRequest, "Bad ID given") -} - -fun unzipOrderData(od: ByteArray): String { - val mem = SeekableInMemoryByteChannel(od) - val zipFile = ZipFile(mem) - val s = java.lang.StringBuilder() - zipFile.getEntriesInPhysicalOrder().iterator().forEach { entry -> - s.append("<=== File ${entry.name} ===>\n") - s.append(zipFile.getInputStream(entry).readAllBytes().toString(Charsets.UTF_8)) - s.append("\n") - } - return s.toString() -} +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -556,6 +556,44 @@ fun main() { return@post } + /** + * This function triggers the Nexus to perform all those un-submitted payments. + * Ideally, this logic will be moved into some more automatic mechanism. + * NOTE: payments are not yet marked as "done" after this function returns. This + * should be done AFTER the PAIN.002 data corresponding to a payment witnesses it. + */ + post("/ebics/admin/execute-payments-ccc") { + val (paymentRowId, painDoc: String, debtorAccount) = transaction { + val entity = Pain001Entity.find { + (Pain001Table.submitted eq false) and (Pain001Table.invalid eq false) + }.firstOrNull() ?: throw NexusError(HttpStatusCode.Accepted, reason = "No ready payments found") + Triple(entity.id, createPain001document(entity), entity.debtorAccount) + } + logger.debug("Uploading PAIN.001: ${painDoc}") + val subscriberDetails = getSubscriberDetailsFromBankAccount(debtorAccount) + doEbicsUploadTransaction( + client, + subscriberDetails, + "CCT", + painDoc.toByteArray(Charsets.UTF_8), + EbicsStandardOrderParams() + ) + /* flow here == no errors occurred */ + transaction { + val payment = Pain001Entity.findById(paymentRowId) ?: throw NexusError( + HttpStatusCode.InternalServerError, + "Severe internal error: could not find payment in DB after having submitted it to the bank" + ) + payment.submitted = true + } + call.respondText( + "CCT message submitted to the bank", + ContentType.Text.Plain, + HttpStatusCode.OK + ) + return@post + } + post("/ebics/subscribers/{id}/fetch-payment-status") { // FIXME(marcello?): Fetch pain.002 and mark transfers in it as "failed" val id = expectId(call.parameters["id"]) @@ -609,7 +647,7 @@ fun main() { when (response) { is EbicsDownloadSuccessResult -> { call.respondText( - unzipOrderData(response.orderData), + response.orderData.unzip(), ContentType.Text.Plain, HttpStatusCode.OK ) diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -49,8 +49,6 @@ import java.util.zip.DeflaterInputStream import java.util.zip.InflaterInputStream import javax.sql.rowset.serial.SerialBlob import javax.xml.datatype.DatatypeFactory -import org.apache.commons.compress.archivers.ArchiveStreamFactory -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.utils.IOUtils import org.joda.time.DateTime import org.joda.time.Instant @@ -271,23 +269,14 @@ private fun handleEbicsPTK(requestContext: RequestContext): ByteArray { return "Hello I am a dummy PTK response.".toByteArray() } - private fun handleEbicsC52(requestContext: RequestContext): ByteArray { val subscriber = requestContext.subscriber - val camt = constructCamtResponse(52, subscriber.bankCustomer.id.value, requestContext.requestObject.header) - - val baos = ByteArrayOutputStream() - val asf = ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, baos) - val zae = ZipArchiveEntry("Singleton C5{2,3} Entry") - asf.putArchiveEntry(zae) - - val bais = ByteArrayInputStream(camt.toByteArray()) - IOUtils.copy(bais, asf) - bais.close() - asf.closeArchiveEntry() - asf.finish() - baos.close() - return baos.toByteArray() + val camt = constructCamtResponse( + 52, + subscriber.bankCustomer.id.value, + requestContext.requestObject.header + ) + return camt.toByteArray().zip() } private suspend fun ApplicationCall.handleEbicsHia(header: EbicsUnsecuredRequest.Header, orderData: ByteArray) { diff --git a/util/build.gradle b/util/build.gradle @@ -40,10 +40,12 @@ dependencies { implementation 'org.apache.santuario:xmlsec:2.1.4' implementation group: 'org.bouncycastle', name: 'bcprov-jdk16', version: '1.45' implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.28.0' + implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20' testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.50' testImplementation 'org.jetbrains.kotlin:kotlin-test:1.3.50' + } application { diff --git a/util/src/main/kotlin/zip.kt b/util/src/main/kotlin/zip.kt @@ -0,0 +1,38 @@ +package tech.libeufin.util + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import org.apache.commons.compress.archivers.ArchiveStreamFactory +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipFile +import org.apache.commons.compress.utils.IOUtils +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel + + +fun ByteArray.zip(): ByteArray { + + val baos = ByteArrayOutputStream() + val asf = ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, baos) + val zae = ZipArchiveEntry("File 1") + asf.putArchiveEntry(zae) // link Zip archive to output stream. + + val bais = ByteArrayInputStream(this) + IOUtils.copy(bais, asf) + bais.close() + asf.closeArchiveEntry() + asf.finish() + baos.close() + return baos.toByteArray() +} + +fun ByteArray.unzip(): String { + val mem = SeekableInMemoryByteChannel(this) + val zipFile = ZipFile(mem) + val s = java.lang.StringBuilder() + zipFile.getEntriesInPhysicalOrder().iterator().forEach { entry -> + s.append("<=== File ${entry.name} ===>\n") + s.append(zipFile.getInputStream(entry).readAllBytes().toString(Charsets.UTF_8)) + s.append("\n") + } + return s.toString() +} +\ No newline at end of file