summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-28 11:48:15 +0100
committerAntoine A <>2024-03-28 11:48:15 +0100
commit87b44b39a4f0813000aea1bec33b1aef579e7b82 (patch)
tree8bf3b17ab1473039f254ebfdf83c0668aa58064b
parentdc03013dfcc3acc17bb8f54e842ccc6740caa040 (diff)
downloadlibeufin-87b44b39a4f0813000aea1bec33b1aef579e7b82.tar.gz
libeufin-87b44b39a4f0813000aea1bec33b1aef579e7b82.tar.bz2
libeufin-87b44b39a4f0813000aea1bec33b1aef579e7b82.zip
Run HEV and HKD in ebics-setup
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt30
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt6
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt3
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt89
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt10
-rw-r--r--testbench/src/main/kotlin/Main.kt2
6 files changed, 131 insertions, 9 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index dd13eb30..a2b0934a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -198,14 +198,21 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
*/
override fun run() = cliCmd(logger, common.log) {
val cfg = extractEbicsConfig(common.config)
- // Config is sane. Go (maybe) making the private keys.
- val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysPath)
val client = HttpClient {
install(HttpTimeout) {
// It can take a lot of time for the bank to generate documents
socketTimeoutMillis = 5 * 60 * 1000
}
}
+
+ // Check EBICS 3 support
+ val versions = HEV(client, cfg)
+ logger.debug("HEV: $versions")
+ if (!versions.contains(VersionNumber(3.0f, "H005")) && !versions.contains(VersionNumber(3.02f, "H005"))) {
+ throw Exception("EBICS 3 is not supported by your bank")
+ }
+
+ val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysPath)
// Privs exist. Upload their pubs
val keysNotSub = !clientKeys.submitted_ini
if ((!clientKeys.submitted_ini) || forceKeysResubmission)
@@ -215,7 +222,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
if ((!clientKeys.submitted_hia) || forceKeysResubmission)
doKeysRequestAndUpdateState(cfg, clientKeys, client, HIA)
- // Checking if the bank keys exist on disk.
+ // Checking if the bank keys exist on disk
var bankKeys = loadBankKeys(cfg.bankPublicKeysPath)
if (bankKeys == null) {
doKeysRequestAndUpdateState(cfg, clientKeys, client, HPB)
@@ -237,6 +244,23 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
throw Exception("Could not set bank keys as accepted on disk", e)
}
}
+
+ // Check account information
+ logger.info("Doing administrative request HKD")
+ try {
+ ebicsDownload(client, cfg, clientKeys, bankKeys, EbicsOrder.V3("HKD"), null, null) { stream ->
+ val account = EbicsAdministrative.parseHKD(stream)
+ // TODO parse and check more information
+ if (account.currency != null && account.currency != cfg.currency)
+ logger.warn("Expected CURRENCY '${cfg.currency}' from config got '${account.currency}' from bank")
+ if (account.iban != null && account.iban != cfg.account.iban)
+ logger.warn("Expected IBAN '${cfg.account.iban}' from config got '${account.iban}' from bank")
+ if (account.name != null && account.name != cfg.account.name)
+ logger.warn("Expected NAME '${cfg.account.name}' from config got '${account.name}' from bank")
+ }
+ } catch (e: Exception) {
+ logger.warn("HKD failed: ${e.fmt()}")
+ }
println("setup ready")
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
index 6269377a..103795d6 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -145,7 +145,7 @@ data class CustomerAck(
/** Parse HAC pain.002 XML file */
fun parseCustomerAck(xml: InputStream): List<CustomerAck> {
- return destructXml(xml, "Document") {
+ return XmlDestructor.fromStream(xml, "Document") {
one("CstmrPmtStsRpt").map("OrgnlPmtInfAndSts") {
val actionType = one("OrgnlPmtInfId").enum<HacAction>()
one("StsRsnInf") {
@@ -219,7 +219,7 @@ fun parseCustomerPaymentStatusReport(xml: InputStream): PaymentStatus {
Reason(code, "")
}
}
- return destructXml(xml, "Document") {
+ return XmlDestructor.fromStream(xml, "Document") {
// TODO handle batch status
one("CstmrPmtStsRpt") {
val (msgId, msgCode, msgReasons) = one("OrgnlGrpInfAndSts") {
@@ -288,7 +288,7 @@ fun parseTxNotif(
fun notificationForEachTx(
directionLambda: XmlDestructor.(Instant, Boolean, String?) -> Unit
) {
- destructXml(notifXml, "Document") {
+ XmlDestructor.fromStream(notifXml, "Document") {
opt("BkToCstmrDbtCdtNtfctn")?.each("Ntfctn") {
each("Ntry") {
val reversal = opt("RvslInd")?.bool() ?: false
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
index fbed0b7b..1ad16f6c 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
@@ -199,6 +199,3 @@ class XmlDestructor internal constructor(private val el: Element) {
}
}
}
-
-fun <T> destructXml(xml: InputStream, root: String, f: XmlDestructor.() -> T): T
- = XmlDestructor.fromStream(xml, root, f)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt
new file mode 100644
index 00000000..464db37f
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsAdministrative.kt
@@ -0,0 +1,89 @@
+/*
+ * 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
+
+import org.w3c.dom.Document
+import tech.libeufin.common.crypto.CryptoUtil
+import tech.libeufin.common.*
+import tech.libeufin.nexus.*
+import tech.libeufin.nexus.BankPublicKeysFile
+import tech.libeufin.nexus.ClientPrivateKeysFile
+import java.io.InputStream
+import java.time.Instant
+import java.time.ZoneId
+import java.util.*
+import javax.xml.datatype.DatatypeFactory
+import java.security.interfaces.*
+import tech.libeufin.nexus.ebics.EbicsKeyMng.Order.*
+
+data class VersionNumber(val number: Float, val schema: String) {
+ override fun toString(): String = "$number:$schema"
+}
+
+data class AccountInfo(
+ val currency: String?,
+ val iban: String?,
+ val name: String?
+)
+
+object EbicsAdministrative {
+ fun HEV(cfg: NexusConfig): ByteArray {
+ return XmlBuilder.toBytes("ebicsHEVRequest") {
+ attr("xmlns", "http://www.ebics.org/H000")
+ el("HostID", cfg.ebicsHostId)
+ }
+ }
+
+ fun parseHEV(doc: Document): EbicsResponse<List<VersionNumber>> {
+ return XmlDestructor.fromDoc(doc, "ebicsHEVResponse") {
+ val technicalCode = one("SystemReturnCode") {
+ EbicsReturnCode.lookup(one("ReturnCode").text())
+ }
+ val versions = map("VersionNumber") {
+ VersionNumber(text().toFloat(), attr("ProtocolVersion"))
+ }
+ EbicsResponse(
+ technicalCode = technicalCode,
+ bankCode = EbicsReturnCode.EBICS_OK,
+ content = versions
+ )
+ }
+ }
+
+ fun parseHKD(stream: InputStream): AccountInfo {
+ return XmlDestructor.fromStream(stream, "HKDResponseOrderData") {
+ var currency: String? = null
+ var iban: String? = null
+ var name: String? = null
+ one("PartnerInfo") {
+ name = opt("AddressInfo")?.one("Name")?.text()
+ opt("AccountInfo") {
+ currency = attr("Currency")
+ each("AccountNumber") {
+ if (attr("international") == "true") {
+ iban = text()
+ }
+ }
+ }
+ }
+ AccountInfo(currency, iban, name)
+ }
+ }
+}
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 f87c55b3..aa7d40e3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -258,6 +258,16 @@ suspend fun ebicsDownload(
Unit
}
+suspend fun HEV(
+ client: HttpClient,
+ cfg: NexusConfig
+): List<VersionNumber> {
+ logger.info("Doing administrative request HEV")
+ val req = EbicsAdministrative.HEV(cfg)
+ val xml = client.postToBank(cfg.hostBaseUrl, req, "HEV")
+ return EbicsAdministrative.parseHEV(xml).okOrFail("HEV")
+}
+
/**
* Signs and the encrypts the data to send via EBICS.
*
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
index d6d9c3e9..7a3216bb 100644
--- a/testbench/src/main/kotlin/Main.kt
+++ b/testbench/src/main/kotlin/Main.kt
@@ -53,6 +53,7 @@ fun ask(question: String): String? {
fun CliktCommand.run(arg: String): Boolean {
val res = this.test(arg)
+ print(res.output)
if (res.statusCode != 0) {
println("\u001b[;31mERROR ${res.statusCode}\u001b[0m")
} else {
@@ -142,6 +143,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
put("status", "Fetch CustomerPaymentStatusReport", "ebics-fetch $ebicsFlags status")
put("notification", "Fetch BankToCustomerDebitCreditNotification", "ebics-fetch $ebicsFlags notification")
put("submit", "Submit pending transactions", "ebics-submit $ebicsFlags")
+ put("setup", "Setup", "ebics-setup $flags")
put("reset-keys", suspend {
if (kind.test) {
clientKeysPath.deleteIfExists()