commit edee80cc2d3363c68ea7dc7c588545924d0eb522
parent bc35aba3fbac892029ea17ee7ee4d1b4949650c0
Author: Antoine A <>
Date: Wed, 30 Oct 2024 11:06:18 +0100
nexus: add export test cmd and update ktor
Diffstat:
10 files changed, 86 insertions(+), 49 deletions(-)
diff --git a/build.gradle b/build.gradle
@@ -20,7 +20,7 @@ if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)){
allprojects {
ext {
set("kotlin_version", "2.0.21")
- set("ktor_version", "3.0.0")
+ set("ktor_version", "3.0.1")
set("clikt_version", "5.0.1")
set("coroutines_version", "1.9.0")
set("postgres_version", "42.7.4")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Config.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Config.kt
@@ -53,17 +53,21 @@ class NexusSubmitConfig(config: TalerConfig) {
val frequencyRaw = section.string("frequency").require()
}
-class NexusEbicsConfig(
- sect: TalerConfigSection,
-) {
+class NexusHostConfig(sect: TalerConfigSection) {
/** The bank base URL */
- val hostBaseUrl = sect.string("host_base_url").require()
+ val baseUrl = sect.string("host_base_url").require()
/** The bank EBICS host ID */
val ebicsHostId = sect.string("host_id").require()
/** EBICS user ID */
val ebicsUserId = sect.string("user_id").require()
/** EBICS partner ID */
val ebicsPartnerId = sect.string("partner_id").require()
+}
+
+class NexusEbicsConfig(
+ sect: TalerConfigSection,
+) {
+ val host by lazy { NexusHostConfig(sect) }
/** Bank account metadata */
val account = IbanAccountMetadata(
iban = sect.string("iban").require(),
@@ -89,7 +93,7 @@ class ApiConfig(section: TalerConfigSection) {
}
/** Configuration for libeufin-nexus */
-class NexusConfig internal constructor (private val cfg: TalerConfig) {
+class NexusConfig internal constructor (val cfg: TalerConfig) {
private val sect = cfg.section("nexus-ebics")
val dbCfg by lazy { cfg.dbConfig() }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt
@@ -62,9 +62,9 @@ fun generateKeysPdf(
Paragraph(
"""
Datum: $dateStr
- Host-ID: ${cfg.ebicsHostId}
- User-ID: ${cfg.ebicsUserId}
- Partner-ID: ${cfg.ebicsPartnerId}
+ Host-ID: ${cfg.host.ebicsHostId}
+ User-ID: ${cfg.host.ebicsUserId}
+ Partner-ID: ${cfg.host.ebicsPartnerId}
ES version: A006
""".trimIndent()
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSetup.kt
@@ -222,7 +222,7 @@ class EbicsSetup: CliktCommand() {
bankKeys
).download(EbicsOrder.V3.HKD, null, null) { stream ->
val (partner, users) = EbicsAdministrative.parseHKD(stream)
- val user = users.find { it -> it.id == cfg.ebics.ebicsUserId }
+ val user = users.find { it -> it.id == cfg.ebics.host.ebicsUserId }
// Debug logging
logger.debug {
buildString {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt
@@ -22,19 +22,20 @@ package tech.libeufin.nexus.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.subcommands
-import com.github.ajalt.clikt.parameters.arguments.argument
-import com.github.ajalt.clikt.parameters.arguments.convert
+import com.github.ajalt.clikt.parameters.arguments.*
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import com.github.ajalt.clikt.parameters.options.convert
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
-import com.github.ajalt.clikt.parameters.types.enum
+import com.github.ajalt.clikt.parameters.types.*
import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.ebics.*
import tech.libeufin.nexus.iso20022.*
+import java.util.zip.*
import java.time.Instant
+import java.io.*
class Wss: CliktCommand() {
override fun help(context: Context) = "Listen to EBICS instant notification over websocket"
@@ -273,9 +274,35 @@ class ListCmd: CliktCommand("list") {
}
}
+class ExportCmt: CliktCommand("export") {
+ override fun help(context: Context) = "Export pending batches as pain001 messages"
+
+ private val common by CommonOption()
+ private val out by argument().file()
+
+ override fun run() = cliCmd(logger, common.log) {
+ nexusConfig(common.config).withDb { db, cfg ->
+ ZipOutputStream(BufferedOutputStream(FileOutputStream(out))).use { zip ->
+ db.initiated.batch(Instant.now(), randEbicsId())
+ val ebicsCfg = cfg.ebics
+ db.initiated.submittable(cfg.currency).forEach { batch ->
+ val entry = ZipEntry("${batch.creationDate}-${batch.messageId}.xml")
+ zip.putNextEntry(entry)
+ val msg = batchToPain001Msg(ebicsCfg.account, batch)
+ val xml = createPain001(
+ msg = msg,
+ dialect = ebicsCfg.dialect
+ )
+ zip.write(xml)
+ }
+ }
+ }
+ }
+}
+
class TestingCmd : CliktCommand("testing") {
init {
- subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss())
+ subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss(), ExportCmt())
}
override fun help(context: Context) = "Testing helper commands"
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt
@@ -71,7 +71,7 @@ object EbicsAdministrative {
fun HEV(cfg: NexusEbicsConfig): ByteArray {
return XmlBuilder.toBytes("ebicsHEVRequest") {
attr("xmlns", "http://www.ebics.org/H000")
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
}
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt
@@ -50,11 +50,11 @@ class EbicsBTS(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
el("Nonce", nonce.encodeHex())
el("Timestamp", Instant.now().xmlDateTime())
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
+ el("PartnerID", cfg.host.ebicsPartnerId)
+ el("UserID", cfg.host.ebicsUserId)
// SystemID
// Product
el("OrderDetails") {
@@ -107,7 +107,7 @@ class EbicsBTS(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
el("TransactionID", transactionId)
}
el("mutable") {
@@ -131,7 +131,7 @@ class EbicsBTS(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
el("TransactionID", transactionId)
}
el("mutable") {
@@ -154,11 +154,11 @@ class EbicsBTS(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
el("Nonce", nonce.encodeUpHex())
el("Timestamp", Instant.now().xmlDateTime())
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
+ el("PartnerID", cfg.host.ebicsPartnerId)
+ el("UserID", cfg.host.ebicsUserId)
// SystemID
// Product
el("OrderDetails") {
@@ -215,7 +215,7 @@ class EbicsBTS(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
el("TransactionID", transactionId)
}
el("mutable") {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -96,7 +96,7 @@ suspend fun EbicsBTS.postBTS(
phase: String,
stepLogger: StepLogger? = null
): EbicsResponse<BTSResponse> {
- val doc = client.postToBank(cfg.hostBaseUrl, xmlReq, phase, stepLogger)
+ val doc = client.postToBank(cfg.host.baseUrl, xmlReq, phase, stepLogger)
if (!XMLUtil.verifyEbicsDocument(
doc,
bankKeys.bank_authentication_public_key
@@ -281,7 +281,7 @@ suspend fun HEV(
logger.info("Doing administrative request HEV")
val txLog = ebicsLogger.tx("HEV")
val req = EbicsAdministrative.HEV(cfg)
- val xml = client.postToBank(cfg.hostBaseUrl, req, "HEV", txLog.step())
+ val xml = client.postToBank(cfg.host.baseUrl, req, "HEV", txLog.step())
return EbicsAdministrative.parseHEV(xml).okOrFail("HEV")
}
@@ -300,7 +300,7 @@ suspend fun keyManagement(
Dialect.postfinance -> true
}
val req = EbicsKeyMng(cfg, privs, ebics3).request(order)
- val xml = client.postToBank(cfg.hostBaseUrl, req, order.name, txLog.step())
+ val xml = client.postToBank(cfg.host.baseUrl, req, order.name, txLog.step())
return EbicsKeyMng.parseResponse(xml, privs.encryption_private_key)
}
@@ -327,8 +327,8 @@ fun prepareUploadPayload(
payloadDigest,
clientKeys.signature_private_key,
).encodeBase64())
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
+ el("PartnerID", cfg.host.ebicsPartnerId)
+ el("UserID", cfg.host.ebicsUserId)
}
}
// Generate ephemeral transaction key
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
@@ -79,13 +79,13 @@ class EbicsKeyMng(
el("header") {
attr("authenticate", "true")
el("static") {
- el("HostID", cfg.ebicsHostId)
+ el("HostID", cfg.host.ebicsHostId)
if (order == HPB) {
el("Nonce", getNonce(128).encodeUpHex())
el("Timestamp", Instant.now().xmlDateTime())
}
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
+ el("PartnerID", cfg.host.ebicsPartnerId)
+ el("UserID", cfg.host.ebicsUserId)
el("OrderDetails") {
if (ebics3) {
el("AdminOrderType", order.name)
@@ -128,8 +128,8 @@ class EbicsKeyMng(
attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
attr("xmlns", schema)
build()
- el("PartnerID", cfg.ebicsPartnerId)
- el("UserID", cfg.ebicsUserId)
+ el("PartnerID", cfg.host.ebicsPartnerId)
+ el("UserID", cfg.host.ebicsUserId)
}.inputStream().deflate().encodeBase64()
}
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
@@ -106,7 +106,8 @@ class Cli : CliktCommand() {
val cfg = nexusConfig(conf)
// Check if platform is known
- val kind = when (cfg.ebics.hostBaseUrl) {
+ val host = cfg.cfg.section("nexus-ebics").string("host_base_url").orNull()
+ val kind = when (host) {
"https://isotest.postfinance.ch/ebicsweb/ebicsweb" ->
Kind("PostFinance IsoTest", "https://isotest.postfinance.ch/corporates/user/settings/ebics")
"https://iso20022test.credit-suisse.com/ebicsweb/ebicsweb" ->
@@ -172,6 +173,7 @@ class Cli : CliktCommand() {
put("list-initiated", "List initiated payments", "testing list $flags initiated")
put("wss", "Listen to notification over websocket", "testing wss $debugFlags")
put("submit", "Submit pending transactions", "ebics-submit $ebicsFlags")
+ put("export", "Export pending batches as pain001 messages", "testing export $flags payments.zip")
put("setup", "Setup", "ebics-setup $debugFlags")
put("reset-keys", suspend {
if (kind.test) {
@@ -218,25 +220,29 @@ class Cli : CliktCommand() {
put("tx-check", "Check transaction semantic", "testing tx-check $flags")
}
while (true) {
- var clientKeys = loadClientKeys(clientKeysPath)
- val bankKeys = loadBankKeys(bankKeysPath)
- if (!kind.test && clientKeys == null) {
- throw Exception("Clients keys are required to run netzbon tests")
- } else if (clientKeys == null || !clientKeys.submitted_ini || !clientKeys.submitted_hia || bankKeys == null || !bankKeys.accepted) {
- step("Run EBICS setup")
- if (!nexusCmd.run("ebics-setup --auto-accept-keys $debugFlags")) {
- clientKeys = loadClientKeys(clientKeysPath)
- if (kind.test) {
- if (clientKeys == null || !clientKeys.submitted_ini || !clientKeys.submitted_hia) {
- msg("Got to ${kind.settings} and click on 'Reset EBICS user'")
+ // Automatic setup
+ if (host != null) {
+ var clientKeys = loadClientKeys(clientKeysPath)
+ val bankKeys = loadBankKeys(bankKeysPath)
+ if (!kind.test && clientKeys == null) {
+ throw Exception("Clients keys are required to run netzbon tests")
+ } else if (clientKeys == null || !clientKeys.submitted_ini || !clientKeys.submitted_hia || bankKeys == null || !bankKeys.accepted) {
+ step("Run EBICS setup")
+ if (!nexusCmd.run("ebics-setup --auto-accept-keys $debugFlags")) {
+ clientKeys = loadClientKeys(clientKeysPath)
+ if (kind.test) {
+ if (clientKeys == null || !clientKeys.submitted_ini || !clientKeys.submitted_hia) {
+ msg("Got to ${kind.settings} and click on 'Reset EBICS user'")
+ } else {
+ msg("Got to ${kind.settings} and click on 'Activate EBICS user'")
+ }
} else {
- msg("Got to ${kind.settings} and click on 'Activate EBICS user'")
+ msg("Activate your keys at your bank")
}
- } else {
- msg("Activate your keys at your bank")
}
}
}
+ // REPL
val arg = ask("testbench> ")!!.trim()
if (arg == "exit") break
if (arg == "") continue