libeufin

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

commit 55c355a71b389b23963c389ae8be77cbfae10b33
parent b2dff673b9a0d31457ed7648bcd24aa50dca730d
Author: MS <ms@taler.net>
Date:   Wed,  8 Nov 2023 12:54:11 +0100

nexus fetch: helpers to craft a camt.052 request

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt | 6+++---
Mutil/src/main/kotlin/Ebics.kt | 40++++++----------------------------------
Mutil/src/main/kotlin/ebics_h005/Ebics3Request.kt | 15++-------------
6 files changed, 165 insertions(+), 55 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -4,10 +4,63 @@ import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import io.ktor.client.* +import tech.libeufin.util.ebics_h005.Ebics3Request +import tech.libeufin.util.getXmlDate +import java.time.Instant import kotlin.concurrent.fixedRateTimer import kotlin.system.exitProcess /** + * Crafts a date range object, when the caller needs a time range. + * + * @param startDate inclusive starting date for the returned banking events. + * @param endDate inclusive ending date for the returned banking events. + * @return [Ebics3Request.DateRange] + */ +fun getEbics3DateRange( + startDate: Instant, + endDate: Instant +): Ebics3Request.DateRange { + return Ebics3Request.DateRange().apply { + start = getXmlDate(startDate) + end = getXmlDate(endDate) + } +} + +/** + * Prepares the request for camt.052/intraday records. + * + * @param startDate inclusive starting date for the returned banking events. + * @param endDate inclusive ending date for the returned banking events. NOTE: + * if startDate is NOT null and endDate IS null, endDate gets defaulted + * to the current UTC time. + * + * @return [Ebics3Request.OrderDetails.BTOrderParams] + */ +fun prepReportRequest( + startDate: Instant? = null, + endDate: Instant? = null +): Ebics3Request.OrderDetails.BTOrderParams { + val service = Ebics3Request.OrderDetails.Service().apply { + serviceName = "STM" + scope = "CH" + container = Ebics3Request.OrderDetails.Service.Container().apply { + containerType = "ZIP" + } + messageName = Ebics3Request.OrderDetails.Service.MessageName().apply { + value = "camt.052" + version = "08" + } + } + return Ebics3Request.OrderDetails.BTOrderParams().apply { + this.service = service + this.dateRange = if (startDate != null) + getEbics3DateRange(startDate, endDate ?: Instant.now()) + else null + } +} + +/** * Fetches the banking records via EBICS, calling the CAMT * parsing logic and finally updating the database accordingly. * diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -135,7 +135,7 @@ fun createEbics25DownloadInit( * should receive this receipt. * @return receipt request in XML. */ -fun createEbics25ReceiptPhase( +fun createEbics25DownloadReceiptPhase( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, transactionId: String @@ -163,7 +163,7 @@ fun createEbics25ReceiptPhase( * @param transactionId ID of the EBICS transaction that transports all the segments. * @return raw XML string of the request. */ -fun createEbics25TransferPhase( +fun createEbics25DownloadTransferPhase( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, segNumber: Int, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -9,12 +9,110 @@ import tech.libeufin.util.PreparedUploadData import tech.libeufin.util.XMLUtil import tech.libeufin.util.ebics_h005.Ebics3Request import tech.libeufin.util.getNonce -import tech.libeufin.util.toHexString import java.math.BigInteger import java.util.* 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. + * @return the raw XML of the EBICS request. + */ +fun createEbics3DownloadReceiptPhase( + cfg: EbicsSetupConfig, + clientKeys: ClientPrivateKeysFile, + transactionId: String +): String { + val req = Ebics3Request.createForDownloadReceiptPhase( + transactionId, + cfg.ebicsHostId + ) + val doc = XMLUtil.convertJaxbToDocument(req) + XMLUtil.signEbicsDocument( + doc, + clientKeys.authentication_private_key, + withEbics3 = true + ) + return XMLUtil.convertDomToString(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, + transactionId: String, + segmentNumber: Int, + howManySegments: Int +): String { + 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.convertDomToString(doc) +} + +/** + * 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, + orderParams: Ebics3Request.OrderDetails.BTOrderParams +): String { + val nonce = getNonce(128) + val req = Ebics3Request.createForDownloadInitializationPhase( + cfg.ebicsUserId, + cfg.ebicsPartnerId, + cfg.ebicsHostId, + nonce, + DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()), + bankAuthPub = bankkeys.bank_authentication_public_key, + bankEncPub = bankkeys.bank_encryption_public_key, + myOrderParams = orderParams + ) + val doc = XMLUtil.convertJaxbToDocument( + req, + withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd" + ) + XMLUtil.signEbicsDocument( + doc, + clientKeys.authentication_private_key, + withEbics3 = true + ) + return XMLUtil.convertDomToString(doc) +} + +/** * Creates the EBICS 3 document for the init phase of an upload * transaction. * @@ -51,8 +149,6 @@ fun createEbics3RequestForUploadInitialization( req, withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd" ) - tech.libeufin.util.logger.debug("Created EBICS 3 document for upload initialization," + - " nonce: ${nonce.toHexString()}") XMLUtil.signEbicsDocument( doc, clientKeys.authentication_private_key, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -254,7 +254,7 @@ private fun areCodesOk(ebicsResponseContent: EbicsResponseContent) = * @param cfg configuration handle. * @param clientKeys client EBICS private keys. * @param bankKeys bank EBICS public keys. - * @param reqXml raw EBICS XML request. + * @param reqXml raw EBICS XML request of the init phase. * @return the bank response as an XML string, or null if one * error took place. NOTE: any return code other than * EBICS_OK constitutes an error. @@ -297,7 +297,7 @@ suspend fun doEbicsDownload( // proceed with the transfer phase. for (x in 2 .. howManySegments) { // request segment number x. - val transReq = createEbics25TransferPhase(cfg, clientKeys, x, howManySegments, tId) + val transReq = createEbics25DownloadTransferPhase(cfg, clientKeys, x, howManySegments, tId) val transResp = postEbics(client, cfg, bankKeys, transReq, isEbics3) if (!areCodesOk(transResp)) { // FIXME: consider tolerating EBICS_NO_DOWNLOAD_DATA_AVAILABLE. tech.libeufin.nexus.logger.error("EBICS transfer segment #$x failed.") @@ -317,7 +317,7 @@ suspend fun doEbicsDownload( ebicsChunks ) // payload reconstructed, ack to the bank. - val ackXml = createEbics25ReceiptPhase(cfg, clientKeys, tId) + val ackXml = createEbics25DownloadReceiptPhase(cfg, clientKeys, tId) try { postEbics( client, diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt @@ -37,6 +37,7 @@ import java.math.BigInteger import java.security.SecureRandom import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey +import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime import java.util.* @@ -106,7 +107,11 @@ fun getNonce(size: Int): ByteArray { return ret } -private fun getXmlDate(d: ZonedDateTime): XMLGregorianCalendar { +fun getXmlDate(i: Instant): XMLGregorianCalendar { + val zonedTimestamp = ZonedDateTime.ofInstant(i, ZoneId.of("UTC")) + return getXmlDate(zonedTimestamp) +} +fun getXmlDate(d: ZonedDateTime): XMLGregorianCalendar { return DatatypeFactory.newInstance() .newXMLGregorianCalendar( d.year, @@ -364,39 +369,6 @@ fun createEbicsRequestForDownloadInitialization( return XMLUtil.convertDomToString(doc) } -fun createEbicsRequestForDownloadInitialization( - subscriberDetails: EbicsClientSubscriberDetails, - ebics3OrderService: Ebics3Request.OrderDetails.Service, - orderParams: EbicsOrderParams, -): String { - val nonce = getNonce(128) - val req = Ebics3Request.createForDownloadInitializationPhase( - subscriberDetails.userId, - subscriberDetails.partnerId, - subscriberDetails.hostId, - nonce, - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar( - TimeZone.getTimeZone(ZoneId.systemDefault()) - )), - subscriberDetails.bankEncPub ?: throw EbicsProtocolError( - HttpStatusCode.BadRequest, - "Invalid subscriber state 'bankEncPub' missing, please send HPB first" - ), - subscriberDetails.bankAuthPub ?: throw EbicsProtocolError( - HttpStatusCode.BadRequest, - "Invalid subscriber state 'bankAuthPub' missing, please send HPB first" - ), - ebics3OrderService - ) - val doc = XMLUtil.convertJaxbToDocument(req) - XMLUtil.signEbicsDocument( - doc, - subscriberDetails.customerAuthPriv, - withEbics3 = true - ) - return XMLUtil.convertDomToString(doc) -} - fun createEbicsRequestForDownloadTransferPhase( subscriberDetails: EbicsClientSubscriberDetails, transactionID: String?, diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Request.kt b/util/src/main/kotlin/ebics_h005/Ebics3Request.kt @@ -3,7 +3,6 @@ package tech.libeufin.util.ebics_h005 import org.apache.xml.security.binding.xmldsig.SignatureType import tech.libeufin.util.CryptoUtil import tech.libeufin.util.EbicsStandardOrderParams -import tech.libeufin.util.EbicsOrderParams import tech.libeufin.util.makeEbics3DateRange import java.math.BigInteger import java.security.interfaces.RSAPublicKey @@ -388,8 +387,7 @@ class Ebics3Request { date: XMLGregorianCalendar, bankEncPub: RSAPublicKey, bankAuthPub: RSAPublicKey, - myOrderService: OrderDetails.Service, - myOrderParams: EbicsOrderParams? = null + myOrderParams: OrderDetails.BTOrderParams ): Ebics3Request { return Ebics3Request().apply { version = "H005" @@ -407,16 +405,7 @@ class Ebics3Request { partnerID = partnerId orderDetails = OrderDetails().apply { this.adminOrderType = "BTD" - this.btdOrderParams = OrderDetails.BTOrderParams().apply { - service = myOrderService - // Order params only used for date ranges so far. - when (myOrderParams) { - is EbicsStandardOrderParams -> { - this.dateRange = makeEbics3DateRange(myOrderParams.dateRange) - } - else -> {} - } - } + this.btdOrderParams = myOrderParams } bankPubKeyDigests = BankPubKeyDigests().apply { authentication = Ebics3Types.PubKeyDigest().apply {