diff options
author | Antoine A <> | 2024-03-07 10:03:05 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-07 10:03:05 +0100 |
commit | b0387f142c863b9314af99cb232c98aa98c4c889 (patch) | |
tree | 132db84011adefe1f4a93fddb57621a8a9cd13d7 | |
parent | 2cd837fef9dc1516814307f49e1bee5a0a3bcec3 (diff) | |
download | libeufin-b0387f142c863b9314af99cb232c98aa98c4c889.tar.gz libeufin-b0387f142c863b9314af99cb232c98aa98c4c889.tar.bz2 libeufin-b0387f142c863b9314af99cb232c98aa98c4c889.zip |
Use XML builder for all Ebics3 download phases
3 files changed, 151 insertions, 151 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt index bea1c3f3..f42f2571 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -84,13 +84,11 @@ private suspend fun downloadHelper( doc: SupportedDocument, processing: (InputStream) -> Unit ) { - val initXml = createEbics3DownloadInitialization( + val initXml = Ebics3Impl( ctx.cfg, ctx.bankKeys, - ctx.clientKeys, - doc, - lastExecutionTime - ) + ctx.clientKeys + ).downloadInitialization(doc, lastExecutionTime) return ebicsDownload( ctx.httpClient, ctx.cfg, 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 099669c1..8bb3140f 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -36,164 +36,161 @@ import org.w3c.dom.* import javax.xml.datatype.XMLGregorianCalendar import javax.xml.datatype.DatatypeFactory -/** - * Crafts an EBICS request for the receipt phase of a download - * transaction. - * - * @param cfg config handle - * @param clientKeys subscriber private keys. - * @param transactionId EBICS transaction ID as assigned by the - * bank to any successful transaction. - * @param success was the download successfully processed - * @return the raw XML of the EBICS request. - */ -fun createEbics3DownloadReceiptPhase( - cfg: EbicsSetupConfig, - clientKeys: ClientPrivateKeysFile, - transactionId: String, - success: Boolean -): ByteArray { - val req = Ebics3Request.createForDownloadReceiptPhase( - transactionId, - cfg.ebicsHostId, - success - ) - val doc = XMLUtil.convertJaxbToDocument(req) - XMLUtil.signEbicsDocument( - doc, - clientKeys.authentication_private_key, - withEbics3 = true - ) - return XMLUtil.convertDomToBytes(doc) -} -/** - * Crafts an EBICS download request for the transfer phase. - * - * @param cfg config handle - * @param clientKeys subscriber private keys - * @param transactionId EBICS transaction ID. That came from the - * bank after the initialization phase ended successfully. - * @param segmentNumber which (payload's) segment number this requests wants. - * @param howManySegments total number of segments that the payload is split to. - * @return the raw XML EBICS request. - */ -fun createEbics3DownloadTransferPhase( - cfg: EbicsSetupConfig, - clientKeys: ClientPrivateKeysFile, - howManySegments: Int, - segmentNumber: Int, - transactionId: String -): ByteArray { - val req = Ebics3Request.createForDownloadTransferPhase( - cfg.ebicsHostId, - transactionId, - segmentNumber, - howManySegments - ) - val doc = XMLUtil.convertJaxbToDocument(req) - XMLUtil.signEbicsDocument( - doc, - clientKeys.authentication_private_key, - withEbics3 = true - ) - return XMLUtil.convertDomToBytes(doc) -} +//fun String.toDate(): LocalDate = LocalDate.parse(this, DateTimeFormatter.ISO_DATE) +//fun String.toDateTime(): LocalDateTime = LocalDateTime.parse(this, DateTimeFormatter.ISO_DATE_TIME) +//fun String.toYearMonth(): YearMonth = YearMonth.parse(this, DateTimeFormatter.ISO_DATE) +//fun String.toYear(): Year = Year.parse(this, DateTimeFormatter.ISO_DATE) -/** - * Creates the EBICS 3 document for the init phase of a download - * transaction. - * - * @param cfg configuration handle. - * @param bankkeys bank public keys. - * @param clientKeys client private keys. - * @param orderService EBICS 3 document defining the request type - */ -fun createEbics3DownloadInitialization( - cfg: EbicsSetupConfig, - bankKeys: BankPublicKeysFile, - clientKeys: ClientPrivateKeysFile, - whichDoc: SupportedDocument, - startDate: Instant? = null, - endDate: Instant? = null -): ByteArray { - val nonce = getNonce(128) - val timestamp = DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()) - val doc = XmlBuilder.toDom("ebicsRequest", "urn:org:ebics:H005") { - attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H005") - attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - attr("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "urn:org:ebics:H005 ebics_request_H005.xsd") - attr("Version", "H005") - attr("Revision", "1") - el("header") { - attr("authenticate", "true") - el("static") { - el("HostID", cfg.ebicsHostId) - el("Nonce", nonce.toHexString()) - el("Timestamp", timestamp.toXMLFormat() ) - el("PartnerID", cfg.ebicsPartnerId) - el("UserID", cfg.ebicsUserId) - el("OrderDetails") { - if (whichDoc == SupportedDocument.PAIN_002_LOGS) { - el("AdminOrderType", "HAC") - } else { - el("AdminOrderType", "BTD") - el("BTDOrderParams") { - el("Service") { - el("ServiceName", when(whichDoc) { - SupportedDocument.PAIN_002 -> "PSR" - SupportedDocument.CAMT_052 -> "STM" - SupportedDocument.CAMT_053 -> "EOP" - SupportedDocument.CAMT_054 -> "REP" - SupportedDocument.PAIN_002_LOGS -> "HAC" - }) - val (msg_value, msg_version) = when(whichDoc) { - SupportedDocument.PAIN_002 -> Pair("pain.002", "10") - SupportedDocument.CAMT_052 -> Pair("pain.052", "08") - SupportedDocument.CAMT_053 -> Pair("pain.053", "08") - SupportedDocument.CAMT_054 -> Pair("camt.054", "08") - SupportedDocument.PAIN_002_LOGS -> throw Exception("HAC (--only-logs) not available in EBICS 3") - } - el("Scope", "CH") - el("Container") { - attr("containerType", "ZIP") +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) + +class Ebics3Impl( + private val cfg: EbicsSetupConfig, + private val bankKeys: BankPublicKeysFile, + private val clientKeys: ClientPrivateKeysFile +) { + + private fun signedRequest(lambda: XmlBuilder.() -> Unit): ByteArray { + val doc = XmlBuilder.toDom("ebicsRequest", "urn:org:ebics:H005") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H005") + attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + attr("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "urn:org:ebics:H005 ebics_request_H005.xsd") + attr("Version", "H005") + attr("Revision", "1") + lambda() + } + XMLUtil.signEbicsDocument( + doc, + clientKeys.authentication_private_key, + withEbics3 = true + ) + return XMLUtil.convertDomToBytes(doc) + } + + fun downloadInitialization(whichDoc: SupportedDocument, startDate: Instant? = null, endDate: Instant? = null): ByteArray { + val nonce = getNonce(128) + return signedRequest { + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("Nonce", nonce.toHexString()) + el("Timestamp", Instant.now().xmlDateTime()) + el("PartnerID", cfg.ebicsPartnerId) + el("UserID", cfg.ebicsUserId) + // SystemID + // Product + el("OrderDetails") { + if (whichDoc == SupportedDocument.PAIN_002_LOGS) { + el("AdminOrderType", "HAC") + } else { + el("AdminOrderType", "BTD") + el("BTDOrderParams") { + el("Service") { + el("ServiceName", when (whichDoc) { + SupportedDocument.PAIN_002 -> "PSR" + SupportedDocument.CAMT_052 -> "STM" + SupportedDocument.CAMT_053 -> "EOP" + SupportedDocument.CAMT_054 -> "REP" + SupportedDocument.PAIN_002_LOGS -> throw Exception("Unreachable") + }) + val (msg_value, msg_version) = when (whichDoc) { + SupportedDocument.PAIN_002 -> Pair("pain.002", "10") + SupportedDocument.CAMT_052 -> Pair("pain.052", "08") + SupportedDocument.CAMT_053 -> Pair("pain.053", "08") + SupportedDocument.CAMT_054 -> Pair("camt.054", "08") + SupportedDocument.PAIN_002_LOGS -> throw Exception("Unreachable") + } + el("Scope", "CH") + el("Container") { + attr("containerType", "ZIP") + } + el("MsgName") { + attr("version", msg_version) + text(msg_value) + } } - el("MsgName") { - attr("version", msg_version) - text(msg_value) + if (startDate != null) { + el("DateRange") { + el("Start", startDate.xmlDate()) + el("End", (endDate ?: Instant.now()).xmlDate()) + } } } } } - } - el("BankPubKeyDigests") { - el("Authentication") { - attr("Version", "X002") - attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") - text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_authentication_public_key).encodeBase64()) + el("BankPubKeyDigests") { + el("Authentication") { + attr("Version", "X002") + attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") + text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_authentication_public_key).encodeBase64()) + } + el("Encryption") { + attr("Version", "E002") + attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") + text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).encodeBase64()) + } + // Signature } - el("Encryption") { - attr("Version", "E002") - attr("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256") - text(CryptoUtil.getEbicsPublicKeyHash(bankKeys.bank_encryption_public_key).encodeBase64()) + el("SecurityMedium", "0000") + } + el("mutable") { + el("TransactionPhase", "Initialisation") + } + } + el("AuthSignature") + el("body") + } + } + + fun downloadTransfer( + howManySegments: Int, + segmentNumber: Int, + transactionId: String + ): ByteArray { + 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", if (howManySegments == segmentNumber) "true" else "false") } } - el("SecurityMedium", "0000") } - el("mutable") { - el("TransactionPhase", "Initialisation") + el("AuthSignature") + el("body") + } + } + + fun downloadReceipt( + transactionId: String, + success: Boolean + ): ByteArray { + return signedRequest { + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("TransactionID", transactionId) + } + el("mutable") { + el("TransactionPhase", "Receipt") + } + } + el("AuthSignature") + el("body/TransferReceipt") { + attr("authenticate", "true") + el("ReceiptCode", if (success) "0" else "1") } } - el("AuthSignature") - el("body") } - XMLUtil.signEbicsDocument( - doc, - clientKeys.authentication_private_key, - withEbics3 = true - ) - return XMLUtil.convertDomToBytes(doc) } /** 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 edbef2e9..a44839a6 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -286,6 +286,11 @@ suspend fun ebicsDownload( reqXml: ByteArray, processing: (InputStream) -> Unit ) = coroutineScope { + val impl = Ebics3Impl( + cfg, + bankKeys, + clientKeys + ) val scope = this // We need to run the logic in a non-cancelable context because we need to send // a receipt for each open download transaction, otherwise we'll be stuck in an @@ -331,7 +336,7 @@ suspend fun ebicsDownload( for (x in 2 .. howManySegments) { if (!scope.isActive) break // request segment number x. - val transReq = createEbics3DownloadTransferPhase(cfg, clientKeys, x, howManySegments, tId) + val transReq = impl.downloadTransfer(x, howManySegments, tId) val transResp = postEbics(client, cfg, bankKeys, transReq, true) if (!areCodesOk(transResp)) { @@ -347,7 +352,7 @@ suspend fun ebicsDownload( ebicsChunks.add(chunk) } suspend fun receipt(success: Boolean) { - val receiptXml = createEbics3DownloadReceiptPhase(cfg, clientKeys, tId, success) + val receiptXml = impl.downloadReceipt(tId, success) // Sending the receipt to the bank. postEbics( |