summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-11 15:54:26 +0100
committerAntoine A <>2024-03-11 15:54:26 +0100
commit861389ea98224e86f28fdf06570a260c3ae12f90 (patch)
tree74aef9cd1afaaf770665323deb5620fafb090b8c
parent30eefb68a131b44edc78d3dbf346be840f999b0a (diff)
downloadlibeufin-861389ea98224e86f28fdf06570a260c3ae12f90.tar.gz
libeufin-861389ea98224e86f28fdf06570a260c3ae12f90.tar.bz2
libeufin-861389ea98224e86f28fdf06570a260c3ae12f90.zip
Clean EBICS dialect logic
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt47
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt198
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt11
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsDialect.kt49
4 files changed, 157 insertions, 148 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index 2b713432..c74ea2f1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -65,39 +65,6 @@ data class FetchContext(
)
/**
- * Downloads content via EBICS, according to the order params passed
- * by the caller.
- *
- * @param T [Ebics2Request] for EBICS 2 or [Ebics3Request.OrderDetails.BTOrderParams] for EBICS 3
- * @param ctx [FetchContext]
- * @param req contains the instructions for the download, namely
- * which document is going to be downloaded from the bank.
- * @return the [ByteArray] payload. On an empty response, the array
- * length is zero. It returns null, if the bank assigned an
- * error to the EBICS transaction.
- */
-private suspend fun downloadHelper(
- ctx: FetchContext,
- lastExecutionTime: Instant? = null,
- doc: SupportedDocument,
- processing: (InputStream) -> Unit
-) {
- val initXml = Ebics3BTS(
- ctx.cfg,
- ctx.bankKeys,
- ctx.clientKeys
- ).downloadInitializationDoc(doc, lastExecutionTime)
- return ebicsDownload(
- ctx.httpClient,
- ctx.cfg,
- ctx.clientKeys,
- ctx.bankKeys,
- initXml,
- processing
- )
-}
-
-/**
* Converts the 2-digits fraction value as given by the bank
* (postfinance dialect), to the Taler 8-digit value (db representation).
*
@@ -316,9 +283,19 @@ private suspend fun fetchDocuments(
} else {
logger.info("Fetching '${doc.fullDescription()}' from timestamp: $lastExecutionTime")
}
- val doc = doc.doc()
// downloading the content
- downloadHelper(ctx, lastExecutionTime, doc) { stream ->
+ val doc = doc.doc()
+ val (orderType, service) = downloadDocService(doc)
+ ebicsDownload(
+ ctx.httpClient,
+ ctx.cfg,
+ ctx.clientKeys,
+ ctx.bankKeys,
+ orderType,
+ service,
+ lastExecutionTime,
+ null
+ ) { stream ->
val loggedStream = ctx.fileLogger.logFetch(
stream,
doc == SupportedDocument.PAIN_002_LOGS
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
index 11ccf30d..5acba13b 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
@@ -36,14 +36,6 @@ import java.security.interfaces.*
fun Instant.xmlDate(): String = DateTimeFormatter.ISO_DATE.withZone(ZoneId.of("UTC")).format(this)
fun Instant.xmlDateTime(): String = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("UTC")).format(this)
-data class Ebics3Service(
- val name: String,
- val scope: String,
- val messageName: String,
- val messageVersion: String,
- val container: String?
-)
-
// TODO WIP
fun iniRequest(
cfg: EbicsSetupConfig,
@@ -94,110 +86,9 @@ class Ebics3BTS(
private val bankKeys: BankPublicKeysFile,
private val clientKeys: ClientPrivateKeysFile
) {
-
- /* ----- Ergonomic entrypoints ----- */
-
- fun downloadInitializationDoc(whichDoc: SupportedDocument, startDate: Instant? = null, endDate: Instant? = null): ByteArray {
- val (orderType, service) = when (whichDoc) {
- SupportedDocument.PAIN_002 -> Pair("BTD", Ebics3Service("PSR", "CH", "pain.002", "10", "ZIP"))
- SupportedDocument.CAMT_052 -> Pair("BTD", Ebics3Service("STM", "CH", "camt.052", "08", "ZIP"))
- SupportedDocument.CAMT_053 -> Pair("BTD", Ebics3Service("EOP", "CH", "camt.053", "08", "ZIP"))
- SupportedDocument.CAMT_054 -> Pair("BTD", Ebics3Service("REP", "CH", "camt.054", "08", "ZIP"))
- SupportedDocument.PAIN_002_LOGS -> Pair("HAC", null)
- }
- return downloadInitialization(orderType, service, startDate, endDate)
- }
-
- /* ----- Upload ----- */
-
- fun uploadInitialization(service: Ebics3Service, preparedUploadData: PreparedUploadData): ByteArray {
- val nonce = getNonce(128)
- return signedRequest {
- el("header") {
- attr("authenticate", "true")
- el("static") {
- el("HostID", cfg.ebicsHostId)
- el("Nonce", nonce.encodeUpHex())
- el("Timestamp", Instant.now().xmlDateTime())
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
- // SystemID
- // Product
- el("OrderDetails") {
- el("AdminOrderType", "BTU")
- el("BTUOrderParams") {
- el("Service") {
- el("ServiceName", service.name)
- el("Scope", service.scope)
- el("MsgName") {
- attr("version", service.messageVersion)
- text(service.messageName)
- }
- }
- el("SignatureFlag", "true")
- }
- }
- bankDigest()
- el("NumSegments", "1") // TODO test upload of many segment
-
- }
- el("mutable") {
- el("TransactionPhase", "Initialisation")
- }
- }
- el("AuthSignature")
- el("body") {
- el("DataTransfer") {
- el("DataEncryptionInfo") {
- attr("authenticate", "true")
- el("EncryptionPubKeyDigest") {
- attr("Version", "E002")
- attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256")
- text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).encodeBase64())
- }
- el("TransactionKey", preparedUploadData.transactionKey.encodeBase64())
- }
- el("SignatureData") {
- attr("authenticate", "true")
- text(preparedUploadData.userSignatureDataEncrypted.encodeBase64())
- }
- el("DataDigest") {
- attr("SignatureVersion", "A006")
- text(preparedUploadData.dataDigest.encodeBase64())
- }
- }
- }
- }
- }
-
- fun uploadTransfer(
- transactionId: String,
- uploadData: PreparedUploadData
- ): ByteArray {
- val chunkIndex = 1 // TODO test upload of many segment
- return signedRequest {
- el("header") {
- attr("authenticate", "true")
- el("static") {
- el("HostID", cfg.ebicsHostId)
- el("TransactionID", transactionId)
- }
- el("mutable") {
- el("TransactionPhase", "Transfer")
- el("SegmentNumber") {
- attr("lastSegment", "true")
- text(chunkIndex.toString())
- }
- }
- }
- el("AuthSignature")
- el("body/DataTransfer/OrderData", uploadData.encryptedPayloadChunks[chunkIndex - 1])
- }
- }
-
/* ----- Download ----- */
- fun downloadInitialization(orderType: String, service: Ebics3Service? = null, startDate: Instant? = null, endDate: Instant? = null): ByteArray {
+ fun downloadInitialization(orderType: String, service: Ebics3Service?, startDate: Instant?, endDate: Instant?): ByteArray {
val nonce = getNonce(128)
return signedRequest {
el("header") {
@@ -294,6 +185,93 @@ class Ebics3BTS(
}
}
+ /* ----- Upload ----- */
+
+ fun uploadInitialization(service: Ebics3Service, preparedUploadData: PreparedUploadData): ByteArray {
+ val nonce = getNonce(128)
+ return signedRequest {
+ el("header") {
+ attr("authenticate", "true")
+ el("static") {
+ el("HostID", cfg.ebicsHostId)
+ el("Nonce", nonce.encodeUpHex())
+ el("Timestamp", Instant.now().xmlDateTime())
+ el("PartnerID", cfg.ebicsPartnerId)
+ el("UserID", cfg.ebicsUserId)
+ // SystemID
+ // Product
+ el("OrderDetails") {
+ el("AdminOrderType", "BTU")
+ el("BTUOrderParams") {
+ el("Service") {
+ el("ServiceName", service.name)
+ el("Scope", service.scope)
+ el("MsgName") {
+ attr("version", service.messageVersion)
+ text(service.messageName)
+ }
+ }
+ el("SignatureFlag", "true")
+ }
+ }
+ bankDigest()
+ el("NumSegments", "1") // TODO test upload of many segment
+
+ }
+ el("mutable") {
+ el("TransactionPhase", "Initialisation")
+ }
+ }
+ el("AuthSignature")
+ el("body") {
+ el("DataTransfer") {
+ el("DataEncryptionInfo") {
+ attr("authenticate", "true")
+ el("EncryptionPubKeyDigest") {
+ attr("Version", "E002")
+ attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256")
+ text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).encodeBase64())
+ }
+ el("TransactionKey", preparedUploadData.transactionKey.encodeBase64())
+ }
+ el("SignatureData") {
+ attr("authenticate", "true")
+ text(preparedUploadData.userSignatureDataEncrypted.encodeBase64())
+ }
+ el("DataDigest") {
+ attr("SignatureVersion", "A006")
+ text(preparedUploadData.dataDigest.encodeBase64())
+ }
+ }
+ }
+ }
+ }
+
+ fun uploadTransfer(
+ transactionId: String,
+ uploadData: PreparedUploadData
+ ): ByteArray {
+ val chunkIndex = 1 // TODO test upload of many segment
+ return signedRequest {
+ el("header") {
+ attr("authenticate", "true")
+ el("static") {
+ el("HostID", cfg.ebicsHostId)
+ el("TransactionID", transactionId)
+ }
+ el("mutable") {
+ el("TransactionPhase", "Transfer")
+ el("SegmentNumber") {
+ attr("lastSegment", "true")
+ text(chunkIndex.toString())
+ }
+ }
+ }
+ el("AuthSignature")
+ el("body/DataTransfer/OrderData", uploadData.encryptedPayloadChunks[chunkIndex - 1])
+ }
+ }
+
/* ----- Helpers ----- */
/** Generate a signed H005 ebicsRequest */
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
index fd969340..69e6da19 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -48,6 +48,7 @@ import java.security.interfaces.RSAPrivateCrtKey
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
+import java.time.Instant
import kotlinx.coroutines.*
import java.security.SecureRandom
import org.w3c.dom.Document
@@ -169,8 +170,11 @@ suspend fun ebicsDownload(
cfg: EbicsSetupConfig,
clientKeys: ClientPrivateKeysFile,
bankKeys: BankPublicKeysFile,
- reqXml: ByteArray,
- processing: (InputStream) -> Unit
+ orderType: String,
+ service: Ebics3Service?,
+ startDate: Instant?,
+ endDate: Instant?,
+ processing: (InputStream) -> Unit,
) = coroutineScope {
val impl = Ebics3BTS(cfg, bankKeys, clientKeys)
val parentScope = this
@@ -181,7 +185,8 @@ suspend fun ebicsDownload(
// TODO find a way to cancel the pending transaction ?
withContext(NonCancellable) {
// Init phase
- val initResp = postBTS(client, cfg, bankKeys, reqXml)
+ val initReq = impl.downloadInitialization(orderType, service, startDate, endDate)
+ val initResp = postBTS(client, cfg, bankKeys, initReq)
if (initResp.bankCode == EbicsReturnCode.EBICS_NO_DOWNLOAD_DATA_AVAILABLE) {
logger.debug("Download content is empty")
return@withContext
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsDialect.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsDialect.kt
new file mode 100644
index 00000000..5d672de0
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsDialect.kt
@@ -0,0 +1,49 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+package tech.libeufin.nexus.ebics
+
+// We will support more dialect in the future
+
+data class Ebics3Service(
+ val name: String,
+ val scope: String,
+ val messageName: String,
+ val messageVersion: String,
+ val container: String?
+)
+
+fun downloadDocService(doc: SupportedDocument): Pair<String, Ebics3Service?> {
+ return when (doc) {
+ SupportedDocument.PAIN_002 -> Pair("BTD", Ebics3Service("PSR", "CH", "pain.002", "10", "ZIP"))
+ SupportedDocument.CAMT_052 -> Pair("BTD", Ebics3Service("STM", "CH", "camt.052", "08", "ZIP"))
+ SupportedDocument.CAMT_053 -> Pair("BTD", Ebics3Service("EOP", "CH", "camt.053", "08", "ZIP"))
+ SupportedDocument.CAMT_054 -> Pair("BTD", Ebics3Service("REP", "CH", "camt.054", "08", "ZIP"))
+ SupportedDocument.PAIN_002_LOGS -> Pair("HAC", null)
+ }
+}
+
+fun uploadPaymentService(): Ebics3Service {
+ return Ebics3Service(
+ name = "MCT",
+ scope = "CH",
+ messageName = "pain.001",
+ messageVersion = "09",
+ container = null
+ )
+}