commit c8bb455941b274526cf46b8f5a9e91b0bd1012fd
parent e7fc0fe36c2f019d93c34aa9dd5a54c81e42abb5
Author: Florian Dold <florian.dold@gmail.com>
Date: Tue, 9 Jun 2020 02:56:15 +0530
support multiple EBICS download segments
Diffstat:
6 files changed, 120 insertions(+), 13 deletions(-)
diff --git a/integration-tests/start-testenv.py b/integration-tests/start-testenv.py
@@ -53,8 +53,6 @@ def assertResponse(response):
return response
-os.chdir("..")
-
startNexus("nexus-testenv.sqlite3")
startSandbox()
@@ -204,4 +202,8 @@ if len(resp.json().get("transactions")) != 1:
fail("Unexpected number of transactions; should be 1")
-input("press enter to stop LibEuFin test environment ...")
+try:
+ input("press enter to stop LibEuFin test environment ...")
+except:
+ pass
+print("exiting!")
diff --git a/integration-tests/test-ebics.py b/integration-tests/test-ebics.py
@@ -167,6 +167,15 @@ assertResponse(
)
)
+# Test download transaction (TSD, LibEuFin-specific test order type)
+assertResponse(
+ post(
+ "http://localhost:5001/bank-connections/my-ebics/ebics/download/tsd",
+ json=dict(),
+ headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
+ )
+)
+
# 2.c, fetch bank account information
assertResponse(
post(
diff --git a/integration-tests/util.py b/integration-tests/util.py
@@ -18,7 +18,7 @@ def checkPort(port):
exit(77)
-def startSandbox(dbname):
+def startSandbox(dbname="sandbox-test.sqlite3"):
db_full_path = str(Path.cwd() / dbname)
check_call(["rm", "-f", db_full_path])
check_call(["../gradlew", "-p", "..", "sandbox:assemble"])
@@ -43,7 +43,7 @@ def startSandbox(dbname):
break
-def startNexus(dbname):
+def startNexus(dbname="nexus-test.sqlite3"):
db_full_path = str(Path.cwd() / dbname)
check_call(["rm", "-f", "--", db_full_path])
check_call(
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
@@ -95,6 +95,44 @@ suspend fun doEbicsDownloadTransaction(
payloadChunks.add(initOrderDataEncChunk)
+ val numSegments = initResponse.numSegments
+ ?: throw NexusError(HttpStatusCode.FailedDependency, "missing segment number in EBICS download init response")
+
+ // Transfer phase
+
+ for (x in 2..numSegments) {
+ val transferReqStr =
+ createEbicsRequestForDownloadTransferPhase(subscriberDetails, transactionID, x, numSegments)
+ val transferResponseStr = client.postToBank(subscriberDetails.ebicsUrl, transferReqStr)
+ val transferResponse = parseAndValidateEbicsResponse(subscriberDetails, transferResponseStr)
+ when (transferResponse.technicalReturnCode) {
+ EbicsReturnCode.EBICS_OK -> {
+ // Success, nothing to do!
+ }
+ else -> {
+ throw NexusError(
+ HttpStatusCode.FailedDependency,
+ "unexpected technical return code ${transferResponse.technicalReturnCode}"
+ )
+ }
+ }
+ when (transferResponse.bankReturnCode) {
+ EbicsReturnCode.EBICS_OK -> {
+ // Success, nothing to do!
+ }
+ else -> {
+ logger.warn("Bank return code was: ${transferResponse.bankReturnCode}")
+ return EbicsDownloadBankErrorResult(transferResponse.bankReturnCode)
+ }
+ }
+ val transferOrderDataEncChunk = transferResponse.orderDataEncChunk
+ ?: throw NexusError(
+ HttpStatusCode.InternalServerError,
+ "transfer response for download transaction does not contain data transfer"
+ )
+ payloadChunks.add(transferOrderDataEncChunk)
+ }
+
val respPayload = decryptAndDecompressResponse(subscriberDetails, encryptionInfo, payloadChunks)
// Acknowledgement phase
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
@@ -260,6 +260,23 @@ fun createEbicsRequestForDownloadInitialization(
return XMLUtil.convertDomToString(doc)
}
+fun createEbicsRequestForDownloadTransferPhase(
+ subscriberDetails: EbicsClientSubscriberDetails,
+ transactionID: String,
+ segmentNumber: Int,
+ numSegments: Int
+): String {
+ val req = EbicsRequest.createForDownloadTransferPhase(
+ subscriberDetails.hostId,
+ transactionID,
+ segmentNumber,
+ numSegments
+ )
+ val doc = XMLUtil.convertJaxbToDocument(req)
+ XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+ return XMLUtil.convertDomToString(doc)
+}
+
fun createEbicsRequestForUploadTransferPhase(
subscriberDetails: EbicsClientSubscriberDetails,
@@ -329,7 +346,10 @@ data class EbicsResponseContent(
val dataEncryptionInfo: DataEncryptionInfo?,
val orderDataEncChunk: String?,
val technicalReturnCode: EbicsReturnCode,
- val bankReturnCode: EbicsReturnCode
+ val bankReturnCode: EbicsReturnCode,
+ val segmentNumber: Int?,
+ // Only present in init phase
+ val numSegments: Int?
)
data class EbicsKeyManagementResponseContent(
@@ -404,7 +424,10 @@ fun parseAndValidateEbicsResponse(
val responseDocument = try {
XMLUtil.parseStringIntoDom(responseStr)
} catch (e: Exception) {
- throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML (as EbicsResponse) received from bank")
+ throw EbicsProtocolError(
+ HttpStatusCode.InternalServerError,
+ "Invalid XML (as EbicsResponse) received from bank"
+ )
}
if (!XMLUtil.verifyEbicsDocument(
@@ -420,7 +443,10 @@ fun parseAndValidateEbicsResponse(
val resp = try {
XMLUtil.convertStringToJaxb<EbicsResponse>(responseStr)
} catch (e: Exception) {
- throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Could not transform string-response from bank into JAXB")
+ throw EbicsProtocolError(
+ HttpStatusCode.InternalServerError,
+ "Could not transform string-response from bank into JAXB"
+ )
}
val bankReturnCodeStr = resp.value.body.returnCode.value
@@ -441,7 +467,9 @@ fun parseAndValidateEbicsResponse(
bankReturnCode = bankReturnCode,
technicalReturnCode = techReturnCode,
orderDataEncChunk = resp.value.body.dataTransfer?.orderData?.value,
- dataEncryptionInfo = dataEncryptionInfo
+ dataEncryptionInfo = dataEncryptionInfo,
+ numSegments = resp.value.header._static.numSegments?.toInt(),
+ segmentNumber = resp.value.header.mutable.segmentNumber?.value?.toInt()
)
}
diff --git a/util/src/main/kotlin/ebics_h004/EbicsRequest.kt b/util/src/main/kotlin/ebics_h004/EbicsRequest.kt
@@ -5,6 +5,7 @@ import tech.libeufin.util.CryptoUtil
import java.math.BigInteger
import java.security.interfaces.RSAPublicKey
import java.util.*
+import javax.swing.text.Segment
import javax.xml.bind.annotation.*
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
import javax.xml.bind.annotation.adapters.HexBinaryAdapter
@@ -360,10 +361,10 @@ class EbicsRequest {
}
securityMedium = "0000"
}
- mutable = MutableHeader().apply {
- transactionPhase =
- EbicsTypes.TransactionPhaseType.INITIALISATION
- }
+ }
+ mutable = MutableHeader().apply {
+ transactionPhase =
+ EbicsTypes.TransactionPhaseType.INITIALISATION
}
}
}
@@ -473,5 +474,34 @@ class EbicsRequest {
}
}
}
+
+ fun createForDownloadTransferPhase(
+ hostID: String,
+ transactionID: String,
+ segmentNumber: Int,
+ numSegments: Int
+ ): EbicsRequest {
+ return EbicsRequest().apply {
+ version = "H004"
+ revision = 1
+ authSignature = SignatureType()
+ body = Body()
+ header = Header().apply {
+ authenticate = true
+ static = StaticHeaderType().apply {
+ this.hostID = hostID
+ this.transactionID = transactionID
+ }
+ mutable = MutableHeader().apply {
+ transactionPhase =
+ EbicsTypes.TransactionPhaseType.TRANSFER
+ this.segmentNumber = EbicsTypes.SegmentNumber().apply {
+ this.value = BigInteger.valueOf(segmentNumber.toLong())
+ this.lastSegment = segmentNumber == numSegments
+ }
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file